android教你打造独一无二的图片加载框架
前言
首先,最近是在忙okhttp没错。不过或许有人问为什么忙着okhttp怎么又扯到了图片加载上了。其实,最近想实现下断点续传以及多文件下载,但并不知道怎么搞。群里有小伙伴提出了控制线程池来实现。然后我就想到了图片加载需要控制线程池,所以在此巩固下。
概述
好了,进入正题了。优秀的图片加载框架不要太多,什么UIL,Picasso,Glide等等。但我们需要了解其中的原理。所以今天我来介绍下如何自己写一个图片加载框架。有人可能会说,自己写会不会很渣,运行效率,内存溢出神马的。至于这个,等你看完这篇博客自己试试就知道了。
缓存
加载图片肯定是需要用到缓存的,这样可以提高我们的加载效率,而且还可以省流量。所有的图片从缓存读取,保证图片的内存不会超过预期的空间。
压缩
除了缓存,更重要的就是压缩了。不然别人用起来。直接oom,肯定要破口大骂,尼玛炸了!!
加载速度
好了。谈完压缩,缓存,基本没问题了把?不不不,你加载速度不快谁用?加载速度分两种,先进先出和后进先出。如果一个列表有几千甚至上万张图片,我一次性滑倒底,你要是在从第一张开始加载。我估计我一把lol打完你还没加载完把。所以我们选择后进先出,当前呈现给用户的,最新加载;当前未呈现的,选择加载。
小结
关于加载网络图片,其实原理差不多,就多了个是否启用硬盘缓存的选项,如果启用了,加载时,先从内存中查找,然后从硬盘上找,最后去网络下载。下载完成后,别忘了写入硬盘,加入内存缓存。如果没有启用,那么就直接从网络压缩获取,加入内存即可。
效果图
终于扯完了,我们先来看下效果图:
加载本地
很流畅对吧,我们在看看网络图片.
加载网络
效果还是不错的,之前花了好久找的100个妹纸图。想录长一点的,结果要求必须小于2M,也是醉了。整体效果应该可以看出来。很流畅。perfect~~~
整体实现
整体实现我采用了建造者模式,对建造者模式不了解的。去看android建造者模式
图片压缩
不管是从网络还是本地的图片,加载都需要进行压缩,然后显示。
首先压缩。我们是需要得到ImageView的宽和高,也就是大小。没大小怎么压缩?
//压缩protected Bitmap SampledBitmap(String path, Edit edit) {// 获得图片的宽和高,并把图片加载到内存中BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFile(path, options);options.inSampleSize = calculateInSampleSize(options, edit.imageView.getWidth(), edit.imageView.getHeight());
// 使用获得到的InSampleSize再次解析图片options.inJustDecodeBounds = false;Bitmap bitmap = BitmapFactory.decodeFile(path, options);if (edit.orientation != 0) {int drawablewidth = bitmap.getWidth();int drawableheight = bitmap.getHeight();Matrix matrix = new Matrix();matrix.setRotate(edit.orientation);bitmap = Bitmap.createBitmap(bitmap, 0, 0, drawablewidth, drawableheight,matrix, true);}return bitmap;}
我们需要设置恰当的insamplesize。根据需求的宽和高以及图片实际的宽和高计算SampleSize 。
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {//源图片的高度和宽度final int height = options.outHeight;//得到要加载的图片高度final int width = options.outWidth;int inSampleSize = 1;if (height > reqHeight || width > reqWidth) {//计算出实际宽高和目标宽高的比例final int heightRatio = Math.round((float) height / (float) reqHeight);final int widthRatio = Math.round((float) width / (float) reqWidth);// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高// 一定都会大于等于目标的宽和高。inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;}return inSampleSize;}
上面是进行本地图片的压缩。网络图片就方便了。只要把网络图片下载到sd卡,然后在进行本地压缩。
public static boolean downloadImgByUrl(String urlStr, File file) {boolean isok = false;FileOutputStream fos = null;InputStream is = null;try {URL url = new URL(urlStr);HttpURLConnection conn = (HttpURLConnection) url.openConnection();is = conn.getInputStream();fos = new FileOutputStream(file);byte[] buf = new byte[1024 * 4];int len = 0;while ((len = is.read(buf)) != -1) {fos.write(buf, 0, len);}isok = true;} catch (Exception e) {e.printStackTrace();} finally {try {if (is != null)is.close();} catch (IOException e) {}try {if (fos != null)fos.close();} catch (IOException e) {}}return isok;}
到这边图片压缩是结束了,接下来是缓存。
缓存
// 图片缓存的核心对象private LruCache<String, Bitmap> lruCache;private Context context;//任务队列private LinkedList<Runnable> taskQueue = new LinkedList<>();//线程池private ExecutorService threadPool;private Semaphore semaphoreThreadPool;private Semaphore semaphorePoolThreadHandler = new Semaphore(0);//UI线程private Handler uiHandler;//后台线程private Thread backthread;private Handler backthreadhandler;//线程数量private static final int THREAD_POOL_COUNT = 3;private void BackGroundThread() {// 后台轮询线程backthread = new Thread() {public void run() {Looper.prepare();backthreadhandler = new Handler() {public void handleMessage(Message msg) {// 线程池去取出一个任务进行执行threadPool.execute(taskQueue.removeLast());try {semaphoreThreadPool.acquire();} catch (InterruptedException e) {}}};semaphorePoolThreadHandler.release();Looper.loop();}};backthread.start();}private void setLruCache() {int maxMemory = (int) Runtime.getRuntime().maxMemory();int cacheSize = maxMemory / 8;lruCache = new LruCache<String, Bitmap>(cacheSize) {protected int sizeOf(String key, Bitmap bitmap) {return bitmap.getRowBytes() * bitmap.getHeight();}};// 创建线程池threadPool = Executors.newFixedThreadPool(THREAD_POOL_COUNT);semaphoreThreadPool = new Semaphore(THREAD_POOL_COUNT);}
对图片进行完压缩以及缓存后,下面就是加载图片了。
加载图片
我们需要调用一个方法来执行加载图片的操作:
public void loadImage(Edit edit) {edit.imageView.setTag(edit.loadUrl);edit.imageView.setImageResource(edit.load_pic);if (uiHandler == null) {uiHandler = new Handler() {public void handleMessage(Message msg) {myImageBean myImageBean = (myImageBean) msg.obj;Bitmap bm = myImageBean.bitmap;ImageView imageview = myImageBean.imageView;String path = myImageBean.path;if (imageview.getTag().toString().equals(path)) {imageview.setImageBitmap(bm);}}};}// 根据path在缓存中获取bitmapBitmap bitmap = getBitmapFromLruCache(edit.loadUrl);if (bitmap != null) {refreashBitmap(edit, bitmap);} else {addTask(buildTask(edit));}}
我们需要得到他的缓存,所以:
private Bitmap getBitmapFromLruCache(String key) {return lruCache.get(key);}
如果缓存存在的话,那么我们需要刷新bitmap。代表这个图片已经完成加载了。
private void refreashBitmap(Edit edit, Bitmap bitmap) {Message message = Message.obtain();myImageBean holder = new myImageBean();holder.bitmap = bitmap;holder.path = edit.loadUrl;holder.imageView = edit.imageView;message.obj = holder;uiHandler.sendMessage(message);}
否则,我们需要去把他添加到lurcache中,以便之后快捷的加载。
private void addTask(Runnable runnable) {taskQueue.add(runnable);try {if (backthreadhandler == null)semaphorePoolThreadHandler.acquire();} catch (InterruptedException e) {}backthreadhandler.sendEmptyMessage(0x250);}private Runnable buildTask(final Edit edit) {return new Runnable() {public void run() {Bitmap bitmap = null;if (edit.isNet) {File file = getDiskCacheDir(context, MD5Utils.hashKeyFromUrl(edit.loadUrl));if (file.exists()) {bitmap = loadImageFromLocal(file.getAbsolutePath(), edit);} else {boolean success = downloadImgByUrl(edit.loadUrl, file);if (success) {bitmap = loadImageFromLocal(file.getAbsolutePath(), edit);}}} else {bitmap = loadImageFromLocal(edit.loadUrl, edit);}if (lruCache.get(edit.loadUrl) == null) {if (bitmap != null)lruCache.put(edit.loadUrl, bitmap);}addBitmapToLruCache(edit.loadUrl, bitmap);refreashBitmap(edit, bitmap);semaphoreThreadPool.release();}};}
下载到本地之后,其实也是执行一个压缩操作,代码很简单:
private Bitmap loadImageFromLocal(String path, Edit edit) {Bitmap bm;bm = SampledBitmap(path, edit);return bm;}
到此,我们的代码应该分析完了,不过好像还缺少了什么,对了。Edit这个。到底做了什么处理呢。
public class Edit {private boolean isNet = true;private int orientation = 0;private String loadUrl;private ImageView imageView;private int load_pic=R.drawable.ic_loading;public Edit ic_loading(int load_pic) {this.load_pic = load_pic;return this;}public Edit into(ImageView imageView) {this.imageView = imageView;return this;}public Edit setUrl(String loadUrl) {this.loadUrl = loadUrl;return this;}public Edit isNet(boolean net) {isNet = net;return this;}public void load() {loadImage(this);}public Edit setOrientation(int orientation) {this.orientation = orientation;return this;}}
一个个介绍。isnet代表是否从网络加载,默认是从网络加载,至于orientation是什么呢,这个是图片的旋转角度,在座的小伙伴如果有三星手机的,你打开boss直聘选择头像,然后读取本地图片会发现,一些图片是经过旋转的,其实这是三星手机的一个bug。当然我们也知道了。他的图片加载其实也是自己写的,并没有用三方的jar。其他属性应该不用介绍了把。
参考
洪洋:http://blog.csdn.net/lmj623565791/article/details/41874561
总结
图片加载主要考虑的就是压缩,缓存,加载速度以及线程池(一次加载几张)。只要掌握好这些,你也可以写一个图片加载框架。
对于开发最常用的就是,网络加载,图片加载以及布局的刷新加载。现在就剩对okhttp的深入了解了。后续我会更上。我们需要知道其中的原理来解决这些问题,虽然网上都有现成的。但我们如果不分析直接拿来用的话,对自己水平提高的实在是太少了。今天就这样吧~~~
android教你打造独一无二的图片加载框架相关推荐
- Android教你打造独一无二的刷新加载框架
2019独角兽企业重金招聘Python工程师标准>>> 其实早在去年七月,群里小伙伴就有让我共享这个.但我当时绝的技术不纯熟.代码有bug什么的.没有写出来.现在感觉整理的差不多了. ...
- Android设计一个图片加载框架
本文不是具体编码去实现一个图片加载的框架,而是从理论上来讲解设计一个图片加载框架的注意事项和涉及的知识点,提供一个思路,或者帮助童鞋们应付面试.目前Android 发展至今优秀的图片加载框架太多,例如 ...
- Android 框架练成 教你打造高效的图片加载框架
转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/41874561 ,本文出自: [张鸿洋的博客] 1.概述 优秀的图片加载框架不要 ...
- Android LruCache和DiskLruCache相结合打造图片加载框架(仿微信图片选择,照片墙)
LrcCache和DiskLruCache相结合打造图片加载框架 转载请标明出处:http://blog.csdn.net/luoshishou/article/details/51299169 源码 ...
- Android Glide图片加载框架(四)回调与监听
文章目录 Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图片加载框架(二)源码解析之with() Android Gl ...
- Android Glide图片加载框架(三)缓存机制
文章目录 一.缓存简介 二.缓存用法 内存缓存方式 磁盘缓存方式 三.缓存KEY 四.内存缓存 内存缓存流程 五.磁盘缓存 磁盘缓存流程 Android Glide图片加载框架系列文章 Android ...
- Android Glide图片加载框架(二)源码解析之into()
文章目录 一.前言 二.源码解析 1.into(ImageView) 2.GlideContext.buildImageViewTarget() 3.RequestBuilder.into(Targe ...
- Android Glide图片加载框架(二)源码解析之load()
文章目录 一.前言 二.源码分析 1.load() Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图片加载框架(二)源 ...
- Android Glide图片加载框架(二)源码解析之with()
文章目录 一.前言 二.如何阅读源码 三.源码解析 1.with() Android Glide图片加载框架系列文章 Android Glide图片加载框架(一)基本用法 Android Glide图 ...
最新文章
- python分类算法_用Python实现KNN分类算法
- 华为4G路由器成软银快速部署宽带业务新利器
- instance “error” 了怎么办?- 每天5分钟玩转 OpenStack(159)
- 算法-- 找到所有数组中消失的数字(Java)
- linux下tomcat的安装和配置
- 【渝粤教育】国家开放大学2018年春季 0674-22T财务管理 参考试题
- 使用方法 yii_如何实现高速卷积?深度学习库使用了这些黑魔法
- C++ STL map 中insert函数返回值问题
- Sublime Text 2 代码编辑器使用技巧
- 解只含加减的一元一次方程
- 语音识别程序c语言,语音识别
- ubuntu虚拟机联网配置
- Go语言学习:Channel
- Unity网络编程教学视频(本人第一次录制)
- AI之Tool:GitHub Copilot(一款人工智能编程小助手—猜你想写的代码)的简介、安装、使用方法之详细攻略
- 计算机文化宣传普及知识展,浅谈计算机文化
- ad18差分布线,设置差分对
- 深度推荐模型之NFM模型
- 2020浙江大学软件学院预推免经验
- TDSQL:腾讯金融级分布式数据库解决方案