zoukankan      html  css  js  c++  java
  • Android 常用开源框架源码解析 系列 (四)Glide

    一、定义 
     
        Glide 一个被google所推荐的图片加载库,作者是bumptech。对Android SDk 最低要求是 API 10 
        与之功能类似的是Square公司的picasso 
     
    二、基本概念
     
        Model :数据来源 :Uri、本地文件、资源ID
        Data :加工数据
        Resource :对原始数据进行解码,解码之后的资源 resource
        Resource decode :资源解码器
        TransformedResource:转换资源
        TranscodedResource:转码,将静态、Gif动态图进行格式转换以便能加载
        Target :目标图片
        
    三、整体流程
     
    A:Model图片数据源 ———ModelLoader加载—>原始数据Data——Decoder解码——>
        Resource——Transform裁剪——>TransformResource——Transcode转码——>TranscodeResource——封装——>Target
     
    四、源码
        引入:compile 'com.github.bumptech.glide:glide:3.7.0'
        
        4.1、使用流程三步曲: Glide
                            .with(“上下文context”)
                            .load(“url”)
                            .into(“显示的控件资源");
     
        4.2、常用加载图片的配置参数:
    public void LoadImage(View view) {
                //with 创建一个加载图片的Glide实例,流式接口
        Glide.with(getApplicationContext()) //指定的Context,传入的参数会决定整个Glide的生命周期
            ps:图片的加载会和传入的Acitivty或是Fragment保持一致,所以建议使用Activity 或是Fragment作为参数而不是单纯的使用this 或是 context
        
                .load("url")//指定的图片的URL
     
                加载占位图:-int or Drawable 
               .placeholder(R.mipmap.ic_launcher) //指定图片未成功加载前现实的图片占位符
                // ,直到加载的网络图片显示就会被替换,仅支持本地图片资源
                 错误占位图:-int or Drawable 
               .error(R.mipmap.ic_launcher)  //指定图片加载失败显示的图片占位图
                
                加载本地缩略图:
               .thumbnail( float ) //0.2f Glide会显示原始图片的20%大小,注意ImageView的ScaleType值的设置
                加载网络缩略图:
               DrawableRequestBuilder<String> thumbnailRequest = Glide.with(context).load(url);            
                Glide.with( context) .load(url) 
                    .thumbnail (thumbnailRequest ).into (imageView);
     
                加载图片动画效果:
                    .crossFade() or crossfade(int duration)  //强制开启GLide默认的图片淡出淡入效果,默认持续时间300ms,设置dontAnimate()设置无任何淡出淡入效果
                    
                显示Gif 和Video 功能:
            String gifUrl = “…xxxxoo.git”;
                .load (gifUrl) 
                .asGif() 
                .error(xxxx)
                // 如果图片类型不是Gif的话 就会当作load 失败来处理
                
                显示静态的Gif图片 //仅仅显示Gif的第一桢图像
            String gifUrl = “…xxxxoo.git”;
                .load (gifUrl) 
                .asBitmap() 
                .error(xxxx)
            
               显示手机本地视频文件:
                String filePath = “/storage/emulated/0/xxxx.mp4"
                .load(Uri.fromFile ( new File (filePath) ))
                
               .override(300, 300)  //不自动适配图片尺寸的时候进行手动进行图片尺寸设置 ,glide可以自动限制图片的尺寸来优化图片的加载
                //ps:Glide不会完整的加载原始图片资源到内存当中,会自动判断imageView大小再进行最优尺寸选择加载
     
               .fitCenter()  //指定图片缩放类型1,显示的图像宽和高都小于等于ImageView的边界范围
                //ps:缺陷 有可能不被填满所需要的ImageView, FIT_CENTER
     
               .centerCrop()  //指定图片缩放类型2。显示的图像填充整个ImageView,然后裁剪额外超出的部分
                //ps:缺陷,有可能完整填充ImageView但是图片不能完整显示,CENTER_CROP
     
               .skipMemoryCache(true)  //不将该图片放在内存缓存中,但是仍然会利用该部分磁盘缓存,依然会在磁盘缓存中建立区域,Glide默认将图片放入内存缓存中
                //ps: Glide默认会将图片缓存到内存缓存中所以无需传入false值,若同一Url地址在首次没有调用该方法则第二次直接从内存中缓存缓存图片,若想调整行为需要保证每次调用行为一致
     
                //硬盘缓存策略-枚举值:默认开启
               .diskCacheStrategy(DiskCacheStrategy.NONE) //禁用硬盘的缓存 让其处于null状态,跳过磁盘缓存
                .diskCacheStrategy(DiskCacheStrategy.SOURCE) //仅仅只缓存原来的全分辨率尺寸图像
                .diskCacheStrategy(DiskCacheStrategy.RESULT) //仅仅只缓存资源最终的加载图像(降低压缩分辨率后的图片)
                .diskCacheStrategy(DiskCacheStrategy.ALL)   //缓存所有版本的图片
     
               .priority(Priority.HIGH)//优先级  先处理优先级高的图片信息 ,但不保证所有图片按照优先级顺序进行加载
     
                .into(imageview); //显示到指定的ImageView
    }
     
       4.3、with()解析: 基础目的:获取RequestManager对象(管理Request请求)
     
        根据当前的上下文和组建选择不同的with构造方法,不同的参数对于图片加载的生命周期产生不同的影响,将图片加载的生命周期和组件相挂钩。
    以context 为例:
    public static RequestManager with(Context context) {
        //用于产生requestManager 图片请求管理 
       RequestManagerRetriever 生产requestManager
        RequestManagerRetrieverretriever = RequestManagerRetriever.get();
        return retriever.get(context);
    }
    //通过RequestManagerRetriever 这个生产Request类来处理,同时Glide绑定了组件的生命周期
     
    RequestManager的创建流程:
    public RequestManager get(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("You cannot start a load on a null Context");
        } 
        //当前Context 是否是在主线程,context是否是application的实例
    else if (Util.isOnMainThread() && !(context instanceof Application)) {
            if (context instanceof FragmentActivity) {
                return get((FragmentActivity) context);
            } else if (context instanceof Activity) {
                return get((Activity) context);
            } else if (context instanceof ContextWrapper) {
                return get(((ContextWrapper) context).getBaseContext());
            }
        }
        //返回一个单例模式的ApplicationManager
        return getApplicationManager(context);
    }
        //双重锁检查机制的单例模型
    获取唯一个RequestManager,进行图片请求的处理
    private RequestManager getApplicationManager(Context context) {
        if (applicationManager == null) {
            synchronized (this) {
                if (applicationManager == null) {
                    applicationManager = new RequestManager(context.getApplicationContext(),
                            new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());
                }
            }
        }
        return applicationManager;
    }
       RequestManagerRetriever的 get()解析:
    a、传入application 类型的Context
    当Glide传入的是整个程序的生命周期的时候,就不需要进行过多处理
     
    b、传入非application 类型的Context (activity、fragment)
    public RequestManager get(FragmentActivity activity) {
       //是否是在后台线程,只有在非UI线程才进else
        if (Util.isOnBackgroundThread()) {
            return get(activity.getApplicationContext());
        } else {
            assertNotDestroyed(activity);
            FragmentManager fm = activity.getSupportFragmentManager();
            return supportFragmentGet(activity, fm);
        }
    }
    supportFragmentGet():
    RequestManager supportFragmentGet(Context context, FragmentManager fm) {
       //Fragment 添加到Activity有两种方式,A有Ui的Fragment B没有Ui界面的Fragment
        //这里使用第二种方式,通过RequestManagerFragment来监听Activity的生命周期来完成绑定生命周期,图片加载选择的过程
            ps:Glide无法直接监听Activity的生命周期
     SupportRequestManagerFragment current 
                                                =     getSupportRequestManagerFragment(fm);
        //创建RequestManager实例,完成Glide对象的构造,通过RequestManager就可以控制整个界面的生命周期的监听,通过监听进行图片的相应操作
        RequestManager requestManager = current.getRequestManager();
        if (requestManager == null) {
            requestManager = new RequestManager(context, 
                        current.getLifecycle(), current.getRequestManagerTreeNode());
            current.setRequestManager(requestManager);
        }
        return requestManager;
    }
     
    setRequestManager():将RequestManagerFragment这个空界面的Fragment和RequestManager进行绑定;
        目的:监听Activity生命周期,管理图片加载的整个流程 绑定到Activity一起操作
    public void setRequestManager(RequestManager requestManager) {
        this.requestManager = requestManager;
    }
    ps:一个RequestManager 对应 一个RequestManagerFragment,一一对应关系
     
        思考:前文提到的 SupportRequestManagerFragment 空的Fragment 是如何和RequestManager建立生命周期关系的呢?
     
        答:在RequestManagerFragment中含有ActivityFragmentLifecycle函数,用于管理组建生命周期的。RequestManager实际上在RequestManagerFragment中注册了一些回调接口,通过接口监听Activity 或是Fragment的生命周期
    例如:
    @Override
    public void onStart() {
        super.onStart();
        lifecycle.onStart();
    //说明RequestManagerFragment 这个无Ui的Fragment是通过LifeCycle进行生命周期的管理
    }
    根据不同的生命周期进行相应的处理
     
      4.4、load()解析: 初始化操作 获取DrawableTypeRequest对象
     
        思考:为什么Glide会有一大堆重载方法?
        答:因为Glide支持多种格式的图片来源,依托于此Glide加载也就需要不同的类型
     
    public DrawableTypeRequest<String> load(String string) {
        return (DrawableTypeRequest<String>)
                        fromString().load(string);
    }
    DrawableTypeRequest() :表示Glide中所有加载图片的Request请求
     
     
    class DrawableTypeRequest<ModelType> extends     DrawableRequestBuilder<ModelType> implements DownloadOptions
       能想象到DrawableTypeRequest 应该就是通过Builder()内部类的构建者模式进行构建初始化的。
    DrawableRequestBuilder:对Glide参数初始化的配置工作
    而DrawableRequestBuilder又extends继承自 GenericRequestBuilder
     
    GenericRequestBuilder:Glide所有配置参数基本的最终类:
    public class DrawableRequestBuilder<ModelType>
            extends GenericRequestBuilder<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable>
            implements BitmapOptions, DrawableOptions 
    ps:DrawableRequestBuilder可以通过链式调用配置参数
     
    在GenericRequestBuilder中有一个很重要的成员变量:
    protected final RequestTracker requestTracker; //负责跟踪整个图片请求的周期
     
    fromString():传入String的Class对象作为参数
    public DrawableTypeRequest<String> fromString() {
        return loadGeneric(String.class);
    }
    loadGeneric():
    private <T> DrawableTypeRequest<T> 
                            loadGeneric(Class<T> modelClass) {
       //创建两个ModelLoader对象,通过数据来源,ML将来源加载成原始数据
        ModelLoader<T, InputStream> streamModelLoader 
                = Glide.buildStreamModelLoader(modelClass, context);
        ModelLoader<T, ParcelFileDescriptor>fileDescriptorModelLoader
                = Glide.buildFileDescriptorModelLoader(modelClass, context);
        if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) {
            throw new IllegalArgumentException(...);
        }
        //创建DrawableTypeRequest 对象
        return optionsApplier.apply(
                new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
                        glide, requestTracker, lifecycle, optionsApplier));
    }
     
    DrawableTypeRequest():相对常用的方法,通过返回不同的TypeRequest来选择需要的方法
    //强制指定加载静态图片
    public BitmapTypeRequest<ModelType> asBitmap() {
        return optionsApplier.apply(new BitmapTypeRequest<ModelType>
                                (this, streamModelLoader,
                        fileDescriptorModelLoader, optionsApplier));
    }
    //强制指定加载动态图片
    public GifTypeRequest<ModelType> asGif() {
        return optionsApplier.apply(new GifTypeRequest<ModelType>        (this, streamModelLoader, optionsApplier));
    }
     
       4.5、into()解析:——在主线程中调用的更新UI操作
    public Target<TranscodeType> into(ImageView view) {
       //1、是否在主线程中操作,非主线程抛出异常
        Util.assertMainThread();
        if (view == null) {
            throw new IllegalArgumentException("You must pass in a non null View");
        }
        //2、判断类型是否进行哪类图片处理
        if (!isTransformationSet && view.getScaleType() != null) {
            switch (view.getScaleType()) {
                case CENTER_CROP:
                    applyCenterCrop();
                    break;
                case FIT_CENTER:
                case FIT_START:
                case FIT_END:
                    applyFitCenter();
                    break;
                //$CASES-OMITTED$
                default:
                    // Do nothing.
            }
        }
        //3、创建一个Target对象,图片所要显示的控件
        return into(glide.buildImageViewTarget(view, transcodeClass));
    }
     
    applyCenterCrop():
    ————>centerCrop()————>centerCrop():
    public DrawableRequestBuilder<ModelType> centerCrop() {
        return transform(glide.getDrawableCenterCrop());
    }
    transform()://图片转换,对transformation进行赋值操作
    public GenericRequestBuilder<ModelType, DataType,
                        ResourceType, TranscodeType> transform(
            Transformation<ResourceType>... transformations) {
        isTransformationSet = true;
        if (transformations.length == 1) {
            transformation = transformations[0];
        } else {
            transformation = new MultiTransformation<ResourceType>(transformations);
        }
        return this;
    }
    glide.buildImageViewTarget() 中Target接口 :
     
    //Glide能绑定生命周期的原因:
    interface Target<R> extends LifecycleListener 
        接口内含有onStart()、onStop()、onDestory()函数
     
    在进行.with()操作的时候,会传入context or Activity or Fragment进行相应生命周期的绑定操作 ,这时候就是通过LifecycleListener进行组件的监听
     
    <R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
        //通过imageViewTargetFactory工厂构建Target
        return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
    }
    //通过传入class对象判断Glide需要加载哪类图片,buildTarget()就创建哪类Target
    public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {
        if (GlideDrawable.class.isAssignableFrom(clazz)) {
                //未调用asBitmap()方法就会默认采取GlideDrawableImageViewTarget()方法
            return (Target<Z>) new GlideDrawableImageViewTarget(view);
        } else if (Bitmap.class.equals(clazz)) {
                //调用了asBitmap()
            return (Target<Z>) new BitmapImageViewTarget(view);
        } else if (Drawable.class.isAssignableFrom(clazz)) {
                //使用较少
            return (Target<Z>) new DrawableImageViewTarget(view);
        } else {
            throw new IllegalArgumentException("Unhandled class: " + clazz
                    + ", try .as*(Class).transcode(ResourceTranscoder)");
        }
    }
     
    into():
     
    3、新建一个Request绑定到Target上
    4、发送Request 交给RequestTracker进行相应的处理
    public <Y extends Target<TranscodeType>> Y into(Y target) {
        //是否在主线程 (更新UI必须在主线程)
        Util.assertMainThread();
                    …
            1、对旧的绑定的Target对象进行清除
        //获取当然Target对象所绑定的旧的Request对象
        Request previous = target.getRequest();
        if (previous != null) {
            //清理操作
            previous.clear();
            //取消当前Request请求避免图片错位
            requestTracker.removeRequest(previous);
            //在该实现类中将成员变量赋值为null 
                ps:并调用REQUEST_POOL.offer(this);当前一个Request不用的时候会被放入请求池以备复用
            previous.recycle();
        }
        2、创建一个加载图片的Request
        ps:list图片错位的问题?
        解决:给View绑定setTag(),将view和图片进行绑定
     
        Request request = buildRequest(target);
        //将图片Request请求和ImageView绑定
        target.setRequest(request);
        lifecycle.addListener(target);
        //3、将Request发送给RequestTracker执行request请求
        requestTracker.runRequest(request);
        return target;
    }
     
    如何buildRequest?
    buildRequest():————>buildRequestRecursive()
        ...
    obtainRequest()————>GenericRequest.obtain():实际创建Request()
     
    GenericRequest.obtain():
       //从线程池中获取一个请求,如果有可以复用的就复用一个请求,如果没有就创建一个新的
        ...
    GenericRequest<A, T, Z, R> request =
         (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll();
        if (request == null) {
            request = new GenericRequest<A, T, Z, R>();
        }
            request.init();//初始化操作
            return request;
     
    requestTracker.runRequest:
    public void runRequest(Request request) {
        //将当前request加入到set<Request> 集合当中
        requests.add(request);
        //对当前状态进行判断,当前是有Request请求进入else ,将当前Request加入pending悬挂中的集合中
        if (!isPaused) {
            request.begin();
        } else {
            pendingRequests.add(request);
        }
    }
     
    request.begin():{
        …
        //判断前面是否调用了overrideWidth 限定宽高的方法,直接执行onSizeReady()
    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
        onSizeReady(overrideWidth, overrideHeight);
    } else { 
       //没有限定款高通过getSize()计算宽高
        target.getSize(this);
    }
        //当前状态是否已经完成,图片是否不是失败状态进行判断
        if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {
        //设定图片(获取占位图)
        target.onLoadStarted(getPlaceholderDrawable());
        ...
    }
     
    target.getSize——>
    public void getSize(SizeReadyCallback cb) {
        sizeDeterminer.getSize(cb);
    }
    getSize():内部根据ImageView宽高进行再次计算后再执行onSizeReady()
    public void getSize(SizeReadyCallback cb) {
        //1、获取宽高
        int currentWidth = getViewWidthOrParam();
        int currentHeight = getViewHeightOrParam();
        //3、当View绘制完时候就会进入onSizeReady()方法
    if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
        cb.onSizeReady(currentWidth, currentHeight);
        …
        //2、当前View没有被测量完,会被添加到viewTreeObserver观察者中
    final ViewTreeObserver observer = view.getViewTreeObserver();
    layoutListener = new SizeDeterminerLayoutListener(this);
    observer.addOnPreDrawListener(layoutListener);    
        
     
        1、通过LoadProvider()方法获取ModelLoader和Transcoder
        2、根据ModelLoader获取DataFetcher
        3、engine.load 进行实际图片加载
     
    ModelLoader:从数据源中获取原始数据,一般是输入流inputStream,在Glide中被封装成Data
    DataFetcher:将Data原始数据转换成能直接用的不同形式的图片数据类型
    ResourceTranscoder:将原始数据解码,将io输入流解码成bitmap的解码工具对象
    Resource:解码后的资源
     
    onSizeReady(int width, int height) {
        …
    ModelLoader<A, T> modelLoader 
                    =     loadProvider.getModelLoader();
    //通过loadProvider接口获取到dataFetcher、loadProvider、transcoder
    final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
                
    loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
            priority, isMemoryCacheable, diskCacheStrategy, this);
    }
    LoadProvider():GenericRequest的成员变量
    public interface LoadProvider<A, T, Z, R> extends DataLoadProvider<T, Z> {
        ModelLoader<A, T> getModelLoader();
        ResourceTranscoder<Z, R> getTranscoder();
     
    LoadProvider初始化:在GenericRequest的实现类
    DrawableTypeRequest(...) {
        super(context, modelClass,
                buildProvider(glide, streamModelLoader, fileDescriptorModelLoader,)
        ...
    }
     
    buildProvider的返回值就是LoadProvider,FixedLoadProvider是LoadProvider的实现类:获取原始图片,进行编解码、转码等功能
    private static <A, Z, R> FixedLoadProvider<A, ImageVideoWrapper, Z, R> buildProvider(Glide glide,
            ModelLoader<A, InputStream> streamModelLoader,
            ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader...) {
       //判断来自不同数据的Loader
        if (streamModelLoader == null && fileDescriptorModelLoader == null) {
            return null;
        }
        //ResourceTranscoder是否为空,为null则通过Glide创建新的Transcoder
    ps:对原始数据进行解码
        if (transcoder == null) {
            transcoder = glide.buildTranscoder(resourceClass, transcodedClass);
        }
        //DataLoadProvider对图片进行编解码的接口,实现类中LoadProvider
        DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider 
                    = glide.buildDataProvider(ImageVideoWrapper.class,resourceClass);
       //封装了两个ModelLoader
        ImageVideoModelLoader<A> modelLoader = new 
            ImageVideoModelLoader<A>(streamModelLoader,fileDescriptorModelLoader);
        
            return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>
                        (modelLoader, transcoder, dataLoadProvider);
    }
     
    DataLoadProvider:负责编解码
    Data:从数据源获取的数据
    Resource:解码后的资源
    解码:Data————>Resource 资源
    编码:将Data、Resource———>持久化到本地的过程 用于实现Glide的磁盘缓存功能
     
    engine.load():
    engine:负责图片加载,管理正在使用和处于缓存的图片类
     
    LoadStatus load(){
        …
    //加载图片的唯一标识id,比如加载网络图片的话就是一个网络图片的url地址
    final String id = fetcher.getId();
    //传入唯一标识id ,其他很多参数依然可以决定EngineKey的值,构建Glide缓存中的一个key值
    EngineKey key = keyFactory.buildKey(id,…);    
        ...
     //从缓存中获取图片
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
        cb.onResourceReady(cached);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from cache", startTime, key);
        }
        //若缓存图片是空的则调用loadFromActiveResources
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
        …
        //若两者都没有获取到就自己开启一个runnable 从磁盘或是网络进行图片的获取
    EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);
        jobs.put(key, engineJob);
        engineJob.addCallback(cb);
        engineJob.start(runnable);
    }
     
    GLide中的内存缓存策略
     
    GLide 构建内存缓存的组成部分:cache、active
     
    A、内存缓存读取操作原理
       1、 LruCache算法:近期最少使用算法,将最近使用的对象的强引用存储在LinkHashMap上;并且把最近最少使用的对象,在缓存池达到预设值之前从内存中移除
       2、弱引用缓存机制
        
    Glide中的内存缓存对象:Engine类中的 Load()函数
        MemoryCache——loadFromCache(EngineKey,boolean isMemoryCacheable)
            cached :最近使用过的而当前不在使用的EngineResource,LoadFromCache内部使用linkHashMap当内存达到阀值会通过LruCache算法进行清除操作
     
    loadFromCache():1、从内存缓存MemoryCache中获取图片
    private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
        //配置Glide 的时候使用的isMemoryCacheable,跳过内存缓存操作skipMemoryCache(true)
        默认情况下为true 的时候开启缓存模式
        if (!isMemoryCacheable) {
            return null;
        }
        //获取实际缓存对象
        EngineResource<?> cached = getEngineResourceFromCache(key);
           //ps:2、在该方法中会使用缓存的key值 从cache中获取值并调用remove函数把这个图片从MemoryCahce缓存中移除掉
                //Resource cached = this.cache.remove(key);
        if (cached != null) {
            cached.acquire();
            //写入一个弱引用的 缓存
            activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
        }
        return cached;
    }
     
    ps:使用弱引用的好处,使得该部分缓存不会被Lru算法给回收掉资源
     
    loadFromActiveResources():3、调用该方法从正在使用的部分中继续获取图片
        ActiveResource——loadFromActiveResources(EngineKey,boolean isMemoryCacheable)——HashMap<key,WeakReference<EngineResource>>
           active:保存当前正在使用的EngineResource对象
          ps:  会以EngineKey为键,以EngineResource的弱引用为值 
     
    HashMap的Get()、 Put()函数对应着 缓存的读和取 的操作
     
    //4、若前两步都没有获取到图片资源,就创建一个Engine runnbale对象加载,从磁盘或是网络获取图片
    EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);
    DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,
            transcoder, diskCacheProvider, diskCacheStrategy, priority);
    EngineRunnablerunnable = new EngineRunnable(engineJob, decodeJob, priority);
    jobs.put(key, engineJob);
     
    B、内存缓存写入操作原理
       
    if (cached != null) {
        cb.onResourceReady(cached);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            logWithTimeAndKey("Loaded resource from cache", startTime, key);
        }
        return null;
    }
    cb:callback回调
    onResourceReady :ResourceCallback的具体实现
    在onResourceReady()中:定义一个handler发送message,将执行逻辑切回到主线程中操作
    public void onResourceReady(final Resource<?> resource) {
        this.resource = resource;
        MAIN_THREAD_HANDLER.obtainMessage(
                                                    MSG_COMPLETE, this).sendToTarget();
    }
    其中MAIN_THREAD_HANDLER是MainThreadCallback回调定义:
    private static final Handler MAIN_THREAD_HANDLER = 
                        new Handler(Looper.getMainLooper(),
                                        new MainThreadCallback());
    MainThreadCallback():接受Message信息并处理的callback回调,通过名字可以知道该消息是在主线程进行结果回调
    private static class MainThreadCallbackimplements Handler.Callback {
     
        @Override
        public boolean handleMessage(Message message) {
            if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) {
                EngineJob job = (EngineJob) message.obj;
                if (MSG_COMPLETE == message.what) {
                    job.handleResultOnMainThread();
                } else {
                    job.handleExceptionOnMainThread();
                }
                  ...
    }
    job.handleResultOnMainThread():
    private void handleResultOnMainThread() {
        //1、如果任务取消则回收资源 recycle
        if (isCancelled) {
            resource.recycle();
            return;
        //2、若callback集合为null 空 则抛出异常
        } else if (cbs.isEmpty()) {
            throw new IllegalStateException("Received a resource without any callbacks to notify");
        }
        //3、通过工厂累创建包含图片资源的engineResource对象
        engineResource = engineResourceFactory.build(resource, isCacheable);
         ...
       //3、将对象回调到onEngineJobComplete()当中
    engineResource.acquire();
        //4、acquire():记录图片被引用的次数,同时在该函数结尾通过 ++this.acquire 来进行图片次数的+1 操作;当acquired >0 表示有图片正在使用中,也就是应该把图片放入到activeResource 这个弱引用缓存中
        listener.onEngineJobComplete(key, engineResource);
    for (ResourceCallback cb : cbs) {
        if (!isInIgnoredCallbacks(cb)) {
            engineResource.acquire();
            cb.onResourceReady(engineResource);
        }
    }
        //5、release() 当图片等于0的时候表示图片没有在使用了就调用onResourceReleased释放资源操作,该实现在Engine()函数中
        engineResource.release();
    }
     
    onEngineJobComplete():
    public void onEngineJobComplete(Key key, EngineResource<?> resource) {
        Util.assertMainThread();
        if (resource != null) {
            resource.setResourceListener(key, this);
            if (resource.isCacheable()) {
               //最终调用activeResources.put()这个函数进行了缓存的写入操作
        ps:这里写入的是弱引用的缓存
                activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
            }
        }
        // TODO: should this check that the engine job is still current?
        jobs.remove(key);
    }
     
    onResourceReleased():
    public void onResourceReleased(Key cacheKey, EngineResource resource) {
        Util.assertMainThread();
        //1、把整个缓存图片从activeResources从缓存图片中删除
        activeResources.remove(cacheKey);
        if (resource.isCacheable()) {
        //2、通过put()加入到LruCache算法中,
        ps:实现正在使用的图片通过弱引用进行缓存,而不在使用的利用LruCache进行缓存
           cache.put(cacheKey, resource);
        } else {
            resourceRecycler.recycle(resource);
        }
    }
     
    磁盘缓存读写 待补充
     
    五、Glide常用
         
      5.1 、Target函数的使用- Glide获取 Bitmap资源本身
            Target其实就是整个图片的加载的生命周期,通过它在图片加载完成之后获取Bitmap
        以 SimpleTarget 为例:
           private SimpleTarget<Bitmap> mSimpleTarget= new SimleTarget<Bitmap> (可添加宽*高 尺寸限定){
                @Override
                public void onResourceReady( Bitmap resource, 
                                                            GlideAnimation< ? super Bitmap>animation) {
                    ImageView.setImageBitmap(resource);
                }
        };    
        private void loadImageSimpleTarget(){
            Glide .with (1 ). load(url ) . asBitmap() . into ( mSimpleTarget );
        }
     
    在这里如果SimpletTarget 使用匿名内部类的方式创建 SimpleTarget 的对象,这样会增大该对象在 Glide 完成图片请求之前就被回收的可能性。
    (1):这里如果传入的是Target ,可能独立于with的生命周期之外,所以最好给with里传入参数context .getApplicationContext(),让Glide持有整个app的生命周期。
     
        5.2、ViewTarget函数的使用
            在自定义View的时候,Glide有时候并不支持加载图片到自定义View中的时候,这时候就需要ViewTarget
    public void loadImageTarget(Context context){ 
            CustomView mCustomView = (CustomView) findViewById(R.id.custom_view);             ViewTarget viewTarget = 
            new ViewTarget<CustomView,GlideDrawable>( mCustomView) { 
                    @Override 
                    public void onResourceReady(GlideDrawable resource,
                                             GlideAnimation<? super GlideDrawable> glideAnimation) {                             this.view.setImage(resource); 
        } 
    }; 
        Glide.with(context) .load(mUrl) .into(viewTarget); 
    }
    onResourceReady回调方法中使用了自定义view自己的方法设置图片,可以看到在创建ViewTarget的时候传入了CustomView 对象
     
        5.3、Transformations函数的使用
             如果需要对图片进行图片切圆角、灰阶处理 等功能 就需要 通过 Transformations来操作bitmap 。通过修改尺寸、范围、颜色、像素、位置等达到需求。
        ps:若是对图片进行常规的bitmap转换的话,可以使用抽象类 BitmapTransformation 进行
       对图片切圆角操作 :getId()是图片的唯一标识符必须唯一
        public class RoundTransformation extends BitmapTransformation {
             private float radius = 0f; 
            
        public RoundTransformation(Context context) { 
                this(context, 4); 
            } 
        
        public RoundTransformation(Context context, int px) {
             super(context); 
            this.radius = px; 
        } 
     
           @Override 
          protected Bitmap transform(BitmapPool pool, Bitmap toTransform,
                                                                                     int outWidth, int outHeight) { 
                return roundCrop(pool, toTransform); 
        } 
        
        private Bitmap roundCrop(BitmapPool pool, Bitmap source) { 
            if (source == null) 
                return null; 
                Bitmap result = pool.get(source.getWidth(),
                                     source.getHeight(), Bitmap.Config.ARGB_8888); 
     
            if (result == null) { 
                result = Bitmap.createBitmap(source.getWidth(), 
                                      source.getHeight(), Bitmap.Config.ARGB_8888); 
            } 
     
                Canvas canvas = new Canvas(result);    
                Paint paint = new Paint(); 
                paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP,
                                                                BitmapShader.TileMode.CLAMP));                
                 paint.setAntiAlias(true); 
                 RectF rectF = new RectF(0f, 0f, source.getWidth(), 
                                                                                source.getHeight());                                                     canvas.drawRoundRect(rectF, radius,radius, paint); 
                return result;
         } 
            @Override 
            public String getId() { 
                return getClass().getName() + Math.round(radius); 
            } 
        }
    下面是transform的调用:
        Glide.with(context) 
                .load(mUrl) 
                    .transform(new RoundTransformation(context , 20)) 
                        //.bitmapTransform( new RoundTransformation(context , 20) )         .into(mImageView);
     
    ps:若需要同时执行多个transformation的话 ,不能再像这样通过链式调用的形式多次调用.transform() 或是.bitmapTransform() 方法。上面的transform会被最后一个覆盖掉。
    这时候可以这样做:
        Glide.with(context) 
            .load(mUrl) 
                .transform(new RoundTransformation(context , 20) 
                    , new RotateTransformation(context , 90f)) 
            .into(mImageView);
     
      对图片旋转操作 
    public class RotateTransformationextends BitmapTransformation { 
            private float rotateRotationAngle = 0f; 
            public RotateTransformation(Context context, float rotateRotationAngle) {
                 super( context );
             this.rotateRotationAngle = rotateRotationAngle;
         } 
            @Override 
            protected Bitmap transform(BitmapPool pool, 
                    Bitmap toTransform, int outWidth, int outHeight) {
                 Matrix matrix = new Matrix(); 
                 matrix.postRotate(rotateRotationAngle); 
                    return Bitmap.createBitmap(toTransform, 0, 0, 
                                toTransform.getWidth(), toTransform.getHeight(), matrix, true); 
        } 
            @Override 
           public String getId() {
                 return getClass().getName() + Math.round(rotateRotationAngle); 
            }
     }
    ps:这里需要注意一点 .centerCrop() 和 .fitCenter() 也都是 Transformation 所以也是遵循同时使用多个 Transformation 的规则的,即:当你使用了自定义转换后你就不能使用 .centerCrop() 或 .fitCenter() 了。
    这里有一个 GLide Transformations 的库,它提供了很多 Transformation 的实现,非常值得去看,不必重复造轮子对吧!
     
    5.4、Animate动画函数的使用
         图片间切换的平滑过渡 是非常重要的!!所以Glide又一个标准动画去柔化Ui中的改变,但是如果是设置自己的动画的话:
    一个小例子:
    <set 
        xmlns:android="http://schemas.android.com/apk/res/android"         android:fillAfter="true”> 
        <scale 
            android:duration="@android:integer/
                                                    config_longAnimTime”     
            android:fromXScale="0.1” 
            android:fromYScale="0.1” 
            android:pivotX="50%”
            android:pivotY="50%”
            android:toXScale="1” 
            android:toYScale="1"/> 
        </set>
        XML 动画缩放动画,图片刚开始小的,然后逐渐增大到原尺寸。我们现在要应用到 Glide 加载图片中去,调用 .animate() 方法传入 XML 动画的 id 即可。
        Glide.with(context)     
            .load(mUrl)
             .transform(new RoundTransformation(this , 20))
             .animate( R.anim.zoom_in )
             .into(mImageView);
     
    animate在Target中的使用:
        ViewPropertyAnimation.Animatoranimator 
                = new ViewPropertyAnimation.Animator() { 
            @Override 
            public void animate(View view) { 
                view.setAlpha( 0f ); 
                ObjectAnimator fadeAnim 
                        = ObjectAnimator.ofFloat( view, "alpha", 0f, 1f );
                     fadeAnim.setDuration( 2500 ); 
                    fadeAnim.start();
             } 
        };
    Glide.with(context)
        .load(mUrl)
        .animate( animator )
        .into(viewTarget);
     
      5.5、Modules函数的使用
        Glide 的Module 是一个可以全局改变Glide 的行为的东西。为了实现需求需要去实现 interface GlideModule 来实现功能
       public class ExampleModule implements GlideModule{
             @Override 
            public void applyOptions(Context context, GlideBuilder builder) { 
            // todo
         } 
            @Override 
            public void registerComponents(Context context, Glide glide) { 
            // todo
         } }
    主要使用的是 applyOptions(Context context, GlideBuilder builder) , 我们自己的需要重新定义的代码写在该方法里就可以了。然后我们还需要去 AndroidManifest.xml 中使用 meta 声明我们上面实现的 Module:
            <application>
                 <meta-data android:name
                                    ="com.mrtrying.demoglide.module.ExampleModule"                         android:value="GlideModule" /> 
                    ... </application>
    到这里我们就完成了 ExampleModule 的声明,Glide 将会在工作是使用我们所定义的 Module
     
    TIPS
    • 我们需要将 android:name 属性改成 包名+类名 的形式,这样的引用才是正确的。如果你想删掉 Glide Module,只需要删除在 AndroidManifest.xml 中的声明就可以了。Java 类可以保存,说不定以后会用呢。如果它没有在 AndroidManifest.xml 中被引用,那它不会被加载或被使用。
    • 定制 module 的话 Glide 会有这样一个优点:你可以同时声明多个 Glide module。Glide 将会(没有特定顺序)得到所有的声明 module。因为你当前不能定义顺序,请确保定制不会引起冲突!
    applyOptions(Context context, GlideBuilder builder) 中有两个参数, 我们通过使用 GlideBuilder 来实现我们的需求。先看看 GlideBuilder 中可用的方法
    • .setMemoryCache(MemoryCache memoryCache)
    • .setBitmapPool(BitmapPool bitmapPool)
    • .setDiskCache(DiskCache.Factory diskCacheFactory)
    • .setDiskCacheService(ExecutorService service)
    • .setResizeService(ExecutorService service)
    • .setDecodeFormat(DecodeFormat decodeFormat)
    可以看到,这个 GlideBuilder 对象给你访问了 Glide 重要的核心组件。接下来我们就要试着去使用这些方法
    增加 Glide 的图片质量
    在 Android 中有两个主要的方法对图片进行解码:ARGB_8888 和 RGB_565 。前者为每个像素使用4个字节,后者每个像素仅使用2个字节。ARGB_8888 的有时就是图像质量更高以及能储存一个 alpha 通道。 Picasso 使用的就是 ARGB_8888 , Glide 默认使用低质量的 RGB_565 ,但是现在你就可以使用 Glide module 来改变图片解码规则。就象这样
    public class QualityModule implements GlideModule{
        @Override
        public void applyOptions(Context context , GlideBuilder builder){
            builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
        }
     
        @Override
        public void registerComponents(Context context , Glide glide){
            // nothing to do here
        }
    }
    这样我们就简单的增加了 Glide 的图片质量。
    往往我们还会遇到一些情况,希望 Glide 可以使用我们自己的网络框架,我们就需要做一些事情来实现这个需求了。Glide 的开发者不强制设置网络库给你,所以Glide可以说和 HTTPS 无关。理论上,它可以与任何的网络库实现,只要覆盖了基本的网络能力就行。同样是需要实现 Glide 的 ModuleLoader 的接口,为了让我们更加易用,Glide 为 OkHttp 和 Volley 两个网络库提供了实现。
    假设我要集成 OkHttp 作为 Glide 的网络库,我可以手动实现一个 GlideModule 也可以在 build.gradle 中添加依赖:
    dependencies{
        //...
        
        // Glide
        compile 'com.github.bumptech.glide:glide:3.7.0'
     
        // Glide's OkHttp Integration
        compile 'com.github.bumptech.glide:okhttp-integration:1.4.0@aar'
        compile 'com.squareup.okhttp:okhttp:3.2.0'
    }
    Gradle 会自动合并必要的 GlideModule 到你的 AndroidManifest.xml , Glide 会认可在 manifest 中存在,然后使用 OkHttp 做到的所有网络连接!!
     
    作者:MrTrying
    來源:简书
    以上一小节选取了 该文章的内容多谢分享!
     
    六、Gilde相关知识补充-bitmap &oom & 优化bitmap
     
        4.6.1 bitmap 导致OOM
    a、在使用list类View
        一、加载大量View组件又没有合理的处理缓存的情况下,大量加载bitmap很容易造成OOM。
        解决方法:
        (1)三级缓存
        (2)设置listView的监听事件,当滑动的时候不进行图片的加载,当停止滑动的时候再加载
     
       二、图片分辨率越来越高,消耗的内存越来越大 
        注意:图片Bitmap所占用的内存 = 图片长度 * 图片宽度 * 一个像素点占用的字节数
     
        三、VM值上限dalvik.vm.heapgrowthlimit 
        限定了每个app 可用的最大内存,如果超过这个值就会出现OOM现象
     
        4.6.2 bitmap的4种优化策略
            一、对图片质量进行压缩
    private Bitmap compressImage(Bitmap image) {
        //1、创建字节输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        //2、Bitmap.CompressFormat进行压缩(类型,值越小代表压缩比例越大,100表示不压缩,压缩好的图片放在字节输出流)
        image.compress(Bitmap.CompressFormat.JPEG
                , 100, baos);
        int options = 100;
        //3、循环判断,压缩好的图片大小是否大于100kb,大于就进一步压缩
        while (baos.toByteArray().length / 1024 > 100) {
                //3.1、清空输出流
            baos.reset();
                //3.2、再次压缩
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);
                //3.3、每次减少10options 数值
            options -= 10;
        }
            //4、将压缩好的数据放入到字节输入流中
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
            //5、通过BitmapFactory.decodeStream完成bitmap的重建
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);
        return bitmap;
    }
     
            二、对图片按比例进行压缩
    /**
    * 图片比例压缩
    */
    private Bitmap comBitmapInSize(Bitmap image) {
        //1、创建字节数组输入输出流
               ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ByteArrayInputStream isBm;
        //2、创建BitmapFactory的Options内部类对象,通过该对象可以对bimtap进行宽高、内存等的控制
                BitmapFactory.Options newOpts = new BitmapFactory.Options();
        //3、解码bitmap只返回它的宽高数值,而不必为他申请内存,通过设为true可以节省内存空间
                newOpts.inJustDecodeBounds = true;
                Bitmap bitmap;
                int w = newOpts.outWidth;
                int h = newOpts.outHeight;
                float hh = 1290f; //设置高度为1280f
                float ww = 720f; //设置宽度为720f
        //4、创建be缩放比例
                int be = 1;
                if (w > h && w > ww) {
            //4.1、如果宽大于高的话,就根据宽的大小进行缩放
                    be = (int) (newOpts.outWidth / ww);
                 } else if (w < h && h > hh) {
            //4.2、如果宽小于高的话,就根据高的大小进行缩放
                    be = (int) (newOpts.outHeight / hh);
                }
                    if (be <= 0)
                        be = 1;
        //5、将计算好的比例数字be 赋值给Options中的缩放比例变量inSamplesize
                    newOpts.inSampleSize = be;
       //6、将Options的inJustDecodeBounds设置为false表示需要将图片加载到内存中
                    newOpts.inJustDecodeBounds = false;
                    isBm = new ByteArrayInputStream(baos.toByteArray());
        //7、重建bitmap
                    bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
                    return bitmap;
    }
            三、关于bitmap的recycle方法(含有争议话题)
    /**
    * Free the native object associated with this bitmap, and clear the
    * reference to the pixel data. This will not free the pixel data synchronously;
    * it simply allows it to be garbage collected if there are no other references.
    * The bitmap is marked as "dead", meaning it will throw an exception if
    * getPixels() or setPixels() is called, and will draw nothing. This operation
    * cannot be reversed, so it should only be called if you are sure there are no
    * further uses for the bitmap. This is an advanced call, and normally need
    * not be called, since the normal GC process will free up this memory when
    * there are no more references to this bitmap.
    */
        /**
            * 当释放这个bitmap相关联的native对象的时候,它会清除像素数据。ps:不会同步的释放像素数据,只是根据在没有其他引用的情况下收集 该GC垃圾,同时将状态标记为dead状态。这时候意味着调用 setPixels() 或是getPixels()时候都会抛出异常。而不回去绘制任务东西。通常情况下不需要手动的调用,当其没有引用这个bitmap的时候,正常的垃圾回收进程会释放掉该部分内存。
            *
            */
        public void recycle() {
            ...
        }
    注解: 手动的调用recycle()本身可能并没有被释放资源。所以硬要调用recycle的时候要将bitmap设置为null,好让GC和垃圾回收器回收这部分的对象。
     
            四、捕获异常
    针对OOM 的区域进行 异常捕获 
    public void handleBitmapCrash() {
        Bitmap bitmap = null;
        String path = "xx/xx/xx";
        try {
            //实例化bitmap
            bitmap = BitmapFactory.decodeFile(path);
        } catch (OutOfMemoryError e) {
            e.printStackTrace();
        }
    }
     
    七、Gilde相关知识补充-三级缓存 /LruChache算法
       Android的缓存策略-三级缓存
            内存 - 本地 - 网络  
    意义:
           第一次从网络获取 ,之后将获取的图片保存到本地和内存各一份,当程序再次用到该图片的时候首先会从内存中判断是否有缓存,有的话直接从内存中获取,没有的话再从本地SD卡中进行获取。如果内存和本地均没有的话,再次从网络上获取。
     
    思考:内存缓存是如何实现的呢?
        缓存主要是通过添加 、获取、删除 进行操作的。
        不论内存缓存还是硬盘缓存,缓存的大小都是固定的,有个max限制。当该部分缓存用满之后其他缓存就无法再添加,所以需要进行删除无用缓存的操作。这时候就需要一个删除旧缓存的算法,也就说常说的LRU(Least Recently Used)近期最少使用缓存算法
     
    LruChache算法
    LRU核心
        当缓存已满的时候会优先淘汰掉最近使用最少的缓存对象
       整个Lru算法的核心是LinkedHashMap,其继承自HashMap,其构造函数中会有一个boolean类型的值,尤其控制插入的顺序,a是Lru顺序,b是正常顺序
     
    public class LruCache<T, Y> {
        private final LinkedHashMap<T, Y> cache= new LinkedHashMap<T, Y>(100, 0.75f(负载因子), true(当为true时候说明整个顺序是通过Lru算法顺序删除元素的));
         
            int size : 当前缓存的大小
            int maxSize : 当前缓存的最大值
            int putCount : put方法调用的次数
            int createCount : create方法调用的次数
            int evictionCount : 当需要淘汰一些缓存变量时候的计数器
            int hitCount : 缓存对象使用到hitCount +1 计数
            int missCount : 缓存未命中 计数 +1
            
    Get():
         传入key值 获取相应的item,若是无缓存会创建并返回相应的item,相应的item会被移动到队列的尾部。
        public final V  get(K key){
            if(key == null ) {
                throw new NullPointerException(“key==null"):
            }
        V mapValue;
            //利用LinkedHashMap的get方法获取相应的value
            synchronized(this){
            mapValue = map.get(key);
            if(mapValue !=null){
                //获取到缓存 hitCount+1 并返回Value值
                hitCount++;
                return mapValue;
            }
            missCount++ ;
        }
       //未命中,调用create()创建一个对象
        V createdValue = create(key);
         if(createdValue == null) {
            return null;    
        }
        synchronized (this ){
                createCount++;   
                //覆盖原有Value值并添加到mapValue中
                mapValue = map.put(key , createdValue);
                if(mapValue !=null){
                   //如果该值不为空,将重新创建好的cratedValue又覆盖成原来的mapValue,逆推上一步操作
                    map.put(key,mapValue);  //插入的新的对象会存储到列表的尾端
                }else{
                    //加入新创建的对象需要重新创建缓存size大小
                size += safeSizeOf(key ,createValue);
        }
            if(mapValue != null){
                entryRemoved(false, key,createdValue,mapValue);
                return mapValue;
            }else {
                //新加入对象都会调用trimToSize(),来查看对象是否需要被回收。根据传入的缓存的最大容量调整缓存的大小。传入 -1 表示清空所有缓存对象
                trimToSize(maxSize);
                return createdValue;
            }
    }
    Put():
        public final V put (K key , V value){
            if(key == null || value == null) {
                    throw new NullPointerException (“key ==null ||value ==null ")
            }
            V previos ;
            synchronized (this ){
                //调用put方法每次计数+1
                putCount ++;
                // 每次put都会造成整个缓存大小增大,所以需要自增当前缓存
                size += safeSizeOf (key ,value );
                previous= map .put(key , value );
               //如果之前存在key为键值的对象,这时候当前的缓存size 需要减掉这个对象的大小
                    if( previous !=null){
                        size -= safeSizeOf (key ,previous);
                        }
                }
                if(previous !=null ){
                    entryRemoved (false ,key ,previous ,value);
                }
               //调整内存缓存大小,插入的新对象会存储在列表的尾端
                trimToSize (maxSize);
                return previous;
        }
    • LruCache中将LinkedHashMap的顺序设置为LRU顺序来实现LRU缓存
    • 每次调用get则将该对象移到链表的尾端
    • 调用put插入新的对象也是存储在链表尾端
    • 当内存缓存达到设定的最大值时候,将链表头部的对象(近期最少使用到的)移除
  • 相关阅读:
    利用Python编写简单的Web静态服务器(TCP协议)
    UDP-TCP介绍与区别
    Linux基本知识-命令
    Python中多线程与join()的应用
    Python实例---对一组含有四则运算,括号,空格的字符串进行计算
    分组查询注意事项
    oracle分页查询
    springMVC文件上传配置
    ssm网站页面乱码问题解决
    redis-server.exe闪退
  • 原文地址:https://www.cnblogs.com/cold-ice/p/9303485.html
Copyright © 2011-2022 走看看