Glide学习笔记之源码分析

一、基本用法:

Glide.with(context).load(url).into(imageview);
    with()方法:给glide添加生命周期的监听;返回的是一个RequestManager对象所以load()方法也是RequestManager的

    RequestManager-->RequestManagerRetriver-->RequestTracker(负责跟踪、取消、重启)

    RequestTracker可知其实就是在Glide内部维护了一个Request请求的list,不管你context传入的是Activity还是    FragmentActivitiy,都会在Activity的基础上创建一个Fragment,这是为什么呢?因为Glide无法监听到Activity的生命周期,所以往Activity中添加一个Fragment来监听Fragment的生命周期,因为如果Activity消亡了,Fragment当然也就消亡了,具体代码看下面,不过传入的参数跟之前Application的不同,这里的SupportRequestManagerFragment的current.getLifecycle()方法我们进去会发现这其实是一个继承了Fragment的类,也就是往Activity里添加的Fragment。


    load()方法:其实就是做一个状态的保存

public DrawableTypeRequest<String> load(String string) {
				return (DrawableTypeRequest<String>) fromString().load(string);
			}
			
			private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) {
				//根据不同的class来创建不同的ModelLoader
				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(
				//通过获取到的ModelLoadder来创建DrawableTypeRequest对象返回给上一级
						new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context,
								glide, requestTracker, lifecycle, optionsApplier));
			}
    提供一个InputStream输入流
public static <T> ModelLoader<T, InputStream> buildStreamModelLoader(Class<T> modelClass, Context context) {
				return buildModelLoader(modelClass, InputStream.class, context);
			}
			
			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);
			}
public synchronized <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass) {
				ModelLoader<T, Y> result = getCachedLoader(modelClass, resourceClass);
				if (result != null) {
					// We've already tried to create a model loader and can't with the currently registered set of factories,
					// but we can't use null to demonstrate that failure because model loaders that haven't been requested
					// yet will be null in the cache. To avoid this, we use a special signal model loader.
					if (NULL_MODEL_LOADER.equals(result)) {
						return null;
					} else {
						return result;
					}
				}


				final ModelLoaderFactory<T, Y> factory = getFactory(modelClass, resourceClass);
				if (factory != null) {
					result = factory.build(context, this);
					cacheModelLoader(modelClass, resourceClass, result);
				} else {
					// We can't generate a model loader for the given arguments with the currently registered set of factories.
					cacheNullLoader(modelClass, resourceClass);
				}
				return result;
			}
			
			
			private <T, Y> ModelLoaderFactory<T, Y> getFactory(Class<T> modelClass, Class<Y> resourceClass) {
				Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> resourceToFactories = modelClassToResourceFactories.get(modelClass);
				ModelLoaderFactory/*T, Y*/ result = null;
				if (resourceToFactories != null) {
					result = resourceToFactories.get(resourceClass);
				}


				
				//   关于注册的   这块在Glide类的构造函数统一注册了
				if (result == null) {
					for (Class<? super T> registeredModelClass : modelClassToResourceFactories.keySet()) {
						// This accounts for model subclasses, our map only works for exact matches. We should however still
						// match a subclass of a model with a factory for a super class of that model if if there isn't a
						// factory for that particular subclass. Uris are a great example of when this happens, most uris
						// are actually subclasses for Uri, but we'd generally rather load them all with the same factory rather
						// than trying to register for each subclass individually.
						if (registeredModelClass.isAssignableFrom(modelClass)) {
							Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/> currentResourceToFactories =
									modelClassToResourceFactories.get(registeredModelClass);
							if (currentResourceToFactories != null) {
								result = currentResourceToFactories.get(resourceClass);
								if (result != null) {
									break;
								}
							}
						}
					}
				}


				return result;
			}
   以上就是生成streamModelLoader和fileDescriptorModelLoader。
    fromString()方法之后返回一个DrawableTypeRequest对象,然后就是调用这个对象的load(string)方法,其实是调用了父类DrawableRequestBuilder里load()
    into():此方法也是在父类DrawableRequestBuilder里
