1. 简单使用
Glide.with(this).load(url).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).centerCrop().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.RESULT).priority(Priority.HIGH).into(mIvMn);
2. with方法
参数类型:Activity,Context,Fragment,ApplicationContext(本质上也是Context) 类型决定生命周期
2.1 with方法
with方法public static RequestManager with(Context context) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(context);}public static RequestManager with(Activity activity) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(activity);}public static RequestManager with(FragmentActivity activity) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(activity);}@TargetApi(Build.VERSION_CODES.HONEYCOMB)public static RequestManager with(android.app.Fragment fragment) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(fragment);}public static RequestManager with(Fragment fragment) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(fragment);}
这几个重载方法最终都是调用RequestManagerRetriever.get()方法获取RequestManagerRetriever对象,然后通过这个对象获取RequestManager对象
2.2 RequestManagerRetriever对象的get方法
public RequestManager get(Context context) {if (context == null) {throw new IllegalArgumentException("You cannot start a load on a null Context");} else if (Util.isOnMainThread() && !(context instanceof Application)) {//判断当前线程是不是主线程 并且context是不是Application//分别转化为相应的Context对象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());}}return getApplicationManager(context);}private RequestManager getApplicationManager(Context context) {if (applicationManager == null) {synchronized (this) {if (applicationManager == null) {applicationManager = new RequestManager(context.getApplicationContext(),new ApplicationLifecycle(), new EmptyRequestManagerTreeNode());}}}return applicationManager;}@TargetApi(Build.VERSION_CODES.HONEYCOMB)public RequestManager get(Activity activity) {if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {return get(activity.getApplicationContext());} else {assertNotDestroyed(activity);android.app.FragmentManager fm = activity.getFragmentManager();return fragmentGet(activity, fm);}}
1) get方法首先会判断传入的Context的类型,如果传入的Context类型是Application或者是在子线程加载的图片,这会通过getApplicationManager方法单例形式创建一个RequestManager对象。
2) 否则会调用fragmentGet方法,传入Context对象和FragmentManager对象。
2.3 fragmentGet方法
@TargetApi(Build.VERSION_CODES.HONEYCOMB)RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {RequestManagerFragment current = getRequestManagerFragment(fm);RequestManager requestManager = current.getRequestManager();if (requestManager == null) {requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());current.setRequestManager(requestManager);}return requestManager;}@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) {RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);if (current == null) {current = pendingRequestManagerFragments.get(fm);if (current == null) {current = new RequestManagerFragment();pendingRequestManagerFragments.put(fm, current);fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();}}return current;}
1) 通过getRequestManagerFragment创建一个没有UI界面的fragment,通过这个fragment获取RequestManager对象。
2) 如果上面获取的对象为空,就直接创建一个RequestManager对象,传入上下文,LifecycleListener对象(这是用来监听activity生命周期的接口),还有一个不知名的对象。
3) 当前fragment绑定RequestManager对象,glide是无法知道activity生命周期的,所以这里添加了一个没有UI界面的fragment来监听Activity的生命周期,这样设计很巧妙。
4) getRequestManagerFragment方法主要是去获取fragment对象,如果对象不存在,就创建一个新的对象,同时添加到activity中,并且通过Handler发送一条消息,在handlemessage中移除当前fm对象。
2.4 RequestManagerFragment类
RequestManagerFragment类public RequestManagerFragment() {this(new ActivityFragmentLifecycle());}// For testing only.@SuppressLint("ValidFragment")RequestManagerFragment(ActivityFragmentLifecycle lifecycle) {this.lifecycle = lifecycle;}ActivityFragmentLifecycle getLifecycle() {return lifecycle;}@Overridepublic void onStart() {super.onStart();lifecycle.onStart();}@Overridepublic void onStop() {super.onStop();lifecycle.onStop();}@Overridepublic void onDestroy() {super.onDestroy();lifecycle.onDestroy();}
构造方法中创建了一个ActivityFragmentLifecycle对象,这个对象就是监听fragment生命周期的,从fragment的生命周期方法中可以很明确看出来。
2.5 ActivityFragmentLifecycle类
ActivityFragmentLifecycle类class ActivityFragmentLifecycle implements Lifecycle {@Overridepublic void addListener(LifecycleListener listener) {lifecycleListeners.add(listener);if (isDestroyed) {listener.onDestroy();} else if (isStarted) {listener.onStart();} else {listener.onStop();}}}public interface Lifecycle {void addListener(LifecycleListener listener);}public interface LifecycleListener {void onStart();void onStop();void onDestroy();}
可以看出LifecycleListener接口才是定义监听fragment生命周期的类,上面的类都不过实现了这个接口类而已。
2.6 小结
width方法最终目的都是通过RequestManagerRetriever对象的get方法获取RequestManager对象,传入参数主要分Application类型和非Application类型
1) 如果with方法传入的是Application,会通过调用getApplicationManager()来获取一个RequestManager对象,不需要处理生命周期,因为Application对象的生命周期就是应用程序的生命周期
2) 如果with方法传入的不是Application类型,最终流程都是一样,那就是会向当前的Activity当中添加一个隐藏的Fragment,app包下的fragment会调用fragmentGet方法创建隐藏fragment,v4包下的fragment会调用supportFragmentGet方法创建fragment,都会返回RequestManager对象,创建隐藏fragment的目的是Glide需要知道加载的生命周期,可是Glide并没有办法知道Activity的生命周期,于是Glide就使用了添加隐藏Fragment的这种小技巧,因为Fragment的生命周期和Activity是同步的,如果Activity被销毁了,Fragment是可以监听到的,这样Glide就可以捕获这个事件并停止图片加载了。
3)如果我们是在非主线程当中使用的Glide,那么不管你是传入的Activity还是Fragment,都会被强制当成Application来处理,调用getApplicationManager()来获取RequestManager对象
3. load方法
参数类型:String,File,byte[],URL,图片资源ID,Uri,我这里只看参数类型为String类型的,这些方法都会返回DrawableTypeRequest对象。
3.1 load方法
public DrawableTypeRequest<String> load(String string) {return (DrawableTypeRequest<String>) fromString().load(string);}public DrawableTypeRequest<String> fromString() {return loadGeneric(String.class);}private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {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("Unknown type " + modelClass + ". You must provide a Model of a type for"+ " which there is a registered ModelLoader, if you are using a custom model, you must first call"+ " Glide#register with a ModelLoaderFactory for your custom model class");}return optionsApplier.apply(new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,glide, requestTracker, lifecycle, optionsApplier));}
load内部调用了fromString方法,fromString方法调用了loadGeneric方法,
loadGeneric()方法也没几行代码,这里分别调用了Glide.buildStreamModelLoader()方法和Glide.buildFileDescriptorModelLoader()方法来获得ModelLoader对象。
ModelLoader对象是用于加载图片的,而我们给load()方法传入不同类型的参数,这里也会得到不同的ModelLoader对象。
3.2 DrawableTypeRequest类
load方法都会返回这个DrawableTypeRequest类,这里面两个重要方法是asBitmap方法和asGif方法,asBitmap方法会创建一个BitmapTypeRequest对象,同时会返回BitmapTypeRequest类型,这个方法会将图片强制转换为静态图片,asGif方法会创建一个GifTypeRequest对象,同时会返回GifTypeRequest类型,这个方法会强制将图片转换为动态图片,如果图片本身是静态图片,则图片加载会失败。
3.3 DrawableRequestBuilder类
它是DrawableTypeRequest的父类,这里面提供了glide加载图片过程的很多方法,比如animate,centerCrop,crossFade,error,fitCenter,placeholder,skipMemoryCache,priority等等…
public class DrawableRequestBuilder<ModelType>extends GenericRequestBuilder<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable>implements BitmapOptions, DrawableOptions {DrawableRequestBuilder(Context context, Class<ModelType> modelClass,LoadProvider<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable> loadProvider, Glide glide,RequestTracker requestTracker, Lifecycle lifecycle) {super(context, modelClass, loadProvider, GlideDrawable.class, glide, requestTracker, lifecycle);// Default to animating.crossFade();}//图片的一种显示格式@SuppressWarnings("unchecked")public DrawableRequestBuilder<ModelType> centerCrop() {return transform(glide.getDrawableCenterCrop());}//图片的一种显示格式@SuppressWarnings("unchecked")public DrawableRequestBuilder<ModelType> fitCenter() {return transform(glide.getDrawableFitCenter());}//图片显示设置渐变时间public DrawableRequestBuilder<ModelType> crossFade(int duration) {super.animate(new DrawableCrossFadeFactory<GlideDrawable>(duration));return this;}//磁盘缓存策略@Overridepublic DrawableRequestBuilder<ModelType> diskCacheStrategy(DiskCacheStrategy strategy) {super.diskCacheStrategy(strategy);return this;}//是否跳过内存缓存@Overridepublic DrawableRequestBuilder<ModelType> skipMemoryCache(boolean skip) {super.skipMemoryCache(skip);return this;}//设置图片尺寸@Overridepublic DrawableRequestBuilder<ModelType> override(int width, int height) {super.override(width, height);return this;}//设置图片加载优先级@Overridepublic DrawableRequestBuilder<ModelType> priority(Priority priority) {super.priority(priority);return this;}}
3.4 GenericRequestBuilder类:DrawableRequestBuilder的父类,这里面提供了绝大部分glide加载图片过程中的方法,它的子类都是实现了这个父类。
一些重要参数protected final RequestTracker requestTracker;protected final Lifecycle lifecycle;private int placeholderId;private int errorId;private Drawable placeholderDrawable;private Drawable errorPlaceholder;private Priority priority = null;private boolean isCacheable = true;private int overrideHeight = -1;private int overrideWidth = -1;构造方法:GenericRequestBuilder(LoadProvider<ModelType, DataType, ResourceType, TranscodeType> loadProvider,Class<TranscodeType> transcodeClass, GenericRequestBuilder<ModelType, ?, ?, ?> other) {this(other.context, other.modelClass, loadProvider, transcodeClass, other.glide, other.requestTracker,other.lifecycle);this.model = other.model;this.isModelSet = other.isModelSet;this.signature = other.signature;this.diskCacheStrategy = other.diskCacheStrategy;this.isCacheable = other.isCacheable;}GenericRequestBuilder(Context context, Class<ModelType> modelClass,LoadProvider<ModelType, DataType, ResourceType, TranscodeType> loadProvider,Class<TranscodeType> transcodeClass, Glide glide, RequestTracker requestTracker, Lifecycle lifecycle) {this.context = context;this.modelClass = modelClass;this.transcodeClass = transcodeClass;this.glide = glide;this.requestTracker = requestTracker;this.lifecycle = lifecycle;this.loadProvider = loadProvider != null? new ChildLoadProvider<ModelType, DataType, ResourceType, TranscodeType>(loadProvider) : null;if (context == null) {throw new NullPointerException("Context can't be null");}if (modelClass != null && loadProvider == null) {throw new NullPointerException("LoadProvider must not be null");}}
4. into方法
4.1 into方法
into(ImageView imageView)方法:@Overridepublic Target<GlideDrawable> into(ImageView view) {return super.into(view);}调用父类GenericRequestBuilder的into方法public Target<TranscodeType> into(ImageView view) {//断言是不是在主线程 因为ui更新操作在主线程Util.assertMainThread();if (view == null) {throw new IllegalArgumentException("You must pass in a non null View");}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.}}return into(glide.buildImageViewTarget(view, transcodeClass));}
into方法最终会调用into(Target target)方法返回一个target对象,通过glide.buildImageViewTarget(view, transcodeClass)创建一个target对象
4.2 buildImageViewTarget方法
<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {return imageViewTargetFactory.buildTarget(imageView, transcodedClass);}@SuppressWarnings("unchecked")public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) {if (GlideDrawable.class.isAssignableFrom(clazz)) {return (Target<Z>) new GlideDrawableImageViewTarget(view);} else if (Bitmap.class.equals(clazz)) {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)");}}
创建target对象主要分两种,如果你在使用Glide加载图片的时候调用了asBitmap()方法,那么这里就会构建出BitmapImageViewTarget对象,返回BitmapImageViewTarget对象,否则返回GlideDrawableImageViewTarget对象。
4.3 into(Target target)方法
public <Y extends Target<TranscodeType>> Y into(Y target) {Util.assertMainThread();if (target == null) {throw new IllegalArgumentException("You must pass in a non null Target");}if (!isModelSet) {throw new IllegalArgumentException("You must first set a model (try #load())");}Request previous = target.getRequest();if (previous != null) {previous.clear();requestTracker.removeRequest(previous);previous.recycle();}Request request = buildRequest(target);target.setRequest(request);lifecycle.addListener(target);//RequestTracker类用于跟踪、取消和重新启动进程中、已完成和失败请求//执行请求requestTracker.runRequest(request);return target;}@Overridepublic void setRequest(Request request) {setTag(request);}
这里首先还是检查是不是在主线程,因为更新ui的操作必须在主线程,这里首先会通过target对象获取request对象,然后清除之前的request对象,回收request对象,然后重新构建一个新的request对象,并且给这个target设置request对象,这其实就是好比listView加载图片时候给图片设置tag,防止图片错位问题,这里requestTracker.runRequest这个方法是执行这个请求。
4.4 recycle方法
@Overridepublic void recycle() {loadProvider = null;model = null;context = null;target = null;placeholderDrawable = null;errorDrawable = null;fallbackDrawable = null;requestListener = null;requestCoordinator = null;transformation = null;animationFactory = null;loadedFromMemoryCache = false;loadStatus = null;REQUEST_POOL.offer(this);}主要是对象置空,boolean值置为false
4.5 buildRequest方法:构建request对象
private Request buildRequest(Target<TranscodeType> target) {图片加载默认优先级是NORMALif (priority == null) {priority = Priority.NORMAL;}return buildRequestRecursive(target, null);}private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) {if (thumbnailRequestBuilder != null) {if (isThumbnailBuilt) {throw new IllegalStateException("You cannot use a request as both the main request and a thumbnail, "+ "consider using clone() on the request(s) passed to thumbnail()");}// Recursive case: contains a potentially recursive thumbnail request builder.if (thumbnailRequestBuilder.animationFactory.equals(NoAnimation.getFactory())) {thumbnailRequestBuilder.animationFactory = animationFactory;}if (thumbnailRequestBuilder.priority == null) {thumbnailRequestBuilder.priority = getThumbnailPriority();}if (Util.isValidDimensions(overrideWidth, overrideHeight)&& !Util.isValidDimensions(thumbnailRequestBuilder.overrideWidth,thumbnailRequestBuilder.overrideHeight)) {thumbnailRequestBuilder.override(overrideWidth, overrideHeight);}ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);// Guard against infinite recursion.isThumbnailBuilt = true;// Recursively generate thumbnail requests.Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator);isThumbnailBuilt = false;coordinator.setRequests(fullRequest, thumbRequest);return coordinator;} else if (thumbSizeMultiplier != null) {// Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator);Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator);coordinator.setRequests(fullRequest, thumbnailRequest);return coordinator;} else {// Base case: no thumbnail.return obtainRequest(target, sizeMultiplier, priority, parentCoordinator);}}buildRequestRecursive方法前面一大部分主要是如果glide调用了override,就调用这个类的override方法,以及缩略图的处理,最终还是调用的obtainRequest方法private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority,RequestCoordinator requestCoordinator) {return GenericRequest.obtain(loadProvider,model,signature,context,priority,target,sizeMultiplier,placeholderDrawable,placeholderId,errorPlaceholder,errorId,fallbackDrawable,fallbackResource,requestListener,requestCoordinator,glide.getEngine(),transformation,transcodeClass,isCacheable,animationFactory,overrideWidth,overrideHeight,diskCacheStrategy);}这个方法又调用了GenericRequest的obtain方法public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain(LoadProvider<A, T, Z, R> loadProvider,A model,Key signature,Context context,Priority priority,Target<R> target,float sizeMultiplier,Drawable placeholderDrawable,int placeholderResourceId,Drawable errorDrawable,int errorResourceId,Drawable fallbackDrawable,int fallbackResourceId,RequestListener<? super A, R> requestListener,RequestCoordinator requestCoordinator,Engine engine,Transformation<Z> transformation,Class<R> transcodeClass,boolean isMemoryCacheable,GlideAnimationFactory<R> animationFactory,int overrideWidth,int overrideHeight,DiskCacheStrategy diskCacheStrategy) {@SuppressWarnings("unchecked")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(loadProvider,model,signature,context,priority,target,sizeMultiplier,placeholderDrawable,placeholderResourceId,errorDrawable,errorResourceId,fallbackDrawable,fallbackResourceId,requestListener,requestCoordinator,engine,transformation,transcodeClass,isMemoryCacheable,animationFactory,overrideWidth,overrideHeight,diskCacheStrategy);return request;}
REQUEST_POOL.poll() 核心代码就这一句,调用了请求池的poll方法,获取request对象,如果请求池中这个对象为空,那么就创建一个GenericRequest对象,然后调用request对象的init方法进行初始化。
4.6 runRequest方法:这个方法就是执行图片请求的方法
public void runRequest(Request request) {requests.add(request);if (!isPaused) {request.begin();} else {pendingRequests.add(request);}}
这个方法内部的逻辑是判断是不是加载状态,如果是加载状态,就调用request.begin()方法,否则就把这个请求加入到等待执行的请求队列中去。
4.7 begin方法:真正执行request请求的方法
@Overridepublic void begin() {startTime = LogTime.getLogTime();if (model == null) {onException(null);return;}status = Status.WAITING_FOR_SIZE;if (Util.isValidDimensions(overrideWidth, overrideHeight)) {onSizeReady(overrideWidth, overrideHeight);} else {target.getSize(this);}if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {target.onLoadStarted(getPlaceholderDrawable());}if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("finished run method in " + LogTime.getElapsedMillis(startTime));}}
这个方法的逻辑是如果glide调用了override方法,则调用onSizeReady方法,否则调用target.getSize(this);其实后面这个方法的内部也是会调用onSizeReady方法。
如果图片没有加载完成,并且图片不是加载失败状态,就给这个target设置默认占位图。
4.8 Target对象的getSize方法
@Overridepublic void getSize(SizeReadyCallback cb) {sizeDeterminer.getSize(cb);}public void getSize(SizeReadyCallback cb) {int currentWidth = getViewWidthOrParam();int currentHeight = getViewHeightOrParam();if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {cb.onSizeReady(currentWidth, currentHeight);} else {if (!cbs.contains(cb)) {cbs.add(cb);}if (layoutListener == null) {final ViewTreeObserver observer = view.getViewTreeObserver();layoutListener = new SizeDeterminerLayoutListener(this);observer.addOnPreDrawListener(layoutListener);}}}
如果图片宽高没有测量完则会执行else部分图片的测量,否则也是调用onSizeReady方法
4.9 GenericRequest的onSizeReady方法
@Overridepublic void onSizeReady(int width, int height) {if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));}if (status != Status.WAITING_FOR_SIZE) {return;}status = Status.RUNNING;width = Math.round(sizeMultiplier * width);height = Math.round(sizeMultiplier * height);ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);if (dataFetcher == null) {onException(new Exception("Failed to load model: \'" + model + "\'"));return;}ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));}loadedFromMemoryCache = true;loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,priority, isMemoryCacheable, diskCacheStrategy, this);loadedFromMemoryCache = resource != null;if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime));}}几个重要的对象获取原始数据的类ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();//final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);//图片资源转换类ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();//Engine类:负责启动负载并管理活动和缓存的资源类loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,priority, isMemoryCacheable, diskCacheStrategy, this);
4.10 DrawableTypeRequest类的buildProvider方法得到LoadProvider对象
private static <A, Z, R> FixedLoadProvider<A, ImageVideoWrapper, Z, R> buildProvider(Glide glide,ModelLoader<A, InputStream> streamModelLoader,ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader, Class<Z> resourceClass,Class<R> transcodedClass,ResourceTranscoder<Z, R> transcoder) {if (streamModelLoader == null && fileDescriptorModelLoader == null) {return null;}if (transcoder == null) {transcoder = glide.buildTranscoder(resourceClass, transcodedClass);}DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider = glide.buildDataProvider(ImageVideoWrapper.class,resourceClass);ImageVideoModelLoader<A> modelLoader = new ImageVideoModelLoader<A>(streamModelLoader,fileDescriptorModelLoader);return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>(modelLoader, transcoder, dataLoadProvider);}@Overridepublic DataFetcher<ImageVideoWrapper> getResourceFetcher(A model, int width, int height) {DataFetcher<InputStream> streamFetcher = null;if (streamLoader != null) {streamFetcher = streamLoader.getResourceFetcher(model, width, height);}DataFetcher<ParcelFileDescriptor> fileDescriptorFetcher = null;if (fileDescriptorLoader != null) {fileDescriptorFetcher = fileDescriptorLoader.getResourceFetcher(model, width, height);}if (streamFetcher != null || fileDescriptorFetcher != null) {return new ImageVideoFetcher(streamFetcher, fileDescriptorFetcher);} else {return null;}}public interface LoadProvider<A, T, Z, R> extends DataLoadProvider<T, Z> {ModelLoader<A, T> getModelLoader();ResourceTranscoder<Z, R> getTranscoder();}public interface DataLoadProvider<T, Z> {ResourceDecoder<File, Z> getCacheDecoder();ResourceDecoder<T, Z> getSourceDecoder();Encoder<T> getSourceEncoder();ResourceEncoder<Z> getEncoder();}
上面主要是创建LoadProvider对象,LoadProvider这个类主要负责图片的编码解码。
4.11 DataFetcher类
public interface DataFetcher<T> {T loadData(Priority priority) throws Exception;void cleanup();String getId();void cancel();}
这个类主要负责将流资源转换为glide实际加载图片需要的数据,比如byte[] ,file,uri,url等。
4.12 Engine类的load方法
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {Util.assertMainThread();long startTime = LogTime.getLogTime();final String id = fetcher.getId();EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);if (cached != null) {cb.onResourceReady(cached);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Loaded resource from cache", startTime, key);}return null;}EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);if (active != null) {cb.onResourceReady(active);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Loaded resource from active resources", startTime, key);}return null;}EngineJob current = jobs.get(key);if (current != null) {current.addCallback(cb);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Added to existing load", startTime, key);}return new LoadStatus(cb, current);}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);EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);jobs.put(key, engineJob);engineJob.addCallback(cb);engineJob.start(runnable);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Started new load", startTime, key);}return new LoadStatus(cb, engineJob);}
1) 通过buildKey方法构建图片缓存key对象,参数有10多个之多,比如id,签名,图片大小,数据的编码解码等等…
2)根据key从缓存中去获取缓存图片数据loadFromCache(key, isMemoryCacheable);
3)如果缓存不为空,则会调用 cb.onResourceReady(cached)方法,加载缓存数据
4)如果上面获取的缓存还是为空,则调用下面的方法继续去获取缓存数据loadFromActiveResources(key, isMemoryCacheable);
5)如果缓存不为空,也会调用cb.onResourceReady(active)方法去加载缓存数据。
6)否则会通过EngineJob这个Runnable类去加载图片。
4.13 EngineRunnable类
@Overridepublic void run() {if (isCancelled) {return;}Exception exception = null;Resource<?> resource = null;try {resource = decode();} catch (Exception e) {if (Log.isLoggable(TAG, Log.VERBOSE)) {Log.v(TAG, "Exception decoding", e);}exception = e;}if (isCancelled) {if (resource != null) {resource.recycle();}return;}if (resource == null) {onLoadFailed(exception);} else {onLoadComplete(resource);}}private void onLoadComplete(Resource resource) {manager.onResourceReady(resource);}@Overridepublic void onResourceReady(final Resource<?> resource) {this.resource = resource;MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();}private void handleResultOnMainThread() {if (isCancelled) {resource.recycle();return;} else if (cbs.isEmpty()) {throw new IllegalStateException("Received a resource without any callbacks to notify");}engineResource = engineResourceFactory.build(resource, isCacheable);hasResource = true;engineResource.acquire();listener.onEngineJobComplete(key, engineResource);for (ResourceCallback cb : cbs) {if (!isInIgnoredCallbacks(cb)) {engineResource.acquire();cb.onResourceReady(engineResource);}}// Our request is complete, so we can release the resource.engineResource.release();}@Overridepublic void onException(final Exception e) {this.exception = e;MAIN_THREAD_HANDLER.obtainMessage(MSG_EXCEPTION, this).sendToTarget();}private void handleExceptionOnMainThread() {if (isCancelled) {return;} else if (cbs.isEmpty()) {throw new IllegalStateException("Received an exception without any callbacks to notify");}hasException = true;listener.onEngineJobComplete(key, null);for (ResourceCallback cb : cbs) {if (!isInIgnoredCallbacks(cb)) {cb.onException(exception);}}}
run方法首先是去解码图片,然后判断有没有图片加载有没有取消,如果用户取消了加载,则回收资源,return,否则如果解码得到的Resource不为空则调用onLoadComplete加载完成的方法,加载完成通过Handler发送一个消息,更新UI,最终会调用EngineJob的handleResultOnMainThread方法,否则调用加载失败的方法,调用onException方法,也是通过Handler发送一个消息,更新UI,最终会调用handleExceptionOnMainThread方法
5. 图片的内存缓存
5.1 生成缓存key
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {Util.assertMainThread();long startTime = LogTime.getLogTime();final String id = fetcher.getId();EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());}
fetcher.getId()方法获得了一个id字符串,这个字符串也就是我们要加载的图片的唯一标识,比如说如果是一张网络上的图片的话,那么这个id就是这张图片的url地址。
EngineKeyl类主要重写了equal()和hashCode()方法。
Glide内存缓存的实现自然也是使用的LruCache算法。不过除了LruCache算法之外,Glide还结合了一种弱引用的机制,共同完成了内存缓存功能。
5.2 ModelLoader对象
我们当时分析到了在loadGeneric()方法中会调用Glide.buildStreamModelLoader()方法来获取一个ModelLoader对象
public static <T> ModelLoader<T, InputStream> buildStreamModelLoader(Class<T> modelClass, Context context) {return buildModelLoader(modelClass, InputStream.class, context);}//构建ModelLoader对象public static <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass,Context context) {if (modelClass == null) {if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "Unable to load null model, setting placeholder only");}return null;}return Glide.get(context).getLoaderFactory().buildModelLoader(modelClass, resourceClass);}//Glide的静态方法,用于返回Glide对象public static Glide get(Context context) {if (glide == null) {synchronized (Glide.class) {if (glide == null) {Context applicationContext = context.getApplicationContext();List<GlideModule> modules = new ManifestParser(applicationContext).parse();GlideBuilder builder = new GlideBuilder(applicationContext);for (GlideModule module : modules) {module.applyOptions(applicationContext, builder);}glide = builder.createGlide();for (GlideModule module : modules) {module.registerComponents(applicationContext, glide);}}}}return glide;}//创建并返回一个Glide对象Glide createGlide() {if (sourceService == null) {final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());sourceService = new FifoPriorityThreadPoolExecutor(cores);}if (diskCacheService == null) {diskCacheService = new FifoPriorityThreadPoolExecutor(1);}MemorySizeCalculator calculator = new MemorySizeCalculator(context);if (bitmapPool == null) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {int size = calculator.getBitmapPoolSize();bitmapPool = new LruBitmapPool(size);} else {bitmapPool = new BitmapPoolAdapter();}}if (memoryCache == null) {memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());}if (diskCacheFactory == null) {diskCacheFactory = new InternalCacheDiskCacheFactory(context);}if (engine == null) {engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);}if (decodeFormat == null) {decodeFormat = DecodeFormat.DEFAULT;}return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);}
你会发现这里new出了一个LruResourceCache,并把它赋值到了memoryCache这个对象上面。你没有猜错,这个就是Glide实现内存缓存所使用的LruCache对象了。
5.3 内存缓存读取
实现内存缓存的读取其实还是在Engine类的load方法,源码看下:public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher,DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder,Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) {Util.assertMainThread();long startTime = LogTime.getLogTime();final String id = fetcher.getId();EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);if (cached != null) {cb.onResourceReady(cached);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Loaded resource from cache", startTime, key);}return null;}EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);if (active != null) {cb.onResourceReady(active);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Loaded resource from active resources", startTime, key);}return null;}EngineJob current = jobs.get(key);if (current != null) {current.addCallback(cb);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Added to existing load", startTime, key);}return new LoadStatus(cb, current);}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);EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);jobs.put(key, engineJob);engineJob.addCallback(cb);engineJob.start(runnable);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Started new load", startTime, key);}return new LoadStatus(cb, engineJob);}
1) 通过buildKey方法构建图片缓存key对象,参数有10多个之多,比如id,签名,图片大小,数据的编码解码等等…
2)根据key从缓存中去获取缓存图片数据 这个使用的是Lrucache实现的loadFromCache(key, isMemoryCacheable);
3)如果缓存不为空,则会调用cb.onResourceReady(cached)方法,加载缓存数据
4)如果上面获取的缓存还是为空,则调用下面的方法继续去获取正在加载的图片数loadFromActiveResources(key,isMemoryCacheable);这个是使用弱引用实现的。
5)如果缓存不为空,也还是调用cb.onResourceReady(active)方法去加载缓存数据。
6)否则会通过EngineJob这个Runnable类去加载图片。
5.3.1 loadFromCache和loadFromActiveResources方法
loadFromCache方法private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {if (!isMemoryCacheable) {return null;}EngineResource<?> cached = getEngineResourceFromCache(key);if (cached != null) {cached.acquire();activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));}return cached;}@SuppressWarnings("unchecked")private EngineResource<?> getEngineResourceFromCache(Key key) {Resource<?> cached = cache.remove(key);final EngineResource result;if (cached == null) {result = null;} else if (cached instanceof EngineResource) {result = (EngineResource) cached;} else {result = new EngineResource(cached, true /*isCacheable*/);}return result;}loadFromActiveResources方法private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {if (!isMemoryCacheable) {return null;}EngineResource<?> active = null;WeakReference<EngineResource<?>> activeRef = activeResources.get(key);if (activeRef != null) {active = activeRef.get();if (active != null) {active.acquire();} else {activeResources.remove(key);}}return active;}
1) loadFromCache()方法是通过 getEngineResourceFromCache()方法来获取缓存。在这个方法中,会使用缓存Key来从cache当中取值,而这里的cache对象就是在构建Glide对象时创建的LruResourceCache,那么说明这里其实使用的就是LruCache算法了。
2)当我们从LruResourceCache中获取到缓存图片之后会将它从缓存中移除,然后将这个缓存图片存储到activeResources当中。activeResources就是一个弱引用的HashMap,用来缓存正在使用中的图片,我们可以看到,loadFromActiveResources()方法就是从activeResources这个HashMap当中取值的。使用activeResources来缓存正在使用中的图片,可以保护这些图片不会被LruCache算法回收掉,概括一下来说,就是如果能从内存缓存当中读取到要加载的图片,那么就直接进行回调,如果读取不到的话,才会开启线程执行后面的图片加载逻辑。
5.4 内存缓存的写入操作
@Overridepublic void onResourceReady(final Resource<?> resource) {this.resource = resource;MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();}private void handleResultOnMainThread() {if (isCancelled) {resource.recycle();return;} else if (cbs.isEmpty()) {throw new IllegalStateException("Received a resource without any callbacks to notify");}engineResource = engineResourceFactory.build(resource, isCacheable);hasResource = true;// Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it// synchronously released by one of the callbacks.engineResource.acquire();listener.onEngineJobComplete(key, engineResource);for (ResourceCallback cb : cbs) {if (!isInIgnoredCallbacks(cb)) {engineResource.acquire();cb.onResourceReady(engineResource);}}// Our request is complete, so we can release the resource.engineResource.release();}static class EngineResourceFactory {public <R> EngineResource<R> build(Resource<R> resource, boolean isMemoryCacheable) {return new EngineResource<R>(resource, isMemoryCacheable);}}
这里通过EngineResourceFactory构建出了一个包含图片资源的EngineResource对象,然后通过 listener.onEngineJobComplete(key, engineResource);这个代码将这个对象回调到Engine的onEngineJobComplete()方法当中
@SuppressWarnings("unchecked")@Overridepublic void onEngineJobComplete(Key key, EngineResource<?> resource) {Util.assertMainThread();if (resource != null) {resource.setResourceListener(key, this);if (resource.isCacheable()) {activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));}}jobs.remove(key);}
回调过来的EngineResource被put到了activeResources当中,也就是在这里写入的缓存,不过这只是弱引用缓存,还有另外一种LruCache缓存是在哪里写入的呢?这就要介绍一下EngineResource中的一个引用机制了,观察刚才的handleResultOnMainThread()方法,分别有调用EngineResource的acquire()方法,和调用它的release()方法。其实,EngineResource是用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1
void acquire() {if (isRecycled) {throw new IllegalStateException("Cannot acquire a recycled resource");}if (!Looper.getMainLooper().equals(Looper.myLooper())) {throw new IllegalThreadStateException("Must call acquire on the main thread");}++acquired;}void release() {if (acquired <= 0) {throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");}if (!Looper.getMainLooper().equals(Looper.myLooper())) {throw new IllegalThreadStateException("Must call release on the main thread");}if (--acquired == 0) {listener.onResourceReleased(key, this);}}
当acquired变量大于0的时候,说明图片正在使用中,也就应该放到activeResources弱引用缓存当中。而经过release()之后,如果acquired变量等于0了,说明图片已经不再被使用了,那么就会调用listener的onResourceReleased()方法来释放资源,这个listener就是Engine对象
@Overridepublic void onResourceReleased(Key cacheKey, EngineResource resource) {Util.assertMainThread();activeResources.remove(cacheKey);if (resource.isCacheable()) {cache.put(cacheKey, resource);} else {resourceRecycler.recycle(resource);}}
这里首先会将缓存图片从activeResources中移除,然后再将它put到LruResourceCache当中。这样也就实现了正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存的功能。
6. 总结
-
with方法总结
width方法最终目的都是通过RequestManagerRetriever对象的get方法获取RequestManager对象,传入参数主要分Application类型和非Application类型。
(1) 如果with方法传入的是Application,会通过调用getApplicationManager()来获取一个RequestManager对象,不需要处理生命周期,因为Application对象的生命周期就是应用程序的生命周期。
(2) 如果with方法传入的不是Application类型,最终流程都是一样,那就是会向当前的Activity当中添加一个隐藏的Fragment,通过调用fragmentGet方法创建隐藏fragment,返回RequestManager对象,创建隐藏fragment的目的是Glide需要知道加载图片加载的生命周期,可是Glide并没有办法知道Activity的生命周期,于是Glide就使用了添加隐藏Fragment的这种小技巧,因为Fragment的生命周期和Activity是同步的,如果Activity被销毁了,Fragment是可以监听到的,这样Glide就可以捕获这个事件并停止图片加载了。
(3) 如果我们是在非主线程当中使用的Glide,那么不管你是传入的Activity还是Fragment,都会被强制当成Application来处理,调用getApplicationManager()来获取RequestManager对象。
-
load方法总结
(1) 参数类型:String,File,byte[],URL,图片资源ID,Uri,我这里只看参数类型为String类型的,这些方法都会返回DrawableTypeRequest对象。
(2) 两个重要方法是asBitmap方法和asGif方法,asBitmap方法会返回BitmapTypeRequest类型,这个方法会将图片强制转换为静态图片,asGif方法会返回GifTypeRequest类型,将图片强制转换为动态图片,如果图片是静态图片,则会加载失败。
(3) DrawableRequestBuilder类:DrawableTypeRequest的直接父类,这里面提供了glide加载图片过程的很多方法,比如animate,centerCrop,crossFade,error,fitCenter,placeholder,skipMemoryCache,priority等等…
(4) GenericRequestBuilder类: DrawableRequestBuilder的父类,这里面提供了绝大部分glide加载图片过程中的方法,它的子类都是直接或间接实现了这个父类。
-
into方法总结
(1)into方法最终会调用into(Target target)方法返回一个target对象,通过glide.buildImageViewTarget(view, transcodeClass)创建一个target对象。
(2) 创建target对象主要分两种,如果你在使用Glide加载图片的时候调用了asBitmap()方法,那么这里就会构建出BitmapImageViewTarget对象,返回BitmapImageViewTarget对象,否则返回GlideDrawableImageViewTarget对象。
(3) into(Target target)方法,这个方法会通过target对象获取request对象,然后清除之前的request对象,回收request对象,然后重新构建一个新的request对象,并且给这个target设置request对象,这其实就是好比listView加载图片时候给图片设置tag,防止图片错位问题,这里其实就是给target设置tag为request,也是为了防止图片错位,这里requestTracker.runRequest这个方法是执行这个请求。
(4) buildRequest方法:构建一个request对象,然后调用target.setRequest给target设置tag,防止图片错乱。
(5) runRequest方法:这个方法是执行图片请求的方法,这个方法内部的逻辑是判断图片当前是不是加载状态,如果是加载状态,就调用request.begin()方法,否则就把这个请求加入到等待执行的请求队列中去等待加载。
(6) begin方法: 这个方法的逻辑是如果glide调用了override方法,指定了图片宽高,则调用onSizeReady方法,否则调用target.getSize(this)去测量图片宽高,但是最终也会通过回调形式调用onSizeReady方法。
(7) onSizeReady方法: 这个方法里面有一些重要的对象,这个主要是调用engine.load方法去执行这个图片请求。
(8) Engine类的load方法:
1) 通过buildKey方法构建图片缓存key对象,参数有10多个之多,比如id,签名,图片大小,数据的编码解码等等...2)根据key从缓存中去获取缓存图片数据EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);3)如果缓存不为空,则会调用 cb.onResourceReady(cached)方法,加载缓存数据4)如果上面获取的缓存还是为空,则调用下面的方法继续去获取正在加载的图片数据EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);5)如果缓存不为空,也会调用cb.onResourceReady(active)方法去加载缓存数据。6)否则会通过EngineJob这个Runnable类去加载图片。
(9) EngineRunnable类的run方法:run方法首先是去解码图片,然后判断有没有图片加载有没有取消,如果用户取消了加载,则回收资源,return,否则如果解码得到的Resource不为空则调用onLoadComplete加载完成的方法,加载完成通过Handler发送一个消息,更新UI,最终会调用EngineJob的handleResultOnMainThread方法,否则调用加载失败的方法,调用onException方法,也是通过Handler发送一个消息,更新UI,最终会调用handleExceptionOnMainThread方法。
(10) 几个重要的类
RequestManager:监听组件生命周期,管理各种图片加载,RequestTracker:跟踪图片请求,执行请求,删除请求,添加请求等等...ModeLoader:数据来源加载成原始数据LifecycleListener:glide绑定生命周期的回调接口DataLoadProvider:数据编解码LoadProvider:继承了DataLoadProviderDataFetcher:原始数据转换为我们可以用的不同数据类型,比如url,字节数组,文件等等...ResourceTranscoder:原始数据解码Engine:开启图片加载的类,两个重要方法 equals和hashCode方法EngineRunnable: 执行图片加载请求的子线程类
(11) 图片的内存缓存是通过LruCache和弱引用的方式来实现的,正在使用中的图片使用弱引用来进行缓存,不再使用中的图片使用LruCache来进行缓存的功能,这就是Glide内存缓存的实现原理。
欢迎关注我的公众号,一起进步!
本文分享自微信公众号 - Android架构师成长之路(gh_07f996f00d9b)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/4611086/blog/4945108