android 图片 LRUCache 原理 Glide 源码解析(上)

Android 库 图片库比较

https://www.jianshu.com/p/44a4ee648ab4

ImageLoader Picasso Glide Fresco

MemoryCache DiskCache


RequestManager,通过 

Engine     GetDataInterface:数据获取接口,负责从各个数据源获取数据。比如

MemoryCache DiskCache

Displayer   

Processor

Displayer     ImageLoader ImageAware

Picasso Glide Target


BitmapProcessor ImageDecoder


Picasso 
load into 
Dispatcher

BitmapHunter

Request Dispatcher RequestHandler MemoryCache

RequestManager,通过 
Sing engi
 ennge
 
 DecodeFormat    EngineJob

上面是 Glide 的总体设计图。整个库分为 RequestManager(请求管理器),Engine(数据获取引擎)、Fetcher(数据获取器)、MemoryCache(内存缓存)、DiskLRUCache、Transformation(图片处理)、Encoder(本地缓存存储)、Registry(图片类型及解析器配置)、Target(目标)等模块。

RequestHandler Fetcher MemoryCache DiskCache


Transformation(Encoder(本地缓存存储)、Registry(图片类型及解析器配置)、Target(目标)等模块。);


RequestManager,通过  trimMemory  FragmentTransaction

UrlConnection   okhttp、Volley 
ImageLoader

active get  remove,再将这个缓存数据放到一个 
value activeResources map     

Fresco

匿名       虚拟机  oom    Bitmap

Picasso.with .load .into

RGB565 ARGB888


GlideConfiguration GlideModule 

GlideBuilder
applyOptions 
    builder.setDecodeFormat DecodeFormat.PREFER_ARGB_8888

registerComponents   


meta-data GlideModule
原因在于:
Picasso是加载了全尺寸的图片到内存,然后让GPU来实时重绘大小;
Glide加载的大小和ImageView的大小是一致的,因此更小。

resize
resize


EditText键盘弹出时,会将布局底部的导航条顶上去(解决方法之一)

weixin_30561177 2016-12-20 09:03:00  50  收藏
文章标签: 移动开发
版权
这只是其中一种方法android:windowSoftInputMode有很多属性可以添加,必须是一个state...|ajust...

我只是觉得这种比较好用

在项目的AndroidManifest.xml文件中界面对应的<activity>里加入android:windowSoftInputMode="stateVisible|adjustResize",这样会让屏幕整体上移。

<activity
    android:name=".S_MainActivity"
    android:windowSoftInputMode="stateAlwaysHidden|adjustPan"/>
    
    获得View高度的几种方式:
我们应该都遇到过在onCreate()方法里面调用view.getWidth()和view.getHeight()获取到的view的宽高都是0的情况,这是因为在onCreate()里还没有执行测量,需要在onResume()之后才能得到正确的高度,那么可不可以在onCreate()里就得到宽高呢?答:可以!常用的有下面几种方式:

1、通过设置View的MeasureSpec.UNSPECIFIED来测量:
int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(w, h);
//获得宽高
int viewWidth=view.getMeasuredWidth();
int viewHeight=view.getMeasuredHeight();
设置我们的SpecMode为UNSPECIFIED,然后去调用onMeasure测量宽高,就可以得到宽高。

2、通过ViewTreeObserver .addOnGlobalLayoutListener来获得宽高,当获得正确的宽高后,请移除这个观察者,否则回调会多次执行:
//获得ViewTreeObserver 
ViewTreeObserver observer=view.getViewTreeObserver();
//注册观察者,监听变化
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
     @Override
     public void onGlobalLayout() {
            //判断ViewTreeObserver 是否alive,如果存活的话移除这个观察者
           if(observer.isAlive()){
             observer.removeGlobalOnLayoutListener(this);
             //获得宽高
             int viewWidth=view.getMeasuredWidth();
             int viewHeight=view.getMeasuredHeight();
           }
        }
   });


    
    谈谈LruCache算法的底层实现原理及其内部源码
    https://blog.csdn.net/u013637594/article/details/81866582

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >
    
    <GridView 
        android:id="@+id/photo_wall"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:columnWidth="90dip"
        android:stretchMode="columnWidth"
        android:numColumns="auto_fit"
        android:verticalSpacing="10dip"
        android:gravity="center"
        ></GridView>
    
</LinearLayout>


数组双向链表


LinkedHashMap<Integer,Integer> map = new HashMap () ;


map.put("0",0);

map.put(1, 1);
    map.put(2, 2);
    map.put(3, 3);
    map.put(4, 4);
    map.put(5, 5);
    map.put(6, 6);

entry entrySet

