Android 图片加载缓存

package com.wangdong.bitmapcache;

import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.Adapter;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import java.util.HashSet;
import java.util.Set;

/**
 * 下面我通过一个例子来演示一下 LruCache 内存缓存 和 DisLruCache 本地缓存 的使用
 *
 * 主要功能就是一个用 GridView 来展示大量的网络图片, 因为GridView 有服用机制,
 *     1.滑出屏幕的 ImageView 持有的图片进行回收,再将 ImagView 指定新的图片,从而避免了OOM
 *     2.同时在 ImageView 加载图片是我们使用了内存 和 硬盘进行双缓存
 *     3.每一个图片的加载和显示任务都运行在独立的线程(异步任务)中,除非这个图片缓存在 LruCache 中,这种情况下图片会立即显示。
 *     4.如果需要的图片缓存在 DiskLruCache,也会开启一个独立的线程队列。如果在缓存中都没有正确的图片,任务线程会从网络下载。
 *
 *     注意:如果是自定义 View 来实现照片墙,没有复用机制的话,那么需要在 ImageView 滑出屏幕后手动回收图片,
 *     即将ImageView加载一张默认空图片,从而切断与之前图片的引用,让系统可以回收之前图片,避免OOM。
 *
 */
public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener {

    private GridView gridview;
    private BitmapCaChe bitmapCache;//LruCache 内存缓存
    private BitmapDiskCache bitmapDiskCache; //本地存储卡缓存

    private Set taskCollection; //图片下载任务

    private int mFirstVisibleItem;
    private int mVisibleItemCount;
    private boolean isFirstEnter = true;

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

        bitmapCache = new BitmapCaChe(); //创建 LruCache 内存缓存
        bitmapDiskCache = new BitmapDiskCache(this); //创建 DisLruCache 本地缓存

        taskCollection = new HashSet(); //创建下载任务集合

        gridview = (GridView) findViewById(R.id.gridview); //初始GridView控件
        gridview.setAdapter(new Adapter()); //GridView设置Adapter
        gridview.setOnScrollListener(this); //设置GridView 滑动监听
    }

    /**OnScrollListener 滚动接口回调之调用结束时
     *
     * @param view  正在报告其滚动状态的视图
     * @param firstVisibleItem 第一个可见单元格的索引(忽略如果visibleItemCount=0)
     * @param visibleItemCount  可见item数
     * @param totalItemCount 列表适配器中的总项数。
     */
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mFirstVisibleItem = firstVisibleItem;
        mVisibleItemCount = visibleItemCount;
        // 下载的任务应该由onScrollStateChanged里调用,但首次进入程序时onScrollStateChanged并不会调用,
        // 因此在这里为首次进入程序开启下载任务。
        if (isFirstEnter && visibleItemCount > 0) {
            loadBitmaps(firstVisibleItem, visibleItemCount);
            isFirstEnter = false;
        }
    }

    /**OnScrollListener 滚动接口回调之触发时候
     *
     * @param view 正在报告其滚动状态的视图。
     * @param scrollState  当前滚动状态。
     *                    SCROLL_STATE_FLING用户之前一直在使用触摸滚动,并进行了一次狂欢。
     *                     SCROLL_STATE_IDLE视图不是滚动的。
     *                     SCROLL_STATE_TOUCH_SCROLL用户正在使用触摸滚动,他们的手指仍然在屏幕上。
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // 仅当GridView静止时才去下载图片,GridView滑动时取消所有正在下载的任务
        if (scrollState == SCROLL_STATE_IDLE) { //停止滚动
            loadBitmaps(mFirstVisibleItem, mVisibleItemCount); //下载图片
        } else {
            cancelAllTasks();  //开始滚动停止下载图片
        }
    }

    private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
        try {
            for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
                String imageUrl = Images.imageUrls[i];
                BitmapDownloadTask task = new BitmapDownloadTask();
                taskCollection.add(task);
                task.execute(imageUrl);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public int dip2px(float dipValue) {
        final float scale = getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    public void showlog(String info) {
        System.out.print("LRU " + info + "\n");
    }

    public void cancelAllTasks() {
        if (taskCollection != null) {
            for (BitmapDownloadTask task : taskCollection) {
                task.cancel(false);
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        bitmapDiskCache.flush();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        bitmapDiskCache.close();
    }




    /**首先继承AsyncTask<开始参数类型,后天执行参数类型,结果返回参数类型*/
    class BitmapDownloadTask extends AsyncTask {

        private String imageUrl;

        /*执行异步任务代码处理*/
        @Override
        protected Bitmap doInBackground(String... params) {
            imageUrl = params[0];//获取图片网址
            Bitmap bitmap = bitmapDiskCache.getBitmapFromDiskCache(imageUrl);//获取
            if (bitmap != null) {
                bitmapCache.addBitmapToMemoryCache(imageUrl, bitmap);
                return bitmap;
            } else {
                bitmapDiskCache.downloadBitmapToDiskCache(imageUrl, new BitmapDiskCache.DownloadListener() {
                    @Override
                    public void downloadSuccess(Bitmap bitmap) {
                        bitmapCache.addBitmapToMemoryCache(imageUrl, bitmap);
                    }
                    @Override
                    public void downloadFail() {
                    }
                });
                return bitmapDiskCache.getBitmapFromDiskCache(imageUrl);
            }
        }
        //ui线程执行
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            ImageView imageView = (ImageView) gridview.findViewWithTag(imageUrl);
            //通过Tag找到对应的ImageView,如果ImageView滑出屏幕,则返回null
            if (imageView != null && bitmap != null) {
                imageView.setImageBitmap(bitmap);
            }
            taskCollection.remove(this);
        }
    }

    /**
     * 适配器 Adapter
     */
    public class Adapter extends BaseAdapter {

        @Override
        public int getCount() {
            return Images.imageUrls.length;
        }

        @Override
        public String getItem(int position) {
            return Images.imageUrls[position];
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder = null;
            if(convertView == null) {
                viewHolder = new ViewHolder();
                convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.image_item, null);
                viewHolder.image = (ImageView) convertView.findViewById(R.id.img);
                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder)convertView.getTag();
            }
            //
            RelativeLayout.LayoutParams pra = (RelativeLayout.LayoutParams) viewHolder.image.getLayoutParams();//获取布局参数
            //item中imageview控件布局 高 等于 :(Gridview的宽 减去 2dp*2 )除以3
            pra.height = (gridview.getWidth() - 2*dip2px(2)) / 3;
            //然后再把布局参数设置给 item中ImagView控件
            viewHolder.image.setLayoutParams(pra);//这样就可以让GridView中每个Item适配所有手机屏幕 显示正方形的图片


            //获取数据
            String imageUrl = getItem(position);

            //给每个ImageView设置唯一的tag,防止图片加载错乱

         /* ListView、GridView都有两个方法setTag/findViewWithTag,简单的说就是给Item中的View设置一个唯一的Tag值,
         之后可以通过这个Tag值找到与其对应的View,因为ListView和GridView都有复用机制,所以当Item被滑出屏幕后,有可能会被复用,
         Item中的View也就被设置了另外一个Tag值,所以这时通过之前的Tag寻找View返回的是null。
         这里给ImageView设置了一个唯一Tag值,就是待加载图片的url,之后图片下载完成,再通过这个Tag找到ImageView,
         如果ImageView为null,说明图片已经滑出了屏幕,此时不再加载,否则ImageView加载刚刚下载完成的图片,
         这样防止了下载好的图片加载到别的ImageView而形成错乱。 */

            viewHolder.image.setTag(imageUrl);

            //通过图片HTTP网址作为key值获取内存中缓存bitmap
            Bitmap bitmap = bitmapCache.getBitmapFromMemCache(imageUrl);
            //如果内存缓存中没有为null值
            if (bitmap != null) {
                //如果内存缓存中获取bitmap不为null 就设置显示
                viewHolder.image.setImageBitmap(bitmap);
            } else {
                //否则显示默认系统本地图片
                viewHolder.image.setImageResource(R.drawable.error);
            }
            return convertView;
        }

        public class ViewHolder {
            public ImageView image;
        }

    }
}
public class Images {

