热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【JavaAndroid开源库代码剖析】のandroidsmartimageview

Android应用开发已经进入到相对成熟的阶段,特别在国外,涌现出了各式各样的成熟稳定的开源库,供普通开发者使用。这种情况虽然极大加速了a

Android应用开发已经进入到相对成熟的阶段,特别在国外,涌现出了各式各样的成熟稳定的开源库,供普通开发者使用。这种情况虽然极大加速了app开发的进程,但同时带来的问题是大多数普通开发者在使用这些开源库的时候只是止步于知道怎么使用它,但对开源库的底层实现原理并不清楚,或者不怎么深究,导致的问题很多:1)当开源库出现bug时,不能够很好很快的定位出问题;2)自己日常的代码编写只局限于实现app的业务逻辑,太上层,对技术水平的提升没有多大的好处;3)对追求完美的人来说,只有对自己项目中所有代码实现的原理都清楚的时候,才会安心,才会有成就感;4)当自己项目需要写基础库代码时,如果已经熟知各种开源库的实现,那么更能设计出好的架构,写出好的代码。

以上种种的解决方案就是多学习多研究开源库的源码,了解其运行机理,从而提升自身的技术积累,这就是本系列的初衷。本系列将选取各种常见或者不常见的开源库,只要它有剖析的价值,刚开始大部分将是基于Java语言的,后续会逐渐覆盖Objective C以及C、C++、PHP等语言。同时欢迎同学们推荐自己想了解的开源库,我会在甄别后排进本系列日程安排中。

James Smith,网名loopj,在Android平台上,因为android-async-http(https://github.com/loopj/android-async-http)这个开源库而知名的,本系列我们会仔细剖析这个库,但不是现在,刚开始我们稍微来个简单一点的,同样出自于loopj之手,名为android-smart-image-view(https://github.com/loopj/android-smart-image-view)

从github上将代码检出,我们可以看到整个项目的代码只包含7个Java源文件,这个库是对Android SDK中的ImageView控件的扩展,方便异步加载网络上指定URL的图片,以及系统联系人的头像等,同时,提供了简单可扩展的框架,方便使用者根据实际图片的来源进行扩展。SmartImageView的使用方法和ImageView类似,具体可参见http://loopj.com/android-smart-image-view/上面的说明。

android-smart-image-view扩展自ImageView,使其方便地显示不同来源的图片资源,因此,首先需要定义一个接口,来表示图片获取这样一个公共的行为。而在Android中,图片最终在绘制到画布canvas上的时候,都是以位图bitmap表示的,因此,接口定义如下:

public interface SmartImage {public Bitmap getBitmap(Context context);
}


根据图片来源的不同,分别实现SmartImage接口,并在getBitmap函数中处理图片获取的逻辑,类图结构如下:

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;}


 

【二级缓存实现】

为了加快图片的加载速度,smart-image库引入了简单的二级缓存,我们知道,数据获取速度取决于物理介质,一般是内存>磁盘>网络,因此,在加载某个URL的图片时,会优先判断是否命中内存缓存,没有则查找磁盘缓存,最终才会考虑从网络上加载,同时更新内存缓存和磁盘缓存记录。

考虑到缓存查找的速度问题,在实现内存缓存时一般都会使用类似哈希表这样查找时间复杂度低的数据结构。由于存在多个线程同时在哈希表中查找的情况,因此需要考虑多线程并发访问的问题,内存缓存的实现使用ConcurrentHashMap也就在情理之中了。Android平台上app的内存是有限制的,当内存超过这个限制时,会出现OOM(OutOfMemory),为了避免这个问题,内存缓存中我们不会直接持有Bitmap实例的引用,而是通过SoftReference来持有Bitmap对象的软引用,如果一个对象具有软引用,内存空间足够时,垃圾回收器不会回收它,只有在内存空间不足时,才会回收这些对象占用的内存。因此,软引用通常用来实现内存敏感的高速缓存。

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

两级缓存空间的建立在WebImageCache类的构造函数中进行,代码如下:

public WebImageCache(Context context) {// Set up in-memory cache storememoryCache = new ConcurrentHashMap>();// Set up disk cache storeContext appContext = context.getApplicationContext();diskCachePath = appContext.getCacheDir().getAbsolutePath() + DISK_CACHE_PATH;File outFile = new File(diskCachePath);outFile.mkdirs();diskCacheEnabled = outFile.exists();// Set up threadpool for image fetching taskswriteThread = Executors.newSingleThreadExecutor();}


 

判断Bitmap是否命中内存缓存的代码如下所示,就是先取出Bitmap的软引用,并判断是否已经被系统回收,如果没有就从软引用中取出Bitmap实例:

private Bitmap getBitmapFromMemory(String url) {Bitmap bitmap = null;SoftReference softRef = memoryCache.get(getCacheKey(url));if(softRef != null){bitmap = softRef.get();}return bitmap;}


 

判断Bitmap是否命中磁盘缓存的代码如下所示,基本原理就是根据URL在磁盘上查找对应的文件,如果存在,就将其转换成Bitmap实例返回。由于URL中可能包含一些不能出现在文件名中的特殊字符,因此,在讲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;}private String getFilePath(String url) {return diskCachePath + getCacheKey(url);}private String getCacheKey(String url) {if(url == null){throw new RuntimeException("Null url passed in");} else {return url.replaceAll("[.:/,%?&=]", "+").replaceAll("[+]+", "+");}}


 

将Bitmap存到内存缓存的步骤很简单,就是往HashMap中添加一个数据而已,不过要注意存的是Bitmap的软引用。代码如下所示。

private void cacheBitmapToMemory(final String url, final Bitmap bitmap) {memoryCache.put(getCacheKey(url), new SoftReference(bitmap));}


 

往磁盘缓存中添加Bitmap是通过线程池ExecutorService实现的,一方面是限制同时存在的线程个数,另一方面是解决同步问题。smart-image库使用的是只有一个线程的线程池,在WebImageCache的构造函数中可以看到,因此,磁盘缓存的添加是顺序进行的。生成缓存的过程是先根据URL在cache目录中生成对应的文件,然后调用Bitmap.compress函数按指定压缩格式和压缩质量将Bitmap写到磁盘文件输出流中。

private void cacheBitmapToDisk(final String url, final Bitmap bitmap) {writeThread.execute(new Runnable() {@Overridepublic 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) {}}}}});}


 

至此,总算将上面类图中相关类介绍完毕。接着就来看另外一个类图:

这个类图中有SmartImageTask和SmartImageView两个类以及onCompleteListener和onCompleteHandler两个接口,而SmartImage类在上文中已经介绍过了。可以很容易的看出SmartImageTask和SmartImageView是聚合的关系,task为view提供处理后台图片加载等操作,view则专注于ui的呈现。

一般这种后台task类会实现Runnable接口,特别是在和线程池配合使用的时候,SmartImageTask也不例外,因为在SmartImageView中就有一个线程池。

SmartImageTask既然实现了Runnable接口,那么它的主要逻辑实现就是在run方法中的。从类图结构中可以看到SmartImageTask聚合了SmartImage,使用SmartImage的getBitmap函数来获取指定URL的Bitmap实例。代码如下:

@Overridepublic void run() {if(image != null) {complete(image.getBitmap(context));context = null;}}


 

除此之外,task类中还实现了回调机制,供view类使用。包括一个静态类型的handler类(将handler定义成static,是为了避免内存泄露),一个图片加载完成的回调接口OnCompleteListener,定义分别如下:

public static class OnCompleteHandler extends Handler {@Overridepublic void handleMessage(Message msg) {Bitmap bitmap = (Bitmap)msg.obj;onComplete(bitmap);}public void onComplete(Bitmap bitmap){};}public abstract static class OnCompleteListener {public abstract void onComplete();}


 

当图片加载还未完成时,如果需要取消加载,那么可以设置标志位cancelled为false即可,这时就算图片加载成功了,也不会发送Message告知上层view类。

SmartImageView是ImageView的子类,定义了包含4个线程的线程池,用来执行SmartImageTask任务。在给ImageView设置图片资源时,可以选择是否设置默认图片,是否设置加载失败的图片,以及是否设置加载完成后的回调接口。在启用新的task任务前,得先判断是否已经存在给当前ImageView设置图片的task在运行中,如果是,就取消它,然后新建task任务并加入线程池中,永远保证一个ImageView有且只有一个最新的task在运行。