public Target<TranscodeType> into(ImageView view) {
				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));
			}

			<R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) {
				return imageViewTargetFactory.buildTarget(imageView, transcodedClass);
			}

			//返回一个Target
			public class ImageViewTargetFactory {

				@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传入进来
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();
				}

				//调用buildRequest()方法构建出了一个Request对象
				Request request = buildRequest(target);
				target.setRequest(request);
				lifecycle.addListener(target);
				//执行这个Request。
				requestTracker.runRequest(request);

				return target;
			}

private Request buildRequest(Target<TranscodeType> target) {
				if (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);
				}
			}

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

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>();
				}
				
				主要看这个init方法主要就是给一些常量赋值
				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;
			}
回到into()方法执行request
public void runRequest(Request request) {
				requests.add(request);
				// 判断是否暂停状态
				if (!isPaused) {
				// begin方法是request接口中的方法,调用的就是GenericRequest中的begin()
					request.begin();
				} else {
					pendingRequests.add(request);
				}
			}
@Override
			public 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));
				}
			}
@Override
			public void onException(Exception e) {
				if (Log.isLoggable(TAG, Log.DEBUG)) {
					Log.d(TAG, "load failed", e);
				}

				status = Status.FAILED;
				//TODO: what if this is a thumbnail request?
				if (requestListener == null || !requestListener.onException(e, model, target, isFirstReadyResource())) {
					setErrorPlaceholder(e);
				}
			}
			
			private void setErrorPlaceholder(Exception e) {
				if (!canNotifyStatusChanged()) {
					return;
				}

				Drawable error = model == null ? getFallbackDrawable() : null;
				if (error == null) {
				  error = getErrorDrawable();
				}
				if (error == null) {
					error = getPlaceholderDrawable();
				}
				
				// 此处的target就是GenericRequestBuilder中into()
				return into(glide.buildImageViewTarget(view, transcodeClass));中的target所以onLoadFailed方法也就是ImageViewTarget中的onLoadFailed
				target.onLoadFailed(e, error);
			}
设置错误图片
@Override
			public void onLoadFailed(Exception e, Drawable errorDrawable) {
				view.setImageDrawable(errorDrawable);
			}
设置loading占位图
@Override
			public void onLoadStarted(Drawable placeholder) {
				view.setImageDrawable(placeholder);
			}
再次回到GenericRequest的begin方法中。。。
判断是否设置override(手动设置宽高)属性的
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
				onSizeReady(overrideWidth, overrideHeight);
			} else {
			// 同理getsize也是ImageViewTarget中的,但是ImageViewTarget中的并没有所以在他的父类ViewTarget中
				target.getSize(this);
			}
@Override
			public void getSize(SizeReadyCallback cb) {
				sizeDeterminer.getSize(cb);
			}
			
			public void getSize(SizeReadyCallback cb) {
				int currentWidth = getViewWidthOrParam();
				int currentHeight = getViewHeightOrParam();
				if (isSizeValid(currentWidth) && isSizeValid(currentHeight)) {
				// 最终还是调用了GenericRequest中的onSizeReady方法
					cb.onSizeReady(currentWidth, currentHeight);
				} else {
					// We want to notify callbacks in the order they were added and we only expect one or two callbacks to
					// be added a time, so a List is a reasonable choice.
					if (!cbs.contains(cb)) {
						cbs.add(cb);
					}
					if (layoutListener == null) {
						final ViewTreeObserver observer = view.getViewTreeObserver();
						layoutListener = new SizeDeterminerLayoutListener(this);
						observer.addOnPreDrawListener(layoutListener);
					}
				}
			}
			
@Override
			public 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);
				
				// load()方法里的loadGeneric()在buildModelLoader()的时候通过getFactory()创建了一个ModelLoaderFactory,在那里生
				// 成返回了一个DrawableTypeRequest对象,在创建DrawableTypeRequest的时候new出了一个FixedLoadProvider类
				// 这个loadProvider其实就是FixedLoadProvider类的一个封装类ChildLoadProvider的一个实体对象
				ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
				
				// 调用的是ImageVideoModelLoader的getResourceFetcher
				final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);

				if (dataFetcher == null) {
					onException(new Exception("Failed to load model: \'" + model + "\'"));
					return;
				}
				
				// 得到的是GIfBitmapWrapperDrawableTranscoder
				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));
				}
			}