   public final static String[] imageUrls = new String[] {
      "https://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/vijrZCYDbRX0Al2mp0nkoUUg081aqGxkPJ0g5YttlT8!/b/dFNm.pqUZAAA&bo=9wEgAwAAAAABB*U!&rf=viewer_4",
        "http://b263.photo.store.qq.com/psb?/V13dV9iu45C4on/ld7gzaKuYPY4FVV3YLPGOlKNcd6tJOXVe*NoND8iDA0!/b/dHy0xZyiFQAA&bo=WAIgAwAAAAABJ3k!&rf=viewer_4",
        "https://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/AVmjKV.U9eNwC3w9BFmMGC7O1CUlBJLkFopsUO0pWGw!/b/dJYE*5qLYwAA&bo=WAIgAwAAAAABB1k!&rf=viewer_4",
        "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/t7i4ojNEchzmj8oVvS3Ww8gShvnCNxJVU6eWvwptngk!/b/dLSUv4ajKgAA&bo=gALgAQAAAAABF1M!&rf=viewer_4",
        "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/ZSVvZcmEmCOXOGZqG*zsoPQXK1xGPYwFoup9CboY0Co!/b/dDOvLpw5WwAA&bo=ngK8AQAAAAABFxE!&rf=viewer_4",
        "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/3VYju5d3RQ4xDvVVWEBbZAEDni.A1Q0QIcq0aAldUmo!/b/dGqvMZyCXAAA&bo=WAIgAwAAAAABJ3k!&rf=viewer_4",
        "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/lffHq5pqnbqzcT4CM1pBzYNrgU1MZjwz9U66wwBHn48!/b/dAAHu4bHKgAA&bo=WAIgAwAAAAABJ3k!&rf=viewer_4",
        "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/fge4NUg88cPsdEexJYo5IO6sZmK0*JjfeW.Cj02AVvg!/b/dHELvobgKgAA&bo=WAKOAQAAAAABF.U!&rf=viewer_4",
        "http://b265.photo.store.qq.com/psb?/V13dV9iu45C4on/x9VFJ.TXojclV6u4KCiccjh38JUW9tzOqMqUUktjL5Q!/b/dA4XA56dFQAA&bo=ngK.AQAAAAABFxM!&rf=viewer_4",
        "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/YAALwLbXuCfmZs0yrwQlvEA0B1Yr*YCwFEOk5pwJErY!/b/dJCG*ZoFZAAA&bo=FQIgAwAAAAABFwQ!&rf=viewer_4",
        "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/r*0TtYwXUvyiz6flf0M*TQ86.amb*iB4W6JDR9wK6Y0!/b/dPm5Bpv6YQAA&bo=wgFYAgAAAAABF6k!&rf=viewer_4",
        "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/NVE9RTyqIbiQm6AzysyEXhRbRtxqjvpcaQ2efZ5RtPY!/b/dHSlK5yTXAAA&bo=dwH0AQAAAAABF7M!&rf=viewer_4",
        "http://b261.photo.store.qq.com/psb?/V13dV9iu45C4on/qKVr2zu99W5es*oSaypR5FxQjf9yldPPYIEhPWSi4gc!/b/dH6pnZsxXwAA&bo=PwKuAQAAAAABF6I!&rf=viewer_4",
        "http://b263.photo.store.qq.com/psb?/V13dV9iu45C4on/iBl*p3eJ31sW5PoxH3Tbwsb0GxBOtsUIiDEMTuTz4nA!/b/dCDDxZyhFQAA&bo=WALCAQAAAAABJ5k!&rf=viewer_4",
        "http://b263.photo.store.qq.com/psb?/V13dV9iu45C4on/g2b1yRkprzhkbNfak5BVEGtWDC2GDFilTYl5ku11A88!/b/dIdHx5ytFQAA&bo=WAKRAQAAAAABF*o!&rf=viewer_4",
        "http://b264.photo.store.qq.com/psb?/V13dV9iu45C4on/AEDj41AXX.uP2GGSniwFK11sjDwKtvXegwHgQV49*WI!/b/dD9qZ52tFQAA&bo=FQIgAwAAAAABFwQ!&rf=viewer_4",
        "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/m3q7UGDFIXWzllGJ8BvWyeKH0BV91YBYQi*XCb0R0b8!/b/dGoD*JqsYwAA&bo=FAIgAwAAAAABFwU!&rf=viewer_4",
        "http://b261.photo.store.qq.com/psb?/V13dV9iu45C4on/C1gh8OtWuASKj04vzt7unB3oGf0o1HrlyW*IAMuaVGU!/b/dOTQoJskXwAA&bo=ngL5AQAAAAABF1Q!&rf=viewer_4",
        "http://b264.photo.store.qq.com/psb?/V13dV9iu45C4on/RvFGR5QUZhkPylO9E9aWNRKewIOqKSwX.vuM2u3USWU!/b/dF*XX52xFQAA&bo=9AGaAgAAAAABF10!&rf=viewer_4",
        "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/1BXVEGEjHmTq0avTc3FCRZDNp*Vgch2zxRBRaVtsMWY!/b/dPYXwYYFKwAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4",
        "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/WNZFEWVctZoBjl7GrgVs*rbuNNusAa2c1IDx1yuYeWw!/b/dFkxCJvBYQAA&bo=WAKPAQAAAAABF.Q!&rf=viewer_4",
        "http://b265.photo.store.qq.com/psb?/V13dV9iu45C4on/wjlbOuA99fS8pW8Kx1z4rg4lZ374LSmiRHrTf*hKmeU!/b/dOATA56eFQAA&bo=xgFaAgAAAAABF68!&rf=viewer_4",
        "http://b263.photo.store.qq.com/psb?/V13dV9iu45C4on/.c0KQfB9wS6jTuBGG7BDlKphn2KQFCJUGFpZxmoIFgw!/b/dCk.xJzoFAAA&bo=WALCAQAAAAABF6k!&rf=viewer_4",
        "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/r7Pk2oHSm68IktNoZZDjY.5Iy7JzbpzuKhNAY027zpk!/b/dCuEvIbgKgAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4",
        "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/4Hz*dWlQ473Y6LBi4oznj0dcAxag5VeP6WpMXfTiGmo!/b/dKVrtoaHKgAA&bo=WALCAQAAAAABF6k!&rf=viewer_4",
        "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/2mX2zpFFk4ylYQYQcVRwyA4z8yHsK.nEzBpn77UrD.Q!/b/dDinBps*YQAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4",
        "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/cr5ZgXxKaxIpmhpF1G6gMfMARG**eTV5vDhy8zf5EcI!/b/dJwbLZxJXAAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4",
        "http://b265.photo.store.qq.com/psb?/V13dV9iu45C4on/1UCeX0OgVRzBgft*UUFky*k48DNkfa5XcphJ9K7u**0!/b/dHGN.53LFQAA&bo=WAKOAQAAAAABF.U!&rf=viewer_4",
        "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/ZTkouoP.d*75aymkf9GgU321K.4yrWfvefP.vDaImKI!/b/dDuSA5sxYwAA&bo=XgGFAQAAAAABF.s!&rf=viewer_4",
        "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/3Y0L4jmW6cSj1hmwQxP6Mvj1bGlRDYUlbR9LFCpnkOw!/b/dH75*prqYwAA&bo=WAIgAwAAAAABJ3k!&rf=viewer_4",
        "http://b261.photo.store.qq.com/psb?/V13dV9iu45C4on/EI0clShfZRE*tll8m5aqsZ5f1kkK33wZhSK0XTS5CmU!/b/dFR*lJu6XgAA&bo=WALVAQAAAAABF74!&rf=viewer_4",
        "http://b265.photo.store.qq.com/psb?/V13dV9iu45C4on/KNEGjdAxSWPiFuCgALNAPomwDSEfrmE9z5YXY.z5LhE!/b/dOaJAZ6fFQAA&bo=EwIgAwAAAAABFwI!&rf=viewer_4",
        "http://b264.photo.store.qq.com/psb?/V13dV9iu45C4on/qyg6ObRxptXvYOZMCp4kZoTQcWbJfq3fFqi8D.piYEU!/b/dH7uYp2kFQAA&bo=ngK.AQAAAAABFxM!&rf=viewer_4",
        "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/36muu2dDA*40NI4gXDFgP28vImFlZMEblYeJPJS80oI!/b/dEVAMJwBWwAA&bo=EwIgAwAAAAABFwI!&rf=viewer_4",
        "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/mOY9P0g3bKYuZ1ypPzLaRPE6hh9.ZtwGqUUTULueMFQ!/b/dPK6LpzeWgAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4",
        "http://b264.photo.store.qq.com/psb?/V13dV9iu45C4on/.Rv6WHl*3VJBs.5oWXAY9l*gN282sMnougFaU2zRuGc!/b/dF*pYp2dFQAA&bo=wgFYAgAAAAABF6k!&rf=viewer_4",
        "http://b264.photo.store.qq.com/psb?/V13dV9iu45C4on/TZPJVMKPjwaQPyZrd1dC*lgRmQ9wzpy5c2ZP0fbtSo8!/b/dBxoYZ2rFQAA&bo=WAKOAQAAAAABF.U!&rf=viewer_4",
        "http://b265.photo.store.qq.com/psb?/V13dV9iu45C4on/SqFHv6iQsyKbLQK2*sP34ocsVTJ.O81.W9kJ64fkCfE!/b/dL0cA56hFQAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4",
        "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/YY.sZiI51CsxDcbEGYCZSjpGmaTtcUiwfv6MA20kWSw!/b/dP09M5zZWwAA&bo=EwIgAwAAAAABFwI!&rf=viewer_4",
        "http://b220.photo.store.qq.com/psb?/V13dV9iu45C4on/kWy12QYZrdda0xxhW2atig6FLyV.51gNj8UyC9zn2m0!/b/YQwWL4MtdgAAYlt2J4NEdQAA&bo=IAMVAgAAAAABFAc!&rf=viewer_4",
        "https://b398.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/LslLujKdmB*NTrinArOUID3xxSKor2Q1z8YpSF3xZyk!/b/dKNHQ.1LCQAA&bo=gAJxBAAAAAABB9c!&rf=viewer_4",
        "http://b397.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/R3p2cy0wQY1O21r3.Yn53Cd7blPiv7MOykfQANMc9Y0!/b/dLH5pew8CQAA&bo=gAJxBAAAAAABF8c!&rf=viewer_4",
        "http://b397.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/ElbfGIY8Iv.RU5xamGbN8LVyfNUqb2DvvHzFqGXxGnU!/b/dGPCrew7CQAA&bo=gAJxBAAAAAABF8c!&rf=viewer_4",
        "http://b399.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/6lMnYjNvzsCIloelIrZHeyJIqwASPF.dGq7RUhy3Nu0!/b/dFhD1.31EwAA&bo=gAJxBAAAAAABF8c!&rf=viewer_4",
        "http://b165.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/poEAD7B4Qwe8bI124U1rAz4Rmc4wZXoXiv1*KnlkJSg!/b/dLkrXGJLIQAA&bo=cQSAAgAAAAABF8c!&rf=viewer_4",
        "http://b165.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/aGUisRhv*y6tB63G4vrYWQ9rloH1CyId57yLaGQA7D0!/b/dACXXWIsIQAA&bo=cQSAAgAAAAABF8c!&rf=viewer_4",
        "http://b168.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/aIXT.CTixGedfOi8PPr0nCCvA29u.2qul*amB88ZxMs!/b/dHRzJ2QFCgAA&bo=gAKAAgAAAAABFzA!&rf=viewer_4",
        "http://b167.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/VY8NcdtZePxle1mn1FXPbadOH.XSqHGU8uScoWezUEo!/b/dCtXkGOkAgAA&bo=IAMWAgAAAAABJzc!&rf=viewer_4",
        "http://b232.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/*RIQpwoj97vmihjQCyoJwX2xHvhimVrMqjePgm5rfP4!/b/dDj4SYqMGwAA&bo=IAMVAgAAAAABFwQ!&rf=viewer_4",
        "http://b228.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/gahu96xz4UXJPkHGv.NVP2DbR9F8urUtvEtrZHtMv.c!/b/dNvR84dHCwAA&bo=wAOAAgAAAAAKF3o!&rf=viewer_4",
        "http://b162.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/ynza6fgyZqqXjcPh9px*ZnhZUhQplwPXE5dEaTsom4Q!/b/dHrfk2BuIwAA&bo=3QJXAgAAAAABF7o!&rf=viewer_4",
        "http://b162.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/H2dN0YjxgWQtyXlWs06ueEDbanylPVNf*h1UVeqrdzk!/b/dKudnmAYHQAA&bo=4QLzAgAAAAABJxI!&rf=viewer_4"
 };
   
}
/**
 * 本地 缓存
 */
