我计划着把需要用到的知识分解开来写,趁着我们要开发这款客户端的机会把安卓所有移动客户端开发中的技术贯穿其中,也是我自己成长的过程。By Ace in 20160121

我们开发一款新闻客户端程序,它的新闻列表中显示从网络获取的列表图片。可是如果用户发现每次进入新闻列表的时候,程序都要重新下载图片才能进行显示,甚至当把图片滑动到底部再重新滑动回顶部的时候,刚才已经加载完成了的图片竟然又变成了空白图片开始重新加载,这将是一种糟糕的用户体验,用户需要花费很多不必要的流量为了重新加载这些已经加载过得图片。为了解决这种问题,我们就需要—LruCache缓存。

什么是Cache?

Cache,高速缓存,原意是指计算机中一块比内存更高速容量更小的存储器。更广义地说,Cache指对于最近使用过的信息的可高速读取的存储块。而本文要讲的Cache技术,指的就是将最近使用过的Bitmap缓存在手机的内存与磁盘中,来实现再次使用Bitmap时的瞬时加载,以节省用户的时间和手机流量。

Android中的两种Cache类型Memory Cache和Disk Cache。下面来对这两种缓存类型进行介绍

有些代码 来自 http://developer.android.com(需要翻墙,多说两句 比本地打开开发文档速度快多了 )

Memory Cache内存中的Cache

Memory Cache使用内存来为应用程序提供Cache。由于内存的读写速度非常快,所以我们应该优先使用它(相对于下面将介绍的Disk Cache来说)。

Android中提供了LruCache类来进行Memory Cache的管理(该类是在Android 3.1时推出的,但我们可以使用android -support-v4.jar的兼容包来对低版本的手机提供支持)。

下来来看一段Bitmap设置LruCache的代码:这是我们新闻客户端列表图片加载的代码:和前面的相接,但也可以独立去看

我先来写下LruCache的步骤

1 创建并初始化LruCache对象

2 给 LruCache分配内存 

3 创建内部类重写sizeOf方法 用value.getByteCount() 来告诉系统这张bitmap占用多少缓存

4创建addBitmapToCache方法 用来向 LruCache中存放bitmap,可以看出来LruCache机制很像map <key , value>

5创建getBitmapFromCache方法 用来从LruCache中读取

6在 showImageByAsyncTask 方法中加入判断 LruCache中是否有缓存 没有的话创建缓存,有的话直接设置Imagview

7在 MyIconSyncTaskdoInBackgroud方法中把得到的图片加入到缓存中

8 NewsAdapter中也要做相应的改变  mImageLoader.showImageByAsyncTask(viewHolder.iconimage, mlist.get(position).newsIconUrl); 不能使用 new ImageLoader(). 因为每new一次都创建一个ImageLoader()这样系统会创建很多的Lrucache来占用内存,在构造方法中创建对象并设置全局变量就行了。 好了 不难,下面就看代码吧! 还是那句话 不明白请问我,说不定哪一年火了呢 哈哈哈哈哈 如此逗比的程序猿 哎累死我了喝两口水先,下面的一会讲 DiskCache

package asynctask.zb.com.asynctask_02;import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Message;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;/*** Created by Administrator on 2016/1/20.*/
public class ImageLoader {private ImageView mImageView;private String mUrl;public LruCache<String,Bitmap> mCache;public ImageLoader (){int maxMemory = (int) Runtime.getRuntime().maxMemory();  //获取虚拟机可用内存(内存占用超过该值的时候,将报OOM异常导致程序崩溃)int cacheSzie = maxMemory/4;                            //使用可用内存的1/4来作为Memory CachemCache = new LruCache<String,Bitmap>(cacheSzie) {@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getByteCount(); //返回Bitmap占用的空间,告诉系统我这张图片要用多少内存
            }};}
