android-smart-image-view源码分析

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路径下载图片,如:

/*******************************************************************************
 *
 * Copyright (c) Weaver Info Tech Co. Ltd
 *
 * SmartImageView
 *
 * com.example.smartimageview.SmartImageView.java
 * TODO: File description or class description.
 *
 * @author: gao_chun
 * @since:  2015-8-13
 * @version: 1.0.0
 *
 * @changeLogs:
 *     1.0.0: First created this class.
 *
 ******************************************************************************/
package com.example.smartimageview;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.widget.ImageView;

/**
 * @author gao_chun
 * 继承ImageView控件
 */
public class SmartImageView extends ImageView{
    /**
     * @param context
     * @param attrs
     * @param defStyle
     */
    public SmartImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
    }
    /**
     * @param context
     * @param attrs
     */
    public SmartImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }
    /**
     * @param context
     */
    public SmartImageView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }
    /**
     * 使用Handler刷新UI
     */
    private Handler mHandler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            Bitmap bitmap = (Bitmap) msg.obj;//获取到Bitmap对象
            SmartImageView.this.setImageBitmap(bitmap);//setImageBitmap
        };
    };

    /**
     * 设置图片的网络路径 ,加载一个网络的图片
     * @param path
     */
    public void setImageUrl(final String path){
        new Thread(){
            public void run() {
                try {
                    //创建链接对象
                    URL url = new URL(path);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    //设置请求方式
                    conn.setRequestMethod("GET");
                    //设置Timeout
                    conn.setConnectTimeout(5000);
                    int code = conn.getResponseCode();
                    //返回码为200时
                    if(code == 200){
                        //读取服务端返回的数据流
                        InputStream is = conn.getInputStream();
                        //将该图片数据流转化成位图
                        Bitmap bitmap = BitmapFactory.decodeStream(is);
                        //Handler发送消息给主线程,更新UI
                        Message msg = Message.obtain();
                        msg.obj = bitmap;
                        mHandler.sendMessage(msg);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
        }.start();
    }
}

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

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="获取网络图片" />

    <com.example.smartimageview.SmartImageView
        android:id="@+id/iv_coco"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/btn"
        android:scaleType="fitXY" />

</RelativeLayout>

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

package com.example.smartimageview;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;

public class MainActivity extends ActionBarActivity implements OnClickListener{

    String mUrl = "http://e.hiphotos.baidu.com/baike/c0%3Dbaike150%2C5%2C5%2C150%2C50/sign=79b94a3be21190ef15f69a8daf72f673/bd3eb13533fa828b5a920735f81f4134960a5ab7.jpg";

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn) {

            SmartImageView siv = (SmartImageView) findViewById(R.id.iv_coco);
            siv.setImageUrl(mUrl);
        }

    }

}

效果图如下:

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

三、源码分析

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

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

package com.loopj.android.image;
import android.content.Context;
import android.graphics.Bitmap;
public interface SmartImage {
    public Bitmap getBitmap(Context context);
}

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

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

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

package com.loopj.android.image;

import android.content.Context;
import android.graphics.Bitmap;

/**
 * 实现SmartImage接口
 */
public class BitmapImage implements SmartImage {

    //定义Bitmap对象
    private Bitmap bitmap;

    //构造方法
    public BitmapImage(Bitmap bitmap) {
        this.bitmap = bitmap;
    }

    //实现getBitmap方法
    public Bitmap getBitmap(Context context) {
        return bitmap;
    }
}

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

package com.loopj.android.image;

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

public class WebImage implements SmartImage {

    //超时设置
    private static final int CONNECT_TIMEOUT = 5000;
    private static final int READ_TIMEOUT = 10000;

    //缓存对象
    private static WebImageCache webImageCache;

    //WebImage的构造方法,获取URL
    private String url;
    public WebImage(String url) {
        this.url = url;
    }

    //实现构造方法,处理相应的业务逻辑
    public Bitmap getBitmap(Context context) {
        // Don‘t leak context
        if(webImageCache == null) {
            webImageCache = new WebImageCache(context);
        }

        // Try getting bitmap from cache first
        //此处做了简单的二级缓存(内存缓存和磁盘缓存)
        Bitmap bitmap = null;

        if(url != null) {
            //先从缓存获取bitmap对象
            bitmap = webImageCache.get(url);
            if(bitmap == null) {
                //未找到则从网络加载
                bitmap = getBitmapFromUrl(url);

                if(bitmap != null){
                    //加载后将bitmap对象put到缓存中
                    webImageCache.put(url, bitmap);
                }
            }
        }

        return bitmap;
    }

