Android SmartImageView源码分析

    前言:突然想把用过的图片加载的框架整理下,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中也没有做大小的限制。尽管还有些缺陷,但是它给我们提供了一个解决网络获取图片的思路,以及二级缓存图片的解决思想。

五、下载传送门


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值