DrawableTypeRequest(Class<ModelType> modelClass, ModelLoader<ModelType, InputStream> streamModelLoader,
					ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader, Context context, Glide glide,
					RequestTracker requestTracker, Lifecycle lifecycle, RequestManager.OptionsApplier optionsApplier) {
				super(context, modelClass,
				// 构建Provider
						buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, GifBitmapWrapper.class,
								GlideDrawable.class, null),
						glide, requestTracker, lifecycle);
				this.streamModelLoader = streamModelLoader;
				this.fileDescriptorModelLoader = fileDescriptorModelLoader;
				this.optionsApplier = optionsApplier;
			}
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) {
				// 构建一个代码转换器ResourceTranscoder(这是一个接口)实际上构建出的是GIfBitmapWrapperDrawableTranscoder对象
					transcoder = glide.buildTranscoder(resourceClass, transcodedClass);
				}
				// 构建一个编解码器DataLoadProvider(这是一个接口)实际上构建出的是ImageVideoGifDrwableLoadProvider对象
				DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider = glide.buildDataProvider(ImageVideoWrapper.class,
						resourceClass);
				// 把之前loadGeneric中生成的两个ModelLoader对象封装到ImageVideoModelLoader中
				ImageVideoModelLoader<A> modelLoader = new ImageVideoModelLoader<A>(streamModelLoader,
						fileDescriptorModelLoader);
				// 把前面生成的对象封装进去就生成了前面onSizeReady()方法中的loadProvider了
				return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>(modelLoader, transcoder, dataLoadProvider);
			}
回到GenericRequest中begin调用的onSizeReady()中的这句代码
ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();
				final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);
				// 调用的是ImageVideoModelLoader的getResourceFetcher
			
			public class ImageVideoModelLoader<A> implements ModelLoader<A, ImageVideoWrapper> {
				...

				@Override
				public DataFetcher<ImageVideoWrapper> getResourceFetcher(A model, int width, int height) {
					DataFetcher<InputStream> streamFetcher = null;
					if (streamLoader != null) {
					// 先调用我们在loadGeneric中构建出的StreamStringLoader的父类StringLoader中的getResourceFetcher(),
					// 最后返回的是uilLoder.getResourceFetcher(uri, width, height);,所以得到的是一个HttpUrlFetcher对象
						streamFetcher = streamLoader.getResourceFetcher(model, width, height);
					}
					DataFetcher<ParcelFileDescriptor> fileDescriptorFetcher = null;
					if (fileDescriptorLoader != null) {
						fileDescriptorFetcher = fileDescriptorLoader.getResourceFetcher(model, width, height);
					}

					if (streamFetcher != null || fileDescriptorFetcher != null) {
					// 最后还是得到一个ImageVideoFetcher
						return new ImageVideoFetcher(streamFetcher, fileDescriptorFetcher);
					} else {
						return null;
					}
				}

				...
			}
回到前面的onSizeReady()中的这句代码
loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder,
						priority, isMemoryCacheable, diskCacheStrategy, this);
						
						
			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对图片进行解码等操作
				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);
			}
EngineRunnable的run方法:

@Override
			public 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 Resource<?> decode() throws Exception {
			// 一个是从资源一个是从缓存
				if (isDecodingFromCache()) {
					return decodeFromCache();
				} else {
					return decodeFromSource();
				}
			}					
								
			private Resource<?> decodeFromSource() throws Exception {
				return decodeJob.decodeFromSource();
			}				
								
			public Resource<Z> decodeFromSource() throws Exception {
				Resource<T> decoded = decodeSource();
				return transformEncodeAndTranscode(decoded);
			}					
						
			// 跟进decodeSource()
			private Resource<T> decodeSource() throws Exception {
				Resource<T> decoded = null;
				try {
					long startTime = LogTime.getLogTime();
					// 就是ImageVideoFetcher的loadData()
					final A data = fetcher.loadData(priority);
					if (Log.isLoggable(TAG, Log.VERBOSE)) {
						logWithTimeAndKey("Fetched data", startTime);
					}
					if (isCancelled) {
						return null;
					}
					// 这个方法其实就是起到一个解码的作用
					decoded = decodeFromSourceData(data);
				} finally {
					fetcher.cleanup();
				}
				return decoded;
			}					
