android-smart-image-view源码分析

转自:http://blog.csdn.net/gao_chun/article/details/47610137

SmartImageView源码分析


一、描述

目前Android应用开发涌出了各种各样出自大牛之手的成熟稳定的开源库,供开发者使用,虽然很明显的提高了App的开发效率,也同样凸显出部分问题:

  • 我只知道如何去使用(也许还并不能随心所欲的使用,只知道简单的使用方式),并不清楚内部实现原理。
  • 出了问题解决不了,冒出想法:这玩意真伤脑筋,怎么老出问题。好慌 ~

相信用过一些开源库的同志都遇到过类似揪心的问题。开源库不是自己写的,也不清楚里面是啥原理,遇到问题无从下手解决显然也是很正常的事情。不过从另一方面想,解读或了解开源库的内部实现原理也对我们技术提升有不小的进步:

  • 日常的代码编写只局限于实现app的业务逻辑,对提升技术水平益处不大。
  • 熟知和了解各种开源库的实现,自己在开发中也能设计出好的架构,写出好的代码。


本文将和大家一起分析一个简单的开源项目 android-smart-image-view 

GitHub地址:https://github.com/loopj/android-smart-image-view

出自James Smith之手,网名loopj,曾写过Android中 android-async-http 开源库,大家应该不陌生。


二、原理和技术实现

这里我们先来谈谈关于SmartImageView的实现原理,既然是原理,那肯定是简单的了解了解关于这个控件的实现了,SmartImageView可完全替代ImageView,毫无疑问,因为其本身便是继承了ImageView,并对其进行了扩展,在ImageView的基础上添加了许多新的功能,一个开源库的成型,并非一朝一夕就可以完成,它需要经过大量时间去实践、升级和维护。比如A程序员写了个开源库发布到网上,程序员B下载并使用,发现其功能不够完善,联系了程序员A后便进去改造,对一些方法进行了升级优化并发布到网上,这时程序员C发现他俩写的功能不能适用于我的需求,然后又进行了复写和改造……,这样一个好的开源库或框架就流传出来了。

下面我们也自定义一个SmartImageView控件来简单看下SmartImageView的实现原理,首先继承ImageView类,并重写ImageView的构造方法,下图为ImageView类中的构造方法:



我们自定义控件后重写其构造方法:


接着在类中写个访问网络请求的方法,如 setImageUrl 方法,让该方法可根据URL路径下载图片,如:

[java]  view plain  copy
 print ?
  1. /******************************************************************************* 
  2.  * 
  3.  * Copyright (c) Weaver Info Tech Co. Ltd 
  4.  * 
  5.  * SmartImageView 
  6.  * 
  7.  * com.example.smartimageview.SmartImageView.java 
  8.  * TODO: File description or class description. 
  9.  * 
  10.  * @author: gao_chun 
  11.  * @since:  2015-8-13 
  12.  * @version: 1.0.0 
  13.  * 
  14.  * @changeLogs: 
  15.  *     1.0.0: First created this class. 
  16.  * 
  17.  ******************************************************************************/  
  18. package com.example.smartimageview;  
  19.   
  20. import java.io.InputStream;  
  21. import java.net.HttpURLConnection;  
  22. import java.net.URL;  
  23.   
  24. import android.content.Context;  
  25. import android.graphics.Bitmap;  
  26. import android.graphics.BitmapFactory;  
  27. import android.os.Handler;  
  28. import android.os.Message;  
  29. import android.util.AttributeSet;  
  30. import android.widget.ImageView;  
  31.   
  32. /** 
  33.  * @author gao_chun 
  34.  * 继承ImageView控件 
  35.  */  
  36. public class SmartImageView extends ImageView{  
  37.     /** 
  38.      * @param context 
  39.      * @param attrs 
  40.      * @param defStyle 
  41.      */  
  42.     public SmartImageView(Context context, AttributeSet attrs, int defStyle) {  
  43.         super(context, attrs, defStyle);  
  44.         // TODO Auto-generated constructor stub  
  45.     }  
  46.     /** 
  47.      * @param context 
  48.      * @param attrs 
  49.      */  
  50.     public SmartImageView(Context context, AttributeSet attrs) {  
  51.         super(context, attrs);  
  52.         // TODO Auto-generated constructor stub  
  53.     }  
  54.     /** 
  55.      * @param context 
  56.      */  
  57.     public SmartImageView(Context context) {  
  58.         super(context);  
  59.         // TODO Auto-generated constructor stub  
  60.     }  
  61.     /** 
  62.      * 使用Handler刷新UI 
  63.      */  
  64.     private Handler mHandler = new Handler(){  
  65.         public void handleMessage(android.os.Message msg) {  
  66.             Bitmap bitmap = (Bitmap) msg.obj;//获取到Bitmap对象  
  67.             SmartImageView.this.setImageBitmap(bitmap);//setImageBitmap  
  68.         };  
  69.     };  
  70.   
  71.     /** 
  72.      * 设置图片的网络路径 ,加载一个网络的图片 
  73.      * @param path 
  74.      */  
  75.     public void setImageUrl(final String path){  
  76.         new Thread(){  
  77.             public void run() {  
  78.                 try {  
  79.                     //创建链接对象  
  80.                     URL url = new URL(path);  
  81.                     HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
  82.                     //设置请求方式  
  83.                     conn.setRequestMethod("GET");  
  84.                     //设置Timeout  
  85.                     conn.setConnectTimeout(5000);  
  86.                     int code = conn.getResponseCode();  
  87.                     //返回码为200时  
  88.                     if(code == 200){  
  89.                         //读取服务端返回的数据流  
  90.                         InputStream is = conn.getInputStream();  
  91.                         //将该图片数据流转化成位图  
  92.                         Bitmap bitmap = BitmapFactory.decodeStream(is);  
  93.                         //Handler发送消息给主线程,更新UI  
  94.                         Message msg = Message.obtain();  
  95.                         msg.obj = bitmap;  
  96.                         mHandler.sendMessage(msg);  
  97.                     }  
  98.                 } catch (Exception e) {  
  99.                     e.printStackTrace();  
  100.                 }  
  101.             };  
  102.         }.start();  
  103.     }  
  104. }  


布局文件中引入我们定义的控件:

[html]  view plain  copy
 print ?
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent">  
  5.   
  6.     <Button  
  7.         android:id="@+id/btn"  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="wrap_content"  
  10.         android:onClick="onClick"  
  11.         android:text="获取网络图片" />  
  12.   
  13.     <com.example.smartimageview.SmartImageView  
  14.         android:id="@+id/iv_coco"  
  15.         android:layout_width="wrap_content"  
  16.         android:layout_height="wrap_content"  
  17.         android:layout_below="@+id/btn"  
  18.         android:scaleType="fitXY" />  
  19.   
  20. </RelativeLayout>  


主Activity中使用我们刚刚定义的控件:

[java]  view plain  copy
 print ?
  1. package com.example.smartimageview;  
  2.   
  3. import android.os.Bundle;  
  4. import android.support.v7.app.ActionBarActivity;  
  5. import android.view.View;  
  6. import android.view.View.OnClickListener;  
  7.   
  8. public class MainActivity extends ActionBarActivity implements OnClickListener{  
  9.   
  10.     String mUrl = "http://e.hiphotos.baidu.com/baike/c0%3Dbaike150%2C5%2C5%2C150%2C50/sign=79b94a3be21190ef15f69a8daf72f673/bd3eb13533fa828b5a920735f81f4134960a5ab7.jpg";  
  11.   
  12.     @Override  
  13.     protected void onCreate(Bundle savedInstanceState) {  
  14.   
  15.         super.onCreate(savedInstanceState);  
  16.         setContentView(R.layout.activity_main);  
  17.   
  18.     }  
  19.   
  20.   
  21.     @Override  
  22.     public void onClick(View v) {  
  23.         if (v.getId() == R.id.btn) {  
  24.   
  25.             SmartImageView siv = (SmartImageView) findViewById(R.id.iv_coco);  
  26.             siv.setImageUrl(mUrl);  
  27.         }  
  28.   
  29.     }  
  30.   
  31. }  


效果图如下:



OK!已经完成主要功能。不过只是简单的完成了下载图片和显示图片的功能,但是并未涉及到图片的缓存,压缩,以及错处理等情况。所以需要一步一步去拓展。比如可以在返回的Code不为200时,加个else并发个消息给Handler,在Handler中去处理加载失败显示的错误图片资源就好。


