您的位置:首页 > 移动开发 > Android开发

Android LRU算法 图片缓存性能改善 <9>

2016-01-13 18:49 736 查看
由于开发过程中经常要使用图片,特别是从互联网上面获取图片,如果要让设备获取互联网上面图片,就意味着连接网络,获取图片,但是从服务器获取图片是需要时间的,当图片非常多,或者图片有非常大的时候,这样获取是很危险的,可能常常遇见OOM的问题,导致APP不稳定,性能非常的差,而且频繁的联网获取,浪费流量和电量,有无数个不爽,所以android就引入LRU算法来改善图片加载的问题.

大致的逻辑应该是这样的:



android系统提供了LRU机制,系统源代码LRUCache.java中,本人大致对其的理解,前提其实需要知道几个概念,源代码中

private final LinkedHashMap<K, V> map;
</pre></p><p></p><p></p><p>LinkedHashMap这个是HashMap的一个子类,不过它相比HashMap有一些区别,LinkedHashMap存储数据是以链表的形式保存的,也就是说是有一定顺序的,可以做一段测试程序:</p><pre class="html" name="code">package org.durian.durianlrubitmap.util;

import android.util.Log;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* Project name : DurianLRUBitmap
* Created by zhibao.liu on 2016/1/13.
* Time : 18:33
* Email warden_sprite@foxmail.com
* Action : durian
*/
public class DurianUtil {

private final static String TAG="DurianUtil";

public static void DurianLinkedHashMap(){

Map<Integer,String> map=new LinkedHashMap<Integer, String>();
map.put(5,"hello,zhibao.liu");
map.put(8,"hello,world !");
map.put(3,"hello,java programe !");

for (Iterator it=map.keySet().iterator();it.hasNext();){
Object key=it.next();
Log.i(TAG,"link key : "+key.toString());
}

Log.i(TAG,"******************************************");

Map<Integer,String> hashmap=new HashMap<Integer, String>();
hashmap.put(5,"hello,zhibao.liu");
hashmap.put(8,"hello,world !");
hashmap.put(3,"hello,java programe !");

for (Iterator it=hashmap.keySet().iterator();it.hasNext();){
Object key=it.next();

Log.i(TAG,"hash key : "+key.toString());

}

}

}

调用运行,打印结果:



这个概念大致清楚了,看一下LRUCache.java程序的另外一个地方:

public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
private int maxSize;

这个变量非常重要,我的理解大概有两点,第一点是缓存空间是有限的,不是无限的,所以是有上限的;第二点时缓存应该是缓存相对较新的内容,移除非常旧的资源,当有新的资源获取时,应该在有限的容器中添加最新的,剔除最旧的资源,这句话和奇怪,下面程序辅助解释:

/**
* Remove the eldest entries until the total of remaining entries is at or
* below the requested size.
*
* @param maxSize the maximum size of the cache before returning. May be -1
*            to evict even 0-sized elements.
*/
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}

if (size <= maxSize) {
break;
}

Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}

key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}

entryRemoved(true, key, value, null);
}
}


注意下面一段:

if (size <= maxSize) {
break;
}

Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}

key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);

如果现在size已经达到maxSize了,那么下面就会从LinkedHashMap中移除最旧的资源,上面map只认识key啦!

下面可以做一个android 测试进行验证上面的:

<1> : 新建Android 工程:



<2> : 具体程序如下:

DurianMainActivity.java

package org.durian.durianlrubitmap;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.util.LruCache;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;

import org.durian.durianlrubitmap.util.DurianUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;

public class DurianMainActivity extends ActionBarActivity implements View.OnClickListener{

private final static String TAG="DurianMainActivity";

private final static int IMAGE_COUNT=3;

private final static String IMAGE_SRC[]={
"http://pic.qiantucdn.com/58pic/13/96/30/41S58PICpI9_1024.png",
"http://pic.qiantucdn.com/58pic/14/79/59/15s58PICMdP_1024.jpg",
"http://pic.qiantucdn.com/58pic/14/64/73/03q58PICp9d_1024.jpg",
};

//网络数据源,格式:json
//json 中罗列了图片的url列表信息
private final static String URL_LIST= "http://pugme.herokuapp.com/bomb?count=" + IMAGE_COUNT;

private LruCache<String,Bitmap> mMemoryCache;

private ImageView imageView;
private Button mButton;
private Button mDownButton;

private Button mLinkButton;

private ListView listView;

private Bitmap bitmap;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.durian_main);

mLinkButton=(Button)findViewById(R.id.link);
mLinkButton.setOnClickListener(this);

listView=(ListView)findViewById(android.R.id.list);

imageView=(ImageView)findViewById(R.id.image);

int maxMemory=(int)(Runtime.getRuntime().maxMemory()/1024);
int cacheSize=maxMemory/8;
mMemoryCache=new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
Log.i(TAG,"bitmap cache size : "+bitmap.getByteCount()/1024);
return bitmap.getByteCount()/1024;
}
};

mButton=(Button)findViewById(R.id.button);
mButton.setOnClickListener(this);

mDownButton=(Button)findViewById(R.id.loadjson);
mDownButton.setOnClickListener(this);

}