@Override
			public ImageVideoWrapper loadData(Priority priority) throws Exception {
				InputStream is = null;
				if (streamFetcher != null) {
					try {
					// 此处的streamFetcher就是前面构建ImageVideoFetcher时传入的HttpUrlFetcher,所以此处是调用的
					HttpUrlFetcher的loadData
						is = streamFetcher.loadData(priority);
					} catch (Exception e) {
						if (Log.isLoggable(TAG, Log.VERBOSE)) {
							Log.v(TAG, "Exception fetching input stream, trying ParcelFileDescriptor", e);
						}
						if (fileDescriptorFetcher == null) {
							throw e;
						}
					}
				}
				ParcelFileDescriptor fileDescriptor = null;
				if (fileDescriptorFetcher != null) {
					try {
						fileDescriptor = fileDescriptorFetcher.loadData(priority);
					} catch (Exception e) {
						if (Log.isLoggable(TAG, Log.VERBOSE)) {
							Log.v(TAG, "Exception fetching ParcelFileDescriptor", e);
						}
						if (is == null) {
							throw e;
						}
					}
				}
				return new ImageVideoWrapper(is, fileDescriptor);
			}
调用的HttpUrlFetcher的loadData
@Override
			public InputStream loadData(Priority priority) throws Exception {
				return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
			}

			private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
					throws IOException {
				if (redirects >= MAXIMUM_REDIRECTS) {
					throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
				} else {
					// Comparing the URLs using .equals performs additional network I/O and is generally broken.
					// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
					try {
						if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
							throw new IOException("In re-direct loop");
						}
					} catch (URISyntaxException e) {
						// Do nothing, this is best effort.
					}
				}
				urlConnection = connectionFactory.build(url);
				for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
				  urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
				}
				urlConnection.setConnectTimeout(2500);
				urlConnection.setReadTimeout(2500);
				urlConnection.setUseCaches(false);
				urlConnection.setDoInput(true);

				// Connect explicitly to avoid errors in decoders if connection fails.
				urlConnection.connect();
				if (isCancelled) {
					return null;
				}
				final int statusCode = urlConnection.getResponseCode();
				if (statusCode / 100 == 2) {
					return getStreamForSuccessfulRequest(urlConnection);
				} else if (statusCode / 100 == 3) {
					String redirectUrlString = urlConnection.getHeaderField("Location");
					if (TextUtils.isEmpty(redirectUrlString)) {
						throw new IOException("Received empty or null redirect url");
					}
					URL redirectUrl = new URL(url, redirectUrlString);
					return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
				} else {
					if (statusCode == -1) {
						throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
					}
					throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
				}
			}
private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
					throws IOException {
				if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
					int contentLength = urlConnection.getContentLength();
					stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
				} else {
					if (Log.isLoggable(TAG, Log.DEBUG)) {
						Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
					}
					stream = urlConnection.getInputStream();
				}
				return stream;
			}

上面就是请求网络的代码,但只是得到了一个inputStream的输入流,再回到DecodeJob的decodeSource方法
private Resource<T> decodeFromSourceData(A data) throws IOException {
				final Resource<T> decoded;
				if (diskCacheStrategy.cacheSource()) {
					decoded = cacheAndDecodeSourceData(data);
				} else {
					long startTime = LogTime.getLogTime();
					loadProvider就是刚刚的FixedLoadProvider,所以getSourceDecoudr()就是GifBitmapWrapperResourceDecoder的一个对象
					decoded = loadProvider.getSourceDecoder().decode(data, width, height);
					if (Log.isLoggable(TAG, Log.VERBOSE)) {
						logWithTimeAndKey("Decoded from source", startTime);
					}
				}
				return decoded;
			}	
来到GifBitmapWrapperResourceDecoder的decode()