    /**
     * 根据Url获取网络图片资源
     * @param url
     * @return
     */
    private Bitmap getBitmapFromUrl(String url) {
        Bitmap bitmap = null;

        try {
            URLConnection conn = new URL(url).openConnection();
            conn.setConnectTimeout(CONNECT_TIMEOUT);
            conn.setReadTimeout(READ_TIMEOUT);
            bitmap = BitmapFactory.decodeStream((InputStream) conn.getContent());
        } catch(Exception e) {
            e.printStackTrace();
        }

        return bitmap;
    }

    /**
     * 提供移除缓存的方法
     * @param url
     */
    public static void removeFromCache(String url) {
        if(webImageCache != null) {
            webImageCache.remove(url);
        }
    }
}

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

package com.loopj.android.image;

import java.io.InputStream;

import android.content.ContentUris;
import android.content.ContentResolver;
import android.content.Context;
import android.provider.ContactsContract;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;

/*
 * 获取联系人头像资源
 */
public class ContactImage implements SmartImage {

    /****************************************************************************************************
     * 注:Android系统中访问其他app的数据时,一般都是通过ContentProvider实现,
     * 一个ContentProvider类实现了一组标准的方法接口,能够让其他app保存或者读取它提供的各种数据类型。
     * 其他app通过ContentResolver接口就可以访问ContentProvider提供的数据。
     * 备注:(获取的是手机的联系人头像,而不是Sim卡中的联系人头像的。Sim卡由于容量限制等原因,无联系人头像数据)
     *
     * --使用时记得添加获取联系人头像的权限
     *****************************************************************************************************/

    //联系人头像ID
    private long contactId;

    public ContactImage(long contactId) {
        this.contactId = contactId;
    }

    public Bitmap getBitmap(Context context) {
        Bitmap bitmap = null;
        //获取ContentResolver实例
        ContentResolver contentResolver = context.getContentResolver();

        try {
            //根据ID生成查找联系人的Uri

            /*
             关于withAppendedId方法:

            Open Declaration Uri android.content.ContentUris.withAppendedId(Uri contentUri, long id)

            Appends the given ID to the end of the path.

            Parameters:
            contentUri -- to start with
            id -- to append

            Returns:
            a new URI with the given ID appended to the end of the path */

            Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
            //调用Contact类中的openContactPhotoInputStream获得图像InputStream对象
            InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, uri);
            if(input != null) {
                //将数据流decode为bitmap对象并返回
                bitmap = BitmapFactory.decodeStream(input);
            }
        } catch(Exception e) {
            e.printStackTrace();
        }

        return bitmap;
    }
}

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

private static final int DISPLAY_NAME_INDEX = 0;
    private static final int PHONE_NUMBER_INDEX = 1;
    private static final int PHOTO_ID_INDEX = 2;
    private static final int CONTACT_ID_INDEX = 3;
    private static final String[] PHONES_PROJECTION = new String[] {
        Phone.DISPLAY_NAME, Phone.NUMBER, Phone.PHOTO_ID,Phone.CONTACT_ID };   

    private void getPhoneContact(Context context) {
        ContentResolver contentResolver = context.getContentResolver();
        Cursor cursor = contentResolver.query(Phone.CONTENT_URI, PHONES_PROJECTION, null, null, null);
        if (cursor != null) {
            while(cursor.moveToNext()) {
                String displayName = cursor.getString(DISPLAY_NAME_INDEX); // 联系人名字
                String phoneNum = cursor.getString(PHONE_NUMBER_INDEX);    // 联系人号码
                Long contactId = cursor.getLong(CONTACT_ID_INDEX);         // 联系人id
                Long photoId = cursor.getLong(PHOTO_ID_INDEX);             // 联系人头像id(photoId大于0时表示联系人有头像)
            }  

            cursor.close();
        }
    } 

好了,讲到这里,相信大家通过注释可以对这三个类有一定的了解了。可能有同学注意到了,那个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类中对内存缓存和磁盘缓存的实现,先看其构造方法:

//构造方法,构建两级缓存空间
	public WebImageCache(Context context) {

		// Set up in-memory cache store
		memoryCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>();

		// Set up disk cache store
		Context appContext = context.getApplicationContext();
		diskCachePath = appContext.getCacheDir().getAbsolutePath() + DISK_CACHE_PATH;
		//先根据URL在cache目录中生成对应的文件
		File outFile = new File(diskCachePath);
		outFile.mkdirs();
		diskCacheEnabled = outFile.exists();

		// Set up threadpool for image fetching tasks
		writeThread = Executors.newSingleThreadExecutor();
	}

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