getKey getName


        Bitmap bitmap = null;
        //获取运行内存大小的八分之一
        int memory = (int)Runtime.getRuntime().totalMemory() / 1024;
        int cache = memory / 8;
        bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
        LruCache<String,Bitmap> lruCache = new LruCache<String,Bitmap>(cache){
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };
        //将数据存储进入缓存
        lruCache.put("cacheBitmap",bitmap);
 
        Bitmap cacheBitmap = lruCache.get("cacheBitmap");
        //在使用的时候判断是否图片为空,因为有可能图片因为内存空间满了而被剔除
        if (cacheBitmap != null){
            //TODO
        }
        
        getRuntime   totalMemory 
        
        decodeResource           sizeOf
        
        bitmap.getRowBytes * bitmap.getHeight / 1024
        
        LinkedHashMap
        put trimToSize get 
        
        
        内存缓存LruCache实现原理
        https://www.cnblogs.com/liuling/p/2015-9-24-1.html
        
        
        putCount
        
        
        createCount
        hitCount
        missCount
        
        
        size -= safeSizeOf
        
        trimToSize maxSize
        
        map.entrySet.iterator.next
        
        hitCount 
        missCount
        createCount
        putCount
        
          * 通过key获取相应的item,或者创建返回相应的item。相应的item会移动到队列的尾部,
 62      * 如果item的value没有被cache或者不能被创建,则返回null。
        
        
         85          * 如果未命中,则试图创建一个对象,这里create方法返回null,并没有实现创建对象的方法
 86          * 如果需要事项创建对象的方法可以重写create方法。因为图片缓存时内存缓存没有命中会去
 87          * 文件缓存中去取或者从网络下载,所以并不需要创建。
        
        
    Android图片加载机制及其优化Android图片加载机制及其优化    
        
        https://www.jianshu.com/p/00b904e84d78
        
        
        decodeByteArray decodeFile decodeFile、decodeResource、decodeStream和decodeByteArray
        
        
    options.inSampleSize 
        
        内存缓存技术最核心的类是LruCache,它的主要算法原理是把最近使用的对象用强引用存储在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除,然后再添加新的缓存对象。图片的三级缓存原理:

        
        LinkedHashMap    最少使用的对象
        
        
        
        LruCache String Bitmap mCaches
        
        ExecutorService      mPool
        
        LinkedHashMap<ImageView,String>
        
        
        Runtime.getRuntime.freeMemory / 4;
        
        LruCache maxSize
        
        sizeOf       key  value Bitmap
        
        getRowBytes value.getHeight
        
        
        Executors.newFixedThreadPool 3
        
        loadFromNet
        
        
        getCacheFile url
        FileOutputStream
        
    compress    
        
        
,ImageView控件的个数其实就比一屏能显示的图片数量稍微多一点而已,移出屏幕的ImageView控件会进入到RecycleBin当中,而新进入屏幕的元素则会从RecycleBin中获取ImageView控件。

contentView的复用,开启异步任务加载图片,设置tag防止图片错位,加载大量数据时候分页加载。

tag

picasso glide等图片加载框架都已经解决了上面的这些问题,用起来就是几句代码搞定,爽歪歪。后期再出文章讲解这些图片加载框架原理和用法。


(2)图片加载原理
https://www.jianshu.com/p/94e37c901107

OOM

trimToSize 
putCount createCount hitCount missCount

而双向链表LinkedHashMap(3个参数构造方法中accessOrder排序模式设置为访问模式时)中,每次get和put数据,则会将改对象移到链表的尾部,这样子内存缓存达到最大值时,map中的第一个元素就是最近最少使用的那个元素。此时,把它删除,从而达到避免OOM的出现。

注释1处safeSizeOf中封装了sizeOf方法,它是用来计算单个对象的大小,这里默认返回1,一般需要重写该方法来计算对象的大小,如果是计算bitmap的大小,这里会重写不返回1,而是返回bitmap的大小bitmap.getRowBytes() * bitmap.getHeight()


safeSizeOf
sizeOf

LinkHashMap

当map中数据有put和get时,当前操作的数据会移动到链表尾部

size+=safeSizeOf key value(Bitmap)
      previous = map.put(key, value);//previous为旧的值
             if (previous != null) {
                 //如果之前存在键为key的对象,则size应该减去原来对象的大小(把旧值大小删掉)
                 size -= safeSizeOf(key, previous);
             }
             
             
             看注释应该明白了,先是增加size,然后判断以前有没有值,如果有就更新当前的额值,并且size要减去以前的值的大小
//每次新加入对象都需要调用trimToSize方法看是否需要回收
trimToSize 

  通过key获取相应的item,或者创建返回相应的item。相应的item会移动到队列的尾部,
    如果item的value没有被cache或者不能被创建,则返回null。*/
 public final V remove(K key) {
         if (key == null) {
             throw new NullPointerException("key == null");
         }
 
         V previous;
         synchronized (this) {
             previous = map.remove(key);
             if (previous != null) {
                 size -= safeSizeOf(key, previous);
             }
         }
 
         if (previous != null) {
             entryRemoved(false, key, previous, null);
         }
 
         return previous;
     }


previous = map.remove(key);

size- = safeSizeOf key previous

从内存缓存中根据key值移除某个对象并返回该对象