三、源码分析

从github上clone该项目,可以看到整个项目的代码只包含7个Java源文件,同时,还可进行扩展,方便使用者根据实际图片的来源进行扩展。我们来看看Class逻辑图:



上面有提到,SmartImageView继承自ImageView并自定义了一些方法,能够方便的显示网络图片。在Android中,图片的显示最终都绘制到画布canvas上以位图的形式显示,所以通过逻辑图可以看出定义了一个 SmartImage 接口,而里面有一个返回值为Bitmap的getBitmap方法:

[java]  view plain  copy
 print ?
  1. package com.loopj.android.image;  
  2. import android.content.Context;  
  3. import android.graphics.Bitmap;  
  4. public interface SmartImage {  
  5.     public Bitmap getBitmap(Context context);  
  6. }  

为什么会定义这个getBitmap方法呢,因为需要加载的图片来源是不一样的,如:从网络加载或从系统联系人头像加载,所以分别让不同来源的类去实现这个接口,然后在该方法中处理逻辑。如图:



我们来看下这三个类的具体代码:

  • 【BitmapImage(仅仅是传入Bitmap的实例然后返回)】---->

[java]  view plain  copy
 print ?
  1. package com.loopj.android.image;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Bitmap;  
  5.   
  6. /** 
  7.  * 实现SmartImage接口 
  8.  */  
  9. public class BitmapImage implements SmartImage {  
  10.   
  11.     //定义Bitmap对象  
  12.     private Bitmap bitmap;  
  13.   
  14.     //构造方法  
  15.     public BitmapImage(Bitmap bitmap) {  
  16.         this.bitmap = bitmap;  
  17.     }  
  18.   
  19.     //实现getBitmap方法  
  20.     public Bitmap getBitmap(Context context) {  
  21.         return bitmap;  
  22.     }  
  23. }  

  • 【WebImage(根据Url获取图片资源,需要注意的是这里用到了缓存,注意代码注释)】---->

[java]  view plain  copy
 print ?
  1. package com.loopj.android.image;  
  2.   
  3. import java.io.InputStream;  
  4. import java.net.URL;  
  5. import java.net.URLConnection;  
  6.   
  7. import android.content.Context;  
  8. import android.graphics.Bitmap;  
  9. import android.graphics.BitmapFactory;  
  10.   
  11. public class WebImage implements SmartImage {  
  12.   
  13.     //超时设置  
  14.     private static final int CONNECT_TIMEOUT = 5000;  
  15.     private static final int READ_TIMEOUT = 10000;  
  16.   
  17.     //缓存对象  
  18.     private static WebImageCache webImageCache;  
  19.   
  20.     //WebImage的构造方法,获取URL  
  21.     private String url;  
  22.     public WebImage(String url) {  
  23.         this.url = url;  
  24.     }  
  25.   
  26.     //实现方法,处理相应的业务逻辑  
  27.     public Bitmap getBitmap(Context context) {  
  28.         // Don't leak context  
  29.         if(webImageCache == null) {  
  30.             webImageCache = new WebImageCache(context);  
  31.         }  
  32.   
  33.         // Try getting bitmap from cache first  
  34.         //此处做了简单的二级缓存(内存缓存和磁盘缓存)  
  35.         Bitmap bitmap = null;  
  36.   
  37.         if(url != null) {  
  38.             //先从缓存获取bitmap对象  
  39.             bitmap = webImageCache.get(url);  
  40.             if(bitmap == null) {  
  41.                 //未找到则从网络加载  
  42.                 bitmap = getBitmapFromUrl(url);  
  43.   
  44.                 if(bitmap != null){  
  45.                     //加载后将bitmap对象put到缓存中  
  46.                     webImageCache.put(url, bitmap);  
  47.                 }  
  48.             }  
  49.         }  
  50.   
  51.         return bitmap;  
  52.     }  
  53.   
  54.     /** 
  55.      * 根据Url获取网络图片资源 
  56.      * @param url 
  57.      * @return 
  58.      */  
  59.     private Bitmap getBitmapFromUrl(String url) {  
  60.         Bitmap bitmap = null;  
  61.   
  62.         try {  
  63.             URLConnection conn = new URL(url).openConnection();  
  64.             conn.setConnectTimeout(CONNECT_TIMEOUT);  
  65.             conn.setReadTimeout(READ_TIMEOUT);  
  66.             bitmap = BitmapFactory.decodeStream((InputStream) conn.getContent());  
  67.         } catch(Exception e) {  
  68.             e.printStackTrace();  
  69.         }  
  70.   
  71.         return bitmap;  
  72.     }  
  73.   
  74.     /** 
  75.      * 提供移除缓存的方法 
  76.      * @param url 
  77.      */  
  78.     public static void removeFromCache(String url) {  
  79.         if(webImageCache != null) {  
  80.             webImageCache.remove(url);  
  81.         }  
  82.     }  
  83. }  


  • 【ContactImage(系统联系人头像的获取,通过在构造函数传入联系人的ID,然后在getBitmap方法中根据ID查找对应的联系人头像)】---->

