Glide手写实现之网络图片加载实现

系列文章

Glide手写实现之资源封装
Glide手写实现之活动缓存
Glide手写实现之内存缓存
Glide手写实现之磁盘缓存
Glide手写实现之生命周期关联
Glide手写实现之网络图片加载实现
Glide手写实现之复用池

图片加载理论分析(网络图片)

前面几篇文章中,我们了解到Glide的三级缓存和生命周期与Activity的绑定。 本文章会把之前的三级缓存融合在一起,成为一套缓存策略。根据生命周期,调度缓存和释放缓存。理论上加载网络图片的时候,先要从缓存中获取,如果缓存中拿不到,再从网络中获取。网络中获取到的图片需要保存到缓存中,以备下次使用。

RequestTargetEngine

加载图片的引擎,先从活动缓存中获取,获取到直接可以设置到ImageView中,否则从内存缓存中获取,获取到设置到ImageView中,否则从磁盘中获取,获取到设置到ImageView中,否则从网络中加载,在回调中设置到ImageView中,并且放到磁盘缓存中。

/**
 * 加载图片资源
 */
public class RequestTargetEngine implements LifecycleCallback, ValueCallback, MemoryCacheCallback, ResponseListener {

    private final String TAG = RequestTargetEngine.class.getSimpleName();

    @Override
    public void glideInitAction() {
        Log.d(TAG, "glideInitAction: Glide生命周期之 已经开启了 初始化了....");
    }

    @Override
    public void glideStopAction() {
        Log.d(TAG, "glideInitAction: Glide生命周期之 已经停止中 ....");
    }

    @Override
    public void glideRecycleAction() {
        Log.d(TAG, "glideInitAction: Glide生命周期之 进行释放操作 缓存策略释放操作等 >>>>>> ....");

        if (activeCache != null) {
            activeCache.closeThread(); // 把活动缓存给释放掉
        }

        // 把内存缓存移除

    }

    private ActiveCache activeCache; // 活动缓存
    private MemoryCache memoryCache; // 内存缓存
    private DiskLruCacheImpl diskLruCache; // 磁盘缓存
                                           // 复用池
    private final int MEMORY_MAX_SIZE = 1024 * 1024 * 60;

    public RequestTargetEngine() {
        if (activeCache == null) {
            activeCache = new ActiveCache(this); // 回调告诉外界,Value资源不再使用了 设置监听
        }
        if (memoryCache == null) {
            memoryCache = new MemoryCache(MEMORY_MAX_SIZE); //LRU最少使用的元素会被移除 设置监听
            memoryCache.setMemoryCacheCallback(this);
        }
        // 初始化磁盘缓存
        diskLruCache = new DiskLruCacheImpl();
    }

    private String path;
    private Context glideContext;
    private String key; // ac037ea49e34257dc5577d1796bb137dbaddc0e42a9dff051beee8ea457a4668
    private ImageView imageView; // 显示的目标

    /**
     * RequestManager传递的值
     */
    public void loadValueInitAction(String path, Context glideContext) {
        this.path = path;
        this.glideContext = glideContext;
        key = new Key(path).getKey();
    }

    public void into(ImageView imageView) {
        this.imageView = imageView;

        Tool.checkNotEmpty(imageView);
        Tool.assertMainThread();

        // TODO 加载资源 --》 缓存 ---》网络/SD/ 加载资源 成功后 --》资源 保存到缓存中 >>>
        Value value = cacheAction();
        if (null != value) {
            // 使用完成了 减一
            value.nonUseAction();
            imageView.setImageBitmap(value.getmBitmap());
        }
    }

    // TODO 加载资源 --》 缓存 ---》网络/SD/ 加载资源 成功后 --》资源 保存到缓存中 >>>
    private Value cacheAction() {
        // TODO 第一步,判断活动缓存是否有资源,如果有资源 就返回, 否则就继续往下找
        Value value = activeCache.get(key);
        if (null != value) {
            Log.d(TAG, "cacheAction: 本次加载是在(活动缓存)中获取的资源>>>");

            // 返回 代表 使用了一次 Value
            value.useAction(); // 使用了一次 加一
            return value;
        }

        // TODO 第二步,从内存缓存中去找,如果找到了,内存缓存中的元素 “移动” 到 活动缓存, 然后再返回
        value = memoryCache.get(key);
        if (null != value) {
            // 移动操作
            memoryCache.shoudonRemove(key); // 移除内存缓存
            activeCache.put(key, value); // 把内存缓存中的元素 加入到活动缓存中

            Log.d(TAG, "cacheAction: 本次加载是在(内存缓存)中获取的资源>>>");

            // 返回 代表 使用了一次 Value
            value.useAction(); // 使用了一次 加一
            return value;
        }

        // TODO 第三步,从磁盘缓存中去找,如果找到了,把磁盘缓存中的元素 加入到 活动缓存中
        value = diskLruCache.get(key);
        if (null != value) {
            // 把磁盘缓存中的元素 --> 加入到活动缓存中
            activeCache.put(key, value);

            // 把磁盘缓存中的元素 --> 加入到内存缓存中
            // memoryCache.put(key, value);

            Log.d(TAG, "cacheAction: 本次加载是在(磁盘缓存)中获取的资源>>>");

            // 返回 代表 使用了一次 Value
            value.useAction(); // 使用了一次 加一
            return value;
        }

        // TODO 第四步,真正的去加载外部资源了, 去网络上加载/去SD本地上加载
        value = new LoadDataManager().loadResource(path, this, glideContext);
        if (value != null)
            return value;

        return null;
    }

    /**
     * 活动缓存间接的调用Value所发出的
     * 回调告诉外界,Value资源不再使用了
     * 监听的方法(Value不再使用了)
     * @param key
     * @param value
     */
    @Override
    public void valueNonUseListener(String key, Value value) {
        // 把活动缓存操作的Value资源 加入到 内存缓存
        if (key != null && value != null) {
            memoryCache.put(key, value);
        }
    }

    /**
     * 内存缓存发出的
     * LRU最少使用的元素会被移除
     * @param key
     * @param oldValue
     */
    @Override
    public void entryRemovedMemoryCache(String key, Value oldValue) {
        // 添加到复用池 ...... ,空留的功能点
    }

    // 加载外部资源成功
    @Override
    public void responseSuccess(Value value) {
        if (null != value) {
            saveCahce(key, value);
            imageView.setImageBitmap(value.getmBitmap());
        }
    }

    // 加载外部资源失败
    @Override
    public void responseException(Exception e) {
        Log.d(TAG, "responseException: 加载外部资源失败 e:" + e.getMessage());
    }

    /**
     * 保存到缓存中
     * @param key
     * @param value
     */
    private void saveCahce(String key, Value value) {
        Log.d(TAG, "saveCahce: >>>>>>>>>>>>>>>>>>>>>> 加载外部资源成功后,保存到缓存中 key:" + key + " value:" + value);
        value.setKey(key);

        if (diskLruCache != null) {
            diskLruCache.put(key, value); // 保存到磁盘缓存中
        }
    }
}

ILoadData

从外部加载资源的统一标准接口

/**
 * 加载外部资源 标准
 */
public interface ILoadData {

    // 加载外部资源的行为
    public Value loadResource(String path, ResponseListener responseListener, Context context);

}

ResponseListener

从外部资源加载的回调接口,成功或者失败都要回调

/**
 * 加载外部资源 成功与失败的 回调
 */
public interface ResponseListener {

    public void responseSuccess(Value value);

    public void responseException(Exception e);

}

LoadDataManager

加载外部资源的实现类,本文只讨论从网络中获取资源。

public class LoadDataManager implements ILoadData, Runnable {

    private final String TAG = LoadDataManager.class.getSimpleName();

    private String path;
    private ResponseListener responseListener;
    private Context context;

    @Override
    public Value loadResource(String path, ResponseListener responseListener, Context context) {
        this.path = path;
        this.responseListener = responseListener;
        this.context = context;

        // 加载 网络图片/SD本地图片/..

        Uri uri = Uri.parse(path);

        // 网络图片
        if ("HTTP".equalsIgnoreCase(uri.getScheme()) || "HTTPS".equalsIgnoreCase(uri.getScheme())) {
                new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                        60, TimeUnit.SECONDS,
                        new SynchronousQueue<Runnable>()).execute(this);
        }

        // SD本地图片 返回Value

        // ....

        return null;
    }


    @Override
    public void run() {
        InputStream inputStream = null;
        HttpURLConnection httpURLConnection = null;

        try {
            URL url = new URL(path);
            URLConnection urlConnection = url.openConnection();
            httpURLConnection = (HttpURLConnection) urlConnection;
            httpURLConnection.setConnectTimeout(5000);
            final int responseCode = httpURLConnection.getResponseCode();
            if (HttpURLConnection.HTTP_OK == responseCode) {
                inputStream = httpURLConnection.getInputStream();
                final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

                // 成功 切换主线程
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        Value value = Value.getInstance();
                        value.setmBitmap(bitmap);

                        // 回调成功
                        responseListener.responseSuccess(value);
                    }
                });
            } else {
                // 失败 切换主线程
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        responseListener.responseException(new IllegalStateException("请求失败 请求码:" + responseCode));
                    }
                });
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.d(TAG, "run: 关闭 inputStream.close(); e:" + e.getMessage());
                }
            }

            if (httpURLConnection != null) {
                httpURLConnection.disconnect();
            }
        }
    }
}

内存缓存插入时机

当活动缓存中的资源不再使用的时候,会将资源缓存到内存缓存

总结

第一次的时候,去网络下载图片,保存到磁盘缓存中(/sd/disk_lru_cache_dir/key)
第二次的时候,从磁盘缓存中获取到,放到活动缓存中,再从活动缓存中,找到了资源
第三次的时候,直接再活动缓存中,找到了资源
第N次的时候,直接再活动缓存中,找到了资源

把Activity退出的时候,生命周期的回调方法,进行释放,活动缓存的释放
又一次加载的时候,从内存缓存中获取了
下一次加载的时候,就是从活动缓存获取了

把App给杀掉
整个活动缓存,整个内存缓存,都没有了
所以启动第一次从磁盘缓存中获取

1.活动缓存,HashMap管理的,用到了弱引用去回收移除 ----> 活动缓存(Value没有被使用了)移除后 交给 内存缓存
2.内存缓存,内存缓存作为第二道防线,LRU算法的
3.磁盘缓存,缓存中最后一道防线,保存手机存储的,LRU算法的
4.生命周期的管理,通过添加Fragment到Activity中实现的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值