//把bitmap加入到缓存public void addBitmapToCache(String mUrl, Bitmap bitmap) {if (getBitmapFromCache(mUrl) == null) {mCache.put(mUrl, bitmap);}}
//从缓存中获取数据public Bitmap getBitmapFromCache(String mUrl) {return mCache.get(mUrl);}android.os.Handler mHandler = new android.os.Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (mImageView.getTag().equals(mUrl))mImageView.setImageBitmap((Bitmap)msg.obj);}};public void showImageByThread(ImageView imageView, final String url) {mImageView =imageView;mUrl = url;//对传过来的imageView 和url进行缓存(为了避免程序逻辑顺序错误,和viewholder的机制差不多)new Thread() {@Overridepublic void run() {super.run();Bitmap bitmap = getBitmapFromURL(url);Message message = Message.obtain();message.obj = bitmap;mHandler.sendMessage(message);}}.start();}//创建从URL获取Bitmap的放方法public Bitmap getBitmapFromURL(String stringUrl) {Bitmap bitmap;BufferedInputStream bis = null;URL url1 = null;try {url1 = new URL(stringUrl);} catch (MalformedURLException e) {e.printStackTrace();}HttpURLConnection connection = null;try {connection = (HttpURLConnection) url1.openConnection();} catch (IOException e) {e.printStackTrace();}try {bis = new BufferedInputStream(connection.getInputStream());bitmap = BitmapFactory.decodeStream(bis);connection.disconnect();return bitmap;} catch (IOException e) {e.printStackTrace();} finally {try {bis.close();} catch (IOException e) {e.printStackTrace();}}return null;}public void showImageByAsyncTask (ImageView imageView, String url){Bitmap bitmap = getBitmapFromCache(url);if (bitmap == null){new MyIconSyncTask(imageView,url).execute(url);}else{imageView.setImageBitmap(bitmap);}}class MyIconSyncTask extends AsyncTask<String,Void,Bitmap> {private ImageView mImageView;private String mUrl;public MyIconSyncTask(ImageView imageView,String url){mUrl = url;mImageView = imageView;}@Overrideprotected Bitmap doInBackground(String... params) {String mUrl = params[0];//从网络获取图片,并存入缓存中Bitmap bitmap = getBitmapFromURL(mUrl);if (bitmap != null){addBitmapToCache(mUrl,bitmap);}return bitmap;}@Overrideprotected void onPostExecute(Bitmap bitmap) {super.onPostExecute(bitmap);if (mImageView.getTag().equals(mUrl)) {mImageView.setImageBitmap(bitmap);}}}}

newAdapter 也要做些改变

package asynctask.zb.com.asynctask_02;import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;import java.net.URL;
import java.util.List;/*** Created by Ace on 2016/1/20.*/
public class NewsAdapter extends BaseAdapter {private List<NewsBean> mlist;private LayoutInflater mInflater;private ImageLoader mImageLoader;public NewsAdapter(Context context,List<NewsBean>data){//映射下 把data传给mlistmlist = data;//从一个上下文中(这里的上下文是MainActivity),获得一个布局填充器,这样你就可以使用这个填充器的inflater.inflate()来把xml布局文件转为View对象了,然后利用view对象,findViewById就可以找到布局中的组件mInflater = LayoutInflater.from(context);mImageLoader = new ImageLoader(); //再适配器初始化ImageLoader
    }@Overridepublic Object getItem(int position) {return mlist.get(position);}@Overridepublic int getCount() {return mlist.size();}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder viewHolder= null;if (convertView == null){viewHolder = new ViewHolder();convertView = mInflater.inflate(R.layout.adapter_item,null);viewHolder.iconimage = (ImageView)convertView.findViewById(R.id.tvimage);viewHolder.title = (TextView)convertView.findViewById(R.id.tvtitle);viewHolder.content = (TextView)convertView.findViewById(R.id.tvcontent);convertView.setTag(viewHolder);}else{viewHolder = (ViewHolder)convertView.getTag();viewHolder.iconimage.setImageResource(R.mipmap.ic_launcher);String   url = mlist.get(position).newsIconUrl;viewHolder.iconimage.setTag(url);//给imageview设置标签 是为了增加一个判断的标准(再imageloader类里),只有URL地址和当前位置的Item的图片相匹配才显示
//            new ImageLoader().showImageByThread(viewHolder.iconimage, mlist.get(position).newsIconUrl);mImageLoader.showImageByAsyncTask(viewHolder.iconimage, mlist.get(position).newsIconUrl);//不能使用 new ImageLoader(). 因为每new一次都创建一个ImageLoader()这样就会有很多的lru
            viewHolder.title.setText(mlist.get(position).newsTitle);viewHolder.content.setText(mlist.get(position).newsContent);}return convertView;}class  ViewHolder{public TextView title;public ImageView iconimage;public TextView content;}
}