[java]  view plain  copy
 print ?
  1. package com.loopj.android.image;  
  2.   
  3. import java.io.InputStream;  
  4.   
  5. import android.content.ContentUris;  
  6. import android.content.ContentResolver;  
  7. import android.content.Context;  
  8. import android.provider.ContactsContract;  
  9. import android.graphics.Bitmap;  
  10. import android.graphics.BitmapFactory;  
  11. import android.net.Uri;  
  12.   
  13. /* 
  14.  * 获取联系人头像资源 
  15.  */  
  16. public class ContactImage implements SmartImage {  
  17.   
  18.     /**************************************************************************************************** 
  19.      * 注:Android系统中访问其他app的数据时,一般都是通过ContentProvider实现, 
  20.      * 一个ContentProvider类实现了一组标准的方法接口,能够让其他app保存或者读取它提供的各种数据类型。 
  21.      * 其他app通过ContentResolver接口就可以访问ContentProvider提供的数据。 
  22.      * 备注:(获取的是手机的联系人头像,而不是Sim卡中的联系人头像的。Sim卡由于容量限制等原因,无联系人头像数据) 
  23.      * 
  24.      * --使用时记得添加获取联系人头像的权限 
  25.      *****************************************************************************************************/  
  26.   
  27.     //联系人头像ID  
  28.     private long contactId;  
  29.   
  30.     public ContactImage(long contactId) {  
  31.         this.contactId = contactId;  
  32.     }  
  33.   
  34.     public Bitmap getBitmap(Context context) {  
  35.         Bitmap bitmap = null;  
  36.         //获取ContentResolver实例  
  37.         ContentResolver contentResolver = context.getContentResolver();  
  38.   
  39.         try {  
  40.             //根据ID生成查找联系人的Uri  
  41.   
  42.             /* 
  43.              关于withAppendedId方法: 
  44.  
  45.             Open Declaration Uri android.content.ContentUris.withAppendedId(Uri contentUri, long id) 
  46.  
  47.             Appends the given ID to the end of the path. 
  48.  
  49.             Parameters: 
  50.             contentUri -- to start with 
  51.             id -- to append 
  52.  
  53.             Returns: 
  54.             a new URI with the given ID appended to the end of the path */  
  55.   
  56.             Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);  
  57.             //调用Contact类中的openContactPhotoInputStream获得图像InputStream对象  
  58.             InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, uri);  
  59.             if(input != null) {  
  60.                 //将数据流decode为bitmap对象并返回  
  61.                 bitmap = BitmapFactory.decodeStream(input);  
  62.             }  
  63.         } catch(Exception e) {  
  64.             e.printStackTrace();  
  65.         }  
  66.   
  67.         return bitmap;  
  68.     }  
  69. }  


那ContactImage中联系人ID是怎么来的呢,其实也是通过ContentResolver查出来的,下面示例代码:

[java]  view plain  copy
 print ?
  1. private static final int DISPLAY_NAME_INDEX = 0;    
  2.     private static final int PHONE_NUMBER_INDEX = 1;    
  3.     private static final int PHOTO_ID_INDEX = 2;    
  4.     private static final int CONTACT_ID_INDEX = 3;    
  5.     private static final String[] PHONES_PROJECTION = new String[] {      
  6.         Phone.DISPLAY_NAME, Phone.NUMBER, Phone.PHOTO_ID,Phone.CONTACT_ID };     
  7.         
  8.     private void getPhoneContact(Context context) {    
  9.         ContentResolver contentResolver = context.getContentResolver();    
  10.         Cursor cursor = contentResolver.query(Phone.CONTENT_URI, PHONES_PROJECTION, nullnullnull);    
  11.         if (cursor != null) {    
  12.             while(cursor.moveToNext()) {    
  13.                 String displayName = cursor.getString(DISPLAY_NAME_INDEX); // 联系人名字    
  14.                 String phoneNum = cursor.getString(PHONE_NUMBER_INDEX);    // 联系人号码    
  15.                 Long contactId = cursor.getLong(CONTACT_ID_INDEX);         // 联系人id    
  16.                 Long photoId = cursor.getLong(PHOTO_ID_INDEX);             // 联系人头像id(photoId大于0时表示联系人有头像)    
  17.             }    
  18.                 
  19.             cursor.close();    
  20.         }    
  21.     }   


好了,讲到这里,相信大家通过注释可以对这三个类有一定的了解了。可能有同学注意到了,那个WebImage中的  WebImageCache  是什么东西,里面是如何实现的呢?好,我们下面来说说关于这个类的源码实现,相信大家看了会对“缓存”这个高大上的词有一定认识。在WebImageCache中其实是实现了二级缓存, 接着我们就来说说这个类。


SmartImageView中的二级缓存

相信大家都知道,在开发中,为了加快图片的访问速度,避免系统资源的浪费,用户体验上的流畅,都会引入缓存的机制,由于App内存有限,若超过了这个限制,系统便会报错OutOfMemory,这是个很头疼的问题。引入缓存的机制目的就是为了让App在使用中更加流畅,体验更好,减少不必要的资源开销。SmartImageView库中也引入了简单的二级缓存,数据获取速度取决于物理介质,一般是 内存>磁盘>网络,故在加载图片时,会优先判断是否命中内存缓存,没有则查找磁盘缓存,最终才会考虑从网络上加载,同时更新内存缓存和磁盘缓存记录。

    考虑到缓存查找的速度问题,在实现内存缓存时一般都会使用类似哈希表这样查找时间复杂度低的数据结构。由于存在多个线程同时在哈希表中查找的情况,需要考虑多线程并发访问的问题。故使用 ConcurrentHashMap内存缓存中我们不会直接持有Bitmap实例的引用,而是通过SoftReference来持有Bitmap对象的软引用,如果一个对象具有软引用,内存空间足够时,垃圾回收器不会回收它,只有在内存空间不足时,才会回收这些对象占用的内存。因此,软引用通常用来实现内存敏感的高速缓存。关于引用问题,可具体参见博文java中对象的引用(强引用、软引用、弱引用、虚引用)

    Android系统上磁盘缓存可以放在内部存储空间,也可以放在外部存储空间(即SD卡)。对于小图片的缓存可以放在内部存储空间中,但当图片比较大,数量比较多时,那么就应该将图片缓存放到SD卡上,毕竟内部存储空间一般比SD卡空间要小很多。SmartImageView库的磁盘缓存是放在内部存储空间中的,也就是app的缓存目录,该目录使用 Context.getCacheDir() 函数来获取,格式类似于:/data/data/app的包名/cache。cache目录主要用于存放缓存文件,当系统的内部存储空间不足时,该目录下面的文件会被删除;当然,不能依赖系统来清理这些缓存文件,而是应该对这些缓存文件设置最大存储空间,当实际占用空间超过这个最大值时,就需要对使用一定的算法对缓存文件进行清理。这一点在SmartImage库中并没有做考虑。

我们来看看WebImageCache类中对内存缓存和磁盘缓存的实现,先看其构造方法:

[java]  view plain  copy
 print ?
  1. //构造方法,构建两级缓存空间  
  2.     public WebImageCache(Context context) {  
  3.   
  4.         // Set up in-memory cache store  
  5.         memoryCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>();  
  6.   
  7.         // Set up disk cache store  
  8.         Context appContext = context.getApplicationContext();  
  9.         diskCachePath = appContext.getCacheDir().getAbsolutePath() + DISK_CACHE_PATH;  
  10.         //先根据URL在cache目录中生成对应的文件  
  11.         File outFile = new File(diskCachePath);  
  12.         outFile.mkdirs();  
  13.         diskCacheEnabled = outFile.exists();  
  14.   
  15.         // Set up threadpool for image fetching tasks  
  16.         writeThread = Executors.newSingleThreadExecutor();  
  17.     }  

具体从缓存获取Bitmap实例的实现方法:

[java]  view plain  copy
 print ?
  1. /** 
  2.      * 从Memory获取bitmap实例 
  3.      * @param url 
  4.      * @return 
  5.      */  
  6.      private Bitmap getBitmapFromMemory(String url) {  
  7.         Bitmap bitmap = null;  
  8.         //通过memoryCache取出软引用  
  9.         SoftReference<Bitmap> softRef = memoryCache.get(getCacheKey(url));  
  10.         //判断系统有无回收该引用  
  11.         if(softRef != null){  
  12.             //get  
  13.             bitmap = softRef.get();  
  14.         }  
  15.   
  16.         return bitmap;  
  17.      }  
  18.   
  19.      /** 
  20.       * 从Disk获取Bitmap实例 
  21.       * @param url 
  22.       * @return 
  23.       */  
  24.      private Bitmap getBitmapFromDisk(String url) {  
  25.          Bitmap bitmap = null;  
  26.          if(diskCacheEnabled){  
  27.              //根据URL在磁盘上查找对应的文件  
  28.              String filePath = getFilePath(url);  
  29.              File file = new File(filePath);  
  30.              //若存在则decode为Bitmap实例  
  31.              if(file.exists()) {  
  32.                  bitmap = BitmapFactory.decodeFile(filePath);  
  33.              }  
  34.          }  
  35.          return bitmap;  
  36.      }  
  37.   
  38.      /** 
  39.       * 获取磁盘缓存路径 
  40.       * @param url 
  41.       * @return 
  42.       */  
  43.      private String getFilePath(String url) {  
  44.          return diskCachePath + getCacheKey(url);  
  45.      }  
  46.   
  47.      /** 
  48.       * 获取缓存Key 
  49.       * @param url 
  50.       * @return 
  51.       */  
  52.      private String getCacheKey(String url) {  
  53.          if(url == null){  
  54.              throw new RuntimeException("Null url passed in");  
  55.          } else {  
  56.              //URL中可能包含一些特殊字符,在将URL转换成文件名时需要做预处理,过滤掉这些字符。  
  57.              return url.replaceAll("[.:/,%?&=]""+").replaceAll("[+]+""+");  
  58.          }  
  59.      }  


那是如何缓存的呢?我们再来看两个方法:

[java]  view plain  copy
 print ?
  1. /** 
  2.      * 将Bitmap存到内存缓存 
  3.      * @param url 
  4.      * @param bitmap 
  5.      */  
  6.     private void cacheBitmapToMemory(final String url, final Bitmap bitmap) {  
  7.         //将数据put到HashMap中,存入的是Bitmap的软引用  
  8.         memoryCache.put(getCacheKey(url), new SoftReference<Bitmap>(bitmap));  
  9.     }  
  10.   
  11.     /** 
  12.      * 将Bitmap存入到磁盘 
  13.      * @param url 
  14.      * @param bitmap 
  15.      */  
  16.     private void cacheBitmapToDisk(final String url, final Bitmap bitmap) {  
  17.   
  18.         /******************************************************* 
  19.          * 将Bitmap存入磁盘缓存是通过线程池ExecutorService实现 
  20.          * 
  21.          * 1.限制同时存在的线程个数 
  22.          * 2.是解决同步问题。smart-image库使用的是只有一个线程的线程池, 
  23.          *******************************************************/  
  24.   
  25.         writeThread.execute(new Runnable() {  
  26.             @Override  
  27.   
  28.             public void run() {  
  29.   
  30.         if(diskCacheEnabled) {  
  31.             BufferedOutputStream ostream = null;  
  32.         <span style="white-space:pre">  </span>try {  
  33.         //调用Bitmap.compress函数按指定压缩格式和压缩质量将Bitmap写到磁盘文件输出流中(在构造方法中根据URL创建对应文件)  
  34.             ostream = new BufferedOutputStream(new FileOutputStream(new File(diskCachePath, getCacheKey(url))), 2*1024);  
  35.             bitmap.compress(CompressFormat.PNG, 100, ostream);  
  36.             } catch (FileNotFoundException e) {  
  37.                 e.printStackTrace();  
  38.             } finally {  
  39.                 try {  
  40.                     if(ostream != null) {  
  41.                         ostream.flush();  
  42.                         ostream.close();  
  43.                     }  
  44.                     } catch (IOException e) {}  
  45.                     }  
  46.                 }  
  47.             }  
  48.         });  
  49.     }  

到此,关于其缓存原理大概就是这么个样子了,如果大家还有不明白的,我后面给出源码,可根据注释进行学习。


我们接着往下看关于SmartImageView和SmartImageTask两个类的类图结构:



从类图可以看到,有两个类:SmartImageTaskSmartImageView,另外还有两个静态类,一个是继承Handler的静态类:

[java]  view plain  copy
 print ?
  1. public static class OnCompleteHandler extends Handler{}  
另一个是抽象的静态类:

[java]  view plain  copy
 print ?
  1. public abstract static class OnCompleteListener{}  

那么如何理解这两个类呢?可能有同学已经从关系图和字面上理解了,咦~ ,难道是一个类专注于后台图片加载处理、另一个则专注于UI处理。哟西~,这位同学,你说对了!确实就是如此。来来来,脸挪过来 ……

SmartImageTask实现了Runnable接口,我们来看看这个类的源码:

[java]  view plain  copy
 print ?
  1. package com.loopj.android.image;  
  2. import android.content.Context;  
  3. import android.graphics.Bitmap;  
  4. import android.os.Handler;  
  5. import android.os.Message;  
  6. /** 
  7.  * 专注于后台图片加载处理的Task类 
  8.  * 实现了Runnable接口 
  9.  */  
  10. public class SmartImageTask implements Runnable {  
  11.     private static final int BITMAP_READY = 0;  
  12.     private boolean cancelled = false;  
  13.     private SmartImage image;  
  14.     private Context context;  
  15.     private OnCompleteHandler onCompleteHandler;  
  16.     public void setOnCompleteHandler(OnCompleteHandler handler){  
  17.         this.onCompleteHandler = handler;  
  18.     }  
  19.   
  20.     //图片加载完成的回调接口OnCompleteListener  
  21.     public static class OnCompleteHandler extends Handler {  
  22.         //将handler定义成static,是为了避免内存泄露,可参考博客:http://blog.csdn.net/gao_chun/article/details/46046637  
  23.         @Override  
  24.         public void handleMessage(Message msg) {  
  25.             Bitmap bitmap = (Bitmap)msg.obj;  
  26.             onComplete(bitmap);  
  27.         }  
  28.         public void onComplete(Bitmap bitmap){};  
  29.     }  
  30.   
  31.     //用于SmartImageView类中加载完成后的回调接口  
  32.     public abstract static class OnCompleteListener {  
  33.         public abstract void onComplete();  
  34.         //这里也说明了此方法的作用:加载图片回调的方法,重写此方法以获取位图的句柄,增加了重载的实现使其与以前版本兼容  
  35.         /*** 
  36.          *  Convient method to get Bitmap after image is loaded. 
  37.          *  Override this method to get handle of bitmap 
  38.          *  Added overloaded implementation to make it backward compatible with previous versions 
  39.          */  
  40.         public void onComplete(Bitmap bitmap){  
  41.             onComplete();  
  42.         }  
  43.     }  
  44.   
  45.     //构造方法,将SmartImage作为参数  
  46.     public SmartImageTask(Context context, SmartImage image) {  
  47.         this.image = image;  
  48.         this.context = context;  
  49.     }  
  50.   
  51.     @Override  
  52.     public void run() {  
  53.         //使用SmartImage的getBitmap函数来获取URL的Bitmap实例  
  54.         if(image != null) {  
  55.             complete(image.getBitmap(context));  
  56.             context = null;  
  57.         }  
  58.     }  
  59.   
  60.     public void cancel() {  
  61.         cancelled = true;  
  62.     }  
  63.   
  64.     /** 
  65.      * 获取Bitmap实例 
  66.      * @param bitmap 
  67.      */  
  68.     public void complete(Bitmap bitmap){  
  69.         if(onCompleteHandler != null && !cancelled) {  
  70.             //获取实例完成后发送消息个Handler  
  71.             onCompleteHandler.sendMessage(onCompleteHandler.obtainMessage(BITMAP_READY, bitmap));  
  72.         }  
  73.     }  
  74. }  

可以看到,在SmartImageTask中实现了回调机制供SmartImageView使用,大家可以结合注释进行阅读。回调主要是以下实现:

[java]  view plain  copy
 print ?
  1. public static class OnCompleteHandler extends Handler {  
  2.         //将handler定义成static,是为了避免内存泄露,可参考博客:http://blog.csdn.net/gao_chun/article/details/46046637  
  3.         @Override  
  4.         public void handleMessage(Message msg) {  
  5.             Bitmap bitmap = (Bitmap)msg.obj;  
  6.             onComplete(bitmap);  
  7.         }  
  8.         public void onComplete(Bitmap bitmap){};  
  9.     }  
  10.     //用于SmartImageView类中加载完成后的回调接口  
  11.     public abstract static class OnCompleteListener {  
  12.         public abstract void onComplete();  
  13.         //这里也说明了此方法的作用:加载图片回调的方法,重写此方法以获取位图的句柄,增加了重载的实现使其与以前版本兼容  
  14.         /*** 
  15.          *  Convient method to get Bitmap after image is loaded. 
  16.          *  Override this method to get handle of bitmap 
  17.          *  Added overloaded implementation to make it backward compatible with previous versions 
  18.          */  
  19.         public void onComplete(Bitmap bitmap){  
  20.             onComplete();  
  21.         }  
  22.     }  

下面我们看看SmartImageView中的核心方法:

[java]  view plain  copy
 print ?
  1. /** 
  2.      * 核心方法,加载图片 
  3.      * @param image SmartImage实例 
  4.      * @param fallbackResource 加载失败的图片 
  5.      * @param loadingResource 加载中的图片 
  6.      * @param completeListener 加载完成后的回调接口 
  7.      */  
  8.     public void setImage(final SmartImage image, final Integer fallbackResource, final Integer loadingResource, final SmartImageTask.OnCompleteListener completeListener) {  
  9.         // Set a loading resource  
  10.         if(loadingResource != null){  
  11.             //不为空则设置loading中的图片  
  12.             setImageResource(loadingResource);  
  13.         }  
  14.   
  15.         // Cancel any existing tasks for this image view  
  16.         if(currentTask != null) {  
  17.             //取消当前执行任务  
  18.             currentTask.cancel();  
  19.             currentTask = null;  
  20.         }  
  21.   
  22.         // Set up the new task  
  23.         currentTask = new SmartImageTask(getContext(), image);  
  24.   
  25.         currentTask.setOnCompleteHandler(new SmartImageTask.OnCompleteHandler() {  
  26.             @Override  
  27.             public void onComplete(Bitmap bitmap) {  
  28.   
  29.                 if(bitmap != null) {  
  30.                     setImageBitmap(bitmap);  
  31.                 } else {  
  32.                     // Set fallback resource  
  33.                     if(fallbackResource != null) {  
  34.                         setImageResource(fallbackResource);  
  35.                     }  
  36.                 }  
  37.   
  38.                 if(completeListener != null){  
  39.                     completeListener.onComplete(bitmap);  
  40.                 }  
  41.             }  
  42.         });  
  43.   
  44.         // Run the task in a threadpool  
  45.         threadPool.execute(currentTask);  
  46.     }  


当需要取消线程池中所有等待和运行的Tast时,刻调用该方法销毁:

[java]  view plain  copy
 print ?
  1. /** 
  2.      * 取消线程池中所有在等待和运行的Task 
  3.      */  
  4.     public static void cancelAllTasks() {  
  5.         threadPool.shutdownNow();  
  6.         threadPool = Executors.newFixedThreadPool(LOADING_THREADS);  
  7.     }  


四、总结

到此,该项目的大致源码就是这么回事了,源码比较简单,个人觉得对于初学者还是有一定帮助的,也建议大家抽时间自己试着去分析一些其他的开源库。记录记录一些东西,正所谓,好记性不如烂博客。



原理Demo链接:http://download.csdn.net/download/gao_chun/9005341

源码分析链接:http://download.csdn.net/download/gao_chun/9005229



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值