public class BitmapDiskCache {

    private DiskLruCache mDiskLruCache = null;

    //
    public BitmapDiskCache(Context context) {
        try {
            File cacheDir = getDiskCacheDir(context, "bitmap");
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 本地缓存路径
     * @param context   环境变量
     * @param uniqueName 文件名称
     * @return {File}
     */
    public File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        /**
         * 判断SD卡是否挂载,和存在
         */
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
            //获取sd卡的缓存文件地址
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            //获取应用下缓存地址
            cachePath = context.getCacheDir().getPath();
        }
        /**
         * 在缓存文件下创建自定义命名的文件夹并返回File
         */
        return new File(cachePath + File.separator + uniqueName);
    }

    /**
     * 通过应用管理获取应用 版本code号
     * @param context
     * @return
     */
    public int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

    //因为涉及下载,需要在线程中执行此方法
    public void downloadBitmapToDiskCache(String imageUrl, DownloadListener mDownloadListener) {
        try {
            String key = getMD5String(imageUrl);
            DiskLruCache.Editor editor = mDiskLruCache.edit(key);
            if (editor != null) {
                OutputStream outputStream = editor.newOutputStream(0);
                if (downloadUrlToStream(imageUrl, outputStream)) {
                    editor.commit();
                    if (mDownloadListener != null) {
                        Bitmap bmp = getBitmapFromDiskCache(imageUrl);
                        mDownloadListener.downloadSuccess(bmp);
                    }
                } else {
                    editor.abort();
                    if (mDownloadListener != null) {
                        mDownloadListener.downloadFail();
                    }
                }
            }
          mDiskLruCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * MD5加密
     * @param key
     * @return
     */
    public String getMD5String(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;
    }

    /**
     *  字节 转 16进制 转 String
     * @param bytes
     * @return
     */
    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();
    }

    private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        //系统版本高 HttpURLConnection 使用回报错 需要在清单文件中添加 android:usesCleartextTraffic="true"
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream());
            out = new BufferedOutputStream(outputStream);
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 获取本地缓存中的图片
     * @param imageUrl 通过图片的url
     * @return
     */
    public Bitmap getBitmapFromDiskCache(String imageUrl) {
        try {
            String key = getMD5String(imageUrl);
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
            if (snapShot != null) {
                InputStream is = snapShot.getInputStream(0);
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                return bitmap;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 移除 指定的imageUrl的图片
     * @param imageUrl
     * @return
     */
    public boolean removeBitmapFromDiskCache(String imageUrl) {
        try {
            String key = getMD5String(imageUrl);
            return mDiskLruCache.remove(key);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 清空本地图片缓存
     */
    public void removeAll() {
        try {
            mDiskLruCache.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取本地缓存 图片个数
     */
    public void getAllSize() {
        mDiskLruCache.size();
    }

    public void flush() {
        try {
            mDiskLruCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void close() {
        try {
            mDiskLruCache.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public interface DownloadListener {
        public void downloadSuccess(Bitmap bitmap);
        public void downloadFail();
    }
}
public class BitmapCaChe {

    private LruCache mMemoryCache;

    public BitmapCaChe() {
        //获取到可用内存的最大值,使用内存超出这个值会引起OutofMemory异常
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // 使用最大可用内存值的1/8作为缓存的大小。
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // 重写此方法来衡量每张图片的大小,默认返回图片数量。
                return bitmap.getByteCount() / 1024;
            }
        };
    }
    /**
     * 添加图片
     * 如果内存缓存中没有就添加到内存缓存中如果有就不执行 key判断
     */
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    /**
     *  获取内存缓存中的图片并返回,
     *  如果就没就通过 LruCache 内部方法 创建返回null值
     *             V createdValue = create(key);
     *             if (createdValue == null) {
     *            return null;
     *         }
     * @param key
     * @return
     */
    public Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }

}
/**
 ******************************************************************************
 * Taken from the JB source code, can be found in:
 * libcore/luni/src/main/java/libcore/io/DiskLruCache.java
 * or direct link:
 * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
 ******************************************************************************
 *
 * A cache that uses a bounded amount of space on a filesystem. Each cache
 * entry has a string key and a fixed number of values. Values are byte
 * sequences, accessible as streams or files. Each value must be between {@code
 * 0} and {@code Integer.MAX_VALUE} bytes in length.
 *
 * 

The cache stores its data in a directory on the filesystem. This * directory must be exclusive to the cache; the cache may delete or overwrite * files from its directory. It is an error for multiple processes to use the * same cache directory at the same time. * *

This cache limits the number of bytes that it will store on the * filesystem. When the number of stored bytes exceeds the limit, the cache will * remove entries in the background until the limit is satisfied. The limit is * not strict: the cache may temporarily exceed it while waiting for files to be * deleted. The limit does not include filesystem overhead or the cache * journal so space-sensitive applications should set a conservative limit. * *

Clients call {@link #edit} to create or update the values of an entry. An * entry may have only one editor at one time; if a value is not available to be * edited then {@link #edit} will return null. *

    *
  • When an entry is being created it is necessary to * supply a full set of values; the empty value should be used as a * placeholder if necessary. *
  • When an entry is being edited, it is not necessary * to supply data for every value; values default to their previous * value. *
* Every {@link #edit} call must be matched by a call to {@link Editor#commit} * or {@link Editor#abort}. Committing is atomic: a read observes the full set * of values as they were before or after the commit, but never a mix of values. * *

Clients call {@link #get} to read a snapshot of an entry. The read will * observe the value at the time that {@link #get} was called. Updates and * removals after the call do not impact ongoing reads. * *

This class is tolerant of some I/O errors. If files are missing from the * filesystem, the corresponding entries will be dropped from the cache. If * an error occurs while writing a cache value, the edit will fail silently. * Callers should handle other problems by catching {@code IOException} and * responding appropriately. */ public final class DiskLruCache implements Closeable { static final String JOURNAL_FILE = "journal"; static final String JOURNAL_FILE_TMP = "journal.tmp"; static final String MAGIC = "libcore.io.DiskLruCache"; static final String VERSION_1 = "1"; static final long ANY_SEQUENCE_NUMBER = -1; private static final String CLEAN = "CLEAN"; private static final String DIRTY = "DIRTY"; private static final String REMOVE = "REMOVE"; private static final String READ = "READ"; private static final Charset UTF_8 = Charset.forName("UTF-8"); private static final int IO_BUFFER_SIZE = 8 * 1024; /* * This cache uses a journal file named "journal". A typical journal file * looks like this: * libcore.io.DiskLruCache * 1 * 100 * 2 * * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 * DIRTY 335c4c6028171cfddfbaae1a9c313c52 * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 * REMOVE 335c4c6028171cfddfbaae1a9c313c52 * DIRTY 1ab96a171faeeee38496d8b330771a7a * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 * READ 335c4c6028171cfddfbaae1a9c313c52 * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 * * The first five lines of the journal form its header. They are the * constant string "libcore.io.DiskLruCache", the disk cache's version, * the application's version, the value count, and a blank line. * * Each of the subsequent lines in the file is a record of the state of a * cache entry. Each line contains space-separated values: a state, a key, * and optional state-specific values. * o DIRTY lines track that an entry is actively being created or updated. * Every successful DIRTY action should be followed by a CLEAN or REMOVE * action. DIRTY lines without a matching CLEAN or REMOVE indicate that * temporary files may need to be deleted. * o CLEAN lines track a cache entry that has been successfully published * and may be read. A publish line is followed by the lengths of each of * its values. * o READ lines track accesses for LRU. * o REMOVE lines track entries that have been deleted. * * The journal file is appended to as cache operations occur. The journal may * occasionally be compacted by dropping redundant lines. A temporary file named * "journal.tmp" will be used during compaction; that file should be deleted if * it exists when the cache is opened. */ private final File directory; private final File journalFile; private final File journalFileTmp; private final int appVersion; private final long maxSize; private final int valueCount; private long size = 0; private Writer journalWriter; private final LinkedHashMap lruEntries = new LinkedHashMap(0, 0.75f, true); private int redundantOpCount; /** * To differentiate between old and current snapshots, each entry is given * a sequence number each time an edit is committed. A snapshot is stale if * its sequence number is not equal to its entry's sequence number. */ private long nextSequenceNumber = 0; /* From java.util.Arrays */ @SuppressWarnings("unchecked") private static T[] copyOfRange(T[] original, int start, int end) { final int originalLength = original.length; // For exception priority compatibility. if (start > end) { throw new IllegalArgumentException(); } if (start < 0 || start > originalLength) { throw new ArrayIndexOutOfBoundsException(); } final int resultLength = end - start; final int copyLength = Math.min(resultLength, originalLength - start); final T[] result = (T[]) Array .newInstance(original.getClass().getComponentType(), resultLength); System.arraycopy(original, start, result, 0, copyLength); return result; } /** * Returns the remainder of 'reader' as a string, closing it when done. */ public static String readFully(Reader reader) throws IOException { try { StringWriter writer = new StringWriter(); char[] buffer = new char[1024]; int count; while ((count = reader.read(buffer)) != -1) { writer.write(buffer, 0, count); } return writer.toString(); } finally { reader.close(); } } /** * Returns the ASCII characters up to but not including the next "\r\n", or * "\n". * * @throws EOFException if the stream is exhausted before the next newline * character. */ public static String readAsciiLine(InputStream in) throws IOException { // TODO: support UTF-8 here instead StringBuilder result = new StringBuilder(80); while (true) { int c = in.read(); if (c == -1) { throw new EOFException(); } else if (c == '\n') { break; } result.append((char) c); } int length = result.length(); if (length > 0 && result.charAt(length - 1) == '\r') { result.setLength(length - 1); } return result.toString(); } /** * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null. */ public static void closeQuietly(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (RuntimeException rethrown) { throw rethrown; } catch (Exception ignored) { } } } /** * Recursively delete everything in {@code dir}. */ // TODO: this should specify paths as Strings rather than as Files public static void deleteContents(File dir) throws IOException { File[] files = dir.listFiles(); if (files == null) { throw new IllegalArgumentException("not a directory: " + dir); } for (File file : files) { if (file.isDirectory()) { deleteContents(file); } if (!file.delete()) { throw new IOException("failed to delete file: " + file); } } } /** This cache uses a single background thread to evict entries. */ private final ExecutorService executorService = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); private final Callable cleanupCallable = new Callable() { @Override public Void call() throws Exception { synchronized (DiskLruCache.this) { if (journalWriter == null) { return null; // closed } trimToSize(); if (journalRebuildRequired()) { rebuildJournal(); redundantOpCount = 0; } } return null; } }; private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { this.directory = directory; this.appVersion = appVersion; this.journalFile = new File(directory, JOURNAL_FILE); this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP); this.valueCount = valueCount; this.maxSize = maxSize; } /** * Opens the cache in {@code directory}, creating a cache if none exists * there. * * @param directory a writable directory * @param appVersion * @param valueCount the number of values per cache entry. Must be positive. * @param maxSize the maximum number of bytes this cache should use to store * @throws IOException if reading or writing the cache directory fails */ public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) throws IOException { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } if (valueCount <= 0) { throw new IllegalArgumentException("valueCount <= 0"); } // prefer to pick up where we left off DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); if (cache.journalFile.exists()) { try { cache.readJournal(); cache.processJournal(); cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true), IO_BUFFER_SIZE); return cache; } catch (IOException journalIsCorrupt) { // System.logW("DiskLruCache " + directory + " is corrupt: " // + journalIsCorrupt.getMessage() + ", removing"); cache.delete(); } } // create a new empty cache directory.mkdirs(); cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); cache.rebuildJournal(); return cache; } private void readJournal() throws IOException { InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE); try { String magic = readAsciiLine(in); String version = readAsciiLine(in); String appVersionString = readAsciiLine(in); String valueCountString = readAsciiLine(in); String blank = readAsciiLine(in); if (!MAGIC.equals(magic) || !VERSION_1.equals(version) || !Integer.toString(appVersion).equals(appVersionString) || !Integer.toString(valueCount).equals(valueCountString) || !"".equals(blank)) { throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " + valueCountString + ", " + blank + "]"); } while (true) { try { readJournalLine(readAsciiLine(in)); } catch (EOFException endOfJournal) { break; } } } finally { closeQuietly(in); } } private void readJournalLine(String line) throws IOException { String[] parts = line.split(" "); if (parts.length < 2) { throw new IOException("unexpected journal line: " + line); } String key = parts[1]; if (parts[0].equals(REMOVE) && parts.length == 2) { lruEntries.remove(key); return; } Entry entry = lruEntries.get(key); if (entry == null) { entry = new Entry(key); lruEntries.put(key, entry); } if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) { entry.readable = true; entry.currentEditor = null; entry.setLengths(copyOfRange(parts, 2, parts.length)); } else if (parts[0].equals(DIRTY) && parts.length == 2) { entry.currentEditor = new Editor(entry); } else if (parts[0].equals(READ) && parts.length == 2) { // this work was already done by calling lruEntries.get() } else { throw new IOException("unexpected journal line: " + line); } } /** * Computes the initial size and collects garbage as a part of opening the * cache. Dirty entries are assumed to be inconsistent and will be deleted. */ private void processJournal() throws IOException { deleteIfExists(journalFileTmp); for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) { Entry entry = i.next(); if (entry.currentEditor == null) { for (int t = 0; t < valueCount; t++) { size += entry.lengths[t]; } } else { entry.currentEditor = null; for (int t = 0; t < valueCount; t++) { deleteIfExists(entry.getCleanFile(t)); deleteIfExists(entry.getDirtyFile(t)); } i.remove(); } } } /** * Creates a new journal that omits redundant information. This replaces the * current journal if it exists. */ private synchronized void rebuildJournal() throws IOException { if (journalWriter != null) { journalWriter.close(); } Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE); writer.write(MAGIC); writer.write("\n"); writer.write(VERSION_1); writer.write("\n"); writer.write(Integer.toString(appVersion)); writer.write("\n"); writer.write(Integer.toString(valueCount)); writer.write("\n"); writer.write("\n"); for (Entry entry : lruEntries.values()) { if (entry.currentEditor != null) { writer.write(DIRTY + ' ' + entry.key + '\n'); } else { writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); } } writer.close(); journalFileTmp.renameTo(journalFile); journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE); } private static void deleteIfExists(File file) throws IOException { // try { // Libcore.os.remove(file.getPath()); // } catch (ErrnoException errnoException) { // if (errnoException.errno != OsConstants.ENOENT) { // throw errnoException.rethrowAsIOException(); // } // } if (file.exists() && !file.delete()) { throw new IOException(); } } /** * Returns a snapshot of the entry named {@code key}, or null if it doesn't * exist is not currently readable. If a value is returned, it is moved to * the head of the LRU queue. */ public synchronized Snapshot get(String key) throws IOException { checkNotClosed(); validateKey(key); Entry entry = lruEntries.get(key); if (entry == null) { return null; } if (!entry.readable) { return null; } /* * Open all streams eagerly to guarantee that we see a single published * snapshot. If we opened streams lazily then the streams could come * from different edits. */ InputStream[] ins = new InputStream[valueCount]; try { for (int i = 0; i < valueCount; i++) { ins[i] = new FileInputStream(entry.getCleanFile(i)); } } catch (FileNotFoundException e) { // a file must have been deleted manually! return null; } redundantOpCount++; journalWriter.append(READ + ' ' + key + '\n'); if (journalRebuildRequired()) { executorService.submit(cleanupCallable); } return new Snapshot(key, entry.sequenceNumber, ins); } /** * Returns an editor for the entry named {@code key}, or null if another * edit is in progress. */ public Editor edit(String key) throws IOException { return edit(key, ANY_SEQUENCE_NUMBER); } private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { checkNotClosed(); validateKey(key); Entry entry = lruEntries.get(key); if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) { return null; // snapshot is stale } if (entry == null) { entry = new Entry(key); lruEntries.put(key, entry); } else if (entry.currentEditor != null) { return null; // another edit is in progress } Editor editor = new Editor(entry); entry.currentEditor = editor; // flush the journal before creating files to prevent file leaks journalWriter.write(DIRTY + ' ' + key + '\n'); journalWriter.flush(); return editor; } /** * Returns the directory where this cache stores its data. */ public File getDirectory() { return directory; } /** * Returns the maximum number of bytes that this cache should use to store * its data. */ public long maxSize() { return maxSize; } /** * Returns the number of bytes currently being used to store the values in * this cache. This may be greater than the max size if a background * deletion is pending. */ public synchronized long size() { return size; } private synchronized void completeEdit(Editor editor, boolean success) throws IOException { Entry entry = editor.entry; if (entry.currentEditor != editor) { throw new IllegalStateException(); } // if this edit is creating the entry for the first time, every index must have a value if (success && !entry.readable) { for (int i = 0; i < valueCount; i++) { if (!entry.getDirtyFile(i).exists()) { editor.abort(); throw new IllegalStateException("edit didn't create file " + i); } } } for (int i = 0; i < valueCount; i++) { File dirty = entry.getDirtyFile(i); if (success) { if (dirty.exists()) { File clean = entry.getCleanFile(i); dirty.renameTo(clean); long oldLength = entry.lengths[i]; long newLength = clean.length(); entry.lengths[i] = newLength; size = size - oldLength + newLength; } } else { deleteIfExists(dirty); } } redundantOpCount++; entry.currentEditor = null; if (entry.readable | success) { entry.readable = true; journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); if (success) { entry.sequenceNumber = nextSequenceNumber++; } } else { lruEntries.remove(entry.key); journalWriter.write(REMOVE + ' ' + entry.key + '\n'); } if (size > maxSize || journalRebuildRequired()) { executorService.submit(cleanupCallable); } } /** * We only rebuild the journal when it will halve the size of the journal * and eliminate at least 2000 ops. */ private boolean journalRebuildRequired() { final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000; return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD && redundantOpCount >= lruEntries.size(); } /** * Drops the entry for {@code key} if it exists and can be removed. Entries * actively being edited cannot be removed. * * @return true if an entry was removed. */ public synchronized boolean remove(String key) throws IOException { checkNotClosed(); validateKey(key); Entry entry = lruEntries.get(key); if (entry == null || entry.currentEditor != null) { return false; } for (int i = 0; i < valueCount; i++) { File file = entry.getCleanFile(i); if (!file.delete()) { throw new IOException("failed to delete " + file); } size -= entry.lengths[i]; entry.lengths[i] = 0; } redundantOpCount++; journalWriter.append(REMOVE + ' ' + key + '\n'); lruEntries.remove(key); if (journalRebuildRequired()) { executorService.submit(cleanupCallable); } return true; } /** * Returns true if this cache has been closed. */ public boolean isClosed() { return journalWriter == null; } private void checkNotClosed() { if (journalWriter == null) { throw new IllegalStateException("cache is closed"); } } /** * Force buffered operations to the filesystem. */ public synchronized void flush() throws IOException { checkNotClosed(); trimToSize(); journalWriter.flush(); } /** * Closes this cache. Stored values will remain on the filesystem. */ public synchronized void close() throws IOException { if (journalWriter == null) { return; // already closed } for (Entry entry : new ArrayList(lruEntries.values())) { if (entry.currentEditor != null) { entry.currentEditor.abort(); } } trimToSize(); journalWriter.close(); journalWriter = null; } private void trimToSize() throws IOException { while (size > maxSize) { // Map.Entry toEvict = lruEntries.eldest(); final Map.Entry toEvict = lruEntries.entrySet().iterator().next(); remove(toEvict.getKey()); } } /** * Closes the cache and deletes all of its stored values. This will delete * all files in the cache directory including files that weren't created by * the cache. */ public void delete() throws IOException { close(); deleteContents(directory); } private void validateKey(String key) { if (key.contains(" ") || key.contains("\n") || key.contains("\r")) { throw new IllegalArgumentException( "keys must not contain spaces or newlines: \"" + key + "\""); } } private static String inputStreamToString(InputStream in) throws IOException { return readFully(new InputStreamReader(in, UTF_8)); } /** * A snapshot of the values for an entry. */ public final class Snapshot implements Closeable { private final String key; private final long sequenceNumber; private final InputStream[] ins; private Snapshot(String key, long sequenceNumber, InputStream[] ins) { this.key = key; this.sequenceNumber = sequenceNumber; this.ins = ins; } /** * Returns an editor for this snapshot's entry, or null if either the * entry has changed since this snapshot was created or if another edit * is in progress. */ public Editor edit() throws IOException { return DiskLruCache.this.edit(key, sequenceNumber); } /** * Returns the unbuffered stream with the value for {@code index}. */ public InputStream getInputStream(int index) { return ins[index]; } /** * Returns the string value for {@code index}. */ public String getString(int index) throws IOException { return inputStreamToString(getInputStream(index)); } @Override public void close() { for (InputStream in : ins) { closeQuietly(in); } } } /** * Edits the values for an entry. */ public final class Editor { private final Entry entry; private boolean hasErrors; private Editor(Entry entry) { this.entry = entry; } /** * Returns an unbuffered input stream to read the last committed value, * or null if no value has been committed. */ public InputStream newInputStream(int index) throws IOException { synchronized (DiskLruCache.this) { if (entry.currentEditor != this) { throw new IllegalStateException(); } if (!entry.readable) { return null; } return new FileInputStream(entry.getCleanFile(index)); } } /** * Returns the last committed value as a string, or null if no value * has been committed. */ public String getString(int index) throws IOException { InputStream in = newInputStream(index); return in != null ? inputStreamToString(in) : null; } /** * Returns a new unbuffered output stream to write the value at * {@code index}. If the underlying output stream encounters errors * when writing to the filesystem, this edit will be aborted when * {@link #commit} is called. The returned output stream does not throw * IOExceptions. */ public OutputStream newOutputStream(int index) throws IOException { synchronized (DiskLruCache.this) { if (entry.currentEditor != this) { throw new IllegalStateException(); } return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index))); } } /** * Sets the value at {@code index} to {@code value}. */ public void set(int index, String value) throws IOException { Writer writer = null; try { writer = new OutputStreamWriter(newOutputStream(index), UTF_8); writer.write(value); } finally { closeQuietly(writer); } } /** * Commits this edit so it is visible to readers. This releases the * edit lock so another edit may be started on the same key. */ public void commit() throws IOException { if (hasErrors) { completeEdit(this, false); remove(entry.key); // the previous entry is stale } else { completeEdit(this, true); } } /** * Aborts this edit. This releases the edit lock so another edit may be * started on the same key. */ public void abort() throws IOException { completeEdit(this, false); } private class FaultHidingOutputStream extends FilterOutputStream { private FaultHidingOutputStream(OutputStream out) { super(out); } @Override public void write(int oneByte) { try { out.write(oneByte); } catch (IOException e) { hasErrors = true; } } @Override public void write(byte[] buffer, int offset, int length) { try { out.write(buffer, offset, length); } catch (IOException e) { hasErrors = true; } } @Override public void close() { try { out.close(); } catch (IOException e) { hasErrors = true; } } @Override public void flush() { try { out.flush(); } catch (IOException e) { hasErrors = true; } } } } private final class Entry { private final String key; /** Lengths of this entry's files. */ private final long[] lengths; /** True if this entry has ever been published */ private boolean readable; /** The ongoing edit or null if this entry is not being edited. */ private Editor currentEditor; /** The sequence number of the most recently committed edit to this entry. */ private long sequenceNumber; private Entry(String key) { this.key = key; this.lengths = new long[valueCount]; } public String getLengths() throws IOException { StringBuilder result = new StringBuilder(); for (long size : lengths) { result.append(' ').append(size); } return result.toString(); } /** * Set lengths using decimal numbers like "10123". */ private void setLengths(String[] strings) throws IOException { if (strings.length != valueCount) { throw invalidLengths(strings); } try { for (int i = 0; i < strings.length; i++) { lengths[i] = Long.parseLong(strings[i]); } } catch (NumberFormatException e) { throw invalidLengths(strings); } } private IOException invalidLengths(String[] strings) throws IOException { throw new IOException("unexpected journal line: " + Arrays.toString(strings)); } public File getCleanFile(int i) { return new File(directory, key + "." + i); } public File getDirtyFile(int i) { return new File(directory, key + "." + i + ".tmp"); } } }

你可能感兴趣的:(android)