(一)LruCache缓存处理
1、加载图片的正确流程是:“内存-文件-网络 三层cache策略”
a、先从内存缓存中获取,取到则返回,取不到则进行下一步;
b、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;
c、从网络下载图片,并更新到内存缓存和文件缓存。
2、内存缓存分类:四种级别由高到低依次为:强引用、软引用、弱引用和虚引用
a、强引用:(在Android中LruCache就是强引用缓存)
如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OOM异常,使程序异常终止,也不会回收具有强引用的对象来解决内存不足问题。
b、软引用(SoftReference):
软引用类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。使用软引用能防止内存泄露,增强程序的健壮性。
c、弱引用(WeakReference):
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
d、虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。
Demo1:
点击download更新图片,
若可以从强引用中获取,则从强引用中获取,
如不能从强引用中获取,则从软引用中寻找,如果找到且不为null,则将bitmap添加到强引用中,并从软引用中移除
如果在软引用中找不到,则开启线程(注意:下载图片等费时操作要在子线程中完成)从网络下载图片,并将bitmap添加到强引用中
代码如下:
MainActivity:
public class MainActivity extends AppCompatActivity {
private ImageView ivPic = null;
private String url = "http://ww2.sinaimg.cn/mw690/4b08ac5ejw1f8ffqk2hicj21ag1gxaz9.jpg";
private Bitmap bitmap = null;
private ProgressDialog pdshow = null;
private LruCache<String,Bitmap> lruCache = null;
private HashMap<String,SoftReference<Bitmap>> softMap = new HashMap<String,SoftReference<Bitmap>>();
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
pdshow = new ProgressDialog(MainActivity.this);
pdshow.setTitle("title");
pdshow.setMessage("洪荒之力已开启......");
pdshow.setIcon(R.mipmap.ic_launcher);
pdshow.show();
break;
case 1:
Bitmap bitmap = (Bitmap) msg.obj;
if(bitmap != null){
ivPic.setImageBitmap(bitmap);
}
pdshow.dismiss();
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
// Runtime.getRuntime().maxMemory()返回的是java虚拟机(这个进程)能构从操作系统那里挖到的最大的内存
int maxSize = (int) (Runtime.getRuntime().maxMemory()/8);
lruCache = new LrucacheUtils(maxSize,softMap);
}
public Bitmap getBitmapFromMemoryByUrl(String url){
bitmap = lruCache.get(url);
// 从强引用中拿数据
if(bitmap != null){
Log.d("test","---------强引用获取图片--------");
return bitmap;
}else{
// 从软引用中拿数据
SoftReference<Bitmap> soft = softMap.get(url);
if(soft != null){
// 一旦SoftReference保存了对一个Java对象的软引用后,
// 在垃圾线程对 这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。
// 另外,一旦垃圾线程回收该Java对象之 后,get()方法将返回null
bitmap = soft.get();
Log.d("test","===========软引用中获取图片============");
if(bitmap != null){
lruCache.put(url,bitmap);
Log.d("test","*******从软引用中添加到强引用*******");
softMap.remove(url);
Log.d("test","++++++++添加到强引用中后从软引用中移除+++++");
return bitmap;
}else{
// 若对应bitmap为null,将此软引用移除
softMap.remove(url);
}
}
}
return null;
}
private void initView() {
ivPic = (ImageView) findViewById(R.id.ivPic);
}
public void download(View view) {
bitmap = getBitmapFromMemoryByUrl(url);
if(bitmap != null){
ivPic.setImageBitmap(bitmap);
Log.d("test","--------从缓存中获取到数据-------");
}else {
new Thread(){
@Override
public void run() {
super.run();
handler.sendEmptyMessage(0);
Message message = Message.obtain();
message.what = 1;
byte[] data = OkHttpUtils.getBytesByUrl(url);
bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
Log.d("test","----------从网络下载图片-------------");
// 将数据放到强引用中(不然每次点download都会重新下载图片)
lruCache.put(url,bitmap);
message.obj = bitmap;
handler.sendMessage(message);
}
}.start();
}
}
}
LruCacheUtils:
public class LrucacheUtils extends LruCache<String,Bitmap> {
private HashMap<String,SoftReference<Bitmap>> softMap = null;
// maxSize 规定的最大存储空间
public LrucacheUtils(int maxSize,HashMap<String,SoftReference<Bitmap>> softMap) {
super(maxSize);
this.softMap = softMap;
}
}
Demo2:
点击download下载图片,点击save根据url将下载好的图片保存到缓存中,点击get从缓存中根据指定url获取对应的缓存图片,点击delete从缓存中根据指定url删除对应的缓存图片。
MainActivity:
public class MainActivity extends AppCompatActivity {
private ImageView ivPic = null;
private String url = "http://ww3.sinaimg.cn/mw690/4b08ac5ejw1f8ab0ibt6jj22yo4g0npj.jpg";
private LruCacheUtils lruCacheUtiles = null;
// 从网络上下载的图片
private Bitmap bitmap = null;
// 从缓存中获取的图片
private Bitmap lruCacheBitmap = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
lruCacheUtiles = new LruCacheUtils();
lruCacheUtiles.initLruche();
}
public void clickMe(View view){
switch (view.getId()){
// 从网络上下载指定图片
case R.id.btnDown:
new Thread(){
@Override
public void run() {
super.run();
byte[] data = OkHttpUtils.getBytesByUrl(url);
bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
Log.d("test","图片下载完毕"+bitmap);
}
}.start();
break;
// 将网络上下载的图片保存到lruCache对象
case R.id.btnSave:
if(bitmap != null){
lruCacheUtiles.setBitmap2URL(url,bitmap);
Log.d("test","图片已保存");
}
break;
// 从lruCache对象中根据指定url对应的缓存图片,并将缓存图片设置给ImageView控件
case R.id.btnGet:
lruCacheBitmap = lruCacheUtiles.getBitmapByURL(url);
Log.d("test","取出来的图片");
ivPic.setImageBitmap(lruCacheBitmap);
break;
// 从lruCache对象中删除指定url对应的图片资源
case R.id.btnDel:
lruCacheUtiles.deleteBitmapByURL(url);
break;
}
}
private void initView() {
ivPic = (ImageView) findViewById(R.id.ivPic);
}
}
LruCacheUtils:
public class LruCacheUtils {
private LruCache<String,Bitmap> lruCache = null;
// 初始化缓存
public LruCache initLruche(){
int maxSize = 4 * 1024 * 1024;
lruCache = new LruCache<>(maxSize);
return lruCache;
}
// 获取指定url的Bitmap对象
public Bitmap getBitmapByURL(String url){
if(lruCache != null){
return lruCache.get(url);
}
return null;
}
// 把Bitmap对象放入key值为url的缓存中
public void setBitmap2URL(String url,Bitmap bitmap){
if(getBitmapByURL(url) == null){
lruCache.put(url,bitmap);
}
}
// 从缓存中删除指定url的Bitmap对象
public void deleteBitmapByURL(String url){
if(getBitmapByURL(url) != null){
lruCache.remove(url);
}
}
}
(二)Bitmap二次采样:
参考自:http://www.th7.cn/Program/Android/201503/405000.shtml
意义:BitmapFactory解码一张图片时,有时会遇到OOM错误。这往往是由于图片过大造成的。要想正常使用,则需要分配更少的内存空间来存储。
Android使用BitmapFactory.Options解决加载大图片内存溢出问题;
1、BitmapFactory.Options.inJustDecodeBounds:
注意:并不是在此属性的true和false之间才能对图片进行操作,此属性仅代表是否返回实际的Bitmap
如果该值设为true那么将不返回实际的bitmap不给其分配内存空间而里面只包括一些解码边界信息即图片大小信息。[通过设置inJustDecodeBounds为true,获取到outHeight(图片原始高度)和 outWidth(图片的原始宽度),然后计算一个inSampleSize(缩放值),然后就可以取图片了,这里要注意的是,inSampleSize 可能小于0,必须做判断]。
2、BitmapFactory.Options.inSampleSize:设置缩放值
设置恰当的inSampleSize可以使BitmapFactory分配更少的空间以消除该错误
3、BitmapFactory.Option.inPreferredConfig:设置单位像素占用的字节数
inPreferredConfig的值为Bitmap.Config类型,Bitmap.Config类是个枚举类型
可以为以下值:
(1)、Bitmap.Config ALPHA_8 :
(2)、Bitmap.Config ARGB_4444 :不推荐使用
(3)、Bitmap.Config ARGB_8888:
一个像素占用四个字节,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各占8个bites,共32bites,即4个字节。这是一种高质量的图片格式,电脑上普通采用的格式。它也是Android手机上一个Bitmap的默认格式。
(4)、Bitmap.Config RGB_565:一个像素占用2个字节,没有alpha(A)值,能够达到比较好的呈现效果,相对于ARGB_8888来说也能减少一半的内存开销。
Bitmap占用内存的计算:
Android中一张图片(Bitmap)占用的内存主要和以下几个因素有关:图片长度,图片宽度,单位像素占用的字节数。
一张图片(Bitmap)占用的内存=图片长度图片宽度单位像素占用的字节数(图片长度和图片宽度的单位是像素)。图片(Bitmap)占用的内存和屏幕密度(Density)无关。
创建一个Bitmap时,其单位像素占用的字节数由其参数BitmapFactory.Options的inPreferredConfig变量决定。
**备注:**ARGB指的是一种色彩模式,里面A代表Alpha,R表示red,G表示green,B表示blue,其实所有的可见色都是红绿蓝组成的,所以红绿蓝又称为三原色。 ARGB就是:透明度 红色 绿色 蓝色。
步骤:
先设置BitmapFactory.Options的inJustDecodeBounds为true
通过BitmapFactory.decodeByteArray()方法返回图片大小
设置缩放值inSampleSize
设置单位像素占用的字节数inPreferredConfig
设置BitmapFactory.Options的inJustDecodeBounds为false
再调用BitmapFactory.decodeByteArray()方法返回的即为二次采样后的Bitmap对象
代码:
public class BitmapUtils {
public static Bitmap getOptionBitmapByByteArray(byte[] data){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// 若options.inJustDecodeBounds为true,
// 那么将不返回实际的bitmap不给其分配内存空间而里面只包括一些解码边界信息即图片大小信息,也就不会那么频繁的发生OOM了
BitmapFactory.decodeByteArray(data,0,data.length,options);
// 设置缩放值
options.inSampleSize = 3;
// 设置单位像素占用的字节数
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
// 设置options.inJustDecodeBounds为false
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(data,0,data.length,options);
}
}
Demo:
MainActiviy:
public class MainActivity extends AppCompatActivity {
private ListView lvShow = null;
private List<DataInfo> list = null;
private MyAdapter adapter = null;
String[] urls={
"http://img2.duitang.com/uploads/item/201210/16/20121016190338_z3jFA.jpeg",
"http://m.chanyouji.cn/tips/hanju.jpg",
"http://m.chanyouji.cn/articles/534/51b6a7a15a4c35948f09cfb4ec6bfe69.jpg",
"http://m.chanyouji.cn/articles/578/50e0f1407caccc6e7379d5699df8582e.jpg",
"http://m.chanyouji.cn/articles/587/e14f0f98ab6c0551a077a0acc63e1a55.jpg",
"http://m.chanyouji.cn/articles/596/46eab7447b19db0759c3f29c40661595.jpg",
"http://m.chanyouji.cn/articles/610/56fa2310ba3935bc9bb3d97c668e5052.jpg",
"http://m.chanyouji.cn/articles/573/eb25779a20b80a891397325aeeebc715.jpg",
"http://p.chanyouji.cn/58129/1375088377136p180kqdqfb1pfet5815hr1qf4107s8.jpg",
"http://m.chanyouji.cn/articles/609/b67b9b38ef6fdd70788f12d1fcd226bd.jpg"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
setData();
adapter = new MyAdapter(list,this);
lvShow.setAdapter(adapter);
}
private void setData() {
list = new ArrayList<DataInfo>();
DataInfo dataInfo = null;
for (int i = 0;i<urls.length;i++){
String title = "title"+i;
String url = urls[i];
dataInfo = new DataInfo(title,url);
list.add(dataInfo);
}
}
private void initView() {
lvShow = (ListView) findViewById(R.id.lvShow);
}
}
MyAdapter:
public class MyAdapter extends BaseAdapter {
private List<DataInfo> list = null;
private Context context = null;
private LruCacheUtils lruCacheUtils = null;
private Handler handler = null;
public MyAdapter(List<DataInfo> list, Context context) {
this.list = list;
this.context = context;
lruCacheUtils = new LruCacheUtils();
lruCacheUtils.initLruCache();
handler = new Handler();
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int i) {
return list.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(final int i, View view, ViewGroup viewGroup) {
View v = null;
final ViewHolder holder ;
if(view == null){
holder = new ViewHolder();
v = LayoutInflater.from(context).inflate(R.layout.list_item,viewGroup,false);
holder.txtTitle = (TextView) v.findViewById(R.id.txtTitle);
holder.ivPic = (ImageView) v.findViewById(R.id.ivPic);
v.setTag(holder);
}else{
v = view;
holder = (ViewHolder) v.getTag();
}
DataInfo dataInfo = list.get(i);
if(dataInfo != null){
String title = dataInfo.getTitle();
final String url = dataInfo.getUrl();
holder.txtTitle.setText(title);
// 先从缓存中获取图片,获取不到指定图片再从网络下载
if(lruCacheUtils.getBitmapByUrl(url) != null){
Log.d("test","从缓存中获取图片");
holder.ivPic.setImageBitmap(lruCacheUtils.getBitmapByUrl(url));
}else{
new Thread(){
@Override
public void run() {
super.run();
byte[] data = OkHttpUtils.getBytesByUrl(url);
// ???通过二次采样获得的Bitmap
final Bitmap bitmap = BitmapUtils.getOptionBitmapByByteArray(data);
Log.d("test","网络下载的图片"+i);
if (bitmap != null){
Log.d("test","将图片保存到缓存中"+i);
lruCacheUtils.setBitmap2Url(url,bitmap);
}
// **重点:**子线程不能更新UI,通过handler的post方法将更新操作添加到主线程
handler.post(new Runnable() {
@Override
public void run() {
holder.ivPic.setImageBitmap(bitmap);
}
});
}
}.start();
}
}
return v;
}
class ViewHolder{
TextView txtTitle;
ImageView ivPic;
}
}