/**
	 * 从Memory获取bitmap实例
	 * @param url
	 * @return
	 */
	 private Bitmap getBitmapFromMemory(String url) {
		Bitmap bitmap = null;
		//通过memoryCache取出软引用
		SoftReference<Bitmap> softRef = memoryCache.get(getCacheKey(url));
		//判断系统有无回收该引用
		if(softRef != null){
			//get
			bitmap = softRef.get();
		}

		return bitmap;
	 }

	 /**
	  * 从Disk获取Bitmap实例
	  * @param url
	  * @return
	  */
	 private Bitmap getBitmapFromDisk(String url) {
		 Bitmap bitmap = null;
		 if(diskCacheEnabled){
			 //根据URL在磁盘上查找对应的文件
			 String filePath = getFilePath(url);
			 File file = new File(filePath);
			 //若存在则decode为Bitmap实例
			 if(file.exists()) {
				 bitmap = BitmapFactory.decodeFile(filePath);
			 }
		 }
		 return bitmap;
	 }

	 /**
	  * 获取磁盘缓存路径
	  * @param url
	  * @return
	  */
	 private String getFilePath(String url) {
		 return diskCachePath + getCacheKey(url);
	 }

	 /**
	  * 获取缓存Key
	  * @param url
	  * @return
	  */
	 private String getCacheKey(String url) {
		 if(url == null){
			 throw new RuntimeException("Null url passed in");
		 } else {
			 //URL中可能包含一些特殊字符,在将URL转换成文件名时需要做预处理,过滤掉这些字符。
			 return url.replaceAll("[.:/,%?&=]", "+").replaceAll("[+]+", "+");
		 }
	 }

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

/**
	 * 将Bitmap存到内存缓存
	 * @param url
	 * @param bitmap
	 */
	private void cacheBitmapToMemory(final String url, final Bitmap bitmap) {
		//将数据put到HashMap中,存入的是Bitmap的软引用
		memoryCache.put(getCacheKey(url), new SoftReference<Bitmap>(bitmap));
	}

	/**
	 * 将Bitmap存入到磁盘
	 * @param url
	 * @param bitmap
	 */
	private void cacheBitmapToDisk(final String url, final Bitmap bitmap) {

		/*******************************************************
		 * 将Bitmap存入磁盘缓存是通过线程池ExecutorService实现
		 *
		 * 1.限制同时存在的线程个数
		 * 2.是解决同步问题。smart-image库使用的是只有一个线程的线程池,
		 *******************************************************/

		writeThread.execute(new Runnable() {
			@Override

			public void run() {

		if(diskCacheEnabled) {
			BufferedOutputStream ostream = null;
			try {
		//调用Bitmap.compress函数按指定压缩格式和压缩质量将Bitmap写到磁盘文件输出流中(在构造方法中根据URL创建对应文件)
			ostream = new BufferedOutputStream(new FileOutputStream(new File(diskCachePath, getCacheKey(url))), 2*1024);
			bitmap.compress(CompressFormat.PNG, 100, ostream);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} finally {
				try {
					if(ostream != null) {
						ostream.flush();
						ostream.close();
					}
					} catch (IOException e) {}
					}
				}
			}
		});
	}

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

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

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

public static class OnCompleteHandler extends Handler{}

另一个是抽象的静态类:

public abstract static class OnCompleteListener{}

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

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

package com.loopj.android.image;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
/**
 * 专注于后台图片加载处理的Task类
 * 实现了Runnable接口
 */
public class SmartImageTask implements Runnable {
	private static final int BITMAP_READY = 0;
	private boolean cancelled = false;
	private SmartImage image;
	private Context context;
	private OnCompleteHandler onCompleteHandler;
	public void setOnCompleteHandler(OnCompleteHandler handler){
		this.onCompleteHandler = handler;
	}

	//图片加载完成的回调接口OnCompleteListener
	public static class OnCompleteHandler extends Handler {
		//将handler定义成static,是为了避免内存泄露,可参考博客:http://blog.csdn.net/gao_chun/article/details/46046637
		@Override
		public void handleMessage(Message msg) {
			Bitmap bitmap = (Bitmap)msg.obj;
			onComplete(bitmap);
		}
		public void onComplete(Bitmap bitmap){};
	}