public void setImage(final SmartImage image, final Integer fallbackResource, final Integer loadingResource, final SmartImageTask.OnCompleteListener completeListener) {// Set a loading resourceif(loadingResource != null){setImageResource(loadingResource);}// Cancel any existing tasks for this image viewif(currentTask != null) {currentTask.cancel();currentTask = null;}// Set up the new taskcurrentTask = new SmartImageTask(getContext(), image);currentTask.setOnCompleteHandler(new SmartImageTask.OnCompleteHandler() {@Overridepublic void onComplete(Bitmap bitmap) {if(bitmap != null) {setImageBitmap(bitmap);} else {// Set fallback resourceif(fallbackResource != null) {setImageResource(fallbackResource);}}if(completeListener != null){completeListener.onComplete();}}});// Run the task in a threadpoolthreadPool.execute(currentTask);}


 

最后,当要取消线程池中所有在等待和运行的task时,可调用ExecutorService的shutdownNow函数,线程池的创建和销毁如下代码所示:

private static final int LOADING_THREADS = 4;private static ExecutorService threadPool = Executors.newFixedThreadPool(LOADING_THREADS);public static void cancelAllTasks() {threadPool.shutdownNow();threadPool = Executors.newFixedThreadPool(LOADING_THREADS);}


 

【扩展和优化】

前面说到如果图片有除了URL和联系人头像之外的其他来源的话,那么需要开发者实现SmartImage接口来进行扩展。国外另一位开发者commonsguy(以后会介绍他的开源项目)

Post了一个SmartImage的实现类VideoImage ,用于获取系统中视频的缩略图。

class VideoImage implements SmartImage {private int videoId; // 视频id private int thumbnailKind; // MICRO_KIND-微型缩略模式;MINI_KIND-迷你缩略模式,前者分辨率更低public VideoImage(int videoId, int thumbnailKind) {this.videoId = videoId;this.thumbnailKind = thumbnailKind;}@Overridepublic Bitmap getBitmap(Context context) {return (MediaStore.Video.Thumbnails.getThumbnail(context.getContentResolver(), videoId, thumbnailKind, null));}}


 

在Android开发中,如果系统内存不足的情况下,继续创建Bitmap实例的话,会导致OutOfMemoryError,从而导致app crash。因此,是否需要在创建Bitmap之前判断系统可用的内存大小呢?是否应该捕获OOME呢,这一点在smart-image库中目前没有考虑,因为毕竟这个库只适用于小图片的加载。如果非要优化的话,那么可以在WebImage类的创建Bitmap对象的地方加入低内存的判断,如果内存过低,那么可以将图片的采样值inSample降低,从而降低图片质量,降低其占用的内存空间,改进后的getBitmapFromUrl函数如下所示:

private Bitmap getBitmapFromUrl(String url) {Bitmap bitmap = null;try {URLConnection conn = new URL(url).openConnection();conn.setConnectTimeout(CONNECT_TIMEOUT);conn.setReadTimeout(READ_TIMEOUT);ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();int inSample = 1;if (memInfo.lowMemory) {inSample = 12;}BitmapFactory.Options options = new BitmapFactory.Options();options.inSampleSize = inSample;bitmap = BitmapFactory.decodeStream((InputStream) conn.getContent(), null, options);} catch(Exception e) {e.printStackTrace();}return bitmap;}


 

当然,降低质量后的图片还是超过可分配的内存大小时,还是会出现OutOfMemoryError,那么我们是否可以捕获这个异常呢?答案是可以,但不推荐。Java文档中明确说明的一点是java.lang.Error类是java.lang.Throwable的子类,java.lang.Exception也是Throwable的子类,Exception表示的是可以而且应该被捕获的异常,而Error表示的是会导致程序crash的致命错误,这个一般是不应该进行捕获的。但是,某些情况下,我们的程序在发生OutOfMemoryError异常后,可能需要做一些日志操作,或者能够做一些补救措施,例如释放内存或者降低申请的内存空间等等,那么还是可以catch住OutOfMemoryError异常的。

 

——欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢——

 

 

 


转载于:https://www.cnblogs.com/james1207/p/3275517.html


推荐阅读
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • intellij idea的安装与使用(保姆级教程)
    intellijidea的安装与使用(保姆级教程)IntelliJ在业界被公认为最好的java开发工具,尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(gi ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Google在I/O开发者大会详细介绍Android N系统的更新和安全性提升
    Google在2016年的I/O开发者大会上详细介绍了Android N系统的更新和安全性提升。Android N系统在安全方面支持无缝升级更新和修补漏洞,引入了基于文件的数据加密系统和移动版本的Chrome浏览器可以识别恶意网站等新的安全机制。在性能方面,Android N内置了先进的图形处理系统Vulkan,加入了JIT编译器以提高安装效率和减少应用程序的占用空间。此外,Android N还具有自动关闭长时间未使用的后台应用程序来释放系统资源的机制。 ... [详细]
author-avatar
happy可乐可爱多_376_874
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有