public void addBitmapToMemoryCache(String key,Bitmap bitmap){
if(getBitmapFromMemCache(key)==null){
Log.i(TAG,"add bitmap key : "+key);
mMemoryCache.put(key,bitmap);
}
}

public Bitmap getBitmapFromMemCache(String key){
Log.i(TAG,"get bitmap key : "+key);
return mMemoryCache.get(key);
}

@Override
public void onClick(View v) {

int id=v.getId();
switch (id){
case R.id.button:
Bitmap map=getBitmapFromMemCache(urls);
if(map==null){
ImageLoadTask work=new ImageLoadTask();
work.execute();
}else{
imageView.setImageBitmap(map);
}
break;
case R.id.loadjson:
new ImageLoadListTask().execute();
break;
case R.id.link:
DurianUtil.DurianLinkedHashMap();
break;
default:
break;
}

}

private class ImageLoadListTask extends AsyncTask<String,Integer,ArrayList<String>>{

@Override
protected ArrayList<String> doInBackground(String... params) {

try {
URL url=new URL(URL_LIST);

try {
HttpURLConnection conn=(HttpURLConnection)url.openConnection();
conn.setRequestProperty("ACCEPT","application/json");
InputStream is=conn.getInputStream();

StringBuilder buf=new StringBuilder();
BufferedReader reader=new BufferedReader(new InputStreamReader(is),1024);

String buffer;
for (buffer=reader.readLine();buffer!=null;buffer=reader.readLine()){
buf.append(buffer);
}

is.close();

String resp=buf.toString();
JSONObject jsonObject= null;
JSONArray jsonArray=null;
HashSet<String> pugUrls=null;

try {
jsonObject = new JSONObject(resp);
jsonArray=jsonObject.getJSONArray("pugs");

pugUrls = new HashSet<String>(jsonArray.length());

for (int i = 0, z = jsonArray.length(); i < z; i++) {
//这个网络数据源不是太好,图片TMD太大了,有一些居然10多M,汗死.
//pugUrls.add(jsonArray.getString(i));
//下面提供选择几个不错的数据源用于加载
pugUrls.add(IMAGE_SRC[i]);
Log.i(TAG,"json : "+jsonArray.getString(i));
}

} catch (JSONException e) {
e.printStackTrace();
}

return new ArrayList<>(pugUrls);

} catch (IOException e) {
e.printStackTrace();
}

} catch (MalformedURLException e) {
e.printStackTrace();
}

return null;
}

@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}

@Override
protected void onPreExecute() {
super.onPreExecute();
}

@Override
protected void onPostExecute(ArrayList<String> ret) {
super.onPostExecute(ret);

DurianBaseAdapter adapter=new DurianBaseAdapter(DurianMainActivity.this,ret);
listView.setAdapter(adapter);

}

}

private final static String urls="http://pic.qiantucdn.com/58pic/13/96/30/41S58PICpI9_1024.png";//http://pic.qiantucdn.com/58pic/14/79/59/15s58PICMdP_1024.jpg";
private class ImageLoadTask extends AsyncTask<String,Integer,String>{

@Override
protected String doInBackground(String... params) {

try {
URL url=new URL(urls);
try {

HttpURLConnection conn=(HttpURLConnection)url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");

InputStream is=conn.getInputStream();

bitmap= BitmapFactory.decodeStream(is);
addBitmapToMemoryCache(urls,bitmap);
mHandler.sendEmptyMessage(0);

} catch (IOException e) {
e.printStackTrace();
}

} catch (MalformedURLException e) {
e.printStackTrace();
}

return null;
}

@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
}

@Override
protected void onPreExecute() {
super.onPreExecute();
}

@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
}

private Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
imageView.setImageBitmap(bitmap);
}
};

}


DurianBaseAdapter.java

package org.durian.durianlrubitmap;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.media.Image;
import android.os.AsyncTask;
import android.support.v4.util.LruCache;
import android.util.Log;
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.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;

/**
* Project name : DurianLRUBitmap
* Created by zhibao.liu on 2016/1/13.
* Time : 12:15
* Email warden_sprite@foxmail.com
* Action : durian
*/
public class DurianBaseAdapter extends BaseAdapter {

private final static String TAG="DurianBaseAdapter";

private ArrayList<String> mUrlArray;
private Context mContext;
private LayoutInflater mInflater;
private ViewHolder mViewHolder;

private LruCache<String,Bitmap> mMemoryCache;

public DurianBaseAdapter(Context context, ArrayList<String> url_list){

mContext=context;
mUrlArray=url_list;

mInflater=LayoutInflater.from(mContext);

mViewHolder=new ViewHolder();

int maxMemory=(int)(Runtime.getRuntime().maxMemory()/1024);
int cacheSize=maxMemory/8;
mMemoryCache=new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
Log.i(TAG,"bitmap cache size : "+bitmap.getByteCount()/1024);
return bitmap.getByteCount()/1024;
}
};

}

@Override
public int getCount() {
return 3;
}

@Override
public Object getItem(int position) {
return null;
}

