Bitmap的加载和Cache

参考资料

Android DiskLruCache完全解析,硬盘缓存的最佳方案


目录

    1. Bitmap的高效加载
    1. Android的缓存策略
    • 2.1)LruCache
    • 2.2)DiskLruCache
    1. ImageLoader的使用
    • 3.1)ImageLoader的实现
    • 3.2)优化列表的卡顿

1)Bitmap的高效加载

public static int calculateInSampleSize(BitmapFactory.Options options,  
    int reqWidth, int reqHeight) {  
    // 源图片的高度和宽度  
    final int height = options.outHeight;  
    final int width = options.outWidth;  
    int inSampleSize = 1;  
    if (height > reqHeight || width > reqWidth) {  
        // 计算出实际宽高和目标宽高的比率  
        final int heightRatio = Math.round((float) height / (float) reqHeight);  
        final int widthRatio = Math.round((float) width / (float) reqWidth);  
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;  // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高 ,一定都会大于等于目标的宽和高。 
    }  
    return inSampleSize;  
}  
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,  
        int reqWidth, int reqHeight) {  
    final BitmapFactory.Options options = new BitmapFactory.Options();      
    options.inJustDecodeBounds = true;  // 第一次解析将inJustDecodeBounds设置为true,禁止为bitmap分配内存,从而来获取图片大小 
    BitmapFactory.decodeResource(res, resId, options);  
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);    // 调用上面定义的方法计算inSampleSize值  
    options.inJustDecodeBounds = false;     // 使用获取到的inSampleSize值再次解析图片 ,false为bitmap分配内存
    return BitmapFactory.decodeResource(res, resId, options);  
}  
mImageView.setImageBitmap(  
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));  //将任意一张图片压缩成100*100的缩略图,并在ImageView上展示

当inSampleSize为2时,采样后的图片宽高为原来的1/2,而像素数为原图的1/4,内存大小也为原图的1/4。 例如ARGB8888(每个像素4Byte)格式存储的1024x1024图片,内存为1024x1024x4=4M。采样后位512x512x4=1M。


2)Android的缓存策略


2.1)LruCache (Least Recently Used)

主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除

LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存,每次调用get(也就是从内存缓存中取图片),则将该对象移到链表的尾端。调用put插入新的对象也是存储在链表尾端,这样当内存缓存达到设定的最大值时,将链表头部的对象(近期最少用到的)移除

名称 说明
强引用 直接的对象引用
软引用 当一个对象只有软引用存在时,系统内存不足时会回收此对象
弱引用 当一个对象只有弱引用存在时,随时会回收此对象
private LruCache<String, Bitmap> mMemoryCache;  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。LruCache通过构造函数传入缓存值,以KB为单位
    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
    int cacheSize = maxMemory / 8;      // 使用最大可用内存值的1/8作为缓存的大小。  
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {  
        @Override  
        protected int sizeOf(String key, Bitmap bitmap) {  
            return bitmap.getByteCount() / 1024;  // 重写此方法来衡量每张图片的大小,默认返回图片数量。 
        }  
    };  
}  
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {  
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap); //添加一个缓存对象 
    }  
}  
public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);  //获取一个缓存对象 
}

public void loadBitmap(int resId, ImageView imageView) {  
    final String imageKey = String.valueOf(resId);  
    final Bitmap bitmap = getBitmapFromMemCache(imageKey);  
    if (bitmap != null) {  
        imageView.setImageBitmap(bitmap);  
    } else {  
        imageView.setImageResource(R.drawable.image_placeholder);  
        BitmapWorkerTask task = new BitmapWorkerTask(imageView);  
        task.execute(resId);  
    }  
}  
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {   //异步下载图片
    // 在后台加载图片。  
    @Override  
    protected Bitmap doInBackground(Integer... params) {  
        final Bitmap bitmap = decodeSampledBitmapFromResource(  
                getResources(), params[0], 100, 100);  
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);  
        return bitmap;  
    }  
} 


2.2)DiskLruCache

//提供了open方法创建自身
public static DiskLurCache open(
  File directory,  //存储路径
  int appVersion,  //应用版本号,一般设为1
  int valueCount,  //单个节点对应数据个数,一般为1
  long maxSize)  //缓存总大小
  • 实例代码
屏幕快照 2017-08-08 上午11.47.12.png
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private DiskLruCache mDiskLruCache = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i(TAG, "onCreate: ");
        ImageView img = (ImageView) findViewById(R.id.img);

        //文件路径
        File cacheDir = getDiskCacheDir("bitmap");
        if (!cacheDir.exists()) {
            cacheDir.mkdirs();
        }
        try {
            //创建
            mDiskLruCache = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024);

            String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
            //将url进行md5编码后,成为一个合法的文件命名
            String key = hashKeyForDisk(imageUrl);

            //写入缓存类
            DiskLruCache.Editor editor = mDiskLruCache.edit(key);
            if (editor != null) {
                OutputStream outputStream = editor.newOutputStream(0);
                //远程请求数据 并通过outputstream写入本地
                downloadUrltoStream(imageUrl, outputStream);
                editor.commit();
            }

            //读取缓存类
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
            if (snapShot != null){
                InputStream is = snapShot.getInputStream(0);
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                img.setImageBitmap(bitmap);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获取创建路径
    private File getDiskCacheDir(String name) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
            cachePath = getExternalCacheDir().getPath();
        } else {
            cachePath = getCacheDir().getPath();
        }
        return new File(cachePath + File.pathSeparator + name);
    }

    //远程请求公共方法
    public static void sendRequestPost(String url, Map<String, String> param, Callback callback) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(url).build();
        client.newCall(request).enqueue(callback);
    }

    //远程获取数据并写入outPutStream
    private void downloadUrltoStream(String url, final OutputStream outputStream) {
        sendRequestPost(url, null, new Callback() {
            BufferedInputStream in = null;
            BufferedOutputStream out = null;

            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                InputStream inputStream = response.body().byteStream();
                in = new BufferedInputStream(inputStream, 8 * 1024);
                out = new BufferedOutputStream(outputStream, 8 * 1024);
                int b;
                while ((b = in.read()) != -1) {
                    out.write(b);
                }
            }
        });
    }

    //md5编码
    public String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
}
adb shell查看添加disk缓存结果
  • 其他API
API 说明
remove(key) 根据key删除缓存数据
size() 当前缓存路径下所有缓存数据得字节数Byte
flush() 将内存中的操作记录同步到日志文件(也就是journal文件)当中,不用频繁调,比较标准的做法就是在Activity的onPause()调用
close() 关闭DiskLruCache,只应该在Activity的onDestroy()方法中去调用close()方法
delete() 清除全部缓存数据

3)ImageLoader的使用

3.1)ImageLoader的实现

  • 内存缓存 LruCache
  • 磁盘缓存 DiskLruCache
  • 从内存缓存中读取
  • 交给线程池执行
  • 从磁盘缓存读取,并存入内存缓存
  • 从网络读取,并压缩后存入磁盘缓存

3.2)优化列表的卡顿

  • 异步加载图片
    使用glide框架或imageLoader等
  • 滑动停止后加载
listview.setOnScrollListener(new AbsListView.OnScrollListener() {
  @Override
  public void onScrollStateChanged(AbsListView view, int scrollState) {
      if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE){
            Glide.with(SchoolListActivity.this).resumeRequests();
        }else{
            //glide提供了暂停和恢复的方法
           Glide.with(SchoolListActivity.this).pauseRequests();
        }
  }

  @Override
  public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

  }
});
  • 硬件加速可以解决莫名卡顿问题
android:hardwareAccelerated="true"

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容