【Unity3D自学记录】打造网络图片异步加载与本地缓存工具类

我们在移动端的开发中,异步网络图片加载用的非常的多,在unity当中虽然有AssetBundle的存在,一般是先加载好游戏资源然后再进入场景,但是还有不少地方能够用到异步网络图片的加载以及其缓存机制。

我之前也写过两个版本的ios中的异步网络图片加载helper类,所以今天按照同样的思路,也想做一个好用的helper类给大家使用以及简单的说下实现原理。

首先我们加载一张网络图片,要做的事情分步来讲为:

0.开始之前设置一张固定的图片作为占位图(placeholder),表示我们的图片还没加载好,来填充当前图片控件区域,让用户知道

1.这个图片有个url地址,我们的程序第一次加载这个url地址的图片时

a.异步开始下载这张图片

b.保存到某个指定的目录

c.将图片控件上占位图替换为下载好的图片,可以适当的添加图片切换动画(动画本文略)

2.我们的程序已经加载过这个url地址的图片

a.从上次存进的目录中读取文件,转换为图片

b.将图片控件上占位图替换为下载好的图片



总的来说我们的程序应该是在第一次加载一批网络图片的时候,是需要哪张下哪张,然后显示出来,而后,是可以直接从文件系统中去读取的(第三种情况,从内存中直接读取,取决于设备配置,这里就不做了)


开始做这个工具类之前,先要知道一些unity的特殊限制:

1.MonoBehaviour基类的成员方法 StartCoroutine开启异步任务是不支持静态方法中调用的

2.MonoBehaviour基类的子类不能直接使用关键字new 来创建,相应的,需要创建一个空的GameObject,然后调用这个object的AddComponent方法来实例化,也就是说unity中的脚本对象都依附于游戏对象才能被执行

3.由于unity的跨平台特性,每个不同的平台的文件目录结构有区别


基于上述一些特殊性,我打算把这个工具类作成一个MonoBehaviour的子类,并以单例的形式让其他脚本调用,其中的异步网络请求就可以使用StartCoroutine函数了

首先是这个单例的实现方法:


[csharp]  view plain copy print ?
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using System.IO;  
  4.   
  5. public class AsyncImageDownload :MonoBehaviour {  
  6.   
  7.     public  Texture placeholder;  
  8.     public static AsyncImageDownload  Instance=null;  
  9.   
  10.     private   string path=Application.persistentDataPath+"/ImageCache/" ;  
  11.   
  12.     //构建单例  
  13.     public static AsyncImageDownload CreateSingleton()  
  14.     {  
  15.         if (!Directory.Exists(Application.persistentDataPath+"/ImageCache/")) {  
  16.             Directory.CreateDirectory(Application.persistentDataPath+"/ImageCache/");  
  17.         }  
  18.   
  19.         GameObject obj = new GameObject ();  
  20.         obj.AddComponent<AsyncImageDownload> ();  
  21.   
  22.         AsyncImageDownload loader= obj.GetComponent<AsyncImageDownload>();  
  23.         Instance=loader;  
  24.         loader.placeholder=Resources.Load("placeholder"as Texture;  
  25.         return loader;  
  26.   
  27.     }  


这里没有使用构造函数来创建单例,原因是MonoBehaviour的子类压根不支持new关键字,所以怎么去写构造函数呢

然后这个构建单例的方法是跟构造方法差不多的,只是不是去new出来,而是类名.方法名()来创建这个单例,在创建单例的时候需要创建一个空白游戏体来依附我们的脚本组件,并且把静态成员Instance指向创建出来的这个单例,多次使用这个单例的时候只需要使用 类名.Instance()来获取已经存在的这个脚本组件单例。

这里的Resources.Load(“placeholder”)需要注意,用代码读取项目目录中的图片资源并转换成Texutre对象,需要再Project窗口下的Assets中创建Resources文件夹,然后导入任何一张图片进去,代码中获取他不需要加后缀名。


这个脚本写好以后,我们的程序初始化的时候应该执行以下代码来创建这个单例:

[csharp]  view plain copy print ?
  1. AsyncImageDownload.CreateSingleton()  

而后,给某个控件加载网络图片,我们应该给这个单例加一个方法public  void SetAsyncImage(string url,UITexture texture),那么简化的写法就可以写为:

[csharp]  view plain copy print ?
  1. AsyncImageDownload.Instance.SetAsyncImage ("http://www.cfanz.cn/uploads/jpg/2013/07/13/0/XEPLd7d2C5.jpg", page.GetComponentInChildren<UITexture> ());  

 
 


public  void SetAsyncImage(string url,UITexture texture)

按照前文分析的图片加载步骤来


[csharp]  view plain copy print ?
  1. public  void SetAsyncImage(string url,UITexture texture){  
  2.   
  3.   
  4.   
  5.         //开始下载图片前,将UITexture的主图片设置为占位图  
  6.         texture.mainTexture = placeholder;  
  7.   
  8.         //判断是否是第一次加载这张图片  
  9.   
  10.         if (!File.Exists (path + url.GetHashCode())) {  
  11.                         //如果之前不存在缓存文件  
  12.   
  13.                   
  14.                     StartCoroutine (DownloadImage (url, texture));  
  15.                           
  16.   
  17.                 }  
  18.         else {  
  19.   
  20.                     StartCoroutine(LoadLocalImage(url,texture));  
  21.   
  22.                 }  
  23.   
  24.     }  

这里判断缓存文件是否存在使用的是url.GetHashCode()方法,因为我们的图片文件名采用的是原URL的哈希码直接作为文件名来保存,重名概率可以忽略不计,也缩短了文件名的长度提高效率,这个做法借鉴于 iOS开源框架EGOImageView。

如果是第一次加载图片,这个URL对应的文件不存在,那么我们就去原URL下载图片然后赋值给控件

如果缓存文件夹中已有该文件,直接读取加载

由于前文的铺垫,我们的工具类已经是MonoBehaviour的单例子类,所以可以使用unity的异步函数StartCorutine()


接下来完成方法DownloadImage(string url,UITexture texture)

[csharp]  view plain copy print ?
  1. IEnumerator  DownloadImage(string url,UITexture texture){  
  2.         Debug.Log("downloading new image:"+path+url.GetHashCode());    
  3.         WWW www = new WWW (url);  
  4.         yield return www;  
  5.   
  6.         Texture2D image = www.texture;  
  7.         //将图片保存至缓存路径  
  8.         byte[] pngData = image.EncodeToPNG();    
  9.         File.WriteAllBytes(path+url.GetHashCode(), pngData);    
  10.   
  11.         texture.mainTexture = image;  
  12.   
  13.     }  


这个方法很简单,然后是从缓存文件夹读取已经存在的图片方法LoadLocalImage(string url,UITexture texture)  

注意这里不能使用Resources.Load()方法,因为我们的图片并没有存放在工程目录中,我仔细查阅了相关资料发现比较合适的方法应该还是使用unity的WWW类去加载文件url,即在文件路径的前方加上file:///使之成为一个文件url,然后使用www类读取,但是这个过程是本地的还是比较快


[csharp]  view plain copy print ?
  1. IEnumerator  LoadLocalImage(string url,UITexture texture){  
  2.         string filePath = "file:///" + path + url.GetHashCode ();  
  3.   
  4.         Debug.Log("getting local image:"+filePath);  
  5.         WWW www = new WWW (filePath);  
  6.         yield return www;  
  7.           
  8.   
  9.         //直接贴图  
  10.         texture.mainTexture = www.texture;  
  11.           
  12.     }  


我们的工具类写好了

随便找个Panel添加一个带UITexture组件的节点,然后调用我们的工具方法,测试下运行结果:

第一次运行:


【Unity3D自学记录】打造网络图片异步加载与本地缓存工具类_第1张图片

打开这个文件所在的文件夹(我这里使用的是windows系统,不同系统路径不一样)

【Unity3D自学记录】打造网络图片异步加载与本地缓存工具类_第2张图片



这个图片已经加载在我们的图片控件上并且已经保存至了本地路径,也就是说我们再次运行之后,不会再进入if的第一种情况了,我们关掉程序再次运行:

【Unity3D自学记录】打造网络图片异步加载与本地缓存工具类_第3张图片


正如我们所想,实际上这次是没有产生网络请求的,说明我的缓存已经有用,而且图片一下就出来了,不像上一次要等一会



接下来我们删掉缓存文件再次执行,又会调用第一个方法~

这个工具类先做到这里了,接下来图片切换效果已经加载过程的等待HUD等后面再研究。完整代码:

[csharp]  view plain copy print ?
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using System.IO;  
  4.   
  5. public class AsyncImageDownload :MonoBehaviour {  
  6.   
  7.     public  Texture placeholder;  
  8.     public static AsyncImageDownload  Instance=null;  
  9.   
  10.     private   string path=Application.persistentDataPath+"/ImageCache/" ;  
  11.   
  12.     //构建单例  
  13.     public static AsyncImageDownload CreateSingleton()  
  14.     {  
  15.         if (!Directory.Exists(Application.persistentDataPath+"/ImageCache/")) {  
  16.             Directory.CreateDirectory(Application.persistentDataPath+"/ImageCache/");  
  17.         }  
  18.   
  19.         GameObject obj = new GameObject ();  
  20.         obj.AddComponent<AsyncImageDownload> ();  
  21.   
  22.         AsyncImageDownload loader= obj.GetComponent<AsyncImageDownload>();  
  23.         Instance=loader;  
  24.         loader.placeholder=Resources.Load("placeholder"as Texture;  
  25.         return loader;  
  26.   
  27.     }  
  28.   
  29.   
  30.     public  void SetAsyncImage(string url,UITexture texture){  
  31.   
  32.   
  33.   
  34.         //开始下载图片前,将UITexture的主图片设置为占位图  
  35.         texture.mainTexture = placeholder;  
  36.   
  37.         //判断是否是第一次加载这张图片  
  38.   
  39.         if (!File.Exists (path + url.GetHashCode())) {  
  40.                         //如果之前不存在缓存文件  
  41.   
  42.                   
  43.                     StartCoroutine (DownloadImage (url, texture));  
  44.                           
  45.   
  46.                 }  
  47.         else {  
  48.   
  49.                     StartCoroutine(LoadLocalImage(url,texture));  
  50.   
  51.                 }  
  52.   
  53.     }  
  54.   
  55.     IEnumerator  DownloadImage(string url,UITexture texture){  
  56.         Debug.Log("downloading new image:"+path+url.GetHashCode());  
  57.         WWW www = new WWW (url);  
  58.         yield return www;  
  59.   
  60.         Texture2D image = www.texture;  
  61.         //将图片保存至缓存路径  
  62.         byte[] pngData = image.EncodeToPNG();    
  63.         File.WriteAllBytes(path+url.GetHashCode(), pngData);    
  64.   
  65.         texture.mainTexture = image;  
  66.   
  67.     }  
  68.   
  69.     IEnumerator  LoadLocalImage(string url,UITexture texture){  
  70.         string filePath = "file:///" + path + url.GetHashCode ();  
  71.   
  72.         Debug.Log("getting local image:"+filePath);  
  73.         WWW www = new WWW (filePath);  
  74.         yield return www;  
  75.           
  76.   
  77.         //直接贴图  
  78.         texture.mainTexture = www.texture;  
  79.           
  80.     }  
  81. }  


你可能感兴趣的:(unity3d)