============================================================================逗比的分割符===============================

回来了~下面对Lrucache做些补充

在使用 LruCache 的时候,需要考虑如下一些因素来选择一个合适的缓存数量参数:

1 程序中还有多少内存可用

2 同时在屏幕上显示多少图片?要先缓存多少图片用来显示到即将看到的屏幕上?

3 设备的屏幕尺寸和屏幕密度是多少?超高的屏幕密度(xhdpi 例如 Galaxy note5)
设备显示同样的图片要比低屏幕密度(hdpi 例如 小米2S milestone2 哈哈哈哈暴露年龄了)设备需要更多的内存。

4 图片的尺寸和格式决定了每个图片需要占用多少内存

5 图片访问的频率如何?一些图片的访问频率要比其他图片高很多?如果是这样的话,建议最好把这些经常访问的图片放到内存中。

6 在质量和数量上如何平衡?有些情况下保存大量的低质量的图片是非常有用的,当需要的情况下使用后台线程来加入一个高质量版本的图片。

这里没有万能配方可以适合所有的程序,你需要分析你的使用情况并在指定自己的缓存策略。使用太小的缓存并不能起到应有的效果,而使用太大的缓存会消耗更多
的内存从而有可能导致 java.lang.OutOfMemory 异常或者留下很少的内存供你的程序其他功能使用。

Disk Cache(磁盘中的Cache)

前面已经提到,Memory Cache的优点是读写非常快。但它的缺点就是容量太小了,而且不能持久化,所以在用户在滑动GridView时它很快会被用完,而且切换多个界面时或者是关闭程序重新打开后,再次进入原来的界面,Memory Cache是无能为力的。这个时候,我们就要用到Disk Cache了。

Disk Cache将缓存的数据放在磁盘中,因此不论用户是频繁切换界面,还是关闭程序,Disk Cache是不会消失的。

实际上,Android SDK中并没有一个类来实现Disk Cache这样的功能。但google其实已经提供了实现代码:DiskLruCache。我们只要把它搬到自己的项目中就可以了。

在我们清理手机的时候不知道你有没有注意到 手机里很多缓存图片,比如微信,浏览器,网易新闻等等 他们会把缓存图片存在本地,没错 用Lrucache太占内存了 他们图片多而且大 用lru会经常OOM的,所以他们会存到本地,

来看我们的双缓冲大法:DiskLruCache来配合Memory Cache来缓冲:代码来自Android开发者网站