	//用于SmartImageView类中加载完成后的回调接口
	public abstract static class OnCompleteListener {
		public abstract void onComplete();
		//这里也说明了此方法的作用:加载图片回调的方法,重写此方法以获取位图的句柄,增加了重载的实现使其与以前版本兼容
		/***
		 *  Convient method to get Bitmap after image is loaded.
		 *  Override this method to get handle of bitmap
		 *  Added overloaded implementation to make it backward compatible with previous versions
		 */
		public void onComplete(Bitmap bitmap){
			onComplete();
		}
	}

	//构造方法,将SmartImage作为参数
	public SmartImageTask(Context context, SmartImage image) {
		this.image = image;
		this.context = context;
	}

	@Override
	public void run() {
		//使用SmartImage的getBitmap函数来获取URL的Bitmap实例
		if(image != null) {
			complete(image.getBitmap(context));
			context = null;
		}
	}

	public void cancel() {
		cancelled = true;
	}

	/**
	 * 获取Bitmap实例
	 * @param bitmap
	 */
	public void complete(Bitmap bitmap){
		if(onCompleteHandler != null && !cancelled) {
			//获取实例完成后发送消息个Handler
			onCompleteHandler.sendMessage(onCompleteHandler.obtainMessage(BITMAP_READY, bitmap));
		}
	}
}

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

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

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

/**
	 * 核心方法,加载图片
	 * @param image SmartImage实例
	 * @param fallbackResource 加载失败的图片
	 * @param loadingResource 加载中的图片
	 * @param completeListener 加载完成后的回调接口
	 */
	public void setImage(final SmartImage image, final Integer fallbackResource, final Integer loadingResource, final SmartImageTask.OnCompleteListener completeListener) {
		// Set a loading resource
		if(loadingResource != null){
			//不为空则设置loading中的图片
			setImageResource(loadingResource);
		}

		// Cancel any existing tasks for this image view
		if(currentTask != null) {
			//取消当前执行任务
			currentTask.cancel();
			currentTask = null;
		}

		// Set up the new task
		currentTask = new SmartImageTask(getContext(), image);

		currentTask.setOnCompleteHandler(new SmartImageTask.OnCompleteHandler() {
			@Override
			public void onComplete(Bitmap bitmap) {

				if(bitmap != null) {
					setImageBitmap(bitmap);
				} else {
					// Set fallback resource
					if(fallbackResource != null) {
						setImageResource(fallbackResource);
					}
				}

				if(completeListener != null){
					completeListener.onComplete(bitmap);
				}
			}
		});

		// Run the task in a threadpool
		threadPool.execute(currentTask);
	}

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

/**
	 * 取消线程池中所有在等待和运行的Task
	 */
	public static void cancelAllTasks() {
		threadPool.shutdownNow();
		threadPool = Executors.newFixedThreadPool(LOADING_THREADS);
	}


四、总结

到此,该项目的大致源码就是这么回事了,源码比较简单,个人觉得对于初学者还是有一定帮助的,也建议大家抽时间自己试着去分析一些其他的开源库。记录记录一些东西,你会发现,分享快乐~

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

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

【注:转载注明gao_chun的BLOG http://blog.csdn.net/gao_chun/article/details/47610137

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-10 13:38:43

android-smart-image-view源码分析的相关文章

Android 上千实例源码分析以及开源分析

Android 上千实例源码分析以及开源分析(百度云分享) 要下载的直接翻到最后吧,项目实例有点多. 首先 介绍几本书籍(下载包中)吧. 01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 06_Android的GUI系统 07_Android的Audio系统 08_Android的Video 输入输出系统 09_Android的多媒体系统 10_

深度理解Android InstantRun原理以及源码分析

深度理解Android InstantRun原理以及源码分析 @Author 莫川 Instant Run官方介绍 简单介绍一下Instant Run,它是Android Studio2.0以后新增的一个运行机制,能够显著减少你第二次及以后的构建和部署时间.简单通俗的解释就是,当你在Android Studio中改了你的代码,Instant Run可以很快的让你看到你修改的效果.而在没有Instant Run之前,你的一个小小的修改,都肯能需要几十秒甚至更长的等待才能看到修改后的效果. 传统的代

Android消息处理机制(源码分析)

