前言:突然想把用过的图片加载的框架整理下,SmartImageView是所使用的最早的网络加载图片的小框架了,当时还为它的强大惊叹不已。不过也好久没使用过了,因为它的还算比较简单,就作为开源框架源码分析之旅的开端吧。
一、源码下载
SmartImageView也是托管在GitHub的一个开源项目,在GitHub下载到源码。下载地址
下载到的源码为6个类,我一般习惯用库的形式把框架加载到我的工程中去,所以我建立了一个项目然后把这6个类考进去,然后把项目作为library。 OK,初步工作就做好了。
先看下SmartImageView的自我介绍:Android ImageView replacement which allows image loading from URLs or contact address book, with caching. 可以大致看出SmartImageView是ImageView的一种加强形式,它具有缓存机制并且能直接加载网络图片以及通讯录图片。
二、实例应用
那么来举个例子吧:
先看下布局文件,很简单一个SmartImageView控件,一个按钮:
<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" >
<com.loopj.android.image.SmartImageView
android:id="@+id/main_act_siv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<Button
android:id="@+id/main_act_btn"
android:text="点击加载网络图片"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
然后是MainActivity中,也是很简单,设置按钮的监听,当点击的时候就去给SmartImageView设置网络图片:
/** SmartImageView控件 */
private SmartImageView mSmartImageView;
/** 加载网络图片按钮 */
private Button loadImage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSmartImageView = (SmartImageView) this.findViewById(R.id.main_act_siv);
loadImage = (Button)this.findViewById(R.id.main_act_btn);
loadImage.setOnClickListener(this); // 加载图片按钮监听设置
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.main_act_btn:
mSmartImageView.setImageUrl("http://pic6.nipic.com/20100317/4085465_113938064402_2.jpg");
break;
}
}
需要注意的一点是,这里有访问网络的操作,需要添加网络访问权限。
<uses-permission android:name="android.permission.INTERNET"/>
整体效果图:
三、源码分析
我们就一点点的点进去看看吧,首先从SmartImageView的setImageUrl看起:
// Helpers to set image by URL
public void setImageUrl(String url) {
setImage(new WebImage(url));
}
public void setImageUrl(String url, SmartImageTask.OnCompleteListener completeListener) {
setImage(new WebImage(url), completeListener);
}
public void setImageUrl(String url, final Integer fallbackResource) {
setImage(new WebImage(url), fallbackResource);
}
public void setImageUrl(String url, final Integer fallbackResource, SmartImageTask.OnCompleteListener completeListener) {
setImage(new WebImage(url), fallbackResource, completeListener);
}
public void setImageUrl(String url, final Integer fallbackResource, final Integer loadingResource) {
setImage(new WebImage(url), fallbackResource, loadingResource);
}
public void setImageUrl(String url, final Integer fallbackResource, final Integer loadingResource, SmartImageTask.OnCompleteListener completeListener) {
setImage(new WebImage(url), fallbackResource, loadingResource, completeListener);
}
可以看到有一大串重载的方法,可以设置加载的网络图片url,加载中显示的图片id,加载失败显示的图片id,以及加载完成的接口回调等等,在示例中调用的是最简单的只有一个参数的方法。
public void setImageUrl(String url) {
setImage(new WebImage(url));
}
setImage()这里传入了一个WebImage的对象,跟进入看下具体代码:
public void setImage(final SmartImage image, final Integer fallbackResource, final Integer loadingResource, final SmartImageTask.OnCompleteListener completeListener) {
// 设置下载的时候显示的图片,这里我们传入的参数为空,不去关心
if(loadingResource != null){
setImageResource(loadingResource);
}
// 停止正在为该SmartImageView下载网络图片的线程,代码健壮性考虑,不去关心
if(currentTask != null) {
currentTask.cancel();
currentTask = null;
}
// 创建一个新的线程
currentTask = new SmartImageTask(getContext(), image); // SmartImageTask实现Runnable方法
currentTask.setOnCompleteHandler(new SmartImageTask.OnCompleteHandler() {
@Override
public void onComplete(Bitmap bitmap) {
if(bitmap != null) {
setImageBitmap(bitmap);
} else {
// 如果下载失败,责显示预设的加载失败时显示的图片
if(fallbackResource != null) {
setImageResource(fallbackResource);
}
}
// 设置图片下载完成的回调回调,这里我们传入的参数为空,不去关心
if(completeListener != null){
completeListener.onComplete(bitmap);
}
}
});
// 将该下载图片的线程添加到线程池中并执行
threadPool.execute(currentTask);
}
可以看到,这在里执行了设置图片的操作,在下载之前设置显示为预设的加载之前的图片,然后先将currentTask置为空,然后创建新Task,即SmartImageView只是展示图片的作用,获取图片的操作在将currentTask添加到theadPool线程池后去执行。既然SmartImageTask实现了Runnable接口,那肯定重要的方法就是复写的run方法了。
@Override
public void run() {
if(image != null) {
complete(image.getBitmap(context));
context = null;
}
}
在run方法中只执行了一个方法complete(),但是作为参数的image.getBitmap()绝对是该框架的核心代码,在该方法中去获取了图片,并返还获取到的图片。
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 = webImageCache.get(url);
if(bitmap == null) {
bitmap = getBitmapFromUrl(url);
if(bitmap != null){
webImageCache.put(url, bitmap);
}
}
}
return bitmap;
}
可见在这里加入了缓存机制,稍后再认真分析缓存机制,先瞄一眼网络下载的方法:
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;
}
可见网络下载图片的方法还算写的比较简单的,没有考虑到会出现内存溢出的情况,接着看些complete()方法:
public void complete(Bitmap bitmap){
if(onCompleteHandler != null && !cancelled) {
onCompleteHandler.sendMessage(onCompleteHandler.obtainMessage(BITMAP_READY, bitmap));
}
}
通过这个方法执行的必要条件也看到了在SmartImageView的setImage()中设置setOnCompleteHandler()的必要性,这里是通过传递的Handler对象来进行Handler消息机制的传递的,将获取到的图片通过Handler发送到主线程去执行更新界面的操作,既然是Handler机制,那么重要的方法肯定是handleMessage()方法了。
public static class OnCompleteHandler extends Handler {
@Override
public void handleMessage(Message msg) {
Bitmap bitmap = (Bitmap)msg.obj;
onComplete(bitmap);
}
public void onComplete(Bitmap bitmap){};
}
这个就是我们在SmartImageView中创建的Handler实例对象的主体,通过handleMessage()中调用的onComplete方法将获取到的图片回调给SmartImageView进行处理。
总结下上面分析的过程:
ok,到此访问网络下载图片并显示到界面的过程就分析清楚了。
四、缓存机制分析
其实在源码分析中给出的getBitmap()的代码只是WebImage的一个实现,为什么这么说呢?来看一下类图:
获取图片:
public Bitmap get(final String url) {
Bitmap bitmap = null;
// Check for image in memory
bitmap = getBitmapFromMemory(url);
// Check for image on disk cache
if(bitmap == null) {
bitmap = getBitmapFromDisk(url);
// Write bitmap back into memory cache
if(bitmap != null) {
cacheBitmapToMemory(url, bitmap);
}
}
return bitmap;
}
通过以上可以看到,在缓存获取的时候首先在内存缓存中去获取,如果内存缓存中没有责去sdcard缓存中去获取,返回获取的bitmap,如果都没有获取到责返回null;接下来分别看下具体是如何获取缓存图片的。
private Bitmap getBitmapFromMemory(String url) {
Bitmap bitmap = null;
SoftReference<Bitmap> softRef = memoryCache.get(getCacheKey(url));
if(softRef != null){
bitmap = softRef.get();
}
return bitmap;
}
获取内存缓存,内存缓存的机制是Map的键为String类型的图片url地址(经过转换后的),值为软引用类型的bitmap对象,如果获取到该url对应的图片则将其返回;
private Bitmap getBitmapFromDisk(String url) {
Bitmap bitmap = null;
if(diskCacheEnabled){
String filePath = getFilePath(url);
File file = new File(filePath);
if(file.exists()) {
bitmap = BitmapFactory.decodeFile(filePath);
}
}
return bitmap;
}
获取sdcard缓存,sdcard缓存的机制是将url地址
(经过转换后的)作为文件的名字,将bitmap保存为文件,如果获取到该url对应的图片则将其返回;
保存图片:
public void put(String url, Bitmap bitmap) {
cacheBitmapToMemory(url, bitmap);
cacheBitmapToDisk(url, bitmap);
}
可以看到在网络获取到图片之后分别将该bitmap缓存到内存以及sdcard中:
private void cacheBitmapToMemory(final String url, final Bitmap bitmap) {
memoryCache.put(getCacheKey(url), new SoftReference<Bitmap>(bitmap));
}
private void cacheBitmapToDisk(final String url, final Bitmap bitmap) {
writeThread.execute(new Runnable() {
@Override
public void run() {
if(diskCacheEnabled) {
BufferedOutputStream ostream = null;
try {
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) {}
}
}
}
});
}