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

由于开发过程中经常要使用图片,特别是从互联网上面获取图片,如果要让设备获取互联网上面图片,就意味着连接网络,获取图片,但是从服务器获取图片是需要时间的,当图片非常多,或者图片有非常大的时候,这样获取是很危险的,可能常常遇见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中。这样可以系统设计得更具弹性



















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值