urlKey bitmap


getBitmap

mBitmapLru


ImageLoader主要逻辑是从内存LruCache对象中加载bitmap数据,如果没有,就网络加载数据变为Bitmap,然后存入LruCache对象mBitmapLruCache中。网络加载完的时候在异步线程中ImageLoaderTask把bitmap数据显示到ImageView

ImageLoader  LruCache
mBitmapLruCache

DiskLruCache等)


BitmapRegionDecoder高清加载大图

BitmapRegionDecoder

inSampleSize  inPreferredConfig

            //获得图片的宽、高
            BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
            tmpOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(inputStream, null, tmpOptions);
            int width = tmpOptions.outWidth;
            int height = tmpOptions.outHeight;

            //设置显示图片的中心区域
            BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.RGB_565;
            Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
            mImageView.setImageBitmap(bitmap);


decodeStream  (inputStream,tmpOptions);
decodeRegion(new Rect(
    
));
invalidate onDraw       regionDecoder.decodeRegion解析图片的中间矩形区域,返回bitmap,最终显示在ImageView上。

BitmapRegionDecoder.newInstance(is,false)

requestLayout invalidate


bottm
> imageHeight

imageHeight

   mPreMotionEvent.recycle();
                    mPreMotionEvent = MotionEvent.obtain(event);
                    
                    mPreMotionEvent.recycle
                    mPreMotionEvent = MotionEvent.obtain event;
                    
                    
    Android高效加载大图、多图解决方案,有效避免程序OOM                

https://blog.csdn.net/guolin_blog/article/details/9316683

inJustDecodeBounds = true 
//进制为bitmap分配内存
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法。比如SD卡中的图片可以使用decodeFile方法,网络上的图片可以使用decodeStream方法,资源文件中的图片可以使用decodeResource方法。这些方法会尝试为已经构建的bitmap分配内存,这时就会很容易导致OOM出现。为此每一种解析方法都提供了一个可选的BitmapFactory.Options参数,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。这个技巧让我们可以在加载图片之前就获取到图片的长宽值和MIME类型,从而根据情况对图片进行压缩。如下代码所示:


inJustDecodeBounds = true


outHeight outWidth  BitmapFactory .Options 


预估一下加载整张图片所需占用的内存。
为了加载这一张图片你所愿意提供多少内存。
用于展示这张图片的控件的实际大小。
当前设备的屏幕尺寸和分辨率。

inSampleSize

options.inJustDecodeBounds = true//不占内存 bitmap = null


    options.inJustDecodeBounds = false;

内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

LinkHashMap


getBitmapFromMemCache


inJustDecodeBounds
inSampleSize

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >
    
    <GridView 
        android:id="@+id/photo_wall"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:columnWidth="90dip"
        android:stretchMode="columnWidth"
        android:numColumns="auto_fit"
        android:verticalSpacing="10dip"
        android:gravity="center"
        ></GridView>
    
</LinearLayout>

stretchMode columnWidth
numColumns auto_fit
columnWidth 

ArrayAdapter

LruCache String Bitmap

maxMe

    Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);

 
    /**
     * 从LruCache中获取一张图片,如果不存在就返回null。
     * 
     * @param key
     *            LruCache的键,这里传入图片的URL地址。
     * @return 对应传入键的Bitmap对象,或者null。
     */
    public Bitmap getBitmapFromMemoryCache(String key) {
        return mMemoryCache.get(key);
}


getBitmapFromMemCache findViewWithTag imageUrl


addBitmapToMemoryCache(url,bitmap);


由于我们使用了LruCache来缓存图片,所以不需要担心内存溢出的情况,当LruCache中存储图片的总大小达到容量上限的时候,会自动把最近最少使用的图片从缓存中移除。

Android照片墙完整版,完美结合LruCache和DiskLruCache
https://blog.csdn.net/guolin_blog/article/details/34093441


内存缓存和硬盘缓存

LruCache DiskLruCache


mMemoryCache

bitmap.getByteCount


getBitmapFromMemoryCache
mMemoryCache.put key bitmap

DiskLruCache.Editor     mDiskLruCache.edit

if (fileDescriptor != null) {
                    bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
                }
                if (bitmap != null) {
                    // 将Bitmap对象添加到内存缓存当中
                    addBitmapToMemoryCache(params[0], bitmap);
                }

BitmapFactory(fileDescriptor);
decodeFileDescriptor

addBitmapToMemoryCache(param[0],bitmap);


mPhotoWall.getViewTreeObserver().addOnGlobalLayoutListener(
                new ViewTreeObserver.OnGlobalLayoutListener() {
                    
                    @Override
                    public void onGlobalLayout() {
                        final int numColumns = (int) Math.floor(mPhotoWall
                                .getWidth()
                                / (mImageThumbSize + mImageThumbSpacing));
                        if (numColumns > 0) {
                            int columnWidth = (mPhotoWall.getWidth() / numColumns)
                                    - mImageThumbSpacing;
                            mAdapter.setItemHeight(columnWidth);
                            mPhotoWall.getViewTreeObserver()
                                    .removeGlobalOnLayoutListener(this);
                        }
                    }
                });

        getViewTreeObserver.        addOnGlobalLayoutListener
                
            getViewTreeObserver.    removeGlobalOnLayoutListener

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

https://blog.csdn.net/guolin_blog/article/details/28863651


其实DiskLruCache并没有限制数据的缓存位置,可以自由地进行设定,
但是通常情况下多数应用程序都会将缓存的位置选择为 
/sdcard/Android/data/<application package>/cache 这个路径。

DiskLruCache  cache

open directory appVersion  valueCount  maxSize


getExternalCacheDir
getCacheDir


getDiskCacheDir


bitmap/object


public Editor edit(String key) throws IOException


key和图片的URL

DiskLruCache.editor = mDiskLruCache.edit key ;

public synchronized Snapshot get(String key) throws IOException


Snapshot = mDiskLruCache.get key ;


BitmapFactory.decodeStream

大体套路

首先初始化,创建缓存目录以及线程池,然后加载图片时,先从缓存中获取(要在子线程中进行),如果缓存中有,则显示图片,如果没有则去下载并加入到缓存中,然后从缓存中获取,再显示。

如果在列表中可能会同时加载多个图片,如果只是一直创建线程,那么对app的性能以及体验都是考验,建议使用线程池机制**

Glide源码分析(一),基本加载代码流程
https://www.jianshu.com/p/fe9696ed24b2

pendingSupportRequestManagerFragments

view.getScaleType

CENTER_CROP     CENTER_INSIDE

FIT_CENTER FIT_START 

buildImageViewTarget

imageViewTargetFactory.buildTarget imageView transcodeClass

BitmapImageViewTarget

DrawableImageViewTarget


Target<Drawable.class> target


buildThumbnailRequestRecursive
target
targetListener
requestOptions
parentCoordinator
transitionOptions
priority
overrideWidth
overrideHeight


obtainRequest

  @SuppressWarnings("unchecked") SingleRequest<R> request =
        (SingleRequest<R>) POOL.acquire();

RequestBuilder.into
target.getRequest 
target.setRequest


实现是在SignleRequest中,具体代码大家可自行分析,显然,必须要做的一件事情是告诉Target此时的加载结果,再由Target去通知View做如何的展示,实际上,也是这样子实现的。具体细节这里不展开了。最后回到第17步,还有一个比较简单的方法Target#onLoadStarted

Glide源码分析(二),基本加载类图介绍
https://www.jianshu.com/p/aca6fbb0a441

Glide        RequestManagerRetriever

RequestBuilder   RequestManager  

RequestOptions    decodeFileDescriptor
RequestTracker    TargetTracker

SingleRequest


Request Target 


RequestOptions TransitionOptions

5.RequestOptions
大量的请求参数,涉及到各种placeholder,缓存控制,图片最终的Transformation变换等。

onLoadStarted onResourceReady onLoadFailed onLoadCleared。但是,没有保证。如果资源位于内存中,onLoadStarted可能不会被调用,如果由于空模型对象而导致加载失败。


总结
通过这张类图,其实比较简单的执行过程就是:

Glide#with获取一个全局的单例对象RequestManagerRetriever,由它负责去获取一个和Context相关的并且对Context唯一的RequestManager对象,并且已经和宿主Activity生命周期绑定。
RequestManager#load构建一个RequestBuilder对象,供用户设置大量参数,并返回一个RequestBuilder对象。
RequestBuilder#into构建一个Target和一个Request对象,并完成它们的双向绑定关系。在交由RequestManager之前检查是否需要发起请求,如果需要则进入下一步,否则直接结束。
RequestManager#track触发请求request的执行,从而简单的图片加载至此结束,后续request加载状态会反应至target上面,进而和target相关的控件会得到更新,自此一个图片就正确的呈现在ui界面上。

Glide
RequestManagerRetriever  RequestManager

 load RequestBuilder

 RequestBuilder
into        Target Request


request target

Glide源码分析(三),Engine加载资源过程

https://www.jianshu.com/p/3a45fb8aed8f


SingleRequest#onSizeReady

Engine#load


SingleRequest onSizeReady 

Engine load


Key  DiskCache ActiveResources     EngineKey

Resource DiskCache
ResourceRecycler

  EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);

    cb.onResourceReady(active, DataSource.MEMORY_CACHE);


EngineKey EngineResource    

EngineJob          LoadStatus          

Key
唯一标识一些数据的接口。详细介绍->Key结构

EngineKey
实现了Key接口,用做多路复用负载的内存缓存键

Resource
一个包装了特定类型的资源接口,并且能够汇集和重用。详细介绍->Resource结构

MemoryCache
内存缓存接口,用于在内存缓存中添加和移除资源,这里的实现类是LruResourceCache,继承了LruCache。存放的是Key和Resource键值对。详细介绍->MemoryCache结构

DiskCache
这里的DiskCache是由InternalCacheDiskCacheFactory创建,其继承自DiskLruCacheFactory,最终DiskCache的实现类是DiskLruCacheWrapper对象。详细介绍->DiskCache结构

ActiveResources
存放了已经被Request请求的资源,是内存缓存的一种。

ResourceRecycler
一个回收Resource的辅助类,防止陷入递归,当回收的Resource资源有子资源的时候。

EngineJob
通过添加和删除回调以进行加载并在加载完成时通知回调来管理加载的类

DecodeJob
负责从缓存数据或原始资源解码资源并应用转换和转码的类。

Jobs
一个负责缓存EngineJob的管理类,里面存放了Key与EngineJob的Map对象。

有了上面的几把认知之后,我们来看看代码的实现,先分析Engine的构造方法,如果觉得思路很乱,建议先看本文结尾的总结,宏观有一个大致的了解,再来看这些过程。

loadFromActiveResources


EngineJob 

DecodeJob Jobs

真正去请求代码也很简单,主要涉及到EngineJob与DecodeJob,DecodeJob是一个负责从缓存数据或原始资源解码资源并应用转换和转码的类,它实现了Runnable接口,是真正加载线程的入口。然后是将engineJob缓存至jobs变量中,最后在EngineJob的start方法中请求线程池去执行本次任务,至此,加载就已经被触发,后面我们继续分析加载的核心逻辑DecodeJob的实现。总的来说,加载分为了以下几个过程:

SingleRequest#onSizeReady方法根据前面RequestBuilder设置的参数,请求Engine#load方法
Engine#load方法中,根据相关参数,组装成一个EngineKey,用于标识此次请求的缓存key,首先以这个key去从当前还处理激活状态的Resource资源中去寻找,若查找成功,则返回;否则,进入下一阶段。
若从激活状态的Resource资源查找失败,则进一步去MemoryCache中去查找,若查找成功,则返回;否则,进入下一阶段。
若从MemoryCache中查找失败,则再从jobs中去看是否存在一个已经加载完成或正在加载的EngineJob。若找到,则将回调设置到EngineJob以便接收加载成功或失败的通知;否则,进入下一阶段。
若没有查找到EngineJob,则创建一个EngineJob以及DecodeJob,同时加入到jobs缓存之中,并最终调用EngineJob#start方法,触发加载线程执行真正的加载,从远端获取或者是磁盘获取等。


SingleRequest onSizeReady 

Engine load

EngineKey    MemoryCache jobs

EngineJob

EngineJob.start


Glide源码分析(四),DecodeJob执行过程

https://www.jianshu.com/p/a2c5520ba727


DataFetcher

DataSource


DataCallback

LocalUriFetcher


AssetPathFetcher

FileDescriptorAssetPathFetcher

StreamAssetPathFetcher

HttpUrlFetcher
ThumbFetcher

ModelLoader


LoadData     HttpGlideUrlLoader FileLoader


SingleRequest onSizeReady Engine LoadData

EngineJob  DecodeJob run startNext 

DataFetcherGenerator   loadData方法,开始执行加载。最后在DataFetcher的DataCallback中给予数据加载的状态,成功或者失败。供上层调用模块去处理加载结果。


DataFetcher.DataCallback.   onDataReady


DecodeJob的FetcherReadyCallback


DecodeJob.CallBak


SingleRequest的onSizeReady方法启动Engine的load方法,同时将ResourceCallback传入。这个ResourceCallback最终会调用相关的Target,完成资源的最终渲染。
Engine的load方法,它里面会根据条件判断,这里我们讨论是本地完全没有缓存的情况,这个时候,load中,会创建EngineJob与DecodeJob,DecodeJob是真正开启线程加载数据的地方,EngineJob负责调度DecodeJob以及和上层模块通信,它们是一个一对一的关系。EngineJob中实现了DecodeJob.Callback用于监听下面数据加载的状态,同时在EngineJon中维护了一系列的从Engine#load方法中传入的ResourceCallback信息,用户在监听到数据加载结果之后,通知上层模块,也就是SingleRequest。随之触发启动DecodeJob,开始任务。
DecodeJob的run方法得到执行,在run方法里,它会构造一个DataFetcherGenerator,run方法里面会触发DataFetcherGenerator的startNext方法,同时通过设置到DataFetcherGenerator的FetcherReadyCallback接口,监听数据的获取状态,再将结果上报至EngineJob中(它实现了DecodeJob.Callback接口)。
DataFetcherGenerator的startNext,generator会从DecodeHelper中去获取当前的ModelLoader信息,构造出一个LoadData结构类型的数据,得到相应的DataFetcher对象。DataFetcherGenerator子类实现了DataFetcher.DataCallback接口。它是用于监听DataFetcher#loadData结果。
DataFetcher#loadData完成之后,会将执行结果上报至
DataFetcherGenerator,因为其实现了DataFetcher.DataCallback接口,在其实现上面,回继续回调其成员变量执行的FetcherReadyCallback中的方法,而此时,FetcherReadyCallback实现类正是DecodeJob,因此,代码继续执行到DecodeJob的回调方法中,我们先至考虑简单的情况,忽略线程之间的切换。
回到DecodeJob的FetcherReadyCallback的实现中,它接下来会继续回调设置在其成员变量的类型为DecodeJob.Callback引用,而正是EngineJob实现了这个Callback,因此,代码流程进一步转换给到EngineJob中。
EngineJob实现了DecodeJob.Callback,此时还没有做线程切换,是处于和DecodeJob#run方法相同的线程,此时,EngineJob中利用Handler机制,将继续分发加载到的数据的结果,触发其handleResultOnMainThread方法。见名思义,此时已经切换到了主线程。
EngineJob#handleResultOnMainThread方法中,会回调在上面讲到的,它中维护了一系列的从Engine#load方法中传入的ResourceCallback信息,所以这里会对它们继续进行回调。
ResourceCallback,它的实现是在SingleRequest中,在它的实现中,会将结果交给相应的Target去处理,而我们的ImageView渲染资源正是由这些Target在调度。所以,最终资源就成功的显示到了控件上面。
总体来说,宏观上代码逻辑还是很清晰的,一次加载过程,会创建一个SingleRequest,调用全局的加载引擎Engine,去创建一对EngineJob与DecodeJob,最后在DecodeJob中,根据DataFetcherGenerator获取到相应的DataFetcher,执行数据的加载。成功之后,一步步回调回去。先是DataFetcher到DataFetcherGenerator,再是DataFetcherGenerator到DecodeJob,再是从DecodeJob到EngineJob,进而在EngineJob中做线程切换,回到主线程,将结果回调至SingeRequest,再由SingleRequest中保存的Target引用通知到对应的控件,完成资源的渲染。同时EngineJob也会告知Engine,此次job已经加载完成,是由EngineJobListener完成的。大致就是一个链式的Callback回调过程。后面我会在代码上,根据这个大纲,详细分析一次从网络加载,并缓存到磁盘的详细过程。

Glide源码分析(五),EngineJob与DecodeJob代码详细加载过程


https://www.jianshu.com/p/5493b5e94810


EngineJob DecodeJob

       Glide.with(this)
                .load("https://p.upyun.com/docs/cloud/demo.jpg")
                .into(imageView);

 public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }
  
  
  
  

DataFetcher<?>  notified();

decodeFromRetrievedData

runGenerators


DecodeJob ResourceCacheGenerator.DataCacheGenerator


SourceGenerator

DataCacheGenerator#startNext

reschedule

DecodeJob


getActiveSourceExecutor.execute

job

ResourceCacheGenerator startNext

        .append(String.class, InputStream.class, new DataUrlLoader.StreamFactory<String>())
        .append(String.class, InputStream.class, new StringLoader.StreamFactory())
        .append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())
        .append(String.class, AssetFileDescriptor.class, new StringLoader.AssetFileDescriptorFactory())

InputStream DataUrlLoader.StreamFactory
 StringLoader.StreamFactory
 
 FileDescriptorFactory
 
 AssetFileDescriptorFactory        AssetFileDescriptor

<Model,Data>


  private <Model, Data> ModelLoader<Model, Data> build(@NonNull Entry<?, ?> entry) {
    return (ModelLoader<Model, Data>) Preconditions.checkNotNull(entry.factory.build(this));
  }
  
  ParcelFileDescriptor MultiModelLoaderFactory 
  
  
  Uri InputStream
  
  Uri   ParcelFileDescriptor
  
  Uri AssetFileDescriptor

UriLoa     fetchers

Uri.class, InputStream.class

LoadData: [{GlideUrl, HttpUrlFetcher},{GlideUrl, HttpUrlFetcher}]

Uri.class, ParcelFileDescriptor.class -> MultiModelLoader

LoadData: []

Uri.class, AssetFileDescriptor.class

LoadData:[{ObjectKey, AssetFileDescriptorLocalUriFetcher}]


GlideUrl        HttpUrlFecter

ParcelFileDescriptor.class      MultiModelLoader

ObjectKey AssetFileDescriptorLocalUriFetcher


 Glide(
      @NonNull Context context,
      @NonNull Engine engine,
      @NonNull MemoryCache memoryCache,
      @NonNull BitmapPool bitmapPool,
      @NonNull ArrayPool arrayPool,
      @NonNull RequestManagerRetriever requestManagerRetriever,
      @NonNull ConnectivityMonitorFactory connectivityMonitorFactory,
      int logLevel,
      @NonNull RequestOptions defaultRequestOptions,
      @NonNull Map<Class<?>, TransitionOptions<?, ?>> defaultTransitionOptions) {

       ....

       registry
        .append(ByteBuffer.class, new ByteBufferEncoder())
        .append(InputStream.class, new StreamEncoder(arrayPool))
        /* Bitmaps */
   ....
        .append(int.class, Uri.class, resourceLoaderUriFactory)
        .append(String.class, InputStream.class, new DataUrlLoader.StreamFactory<String>())
        .append(Uri.class, InputStream.class, new DataUrlLoader.StreamFactory<Uri>())
        .append(String.class, InputStream.class, new StringLoader.StreamFactory())
        .append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())
        .append(
            String.class, AssetFileDescriptor.class, new StringLoader.AssetFileDescriptorFactory())
        .append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
        .append(Uri.class, InputStream.class, new AssetUriLoader.StreamFactory(context.getAssets()))
        .append(
            Uri.class,
            ParcelFileDescriptor.class,
            new AssetUriLoader.FileDescriptorFactory(context.getAssets()))
        .append(Uri.class, InputStream.class, new MediaStoreImageThumbLoader.Factory(context))
        .append(Uri.class, InputStream.class, new MediaStoreVideoThumbLoader.Factory(context))
        .append(
            Uri.class,
            InputStream.class,
            new UriLoader.StreamFactory(contentResolver))

         ....


Engine     MemoryCache        BitmapPool 

ArrayPool RequestManagerRetriever

ConnectivityMonitorFactory

RequestOptions

TransitionOptions


  @Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      // We might be being called back on someone else's thread. Before doing anything, we should
      // reschedule to get back onto Glide's thread.
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }

SourceGenerator#onDataReady


  private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      helper.getDiskCache().put(originalKey, writer);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished encoding source to cache"
            + ", key: " + originalKey
            + ", data: " + dataToCache
            + ", encoder: " + encoder
            + ", duration: " + LogTime.getElapsedMillis(startTime));
      }
    } finally {
      loadData.fetcher.cleanup();
    }

    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }

Encoder DataCacheGenerator


  @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    // This data fetcher will be loading from a File and provide the wrong data source, so override
    // with the data source of the original fetcher
    cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey);
  }\\\\
  
  
  
  
  onDataFetcherReady
  
  
总的来说,glide加载过程就是由EngineJob触发DecodeJob,DecodeJob中会有ResourceCacheGenerator->DataCacheGenerator->SourceGenerator对应的ModelLoaders与ModelFetchers依次处理,如果是SourceGenerator则还会更新缓存,这三个不是说一定都会有的,如果有缓存存在且能命中,则不会经历SourceGenerator阶段。在DecodeJob中获取到数据之后,则会层层上报,由Fetcher->Generator->DecodeJob->EngineJob->SingleRequest->Target这样一个序列回调,我们知道Android只有主线程才能操作ui,这里线程切换部分是在EngineJob中进行完成的。至此,宏观和微观上我们理清了加载的一个过程,后面我们会分析有关磁盘缓存的和对图片结果处理的一些小的细节。


EngineJob DecodeJob ResourceCacheGenerator   DataCacheGenerator SourceGenerator

Fetcher Generator-DecodeJob EngineJob SingleRequest Target


Glide源码分析(六),缓存架构、存取命中分析

https://www.jianshu.com/p/876d12d11738