前言 虽然一直在做应用层开发,但是我们组是核心系统BSP,了解底层了解Android的运行机制还是很有必要的.就应用程序而言,Android系统中的Java应用程序和其他系统上相同,都是靠消息驱动来工作的,它们大致的工作原理如下: 1. 有一个消息队列,可以往这个消息队列中投递消息. 2. 有一个消息循环,不断从消息队列中取出消息,然后处理 . 为了更深入的理解Android的消息处理机制,这几天空闲时间,我结合<深入理解Android系统>看了Handler.Looper.Message这几

Android异步消息传递机制源码分析&amp;&amp;相关知识常被问的面试题

1.Android异步消息传递机制有以下两个方式:(异步消息传递来解决线程通信问题) handler 和 AsyncTask 2.handler官方解释的用途: 1).定时任务:通过handler.postDelay(Runnable r, time)来在指定时间执行msg. 2).线程间通信:在执行较为耗时操作的时候,在子线程中执行耗时任务,然后handler(主线程的)把执行的结果通过sendmessage的方式发送给UI线程去执行用于更新UI. 3.handler源码分析 一.在Activ

Android之Volley框架源码分析

临近毕业,各种事情各种忙.我也没有认真专注写过博客,最近仔细看了Volley框架的使用及其源码,思前想后,想挑战一下自己,还是写一篇博客来分享,如有错误,欢迎吐槽. Volley简介 网络请求是一个App很重要的一部分,android系统只是提供了一个平台,而android应用则是基于这个平台上进行展示数据,起到与用户进行交互的作用,数据来源于服务端,而二者之间必须通过互联网进行传输数据,在Android系统发布初期,很多开发者都是在Apache协会的Http协议的基础上进行网络请求方法的封装,

Android 热修复 Tinker 源码分析之DexDiff / DexPatch

在上一篇文章中,我们介绍了Android 热修复 Tinker接入及源码浅析,里面包含了热修的一些背景知识,从tinker对dex文件的处理来看,源码大体上可以分为3部分阅读: 在应用中对patch的合并与加载,已经在上篇文章中详细介绍过了Android 热修复 Tinker接入及源码浅析 详细的dex patch,dex diff算法 tinker gradle plugin相关知识 tinker有个非常大的亮点就是自研发了一套dex diff.patch相关算法.本篇文章主要目的就是分析该算

Android事件分发机制源码分析

小小感慨一下,做android有一段时间了,一直以来都是习惯整理笔记存到有道笔记上,没有写博客的习惯.以后逐步分类整理出来,也算"复习"一遍了 - _ - . android的事件分发相关的方法有三个: 1.public booleandispatchTouchEvent(MotionEvent ev) 2.public boolean onInterceptTouchEvent(MotionEvent ev) 3.public booleanonTouchEvent(MotionEv

android 6.0 SystemUI源码分析(1)-SystemUI介绍 转

1. SystemUI介绍 SystemUI是一个系统应用,主要功能有: 1)状态栏信息显示,比如电池,wifi信号,3G/4G等icon显示 2)通知面板,比如系统消息,第三方应用消息,都是在通知面板显示. 3)近期任务栏显示面板.比如长按主页或近期任务快捷键,可以显示近期使用的应用. 4)提供截图服务.比如电源+音量加可以截图. 5)提供壁纸服务.比如壁纸的显示. 6)提供屏保服务. 7)系统UI显示.比如系统事件到来时,显示系统UI提示用户. SystemUI源码路径: framework

MonkeyRunner和Android设备通讯方式源码分析

如前文<谁动了我的截图?--Monkeyrunner takeSnapshot方法源码跟踪分析>所述,本文主要会尝试描述android的自动化测试框架MonkeyRunner究竟是如何和目标设备进行通信的. 在上一篇文章中我们其实已经描述了其中一个方法,就是通过adb协议发送adb服务器请求的方式驱动android设备的adbd守护进程去获取FrameBuffer的数据生成屏幕截图.那么MonkeyRunner还会用其他方式和目标设备进行通信吗?答案是肯定的,且看我们一步步分析道来. 1.概述

拨云见日---android异步消息机制源码分析

做过windows GUI的同学应该清楚,一般的GUI操作都是基于消息机制的,应用程序维护一个消息队列,开发人员编写对应事件的回调函数就能实现我们想要的操作 其实android系统也和windows GUI一样,也是基于消息机制,今天让我们通过源码来揭开android消息机制的神秘面纱 谈起异步消息,就不能不提及Handler,在安卓中,由于主线程中不能做耗时操作,所以耗时操作必须让子线程执行,而且只能在主线程(即UI线程)中执行UI更新操作,通过Handler发送异步消息,我们就能更新UI,一