Android SmartImageView源码分析

来源:互联网 发布:淘宝工具软件 编辑:程序博客网 时间:2024/06/05 18:13

    前言:突然想把用过的图片加载的框架整理下,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的一个实现,为什么这么说呢?来看一下类图:


这里只关心网络的实现类,也就是WebImage。以下的分析围绕红色方框内进行展开。
    
    通过以上大致可以看出,当输入的url地址不为空的时候首先访问webImageCatch(),如果webImageCatch中不包含所要访问的资源那么久访问网络下载资源,如果在网络下载到该图片资源则将该资源存入到webImageCatch,并返回下载的bitmap对象。那么下次再访问该资源的时候就可以直接在webImageView中获取该图片资源而不必再去访问网络。
    接下来看下webImageCatch中是如何设计的:

可以大致了解到该类的主要作用就是处理内存以及本地图片缓存,分为二级缓存。

获取图片:

    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对应的图片则将其返回;
总结下获取图片:获取图片的时候首先读取缓存,在读取缓存的时候先检测内存缓存中是否存在,如果不存在则再读取sdcard中的本地缓存是否存在,二级缓存都不存在则访问网络下载图片;不过我觉得在访问sdcard返回图片的时候可以向内存中拷贝一份,因为访问sdcard存在的时候肯定是首先访问内存中不存在,那么为了下次再访问提高速度可以向内存中拷贝一份,最好是拷贝之前检查下可用内存还有多少,这样也可在避免内存溢出的情况下提高下次加载该图片的效率。

保存图片:

保存图片到缓存同获取缓存图片一样,也是比较简单的。
    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) {}                    }                }            }        });    }
    至此,SmartImageView开源项目的主体部分,网络下载图片以及图片的二级缓存就分析完毕了,尽管该项目在缓存的处理上还有些欠缺,比如没有限制在内存的缓存大小,这样在快速加载多个比较大的图片的时候还是会引起OutOfMemory(OOM)异常,而且缓存在sdcard中也没有做大小的限制。尽管还有些缺陷,但是它给我们提供了一个解决网络获取图片的思路,以及二级缓存图片的解决思想。

五、下载传送门

  SmartImageView库以及示例程序

2 0
原创粉丝点击