@Override
public long getItemId(int position) {
return 0;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {

if(convertView==null){

convertView=mInflater.inflate(R.layout.list_item,null);
mViewHolder.textView=(TextView)convertView.findViewById(R.id.text);
mViewHolder.imageView=(ImageView)convertView.findViewById(R.id.image);

mViewHolder.textView.setText(mUrlArray.get(position).toString());
//download image
AyncLoadImage mAyncLoadImage=new AyncLoadImage(mViewHolder.imageView,position);
mAyncLoadImage.execute();

}

return convertView;
}

class ViewHolder{
TextView textView;
ImageView imageView;
}

public void addBitmapToMemoryCache(String key,Bitmap bitmap){
if(getBitmapFromMemCache(key)==null){
Log.i(TAG,"add bitmap key : "+key);
mMemoryCache.put(key,bitmap);
}
}

public Bitmap getBitmapFromMemCache(String key){
Log.i(TAG,"get bitmap key : "+key);
return mMemoryCache.get(key);
}

private class AyncLoadImage extends AsyncTask<String,Integer,Bitmap>{

WeakReference<ImageView> imageViewWeakRef;
int postion;
@Override
protected Bitmap doInBackground(String... params) {

Bitmap map;

//检查是否已经有缓存了
//如果有缓存了,就是用缓存
map=getBitmapFromMemCache(mUrlArray.get(postion).toString());
if(map!=null){
return map;
}

//如果没有缓存,就到网络上面去获取
try {
URL url=new URL(mUrlArray.get(postion).toString());
try {

HttpURLConnection conn=(HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
InputStream is=conn.getInputStream();

map= BitmapFactory.decodeStream(is);

return map;

} catch (IOException e) {
e.printStackTrace();
}

} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}

public AyncLoadImage(ImageView imageview,int postion) {
super();

imageViewWeakRef=new WeakReference<ImageView>(imageview);
this.postion=postion;

}

@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}

@Override
protected void onPreExecute() {
super.onPreExecute();
}

@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);

ImageView imageView=imageViewWeakRef.get();
ByteArrayOutputStream out=new ByteArrayOutputStream();
if(bitmap!=null) {
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, out);

addBitmapToMemoryCache(mUrlArray.get(postion).toString(),bitmap);
}
imageView.setImageBitmap(bitmap);

}

}

}


DurianUtil.java

package org.durian.durianlrubitmap.util;

import android.util.Log;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* Project name : DurianLRUBitmap
* Created by zhibao.liu on 2016/1/13.
* Time : 18:33
* Email warden_sprite@foxmail.com
* Action : durian
*/
public class DurianUtil {

private final static String TAG="DurianUtil";

public static void DurianLinkedHashMap(){

Map<Integer,String> map=new LinkedHashMap<Integer, String>();
map.put(5,"hello,zhibao.liu");
map.put(8,"hello,world !");
map.put(3,"hello,java programe !");

for (Iterator it=map.keySet().iterator();it.hasNext();){
Object key=it.next();
Log.i(TAG,"link key : "+key.toString());
}

Log.i(TAG,"******************************************");

Map<Integer,String> hashmap=new HashMap<Integer, String>();
hashmap.put(5,"hello,zhibao.liu");
hashmap.put(8,"hello,world !");
hashmap.put(3,"hello,java programe !");

for (Iterator it=hashmap.keySet().iterator();it.hasNext();){
Object key=it.next();

Log.i(TAG,"hash key : "+key.toString());

}

}

}


<3> : 运行程序后,界面上面有三个按钮,"LINK","BUTTON","JSON"按钮:

LINK按钮是LinkedHashMap的一个测试样例;

BUTTON是初步熟悉LRUCche的基本使用;

JSON是从网络上面获取一组数据,加载到ListView中,在加载的时候,有限判断缓存是否已经有了,如果有,直接加载缓存中的,如果没有再通过网络获取资源.

注意: Mainfiest文件需要添加联网权限:

<uses-permission android:name="android.permission.INTERNET" />


其实还是比较好理解.

程序另外几个地方有几个概念,我觉得下面说的还不错,借过来,如下:

WeakReference与SoftReference都可以用来保存对象的实例引用,这两个类与垃圾回收有关。
WeakReference是弱引用,其中保存的对象实例可以被GC回收掉。这个类通常用于在某处保存对象引用,而又不干扰该对象被GC回收,通常用于Debug、内存监视工具等程序中。因为这类程序一般要求即要观察到对象,又不能影响该对象正常的GC过程。
最近在JDK的Proxy类的实现代码中也发现了Weakrefrence的应用,Proxy会把动态生成的Class实例暂存于一个由Weakrefrence构成的Map中作为Cache。

SoftReference是强引用,它保存的对象实例,除非JVM即将OutOfMemory,否则不会被GC回收。这个特性使得它特别适合设计对象Cache。对于Cache,我们希望被缓存的对象最好始终常驻内存,但是如果JVM内存吃紧,为了不发生OutOfMemoryError导致系统崩溃,必要的时候也允许JVM回收Cache的内存,待后续合适的时机再把数据重新Load到Cache中。这样可以系统设计得更具弹性


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: