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();