private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();//声明锁对象
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB ,分配Disk内存
private static final String DISK_CACHE_SUBDIR = "thumbnails";//缓存文件夹@Override
protected void onCreate(Bundle savedInstanceState) {...// 初始化memory cache
    ...// 开启后台线程初始化disk cacheFile cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);//指定缓冲到哪^ ^new InitDiskCacheTask().execute(cacheDir);...
}class InitDiskCacheTask extends AsyncTask<File, Void, Void> {@Overrideprotected Void doInBackground(File... params) {synchronized (mDiskCacheLock) {File cacheDir = params[0];mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);mDiskCacheStarting = false; // 初始化完成mDiskCacheLock.notifyAll(); // 唤醒被hold住的线程
        }return null;}
}class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {...// 在后台加载图片
    @Overrideprotected Bitmap doInBackground(Integer... params) {final String imageKey = String.valueOf(params[0]);// 通过后台线程检查disk cacheBitmap bitmap = getBitmapFromDiskCache(imageKey);if (bitmap == null) { // 如果没有在disk cache中发现这个bitmap// 加载这个bitmapfinal Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100));}// 把这个bitmap加入cache
        addBitmapToCache(imageKey, bitmap);return bitmap;}...
}public void addBitmapToCache(String key, Bitmap bitmap) {// 把bitmap加入memory cacheif (getBitmapFromMemCache(key) == null) {mMemoryCache.put(key, bitmap);}// 同样,也加入disk cachesynchronized (mDiskCacheLock) {if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {mDiskLruCache.put(key, bitmap);}}
}public Bitmap getBitmapFromDiskCache(String key) {synchronized (mDiskCacheLock) {// 等待disk cache初始化完毕while (mDiskCacheStarting) {try {mDiskCacheLock.wait();} catch (InterruptedException e) {}}if (mDiskLruCache != null) {return mDiskLruCache.get(key);}}return null;
}// 在自带的cache目录下建立一个独立的子目录。优先使用外置存储。但如果外置存储不存在,使用内置存储。
public static File getDiskCacheDir(Context context, String uniqueName) {// 如果MEDIA目录已经挂载或者外置存储是手机自带的(Nexus设备都这么干),使用外置存储;否则使用内置存储final String cachePath =Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :context.getCacheDir().getPath();return new File(cachePath + File.separator + uniqueName);
}

上面这段代码看起来比较多,但大致读一下就会发现,它的思路非常简单:

1.读取cache的时候,优先读取memory cache,读不到的时候再读取disk cache;

2.把bitmap保存到cache中的时候,memory cache和disk cache都要保存;

可以看到 官方在初始化mDiskLruCache的 InitDiskCacheTask异步线程里 里用了锁 synchronized(mDiskCacheLock) 再初始化完成后再 mDiskCacheLock.notifyAll(); 唤醒

延伸:什么是synchronized?

概念:为了防止多个后台并发线程同时对同一个对象进行写操作时发成错误,java使用synchronized关键字对一个对象“加锁”,以保证同时只有一个线程可以访问该对象。(百度哒)

举个例子:快过年了,咱俩在12306买票,咱俩再同一时间买同一趟车,而这趟车现在只剩一张票了。然后咱俩同时按下购票按钮,那么后台系统接到两个请求,两个线程同时进行处理,执行了这么两行代码:

if (tickedCount > 0) { // 如果还有票tickedCount -= 1; // 票数减一printTicket(); // 出票
}

12306服务器线程1和线程2几乎同时运行,并且几乎同时执行到第一行代码,线程1一看,哦还有票,行,出票吧!然后执行了第二行代码,票数减一。但它不知道,在他执行第二行代码之前,线程2也执行到了第一行,这线程2也一看,哦还有票,行,出票吧!于是在线程1出票之后,线程2在已经没票的情况下依然把票数减到了-1,并且执行printTicket()方法尝试出票。到了这里,如果出了两张一样的票,这就是你们俩的猿粪了 哈哈哈哈  ,猿粪不猿粪吧你俩肯定觉得对方的假票 如果对方是个漂亮妹子还好解决,如果是汉子会不会打架呢

12306那么牛逼肯定为了不让你们打架也要解决啊,那么怎么解决呢?加锁:

synchronized(this) {if (tickedCount > 0) { // 如果还有票tickedCount -= 1; // 票数减一printTicket(); // 出票
    }
}

上面这段代码由于加了锁,同一时间只有一个线程可以进入这个代码块,当一个线程进入后,其他线程必须等这个线程执行完这段代码后释放了锁,才能进入这个代码块,12306每天访问量那么大虽然卡虽然有个很BT的验证码但是他的机制肯定不会这么简单的

我也就随便说两句,具体什么事锁百度吧。优化讲完了么?当然没有! 今天就到此为止吧 阿冰Ace与你共同学习共同进步~

转载于:https://www.cnblogs.com/AceIsSunshineRain/p/5149614.html

Ace教你一步一步做Android新闻客户端(四) 优化Bitmap大法相关推荐

  1. Ace教你一步一步做Android新闻客户端(三) JSON数据解析

    对于服务器端来说,返回给客户端的数据格式一般分为html.xml和json这三种格式,现在给大家讲解一下json这个知识点, 1 如何通过json-lib和gson这两个json解析库来对解析我们的j ...

  2. 一步一步教你做ios推送 pem证书制作 php推送

    一步一步教你做ios推送 分类: ios2013-03-03 21:48 3385人阅读 评论(8) 收藏 举报 ios推送客户端服务器 最近在研究ios的推送问题,遇到了一些问题,最终整理了一下.放 ...

  3. 教你一步一步用C语言实现sift算法、上

    原文:http://blog.csdn.net/v_july_v/article/details/6245939 引言:     在我写的关于sift算法的前倆篇文章里头,已经对sift算法有了初步的 ...

  4. 一步一步教你使用AgileEAS.NET基础类库进行应用开发-基础篇-基于接口驱动的数据层...

    系列回顾 在前面的文章中,我用了大量的篇幅对UDA及ORM的使用进行了讲解和演示,我们已经知道并熟悉的使用UDA和ORM构建简单的应用,AgileEAS.NET在应用的纵向结构上建议使用分层结构,提出 ...

  5. include_fns.php_一步一步教你用PHP+MySql筹建网站 No.3 管理页面_mysql

    一步一步教你用PHP+mysql搭建网站 No.3 管理页面 先来看一下本篇blog将要介绍的内容. 我们的主页面已经搭建完成了,然后左边的navigation里面的大部分内容也都能点击了,只剩下&q ...

  6. 超级简单:一步一步教你创建一小型的asp.net mvc 应用程序

    超级简单:一步一步教你创建一小型的asp.net mvc 应用程序 这本教程中将帮助你创建一个小型的asp.net mvc示例. 在本教程中,我们将创建自己的 Model , View 和Contro ...

  7. C语言实现寻找极值点,九之再续:教你一步一步用c语言实现sift算法、上

    教你一步一步用c语言实现sift算法.上 作者:July.二零一一年三月十二日 出处:http://blog.csdn.net/v_JULY_v 参考:Rob Hess维护的sift 库 环境:win ...

  8. 通过Dapr实现一个简单的基于.net的微服务电商系统(十一)——一步一步教你如何撸Dapr之自动扩/缩容...

    上一篇我们讲到了dapr提供的bindings,通过绑定可以让我们的程序轻装上阵,在极端情况下几乎不需要集成任何sdk,仅需要通过httpclient+text.json即可完成对外部组件的调用,这样 ...

  9. 通过Dapr实现一个简单的基于.net的微服务电商系统(十)——一步一步教你如何撸Dapr之绑定...

    如果说Actor是dapr有状态服务的内部体现的话,那绑定应该是dapr对serverless这部分的体现了.我们可以通过绑定极大的扩展应用的能力,甚至未来会成为serverless的基础.最开始接触 ...

  10. 通过Dapr实现一个简单的基于.net的微服务电商系统(九)——一步一步教你如何撸Dapr之OAuth2授权-百度版...

    目录: 一.通过Dapr实现一个简单的基于.net的微服务电商系统 二.通过Dapr实现一个简单的基于.net的微服务电商系统(二)--通讯框架讲解 三.通过Dapr实现一个简单的基于.net的微服务 ...

最新文章

  1. Cloud Programming Simplifie : A Berkeley View on Serverless Computing
  2. 为什么比尔盖茨,马斯克、霍金都提醒你:要警惕人工智能?(上)
  3. 集群理论详解(续一)
  4. http参数自动转换java接口参数设置_Springmvc请求参数类型转换器及原生api代码实例...
  5. nodejs的事件处理机制
  6. mysql内外三种连接_mysql之内连接,外连接(左连接,右连接),union,union all的区别...
  7. svn unable to connect to a repository at url 执行上下文错误 不能访问SVN服务器问题
  8. 二叉树的层序遍历_二叉树:你真的会翻转二叉树么?
  9. 苹果MAC系统常用软件 (BY 冷家锋)
  10. Kaggle 机器学习实战 朴素贝叶斯(原理+西瓜数据集实战)
  11. Android 颜色金属效果,金属质感+流线型机身_手机Android频道-中关村在线
  12. docker 之容器编排工具Docker Compose
  13. 通过宽高自适应设计两栏布局和三栏布局
  14. QQ邮箱账号异常登录
  15. 做软件第三方测试报告需要准备哪些材料,靠谱的软件测试中心推荐
  16. 值得收藏-装修攻略全
  17. 网络爬虫学习(二) selenium
  18. SONY VAIO P VPCP118KJ索尼酷袋本 鸡肋上网本初体验
  19. 用MUI花两天时间快速开发『One·一个』App,兼容Android、iOS双平台 1
  20. ags js下载地址

热门文章

  1. 流利说 Level6 全文
  2. 201871010114-李岩松《面向对象程序设计(java)》第一周学习总结
  3. 燕大学子知网使用手册
  4. 修改Android应用名称
  5. kasp技术原理_Massarray技术——中高通量大样本的SNP检测利器!
  6. 主板BIOS中的CSM是什么
  7. 计算机专业普通的期刊,计算机类的普通期刊
  8. 车牌识别-基于模板匹配
  9. gitlab hook declined错误
  10. java23种设计模式(十六) -- 中介者模式(行为设计模式)