@SuppressWarnings("resource")
			// @see ResourceDecoder.decode
			@Override
			public Resource<GifBitmapWrapper> decode(ImageVideoWrapper source, int width, int height) throws IOException {
				ByteArrayPool pool = ByteArrayPool.get();
				byte[] tempBytes = pool.getBytes();

				GifBitmapWrapper wrapper = null;
				try {
					wrapper = decode(source, width, height, tempBytes);
				} finally {
					pool.releaseBytes(tempBytes);
				}
				return wrapper != null ? new GifBitmapWrapperResource(wrapper) : null;
			}				
								
			private GifBitmapWrapper decode(ImageVideoWrapper source, int width, int height, byte[] bytes) throws IOException {
				final GifBitmapWrapper result;
				if (source.getStream() != null) {
					result = decodeStream(source, width, height, bytes);
				} else {
					result = decodeBitmapWrapper(source, width, height);
				}
				return result;
			}					
								
			private GifBitmapWrapper decodeStream(ImageVideoWrapper source, int width, int height, byte[] bytes)
					throws IOException {
				InputStream bis = streamFactory.build(source.getStream(), bytes);
				// 先取出2个字节来判断是否是GIF
				bis.mark(MARK_LIMIT_BYTES);
				ImageHeaderParser.ImageType type = parser.parse(bis);
				bis.reset();

				GifBitmapWrapper result = null;
				if (type == ImageHeaderParser.ImageType.GIF) {
					result = decodeGifWrapper(bis, width, height);
				}
				// Decoding the gif may fail even if the type matches.
				if (result == null) {
					// We can only reset the buffered InputStream, so to start from the beginning of the stream, we need to
					// pass in a new source containing the buffered stream rather than the original stream.
					ImageVideoWrapper forBitmapDecoder = new ImageVideoWrapper(bis, source.getFileDescriptor());
					result = decodeBitmapWrapper(forBitmapDecoder, width, height);
				}
				return result;
			}					
								
			private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException {
				GifBitmapWrapper result = null;
				
				// 这个bitmapDecoder实际上就是ImageVideoBitmapDecode
				Resource<Bitmap> bitmapResource = bitmapDecoder.decode(toDecode, width, height);
				if (bitmapResource != null) {
					result = new GifBitmapWrapper(bitmapResource, null);
				}

				return result;
			}
ImageVideoBitmapDecode的decode()
@SuppressWarnings("resource")
			// @see ResourceDecoder.decode
			@Override
			public Resource<Bitmap> decode(ImageVideoWrapper source, int width, int height) throws IOException {
				Resource<Bitmap> result = null;
				InputStream is = source.getStream();
				if (is != null) {
					try {
					// 其实是StreamBitmapDecoder的decode方法
						result = streamDecoder.decode(is, width, height);
					} catch (IOException e) {
						if (Log.isLoggable(TAG, Log.VERBOSE)) {
							Log.v(TAG, "Failed to load image from stream, trying FileDescriptor", e);
						}
					}
				}

				if (result == null) {
					ParcelFileDescriptor fileDescriptor = source.getFileDescriptor();
					if (fileDescriptor != null) {
						result = fileDescriptorDecoder.decode(fileDescriptor, width, height);
					}
				}
				return result;
			}
StreamBitmapDecoder的decode方法
@Override
			public Resource<Bitmap> decode(InputStream source, int width, int height) {
				Bitmap bitmap = downsampler.decode(source, bitmapPool, width, height, decodeFormat);
				return BitmapResource.obtain(bitmap, bitmapPool);
			}
这里就是对服务器返回的InputStream进行读取,图片的加载和逻辑(旋转、圆角等)的处理也全都在这个方法内
@Override
			public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) {
				final ByteArrayPool byteArrayPool = ByteArrayPool.get();
				final byte[] bytesForOptions = byteArrayPool.getBytes();
				final byte[] bytesForStream = byteArrayPool.getBytes();
				final BitmapFactory.Options options = getDefaultOptions();

				// Use to fix the mark limit to avoid allocating buffers that fit entire images.
				RecyclableBufferedInputStream bufferedStream = new RecyclableBufferedInputStream(
						is, bytesForStream);
				// Use to retrieve exceptions thrown while reading.
				// TODO(#126): when the framework no longer returns partially decoded Bitmaps or provides a way to determine
				// if a Bitmap is partially decoded, consider removing.
				ExceptionCatchingInputStream exceptionStream =
						ExceptionCatchingInputStream.obtain(bufferedStream);
				// Use to read data.
				// Ensures that we can always reset after reading an image header so that we can still attempt to decode the
				// full image even when the header decode fails and/or overflows our read buffer. See #283.
				// 这里读取数据
				MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream);
				try {
					exceptionStream.mark(MARK_POSITION);
					int orientation = 0;
					try {
						orientation = new ImageHeaderParser(exceptionStream).getOrientation();
					} catch (IOException e) {
						if (Log.isLoggable(TAG, Log.WARN)) {
							Log.w(TAG, "Cannot determine the image orientation from header", e);
						}
					} finally {
						try {
							exceptionStream.reset();
						} catch (IOException e) {
							if (Log.isLoggable(TAG, Log.WARN)) {
								Log.w(TAG, "Cannot reset the input stream", e);
							}
						}
					}

					options.inTempStorage = bytesForOptions;

					final int[] inDimens = getDimensions(invalidatingStream, bufferedStream, options);
					final int inWidth = inDimens[0];
					final int inHeight = inDimens[1];

					final int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
					final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight);

					final Bitmap downsampled =
							downsampleWithSize(invalidatingStream, bufferedStream, options, pool, inWidth, inHeight, sampleSize,
									decodeFormat);

					// BitmapFactory swallows exceptions during decodes and in some cases when inBitmap is non null, may catch
					// and log a stack trace but still return a non null bitmap. To avoid displaying partially decoded bitmaps,
					// we catch exceptions reading from the stream in our ExceptionCatchingInputStream and throw them here.
					final Exception streamException = exceptionStream.getException();
					if (streamException != null) {
						throw new RuntimeException(streamException);
					}

					Bitmap rotated = null;
					if (downsampled != null) {
						rotated = TransformationUtils.rotateImageExif(downsampled, pool, orientation);

						if (!downsampled.equals(rotated) && !pool.put(downsampled)) {
							downsampled.recycle();
						}
					}

					return rotated;
				} finally {
					byteArrayPool.releaseBytes(bytesForOptions);
					byteArrayPool.releaseBytes(bytesForStream);
					exceptionStream.release();
					releaseOptions(options);
				}
			}

带着上面返回的bitmap对象-->StreamBitmapDecoder(转化成Resource对象)-->ImageVideoBitmapDecoder-->GIfBitmapWrapperResourceDecoder

private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException {
				GifBitmapWrapper result = null;
				// 经过层层返回的Resource对象
				Resource<Bitmap> bitmapResource = bitmapDecoder.decode(toDecode, width, height);
				if (bitmapResource != null) {
				// 将经过层层返回的Resource对象封装到GifBitmapWrapper中:意思就是既能加载gif也能加载静态图
					result = new GifBitmapWrapper(bitmapResource, null);
				}

				return result;
			}
返回到最外层的decode()发现:
return wrapper != null ? new GifBitmapWrapperResource(wrapper) : null;
public class GifBitmapWrapperResource implements Resource<GifBitmapWrapper> {
				private final GifBitmapWrapper data;

				public GifBitmapWrapperResource(GifBitmapWrapper data) {
					if (data == null) {
						throw new NullPointerException("Data must not be null");
					}
					this.data = data;
				}

				@Override
				public GifBitmapWrapper get() {
					return data;
				}

				@Override
				public int getSize() {
					return data.getSize();
				}

				@Override
				public void recycle() {
					Resource<Bitmap> bitmapResource = data.getBitmapResource();
					if (bitmapResource != null) {
						bitmapResource.recycle();
					}
					Resource<GifDrawable> gifDataResource = data.getGifResource();
					if (gifDataResource != null) {
						gifDataResource.recycle();
					}
				}
			}
经过这一层是图片可以通过Resource接口形式返回,所以在DecodeJob的decodeFromSourceData中返回的其实也就是Resource<GifBitmapWrapper>类型的对象,把这个对象带入到最上面的
public Resource<Z> decodeFromSource() throws Exception {
					Resource<T> decoded = decodeSource();
					return transformEncodeAndTranscode(decoded);
				}
转化成Resource<Z>类型的对象
private Resource<Z> transformEncodeAndTranscode(Resource<T> decoded) {
				long startTime = LogTime.getLogTime();
				Resource<T> transformed = transform(decoded);
				if (Log.isLoggable(TAG, Log.VERBOSE)) {
					logWithTimeAndKey("Transformed resource from source", startTime);
				}

				writeTransformedToCache(transformed);

				startTime = LogTime.getLogTime();
				// T类型转成Z类型
				
				Resource<Z> result = transcode(transformed);
				if (Log.isLoggable(TAG, Log.VERBOSE)) {
					logWithTimeAndKey("Transcoded transformed from source", startTime);
				}
				return result;
			}
private Resource<Z> transcode(Resource<T> transformed) {
				if (transformed == null) {
					return null;
				}
				// 这里的transcoder其实就是很早以前传过来的GifBitmapWrapperDrawableTranscoder
				(load()-->loadGeneric()-->DrawableTYpeRequest-->FixedLoadProvider)
				return transcoder.transcode(transformed);
			}
@SuppressWarnings("unchecked")
			@Override
			public Resource<GlideDrawable> transcode(Resource<GifBitmapWrapper> toTranscode) {
				// 1、从Resource<GifBitmapWrapper>取出GifBitmapWrapper对象
				GifBitmapWrapper gifBitmap = toTranscode.get();
				// 2、从GifBitmapWrapper中取出Resource<Bitmap>对象
				Resource<Bitmap> bitmapResource = gifBitmap.getBitmapResource();

				final Resource<? extends GlideDrawable> result;
				// 3、判断如果为空就是gif反之则是静态图
				if (bitmapResource != null) {
					// 需要转化成为Bitmap或者Drawable对象才能显示到ImageView上,所以在调用GlideBItmapDrawableTranscoder进行转码
					result = bitmapDrawableResourceTranscoder.transcode(bitmapResource);
				} else {
				// gif的加载是使用GifDrawable这个类本身就是一个Drawable对象
					result = gifBitmap.getGifResource();
				}
				// This is unchecked but always safe, anything that extends a Drawable can be safely cast to a Drawable.
				return (Resource<GlideDrawable>) result;
			}
GlideBitmapDrawableTranscoder的transcode()
@Override
			public Resource<GlideBitmapDrawable> transcode(Resource<Bitmap> toTranscode) {
				GlideBitmapDrawable drawable = new GlideBitmapDrawable(resources, toTranscode.get());
				return new GlideBitmapDrawableResource(drawable, bitmapPool);
			}
不过是静态图(Resource<GlideBitmapDrawable>)还是gif(Resource<GifDrawable>)都是Resource<GlideDrawable>的子类GifBitmapWrapperDrawableTranscoder的transcode返回的是一个Resource<GlideDrawable>类型的对象带着返回的Resource<GlideDrawable>回到DecodeJob的transformEncodeAndTranscode()-->decodeFromSource()-->EngineRunnable的run()
@Override
			public void run() {
				if (isCancelled) {
					return;
				}

				Exception exception = null;
				Resource<?> resource = null;
				try {
				// 这个是转码以后的Resource<GlideDrawable>
					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就是EngineJob的对象
				manager.onResourceReady(resource);
			}
EngineJob的onResourceReady()
@Override
			public void onResourceReady(final Resource<?> resource) {
				this.resource = resource;
				// 通过Handle发送了一条MSG_COMPLETE的消息,在MainThreadCallBack中接收消息
				MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
			}
private static class MainThreadCallback implements Handler.Callback {

				@Override
				public boolean handleMessage(Message message) {
					if (MSG_COMPLETE == message.what || MSG_EXCEPTION == message.what) {
						EngineJob job = (EngineJob) message.obj;
						if (MSG_COMPLETE == message.what) {
							job.handleResultOnMainThread();
						} else {
							job.handleExceptionOnMainThread();
						}
						return true;
					}

					return false;
				}
			}
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);
				
				// 通过循环调用所有的ResourceCallback的onResourceReady()方法,而ResourceCallback则是从addCallback得到的
				for (ResourceCallback cb : cbs) {
					if (!isInIgnoredCallbacks(cb)) {
						engineResource.acquire();
						cb.onResourceReady(engineResource);
					}
				}
				// Our request is complete, so we can release the resource.
				engineResource.release();
			}
