说到Android开发中遇到的内存问题
像Bitmap这种吃内存的大户稍微处理不当就很容易造成OOM,当然,目前已经有很多知名的开源图片加载框架,例如:ImageLoader,Picasso等等,这些框架已经能够很好的解决了Bitmap造成的OOM问题,虽然这些框架能够节省很多开发者的宝贵时间,但是也会遇到一种情况,很多初学者只是会简单的去调用这些框架的提供的接口,被问到框架内部的一些实现原理,基本上都是脑中一片空白。从我的观点出发,我认为如果能够掌握一些框架原理,想必对我们进行应用调优的意义是非常重大的,今天,主要是是想谈谈,如果没有了图片加载框架,我们要怎么去处理Bitmap的内存问题呢?
谈到Bitmap处理的问题,我们可能要先来了解一些基础的知识,关于Bitmap在Android虚拟机中的内存分配,在Google的网站上给出了下面的一段话
技术分享
一, 位图重新采样
有效的处理较大的位图
图像有各种不同的形状和大小。在很多情况下,他们往往比一个典型应用程序的用户界面(UI)所需要的资源更大。
读取一个位图的尺寸和类型:
为了从多种资源来创建一个位图,BitmapFactory类提供了几个解码的方法(decodeByteArray(), decodeFile(), decodeResource(), 等等) 。根据你的图像数据资源选择最合适的解码方法。这些方法试图请求分配内存来构造位图,因此很容易导致OutOfMemory异常。每种类型的解码方法都有额外的特征可以让你通过BitMapFactory.Options类指定解码选项。当解码时避免内存分配可以设置inJustDecodeBounds属性为true, 位图对象返回null但是设置了outWidth, outHeight和outMimeType。这种技术允许你在创建位图(和分配内存) 之前去读取图像的尺寸和类型。
位图重新采样
package com.example.bitmaps;
import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends Activity {
private ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv=(ImageView) findViewById(R.id.imageView1);
}
//点击按钮显示图片
public void showClick(View v) {
Bitmap bitmap=showImage(getResources(),R.drawable.a, 100, 100);
iv.setImageBitmap(bitmap);
}
/*
* 计算位图的采样比列
*/
public int showSize(BitmapFactory.Options options,int reqWidth,int reqHeight ) {
//获取位图的原宽高
int w=options.outWidth;
int h=options.outHeight;
int size=1;
if(w>reqWidth || h>reqHeight) {
if(w>h) {
//转成小数相除让后在四舍五入
size=Math.round((float)h / (float)reqHeight);
}else {
size=Math.round((float)w / (float)reqWidth);
}
}
return size;
}
/*
* 位图重新采样
*/
public Bitmap showImage(Resources res,int resid,int reWidth,int reHeight) {
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;//只解析边界不加载内存
BitmapFactory.decodeResource(res, resid,options);//开始解析
//设置采样比列
options.inSampleSize=showSize(options, reWidth, reHeight);
options.inJustDecodeBounds=false;//只加载内存不解析边界
Bitmap bitmap=BitmapFactory.decodeResource(res, resid,options);//开始解析
return bitmap;
}
}
位图缓存之LRUcache缓存(基于Activity)
LRU简介:
内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。
什么是LRU算法? LRU是Least Recently Used的缩写,即最近最久未使用,常用于页面置换算法,是为虚拟页式存储管理服务的。
package com.example.bitmaps;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.LruCache;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends Activity {
private ImageView iv;
//获取缓存对象
private LruCache<String,Bitmap> lruCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv=(ImageView) findViewById(R.id.imageView1);
//安卓原生规定的一个Activity占16M的内存
//获取当前Activity的内存大小
ActivityManager am=(ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
//获取内存大小(字节);
int memory=am.getMemoryClass();
//8/1的内存作为缓存大小
final int cacheSize=memory/8*1024*1024;
lruCache=new LruCache<String, Bitmap>(cacheSize);
}
//******************************************************************
//点击按钮显示图片
public void showClick(View v) {
String key=String.valueOf(R.drawable.a);
//通过key取到缓存中的图片
Bitmap bitmap=getBitmapCache(key);
if(bitmap==null) {
Bitmap bitmap1=showImage(getResources(),R.drawable.a, 100, 100);
//添加到缓存中
addBitmapToCache(key, bitmap1);
}else {
System.out.println("lrucache中有位图");
}
iv.setImageBitmap(bitmap);
}
//******************************************************************
//从缓存中读取图片
public Bitmap getBitmapCache(String key) {
return lruCache.get(key);
}
//添加图片到缓存
public void addBitmapToCache(String key,Bitmap bitmap) {
//如果取不到图片,我们在添加图片
if(getBitmapCache(key)==null) {
lruCache.put(key, bitmap);
}
}
/*
* 计算位图的采样比列
*/
public int showSize(BitmapFactory.Options options,int reqWidth,int reqHeight ) {
//获取位图的原宽高
int w=options.outWidth;
int h=options.outHeight;
int size=1;
if(w>reqWidth || h>reqHeight) {
if(w>h) {
//转成小数相除让后在四舍五入
size=Math.round((float)h / (float)reqHeight);
}else {
size=Math.round((float)w / (float)reqWidth);
}
}
return size;
}
/*
* 位图重新采样
*/
public Bitmap showImage(Resources res,int resid,int reWidth,int reHeight) {
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;//只解析边界不加载内存
BitmapFactory.decodeResource(res, resid,options);//开始解析
options.inSampleSize=showSize(options, reWidth, reHeight);//设置采样比列
options.inJustDecodeBounds=false;//只加载内存不解析边界
Bitmap bitmap=BitmapFactory.decodeResource(res, resid,options);//开始解析
return bitmap;
}
}
位图缓存之DiskLruCache(基于磁盘)
LruCache只是管理了内存中图片的存储与释放,如果图片从内存中被移除的话,那么又需要从网络上重新加载一次图片,这显然非常耗时。对此,Google又提供了一套硬盘缓存的解决方案:DiskLruCache(非Google官方编写,但获得官方认证)。
但是不知道大家有没有发现,这些内容和图片在从网络上获取到之后都会存入到本地缓存中,因此即使手机在没有网络的情况下依然能够加载出以前浏览过的新闻。而使用的缓存技术不用多说,自然是DiskLruCache了,那么首先第一个问题,这些数据都被缓存在了手机的什么位置呢?
其实DiskLruCache并没有限制数据的缓存位置,可以自由地进行设定,但是通常情况下多数应用程序都会将缓存的位置选择为 /sdcard/Android/data//cache 这个路径。选择在这个位置有两点好处:第一,这是存储在SD卡上的,因此即使缓存再多的数据也不会对手机的内置存储空间有任何影响,只要SD卡空间足够就行。第二,这个路径被Android系统认定为应用程序的缓存路径,当程序被卸载的时候,这里的数据也会一起被清除掉,这样就不会出现删除程序之后手机上还有很多残留数据的问题。
小知识:谷歌并没有提供磁盘缓存的解决方案,但是提供了第三方的DiskLruCache磁盘缓存方案,而且他是谷歌认证的
由于老师不是随课敲代码我就不提供DiskLruCache的源码了!
要补的内容!!!
有时间在补上!
Android双缓存之(LRU内存缓存+LRU磁盘缓存)
所谓的双缓存,第一就是缓存在内存里面,第二就是缓存在SD卡里面,当你需要加载数据时,先去内存缓存中查找,如果没有再去SD卡中查找,并且用户可以自选使用哪种缓存!
缓存内存和缓存SD都有一个共同的方法,就是put和get方法(存数据和取数据),因此我们采用工厂模式!
新建一个接口,名字随便取,用来封装内存缓存和sd缓存里面共有的方法,然后新建一个内存缓存类和sd缓存类,双缓存类并且都实现此接口,注意建双缓存类只是为了更方便的使用其他两个缓存,你想想
如果两个缓存类封装到一个类中,并且这种类中会有判断如何使用哪种缓存,这样就减少了你每次调用哪种缓存就要修改代码的过程了!
由于老师不是随课敲代码我就不提供DiskLruCache的源码了!
要补的内容!!!
有时间在补上!