ResourceCacheGenerator
        
        DataCacheGenerator
        
        SourceGenerator
        
        
        @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
        
        
          private void decodeFromRetrievedData() {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Retrieved data", startFetchTime,
          "data: " + currentData
          + ", cache key: " + currentSourceKey
          + ", fetcher: " + currentFetcher);
    }
    Resource<R> resource = null;
    try {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);

        
          originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
    helper.getDiskCache().put(originalKey, writer);
        
        DataCacheKey            getDiskCache
        
        decodeCachedData       decodeFromData
        
        currentFetcher
        我们知道Engine#load会先在内存中查找是否有缓存命中,否则会启动DecodeJob,在它中总共有三个DataFetchGenerator,这里和磁盘缓存相关的就是DataCacheGenerator,
        
        
        DataFetcherGenerator
        
         DataCacheWriter<Object> writer =
        new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
    originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
    
    DataCacheWriter         DataCacheKey
    
    sourceCacheGenerator =
      new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
    
        
        
          Key sourceId = cacheKeys.get(sourceIdIndex);
      // PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times
      // and the actions it performs are much more expensive than a single allocation.
      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      cacheFile = helper.getDiskCache().get(originalKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
        
        getModelLoaders
        
        
        loadData =
          modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
              helper.getOptions());
        
        
        modelLoader.buildLoadData
        
        
        我们假定内存缓存以及在激活的资源池中均没有命中,则此时会根据GlideUrl[https://p.upyun.com/docs/cloud/demo.jpg] 以它和签名组成的DataCacheKey,从DiskCache中去寻找这个缓存文件,DiskLruCacheWrapper#get方法实现如下:
        
        
        
        
        private synchronized DiskLruCache getDiskCache() throws IOException {
   if (diskLruCache == null) {
     diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
   }
   return diskLruCache;
 }
        
        DiskLruCache.open
        
        
        DiskLruCache.get
        
        这里我们介绍了内存缓存,ActiveResources与MemoryCache的命中情况分析,以及DiskCache的DataCacheKey的命中分析,DiskCach还有一个关于ResourceCacheKey的情况,相应的代码在ResourceCacheGenerator中,我们这里不再研究,也是一样的思路。这里再强调几点,DataCacheKey中缓存的是DataFetcher拉取的源数据,也就是原始的数据,ResourceCacheKey则是基于原始数据,做的一层更精细的缓存,从它们的构造方法中我们可以看到。
        
        Glide源码分析(七),总纲思路梳理
        https://blog.csdn.net/nbsp22/article/details/80666090
        
        
        
        
        这样大家面对这个强大的代码库也就不那么不知所措了,最核心的也就是library库,glide相关的绝大多数都是在这个里面的。上层不过是一些简单的扩展,比如volley与okhttp3等。为了方便大家自己研读源码,我这里给出我在研究的时候一些简要的思路。

整个glide库运行时,会构建一个单例对象glide,它里面负责注册一系列的ModelLoaders和对应的ModelFetchers以及一大堆的Encoder与Decoder对象。其中ModelLoaders与ModelFetchers负责从数据源加载数据,远端或本地DickCacke;Encoder负责将数据保存至本地文件,Decoder则是一个反向的操作,负责从本地解码数据。
整个运行中,只会存在一个Engine实例,一个ActiveResources,一个MemoryCache,若干个RequestManager,若干个Target与Request,一个Request中会持有一个其加载到的Resource。RequestManager与Target是与生命周期绑定的,在RequestManager中会根据生命周期方法调度其所有的Request对象,让它们取消或者重新加载。
Request这里我们重点分析的是SingleRequest,它里面加载成功会会持有一个EngineResource资源,EngineResource内部是一个引用计数,当计数减到0时,等待某个时机,Request被clear的时候,会将EngineResource资源从ActiveResources移动至MemoryCache,可以简单理解为一个二级缓存策略,有效提高命中率以及防止过多的操作MemoryCache造成内存抖动。
每一次图片加载,不论是缓存命中,亦或是远端加载得到的,均会创建一个Request对象,同时添加至RequestManager中被管理起来并进行调度,使之也拥有生命周期。
MemoryCache一般是用在于多个页面之间,这里以Activity为单位,比如A页面被销毁,则A中对应的RequestManager中加载的资源会从ActiveResources移动至MemoryCache,这个时候再进入B页面的时候,如果此时B页面也加载相同的资源,那么此时MemoryCache会被命中,同时又会将资源从MemoryCache移动至ActiveResources。
glide一次加载大致是load创建一个RequestBuilder对象,而后通过Builder对象可以设置大量的参数信息,最后是调用into创建相应的Target以及Request对象,最终调用Engine模块启动加载。
Glide在加载成功之后,在onResourceDecoded中会对decode过的资源,再做一次transform,我们设置的一些属性比如CENTER_CROP、FIT_CENTER变换等,会在这里进行转换,最终通过Target渲染到对应的View上面。
源码中涉及到的很多主要的类,需要仔细研究,比如Target、Resource、ModelLoader、DataFetcher、ResourceDecoder、Encoder、Key等,宏观上先理解它们的用处,而后就更容易理解代码的微观过程了,需要很有耐心的研究。
“Reading the fuck source code!”整个Glide源码相对非常复杂,我们也没有必要对每一处细节都掌握的清清楚楚,关键是理解别人设计的思想,可能光看这些文档,显然是无法理解代码中的正在奥妙之处的,这里我也只是列出了我在分析过程中的一些体会和要点,也仅仅只是给大家一个参考。纸上来得终觉浅,如果想深入一个模块,还是必须得亲自实践,不断的看代码Debug,不断的阅读源码,从源码从找到答案,解决自己的疑惑,借鉴别人的优秀设计,这样自己才能进步。讲了很多源码性的东西,Glide提供的自定义扩展模块也是相当的灵活,包括替换成自己的loader,换网络加载库等。最后我会分析Glide一些比较实用的地方,在源码的基础上做一些实践。

下一篇 Glide源码分析(八),Glide的自定义模块扩展与实践
        
        
ModelLoaders ModelFetchers
        
Encoder        Decoder
        
        
        Engine      ActivityResouce
        
    Android图片加载框架最全解析(一),Glide的基本用法    
        
        https://blog.csdn.net/guolin_blog/article/details/53759439
        
    Glide.with(this)
     .load(url)
     .placeholder(R.drawable.loading)
     .diskCacheStrategy(DiskCacheStrategy.NONE)
     .into(imageView);    
        
        diskCacheStrategy DiskCacheStrategy。NO
        
        .override 100,100
        
        
    FragmentManager fm = activity.getSupportFragmentManager();    
        
       FragmentManager fm = fragment.getChildFragmentManager();    
        
        
        

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值