addCallback则是在Engine的load方法中调用的
public void addCallback(ResourceCallback cb) {
				Util.assertMainThread();
				if (hasResource) {
					cb.onResourceReady(engineResource);
				} else if (hasException) {
					cb.onException(exception);
				} else {
					cbs.add(cb);
				}
			}
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) {
				...
				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);
				// 此处调用了addCallback,里面所传的cb又是什么???跟上去到GennericRequest中传的是个this
				engineJob.addCallback(cb);
				engineJob.start(runnable);

				if (Log.isLoggable(TAG, Log.VERBOSE)) {
					logWithTimeAndKey("Started new load", startTime, key);
				}
				return new LoadStatus(cb, engineJob);
			}
EngineJob中addCallback中所调用的onResourceReady()其实就是GenericRequest中的onResourceReady()
@SuppressWarnings("unchecked")
			@Override
			public void onResourceReady(Resource<?> resource) {
				if (resource == null) {
					onException(new Exception("Expected to receive a Resource<R> with an object of " + transcodeClass
							+ " inside, but instead got null."));
					return;
				}
				
				// 获取到一个GlideDrawable对象
				Object received = resource.get();
				if (received == null || !transcodeClass.isAssignableFrom(received.getClass())) {
					releaseResource(resource);
					onException(new Exception("Expected to receive an object of " + transcodeClass
							+ " but instead got " + (received != null ? received.getClass() : "") + "{" + received + "}"
							+ " inside Resource{" + resource + "}."
							+ (received != null ? "" : " "
								+ "To indicate failure return a null Resource object, "
								+ "rather than a Resource object containing null data.")
					));
					return;
				}

				if (!canSetResource()) {
					releaseResource(resource);
					// We can't set the status to complete before asking canSetResource().
					status = Status.COMPLETE;
					return;
				}

				onResourceReady(resource, (R) received);
			}
private void onResourceReady(Resource<?> resource, R result) {
				// We must call isFirstReadyResource before setting status.
				boolean isFirstResource = isFirstReadyResource();
				status = Status.COMPLETE;
				this.resource = resource;

				if (requestListener == null || !requestListener.onResourceReady(result, model, target, loadedFromMemoryCache,
						isFirstResource)) {
					GlideAnimation<R> animation = animationFactory.build(loadedFromMemoryCache, isFirstResource);
					// 这个target实际上就是GenericRequestBuilderinto的into()中glide.buildImageViewTarget()构建出来的target,就是GlideDrawableImageViewTarget
					target.onResourceReady(result, animation);
				}

				notifyLoadSuccess();

				if (Log.isLoggable(TAG, Log.VERBOSE)) {
					logV("Resource ready in " + LogTime.getElapsedMillis(startTime) + " size: "
							+ (resource.getSize() * TO_MEGABYTE) + " fromCache: " + loadedFromMemoryCache);
				}
			}
@Override
			public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) {
				if (!resource.isAnimated()) {
					//TODO: Try to generalize this to other sizes/shapes.
					// This is a dirty hack that tries to make loading square thumbnails and then square full images less costly
					// by forcing both the smaller thumb and the larger version to have exactly the same intrinsic dimensions.
					// If a drawable is replaced in an ImageView by another drawable with different intrinsic dimensions,
					// the ImageView requests a layout. Scrolling rapidly while replacing thumbs with larger images triggers
					// lots of these calls and causes significant amounts of jank.
					float viewRatio = view.getWidth() / (float) view.getHeight();
					float drawableRatio = resource.getIntrinsicWidth() / (float) resource.getIntrinsicHeight();
					if (Math.abs(viewRatio - 1f) <= SQUARE_RATIO_MARGIN
							&& Math.abs(drawableRatio - 1f) <= SQUARE_RATIO_MARGIN) {
						resource = new SquaringDrawable(resource, view.getWidth());
					}
				}
				super.onResourceReady(resource, animation);
				this.resource = resource;
				resource.setLoopCount(maxLoopCount);
				resource.start();
			}
@Override
			public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
				if (glideAnimation == null || !glideAnimation.animate(resource, this)) {
				// 这是一个抽象方法,所以具体实现在子类
					setResource(resource);
				}
			}

@Override
			protected void setResource(GlideDrawable resource) {
				view.setImageDrawable(resource);
			}
至此图片也就终于显示出来了

























猜你喜欢

转载自blog.csdn.net/qq_36447701/article/details/80524713