前言

成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。

tips:文章太长可以先点赞收藏哦,后续再慢慢阅读~

前两篇我们详细地分析了Android的网络底层框架OKHttp和封装框架Retrofit的核心源码,如果对OKHttp或Retrofit内部机制不了解的可以看看Android主流三方库源码分析(一、深入理解OKHttp源码)和Android主流三方库源码分析(二、深入理解Retrofit源码)。本篇,我们将会来深入地分析下目前Android使用最广泛的图片加载框架框架Glide的源码加载流程。

一、基本使用流程

Glide最基本的使用流程就是下面这行代码,其它所有扩展的额外功能都是以其建造者链式调用的基础上增加的。

GlideApp.with(context).load(url).into(iv);

其中的GlideApp是注解处理器自动生成的,要使用GlideApp,必须先配置应用的AppGlideModule模块,里面可以为空配置,也可以根据实际情况添加指定配置。

@GlideModule
public class MyAppGlideModule extends AppGlideModule {@Overridepublic void applyOptions(Context context, GlideBuilder builder) {// 实际使用中根据情况可以添加如下配置<!--builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_RGB_565));--><!--int memoryCacheSizeBytes = 1024 * 1024 * 20;--><!--builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));--><!--int bitmapPoolSizeBytes = 1024 * 1024 * 30;--><!--builder.setBitmapPool(new LruBitmapPool(bitmapPoolSizeBytes));--><!--int diskCacheSizeBytes = 1024 * 1024 * 100;--><!--builder.setDiskCache(new InternalCacheDiskCacheFactory(context, diskCacheSizeBytes));-->}
}

接下来,本文将针对Glide的最新源码版本V4.8.0对Glide加载网络图片的流程进行详细地分析与讲解,力争做到让读者朋友们知其然也知其所以然。

二、GlideApp.with(context)源码详解

首先,用艽野尘梦绘制的这份Glide框架图让我们对Glide的总体框架有一个初步的了解。

image

从GlideApp.with这行代码开始,内部主线执行流程如下。

1、GlideApp#with

return (GlideRequests) Glide.with(context);

2、Glide#with

return getRetriever(context).get(context);return Glide.get(context).getRequestManagerRetriever();// 外部使用了双重检锁的同步方式确保同一时刻只执一次Glide的初始化
checkAndInitializeGlide(context);initializeGlide(context);// 最终执行到Glide的另一个重载方法
initializeGlide(context, new GlideBuilder());@SuppressWarnings("deprecation")private static void initializeGlide(@NonNull Context   context, @NonNull GlideBuilder builder) {Context applicationContext =     context.getApplicationContext();// 1、获取前面应用中带注解的GlideModuleGeneratedAppGlideModule annotationGeneratedModule =     getAnnotationGeneratedGlideModules();// 2、如果GlideModule为空或者可配置manifest里面的标志为true,则获取manifest里面// 配置的GlideModule模块(manifestModules)。List<com.bumptech.glide.module.GlideModule>     manifestModules = Collections.emptyList();if (annotationGeneratedModule == null ||     annotationGeneratedModule.isManifestParsingEnabled(    )) {manifestModules = new   ManifestParser(applicationContext).parse();}...RequestManagerRetriever.RequestManagerFactory     factory =annotationGeneratedModule != null? annotationGeneratedModule.getRequestManag    erFactory() : null;builder.setRequestManagerFactory(factory);for (com.bumptech.glide.module.GlideModule module :     manifestModules) {module.applyOptions(applicationContext, builder);}if (annotationGeneratedModule != null) {annotationGeneratedModule.applyOptions(applicatio  nContext, builder);}// 3、初始化各种配置信息Glide glide = builder.build(applicationContext);// 4、把manifestModules以及annotationGeneratedModule里面的配置信息放到builder// 里面(applyOptions)替换glide默认组件(registerComponents)for (com.bumptech.glide.module.GlideModule module :     manifestModules) {module.registerComponents(applicationContext,   glide, glide.registry);}if (annotationGeneratedModule != null) {annotationGeneratedModule.registerComponents(appl  icationContext, glide, glide.registry);}applicationContext.registerComponentCallbacks(glide    );Glide.glide = glide;
}

3、GlideBuilder#build

@NonNullGlide build(@NonNull Context context) {// 创建请求图片线程池sourceExecutorif (sourceExecutor == null) {sourceExecutor =   GlideExecutor.newSourceExecutor();}// 创建硬盘缓存线程池diskCacheExecutorif (diskCacheExecutor == null) {diskCacheExecutor =   GlideExecutor.newDiskCacheExecutor();}// 创建动画线程池animationExecutorif (animationExecutor == null) {animationExecutor =   GlideExecutor.newAnimationExecutor();}if (memorySizeCalculator == null) {memorySizeCalculator = new   MemorySizeCalculator.Builder(context).build();}if (connectivityMonitorFactory == null) {connectivityMonitorFactory = new   DefaultConnectivityMonitorFactory();}if (bitmapPool == null) {// 依据设备的屏幕密度和尺寸设置各种pool的sizeint size =   memorySizeCalculator.getBitmapPoolSize();if (size > 0) {// 创建图片线程池LruBitmapPool,缓存所有被释放的bitmap// 缓存策略在API大于19时,为SizeConfigStrategy,小于为AttributeStrategy。// 其中SizeConfigStrategy是以bitmap的size和config为key,value为bitmap的HashMapbitmapPool = new LruBitmapPool(size);} else {bitmapPool = new BitmapPoolAdapter();}}// 创建对象数组缓存池LruArrayPool,默认4Mif (arrayPool == null) {arrayPool = new   LruArrayPool(memorySizeCalculator.getArrayPoolSiz  eInBytes());}// 创建LruResourceCache,内存缓存if (memoryCache == null) {memoryCache = new   LruResourceCache(memorySizeCalculator.getMemoryCa  cheSize());}if (diskCacheFactory == null) {diskCacheFactory = new   InternalCacheDiskCacheFactory(context);}// 创建任务和资源管理引擎(线程池,内存缓存和硬盘缓存对象)if (engine == null) {engine =new Engine(memoryCache,diskCacheFactory,diskCacheExecutor,sourceExecutor,GlideExecutor.newUnlimitedSourceExecutor(  ),GlideExecutor.newAnimationExecutor(),isActiveResourceRetentionAllowed);}RequestManagerRetriever requestManagerRetriever =new RequestManagerRetriever(requestManagerFactory);return new Glide(context,engine,memoryCache,bitmapPool,arrayPool,requestManagerRetriever,connectivityMonitorFactory,logLevel,defaultRequestOptions.lock(),defaultTransitionOptions);
}

4、Glide#Glide构造方法

Glide(...) {...// 注册管理任务执行对象的类(Registry)// Registry是一个工厂,而其中所有注册的对象都是一个工厂员工,当任务分发时,// 根据当前任务的性质,分发给相应员工进行处理registry = new Registry();...// 这里大概有60余次的append或register员工组件(解析器、编解码器、工厂类、转码类等等组件)registry.append(ByteBuffer.class, new ByteBufferEncoder()).append(InputStream.class, new StreamEncoder(arrayPool))// 根据给定子类产出对应类型的target(BitmapImageViewTarget / DrawableImageViewTarget)ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory();glideContext =new GlideContext(context,arrayPool,registry,imageViewTargetFactory,defaultRequestOptions,defaultTransitionOptions,engine,logLevel);
}

5、RequestManagerRetriever#get

@NonNull
public RequestManager get(@NonNull 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走相应的get重载方法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());}}// 否则直接将请求与ApplicationLifecycle关联return getApplicationManager(context);
}

这里总结一下,对于当前传入的context是application或当前线程是子线程时,请求的生命周期和ApplicationLifecycle关联,否则,context是FragmentActivity或Fragment时,在当前组件添加一个SupportFragment(SupportRequestManagerFragment),context是Activity时,在当前组件添加一个Fragment(RequestManagerFragment)。

6、GlideApp#with小结

1、初始化各式各样的配置信息(包括缓存,请求线程池,大小,图片格式等等)以及glide对象。
2、将glide请求和application/SupportFragment/Fragment的生命周期绑定在一块。
这里我们再回顾一下with方法的执行流程。

image

三、load(url)源码详解

1、GlideRequest(RequestManager)#load

return (GlideRequest<Drawable>) super.load(string);return asDrawable().load(string);// 1、asDrawable部分
return (GlideRequest<Drawable>) super.asDrawable();return as(Drawable.class);// 最终返回了一个GlideRequest(RequestManager的子类)
return new GlideRequest<>(glide, this, resourceClass, context);// 2、load部分
return (GlideRequest<TranscodeType>) super.load(string);return loadGeneric(string);@NonNull
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {// model则为设置的urlthis.model = model;// 记录url已设置isModelSet = true;return this;
}

可以看到,load这部分的源码很简单,就是给GlideRequest(RequestManager)设置了要请求的mode(url),并记录了url已设置的状态。

这里,我们再看看load方法的执行流程。

image

四、into(iv)源码详解

前方预警,真正复杂的地方开始了。

1、RequestBuilder.into

 @NonNull
public ViewTarget<ImageView, TranscodeType>   into(@NonNull ImageView view) {Util.assertMainThread();Preconditions.checkNotNull(view);RequestOptions requestOptions =     this.requestOptions;if (!requestOptions.isTransformationSet()&& requestOptions.isTransformationAllowed()&& view.getScaleType() != null) {// Clone in this method so that if we use this   RequestBuilder to load into a View and then// into a different target, we don't retain the   transformation applied based on the previous// View's scale type.switch (view.getScaleType()) {// 这个RequestOptions里保存了要设置的scaleType,Glide自身封装了CenterCrop、CenterInside、// FitCenter、CenterInside四种规格。case CENTER_CROP:requestOptions =   requestOptions.clone().optionalCenterCrop();break;case CENTER_INSIDE:requestOptions =   requestOptions.clone().optionalCenterInside()  ;break;case FIT_CENTER:case FIT_START:case FIT_END:requestOptions =   requestOptions.clone().optionalFitCenter();break;case FIT_XY:requestOptions =   requestOptions.clone().optionalCenterInside()  ;break;case CENTER:case MATRIX:default:// Do nothing.}}// 注意,这个transcodeClass是指的drawable或bitmapreturn into(glideContext.buildImageViewTarget(view,     transcodeClass),/*targetListener=*/ null,requestOptions);
}

2、GlideContext#buildImageViewTarget

return imageViewTargetFactory.buildTarget(imageView, transcodeClass);

3、ImageViewTargetFactory#buildTarget

@NonNull
@SuppressWarnings("unchecked")
public <Z> ViewTarget<ImageView, Z>   buildTarget(@NonNull ImageView view,@NonNull Class<Z> clazz) {// 返回展示Bimtap/Drawable资源的目标对象if (Bitmap.class.equals(clazz)) {return (ViewTarget<ImageView, Z>) new   BitmapImageViewTarget(view);} else if (Drawable.class.isAssignableFrom(clazz))     {return (ViewTarget<ImageView, Z>) new   DrawableImageViewTarget(view);} else {throw new IllegalArgumentException("Unhandled class: " + clazz + ", try   .as*(Class).transcode(ResourceTranscoder)");}
}

可以看到,Glide内部只维护了两种target,一种是BitmapImageViewTarget,另一种则是DrawableImageViewTarget,接下来继续深入。

4、RequestBuilder#into

private <Y extends Target<TranscodeType>> Y into(@NonNull Y target,@Nullable RequestListener<TranscodeType>   targetListener,@NonNull RequestOptions options) {Util.assertMainThread();Preconditions.checkNotNull(target);if (!isModelSet) {throw new IllegalArgumentException("You must call   #load() before calling #into()");}options = options.autoClone();// 分析1.建立请求Request request = buildRequest(target,     targetListener, options);Request previous = target.getRequest();if (request.isEquivalentTo(previous)&& !isSkipMemoryCacheWithCompletePreviousReques    t(options, previous)) {request.recycle();// If the request is completed, beginning again   will ensure the result is re-delivered,// triggering RequestListeners and Targets. If   the request is failed, beginning again will// restart the request, giving it another chance   to complete. If the request is already// running, we can let it continue running   without interruption.if (!Preconditions.checkNotNull(previous).isRunni  ng()) {// Use the previous request rather than the new     one to allow for optimizations like skipping// setting placeholders, tracking and     un-tracking Targets, and obtaining View     dimensions// that are done in the individual Request.previous.begin();}return target;}requestManager.clear(target);target.setRequest(request);// 分析2.真正追踪请求的地方requestManager.track(target, request);return target;
}// 分析1
private Request buildRequest(Target<TranscodeType> target,@Nullable RequestListener<TranscodeType>   targetListener,RequestOptions requestOptions) {return buildRequestRecursive(target,targetListener,/*parentCoordinator=*/ null,transitionOptions,requestOptions.getPriority(),requestOptions.getOverrideWidth(),requestOptions.getOverrideHeight(),requestOptions);
}// 分析1
private Request buildRequestRecursive(Target<TranscodeType> target,@Nullable RequestListener<TranscodeType>   targetListener,@Nullable RequestCoordinator parentCoordinator,TransitionOptions<?, ? super TranscodeType>   transitionOptions,Priority priority,int overrideWidth,int overrideHeight,RequestOptions requestOptions) {// Build the ErrorRequestCoordinator first if     necessary so we can update parentCoordinator.ErrorRequestCoordinator errorRequestCoordinator =     null;if (errorBuilder != null) {// 创建errorRequestCoordinator(异常处理对象)errorRequestCoordinator = new   ErrorRequestCoordinator(parentCoordinator);parentCoordinator = errorRequestCoordinator;}// 递归建立缩略图请求Request mainRequest =buildThumbnailRequestRecursive(target,targetListener,parentCoordinator,transitionOptions,priority,overrideWidth,overrideHeight,requestOptions);if (errorRequestCoordinator == null) {return mainRequest;}...Request errorRequest =     errorBuilder.buildRequestRecursive(target,targetListener,errorRequestCoordinator,errorBuilder.transitionOptions,errorBuilder.requestOptions.getPriority(),errorOverrideWidth,errorOverrideHeight,errorBuilder.requestOptions);errorRequestCoordinator.setRequests(mainRequest,     errorRequest);return errorRequestCoordinator;
}// 分析1
private Request buildThumbnailRequestRecursive(Target<TranscodeType> target,RequestListener<TranscodeType> targetListener,@Nullable RequestCoordinator parentCoordinator,TransitionOptions<?, ? super TranscodeType> transitionOptions,Priority priority,int overrideWidth,int overrideHeight,RequestOptions requestOptions) {if (thumbnailBuilder != null) {// Recursive case: contains a potentially recursive thumbnail request builder....ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);// 获取一个正常请求对象Request fullRequest =obtainRequest(target,targetListener,requestOptions,coordinator,transitionOptions,priority,overrideWidth,overrideHeight);isThumbnailBuilt = true;// Recursively generate thumbnail requests.// 使用递归的方式建立一个缩略图请求对象Request thumbRequest =thumbnailBuilder.buildRequestRecursive(target,targetListener,coordinator,thumbTransitionOptions,thumbPriority,thumbOverrideWidth,thumbOverrideHeight,thumbnailBuilder.requestOptions);isThumbnailBuilt = false;// coordinator(ThumbnailRequestCoordinator)是作为两者的协调者,// 能够同时加载缩略图和正常的图的请求coordinator.setRequests(fullRequest, thumbRequest);return coordinator;} else if (thumbSizeMultiplier != null) {// Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse.// 当设置了缩略的比例thumbSizeMultiplier(0 ~  1)时,// 不需要递归建立缩略图请求ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator);Request fullRequest =obtainRequest(target,targetListener,requestOptions,coordinator,transitionOptions,priority,overrideWidth,overrideHeight);RequestOptions thumbnailOptions = requestOptions.clone().sizeMultiplier(thumbSizeMultiplier);Request thumbnailRequest =obtainRequest(target,targetListener,thumbnailOptions,coordinator,transitionOptions,getThumbnailPriority(priority),overrideWidth,overrideHeight);coordinator.setRequests(fullRequest, thumbnailRequest);return coordinator;} else {// Base case: no thumbnail.// 没有缩略图请求时,直接获取一个正常图请求return obtainRequest(target,targetListener,requestOptions,parentCoordinator,transitionOptions,priority,overrideWidth,overrideHeight);}
}private Request obtainRequest(Target<TranscodeType> target,RequestListener<TranscodeType> targetListener,RequestOptions requestOptions,RequestCoordinator requestCoordinator,TransitionOptions<?, ? super TranscodeType>   transitionOptions,Priority priority,int overrideWidth,int overrideHeight) {// 最终实际返回的是一个SingleRequest对象(将制定的资源加载进对应的Targetreturn SingleRequest.obtain(context,glideContext,model,transcodeClass,requestOptions,overrideWidth,overrideHeight,priority,target,targetListener,requestListeners,requestCoordinator,glideContext.getEngine(),transitionOptions.getTransitionFactory());
}

从上源码分析可知,我们在分析1处的buildRequest()方法里建立了请求,且最多可同时进行缩略图和正常图的请求,最后,调用了requestManager.track(target, request)方法,接着看看track里面做了什么。

5、RequestManager#track

// 分析2
void track(@NonNull Target<?> target, @NonNull Request request) {// 加入一个target目标集合(Set)targetTracker.track(target);requestTracker.runRequest(request);
}

6、RequestTracker#runRequest

/**
* Starts tracking the given request.
*/
// 分析2
public void runRequest(@NonNull Request request) {requests.add(request);if (!isPaused) {// 如果不是暂停状态则开始请求request.begin();} else {request.clear();if (Log.isLoggable(TAG, Log.VERBOSE)) {Log.v(TAG, "Paused, delaying request");}// 否则清空请求,加入延迟请求队列(为了对这些请求维持一个强引用,使用了ArrayList实现)pendingRequests.add(request);}
}

7、SingleRequest#begin

// 分析2
@Override
public void begin() {...if (model == null) {...// model(url)为空,回调加载失败onLoadFailed(new GlideException("Received null   model"), logLevel);return;}if (status == Status.RUNNING) {throw new IllegalArgumentException("Cannot   restart a running request");}if (status == Status.COMPLETE) {onResourceReady(resource,   DataSource.MEMORY_CACHE);return;}status = Status.WAITING_FOR_SIZE;if (Util.isValidDimensions(overrideWidth, overrideHeight)) {// 当使用override() API为图片指定了一个固定的宽高时直接执行onSizeReady,// 最终的核心处理位于onSizeReadyonSizeReady(overrideWidth, overrideHeight);} else {// 根据imageView的宽高算出图片的宽高,最终也会走到onSizeReadytarget.getSize(this);}if ((status == Status.RUNNING || status ==     Status.WAITING_FOR_SIZE)&& canNotifyStatusChanged()) {// 预先加载设置的缩略图target.onLoadStarted(getPlaceholderDrawable());}if (IS_VERBOSE_LOGGABLE) {logV("finished run method in " +   LogTime.getElapsedMillis(startTime));}
}

从requestManager.track(target, request)开始,最终会执行到SingleRequest#begin()方法的onSizeReady,可以猜到(因为后面只做了预加载缩略图的处理),真正的请求就是从这里开始的,咱们进去一探究竟~

8、SingleRequest#onSizeReady

// 分析2
@Override
public void onSizeReady(int width, int height) {stateVerifier.throwIfRecycled();...status = Status.RUNNING;float sizeMultiplier =     requestOptions.getSizeMultiplier();this.width = maybeApplySizeMultiplier(width,     sizeMultiplier);this.height = maybeApplySizeMultiplier(height,     sizeMultiplier);...// 根据给定的配置进行加载,engine是一个负责加载、管理活跃和缓存资源的引擎类loadStatus = engine.load(glideContext,model,requestOptions.getSignature(),this.width,this.height,requestOptions.getResourceClass(),transcodeClass,priority,requestOptions.getDiskCacheStrategy(),requestOptions.getTransformations(),requestOptions.isTransformationRequired(),requestOptions.isScaleOnlyOrNoTransform(),requestOptions.getOptions(),requestOptions.isMemoryCacheable(),requestOptions.getUseUnlimitedSourceGeneratorsP    ool(),requestOptions.getUseAnimationPool(),requestOptions.getOnlyRetrieveFromCache(),this);...
}

终于看到Engine类了,感觉距离成功不远了,继续~

9、Engine#load

public <R> LoadStatus load(GlideContext glideContext,Object model,Key signature,int width,int height,Class<?> resourceClass,Class<R> transcodeClass,Priority priority,DiskCacheStrategy diskCacheStrategy,Map<Class<?>, Transformation<?>> transformations,boolean isTransformationRequired,boolean isScaleOnlyOrNoTransform,Options options,boolean isMemoryCacheable,boolean useUnlimitedSourceExecutorPool,boolean useAnimationPool,boolean onlyRetrieveFromCache,ResourceCallback cb) {...// 先从弱引用中查找,如果有的话回调onResourceReady并直接返回EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);if (active != null) {cb.onResourceReady(active,   DataSource.MEMORY_CACHE);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Loaded resource from active     resources", startTime, key);}return null;}// 没有再从内存中查找,有的话会取出并放到ActiveResources(内部维护的弱引用缓存map)里面EngineResource<?> cached = loadFromCache(key,     isMemoryCacheable);if (cached != null) {cb.onResourceReady(cached,   DataSource.MEMORY_CACHE);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Loaded resource from cache",     startTime, key);}return null;}EngineJob<?> current = jobs.get(key,     onlyRetrieveFromCache);if (current != null) {current.addCallback(cb);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Added to existing load",     startTime, key);}return new LoadStatus(cb, current);}// 如果内存中没有,则创建engineJob(decodejob的回调类,管理下载过程以及状态)EngineJob<R> engineJob =engineJobFactory.build(key,isMemoryCacheable,useUnlimitedSourceExecutorPool,useAnimationPool,onlyRetrieveFromCache);// 创建解析工作对象DecodeJob<R> decodeJob =decodeJobFactory.build(glideContext,model,key,signature,width,height,resourceClass,transcodeClass,priority,diskCacheStrategy,transformations,isTransformationRequired,isScaleOnlyOrNoTransform,onlyRetrieveFromCache,options,engineJob);// 放在Jobs内部维护的HashMap中jobs.put(key, engineJob);// 关注点8 后面分析会用到// 注册ResourceCallback接口engineJob.addCallback(cb);// 内部开启线程去请求engineJob.start(decodeJob);if (VERBOSE_IS_LOGGABLE) {logWithTimeAndKey("Started new load", startTime,   key);}return new LoadStatus(cb, engineJob);
}public void start(DecodeJob<R> decodeJob) {this.decodeJob = decodeJob;// willDecodeFromCache方法内部根据不同的阶段stage,如果是RESOURCE_CACHE/DATA_CACHE则返回true,使用diskCacheExecutor,否则调用getActiveSourceExecutor,内部会根据相应的条件返回sourceUnlimitedExecutor/animationExecutor/sourceExecutorGlideExecutor executor =   decodeJob.willDecodeFromCache()? diskCacheExecutor: getActiveSourceExecutor();executor.execute(decodeJob);
}

可以看到,最终Engine(引擎)类内部会执行到自身的start方法,它会根据不同的配置采用不同的线程池使用diskCacheExecutor/sourceUnlimitedExecutor/animationExecutor/sourceExecutor来执行最终的解码任务decodeJob。

10、DecodeJob#run

runWrapped();private void runWrapped() {switch (runReason) {case INITIALIZE:stage = getNextStage(Stage.INITIALIZE);// 关注点1currentGenerator = getNextGenerator();// 关注点2 内部会调用相应Generator的startNext()runGenerators();break;case SWITCH_TO_SOURCE_SERVICE:runGenerators();break;case DECODE_DATA:// 关注点3 将获取的数据解码成对应的资源decodeFromRetrievedData();break;default:throw new IllegalStateException("Unrecognized     run reason: " + runReason);}
}// 关注点1,完整情况下,会异步依次生成这里的ResourceCacheGenerator、DataCacheGenerator和SourceGenerator对象,并在之后执行其中的startNext()
private DataFetcherGenerator getNextGenerator() {switch (stage) {case RESOURCE_CACHE:return new ResourceCacheGenerator(decodeHelper, this);case DATA_CACHE:return new DataCacheGenerator(decodeHelper, this);case SOURCE:return new SourceGenerator(decodeHelper, this);case FINISHED:return null;default:throw new IllegalStateException("Unrecognized     stage: " + stage);}
}

11、SourceGenerator#startNext

// 关注点2
@Override
public boolean startNext() {// dataToCache数据不为空的话缓存到硬盘(第一执行该方法是不会调用的)if (dataToCache != null) {Object data = dataToCache;dataToCache = null;cacheData(data);}if (sourceCacheGenerator != null &&     sourceCacheGenerator.startNext()) {return true;}sourceCacheGenerator = null;loadData = null;boolean started = false;while (!started && hasNextModelLoader()) {// 关注点4 getLoadData()方法内部会在modelLoaders里面找到ModelLoder对象// (每个Generator对应一个ModelLoader),// 并使用modelLoader.buildLoadData方法返回一个loadData列表loadData =   helper.getLoadData().get(loadDataListIndex++);if (loadData != null&& (helper.getDiskCacheStrategy().isDataCache  able(loadData.fetcher.getDataSource())|| helper.hasLoadPath(loadData.fetcher.getDat  aClass()))) {started = true;// 关注点6 通过loadData对象的fetcher对象(有关注点3的分析可知其实现类为HttpUrlFetcher)的// loadData方法来获取图片数据loadData.fetcher.loadData(helper.getPriority(),     this);}}return started;
}

12、DecodeHelper#getLoadData

List<LoadData<?>> getLoadData() {if (!isLoadDataSet) {isLoadDataSet = true;loadData.clear();List<ModelLoader<Object, ?>> modelLoaders =   glideContext.getRegistry().getModelLoaders(model)  ;//noinspection ForLoopReplaceableByForEach to   improve perffor (int i = 0, size = modelLoaders.size(); i <   size; i++) {ModelLoader<Object, ?> modelLoader =     modelLoaders.get(i);// 注意:这里最终是通过HttpGlideUrlLoader的buildLoadData获取到实际的loadData对象LoadData<?> current =modelLoader.buildLoadData(model, width,     height, options);if (current != null) {loadData.add(current);}}}return loadData;
}

13、HttpGlideUrlLoader#buildLoadData

@Override
public LoadData<InputStream> buildLoadData(@NonNull   GlideUrl model, int width, int height,@NonNull Options options) {// GlideUrls memoize parsed URLs so caching them     saves a few object instantiations and time// spent parsing urls.GlideUrl url = model;if (modelCache != null) {url = modelCache.get(model, 0, 0);if (url == null) {// 关注点5modelCache.put(model, 0, 0, model);url = model;}}int timeout = options.get(TIMEOUT);// 注意,这里创建了一个DataFetcher的实现类HttpUrlFetcherreturn new LoadData<>(url, new HttpUrlFetcher(url,     timeout));
}// 关注点5
public void put(A model, int width, int height, B value) {ModelKey<A> key = ModelKey.get(model, width,     height);// 最终是通过LruCache来缓存对应的值,key是一个ModelKey对象(由model、width、height三个属性组成)cache.put(key, value);
}

从这里的分析,我们明白了HttpUrlFetcher实际上就是最终的请求执行者,而且,我们知道了Glide会使用LruCache来对解析后的url来进行缓存,以便后续可以省去解析url的时间。

14、HttpUrlFetcher#loadData

@Override
public void loadData(@NonNull Priority priority,@NonNull DataCallback<? super InputStream>   callback) {long startTime = LogTime.getLogTime();try {// 关注点6// loadDataWithRedirects内部是通过HttpURLConnection网络请求数据InputStream result =   loadDataWithRedirects(glideUrl.toURL(), 0, null,   glideUrl.getHeaders());// 请求成功回调onDataReady()callback.onDataReady(result);} catch (IOException e) {if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "Failed to load data for url", e);}callback.onLoadFailed(e);} finally {if (Log.isLoggable(TAG, Log.VERBOSE)) {Log.v(TAG, "Finished http url fetcher fetch in     " + LogTime.getElapsedMillis(startTime));}}
}private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,Map<String, String> headers) throws IOException {...urlConnection.connect();// Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.stream = urlConnection.getInputStream();if (isCancelled) {return null;}final int statusCode = urlConnection.getResponseCode();// 只要是2xx形式的状态码则判断为成功if (isHttpOk(statusCode)) {// 从urlConnection中获取资源流return getStreamForSuccessfulRequest(urlConnection);} else if (isHttpRedirect(statusCode)) {...// 重定向请求return loadDataWithRedirects(redirectUrl, redirects + 1, url,   headers);} else if (statusCode == INVALID_STATUS_CODE) {throw new HttpException(statusCode);} else {throw new HttpException(urlConnection.getResponseMessage(),   statusCode);}
}private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)throws IOException {if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {int contentLength = urlConnection.getContentLength();stream = ContentLengthInputStream.obtain(urlConnection.getInputStr  eam(), contentLength);} else {if (Log.isLoggable(TAG, Log.DEBUG)) {Log.d(TAG, "Got non empty content encoding: " +     urlConnection.getContentEncoding());}stream = urlConnection.getInputStream();}return stream;
}

在HttpUrlFetcher#loadData方法的loadDataWithRedirects里面,Glide通过原生的HttpURLConnection进行请求后,并调用getStreamForSuccessfulRequest()方法获取到了最终的图片流。

15、DecodeJob#run

在我们通过HtttpUrlFetcher的loadData()方法请求得到对应的流之后,我们还必须对流进行处理得到最终我们想要的资源。这里我们回到第10步DecodeJob#run方法的关注点3处,这行代码将会对流进行解码。

decodeFromRetrievedData();

接下来,继续看看他内部的处理。

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);} catch (GlideException e) {e.setLoggingDetails(currentAttemptingKey, currentDataSource);throwables.add(e);}if (resource != null) {// 关注点8 // 编码和发布最终得到的Resource<Bitmap>对象notifyEncodeAndRelease(resource, currentDataSource);} else {runGenerators();}
}private <Data> Resource<R> decodeFromData(DataFetcher<?> fetcher, Data data,DataSource dataSource) throws GlideException {try {if (data == null) {return null;}long startTime = LogTime.getLogTime();// 核心代码// 进一步包装了解码方法Resource<R> result = decodeFromFetcher(data, dataSource);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Decoded result " + result, startTime);}return result;} finally {fetcher.cleanup();}
}@SuppressWarnings("unchecked")
private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)throws GlideException {LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());// 核心代码// 将解码任务分发给LoadPathreturn runLoadPath(data, dataSource, path);
}private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource,LoadPath<Data, ResourceType, R> path) throws GlideException {Options options = getOptionsWithHardwareConfig(dataSource);// 将数据进一步包装DataRewinder<Data> rewinder =     glideContext.getRegistry().getRewinder(data);try {// ResourceType in DecodeCallback below is required for   compilation to work with gradle.// 核心代码// 将解码任务分发给LoadPathreturn path.load(rewinder, options, width, height, new   DecodeCallback<ResourceType>(dataSource));} finally {rewinder.cleanup();}
}

16、LoadPath#load

public Resource<Transcode> load(DataRewinder<Data> rewinder, @NonNull Options options, int width,int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException {
List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire());
try {// 核心代码return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
} finally {listPool.release(throwables);
}

}

private Resource<Transcode> loadWithExceptionList(DataRewinder<Data> rewinder,@NonNull Options options,int width, int height, DecodePath.DecodeCallback<ResourceType>   decodeCallback,List<Throwable> exceptions) throws GlideException {Resource<Transcode> result = null;//noinspection ForLoopReplaceableByForEach to improve perffor (int i = 0, size = decodePaths.size(); i < size; i++) {DecodePath<Data, ResourceType, Transcode> path =   decodePaths.get(i);try {// 核心代码// 将解码任务又进一步分发给DecodePath的decode方法去解码result = path.decode(rewinder, width, height, options,     decodeCallback);} catch (GlideException e) {exceptions.add(e);}if (result != null) {break;}}if (result == null) {throw new GlideException(failureMessage, new   ArrayList<>(exceptions));}return result;
}

17、DecodePath#decode

public Resource<Transcode> decode(DataRewinder<DataType> rewinder,     int width, int height,@NonNull Options options, DecodeCallback<ResourceType> callback)   throws GlideException {// 核心代码// 继续调用DecodePath的decodeResource方法去解析出数据Resource<ResourceType> decoded = decodeResource(rewinder, width,     height, options);Resource<ResourceType> transformed =     callback.onResourceDecoded(decoded);return transcoder.transcode(transformed, options);
}@NonNull
private Resource<ResourceType> decodeResource(DataRewinder<DataType>   rewinder, int width,int height, @NonNull Options options) throws GlideException {List<Throwable> exceptions =     Preconditions.checkNotNull(listPool.acquire());try {// 核心代码return decodeResourceWithList(rewinder, width, height, options,   exceptions);} finally {listPool.release(exceptions);}
}@NonNull
private Resource<ResourceType>   decodeResourceWithList(DataRewinder<DataType> rewinder, int width,int height, @NonNull Options options, List<Throwable> exceptions)   throws GlideException {Resource<ResourceType> result = null;//noinspection ForLoopReplaceableByForEach to improve perffor (int i = 0, size = decoders.size(); i < size; i++) {ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);try {DataType data = rewinder.rewindAndGet();if (decoder.handles(data, options)) {// 获取包装的数据data = rewinder.rewindAndGet();// 核心代码 // 根据DataType和ResourceType的类型分发给不同的解码器Decoderresult = decoder.decode(data, width, height, options);}} catch (IOException | RuntimeException | OutOfMemoryError e) {if (Log.isLoggable(TAG, Log.VERBOSE)) {Log.v(TAG, "Failed to decode data for " + decoder, e);}exceptions.add(e);}if (result != null) {break;}}if (result == null) {throw new GlideException(failureMessage, new   ArrayList<>(exceptions));}return result;
}

可以看到,经过一连串的嵌套调用,最终执行到了decoder.decode()这行代码,decode是一个ResourceDecoder<DataType, ResourceType>接口(资源解码器),根据不同的DataType和ResourceType它会有不同的实现类,这里的实现类是ByteBufferBitmapDecoder,接下来让我们来看看这个解码器内部的解码流程。

18、ByteBufferBitmapDecoder#decode

/*** Decodes {@link android.graphics.Bitmap Bitmaps} from {@link    java.nio.ByteBuffer ByteBuffers}.*/
public class ByteBufferBitmapDecoder implements     ResourceDecoder<ByteBuffer, Bitmap> {...@Overridepublic Resource<Bitmap> decode(@NonNull ByteBuffer source, int width,   int height,@NonNull Options options)throws IOException {InputStream is = ByteBufferUtil.toStream(source);// 核心代码return downsampler.decode(is, width, height, options);}
}

可以看到,最终是使用了一个downsampler,它是一个压缩器,主要是对流进行解码,压缩,圆角等处理。

19、DownSampler#decode

public Resource<Bitmap> decode(InputStream is, int outWidth, int outHeight,Options options) throws IOException {return decode(is, outWidth, outHeight, options, EMPTY_CALLBACKS);
}@SuppressWarnings({"resource", "deprecation"})
public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,Options options, DecodeCallbacks callbacks) throws IOException {Preconditions.checkArgument(is.markSupported(), "You must provide an     InputStream that supports"+ " mark()");...try {// 核心代码Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,downsampleStrategy, decodeFormat, isHardwareConfigAllowed,   requestedWidth,requestedHeight, fixBitmapToRequestedDimensions, callbacks);// 关注点7   // 解码得到Bitmap对象后,包装成BitmapResource对象返回,// 通过内部的get方法得到Resource<Bitmap>对象return BitmapResource.obtain(result, bitmapPool);} finally {releaseOptions(bitmapFactoryOptions);byteArrayPool.put(bytesForOptions);}
}private Bitmap decodeFromWrappedStreams(InputStream is,BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,int requestedHeight, boolean fixBitmapToRequestedDimensions,DecodeCallbacks callbacks) throws IOException {// 省去计算压缩比例等一系列非核心逻辑...// 核心代码Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);callbacks.onDecodeComplete(bitmapPool, downsampled);...// Bimtap旋转处理...return rotated;
}private static Bitmap decodeStream(InputStream is,     BitmapFactory.Options options,DecodeCallbacks callbacks, BitmapPool bitmapPool) throws   IOException {...TransformationUtils.getBitmapDrawableLock().lock();try {// 核心代码result = BitmapFactory.decodeStream(is, null, options);} catch (IllegalArgumentException e) {...} finally {TransformationUtils.getBitmapDrawableLock().unlock();}if (options.inJustDecodeBounds) {is.reset();}return result;
}

从以上源码流程我们知道,最后是在DownSampler的decodeStream()方法中使用了BitmapFactory.decodeStream()来得到Bitmap对象。然后,我们来分析下图片时如何显示的,我们回到步骤19的DownSampler#decode方法,看到关注点7,这里是将Bitmap包装成BitmapResource对象返回,通过内部的get方法可以得到Resource对象,再回到步骤15的DecodeJob#run方法,这是使用了notifyEncodeAndRelease()方法对Resource对象进行了发布。

20、DecodeJob#notifyEncodeAndRelease

private void notifyEncodeAndRelease(Resource<R> resource, DataSource     dataSource) {...notifyComplete(result, dataSource);...}private void notifyComplete(Resource<R> resource, DataSource     dataSource) {setNotifiedOrThrow();callback.onResourceReady(resource, dataSource);
}

从以上EngineJob的源码可知,它实现了DecodeJob.CallBack这个接口。

class EngineJob<R> implements DecodeJob.Callback<R>,Poolable {...
}

21、EngineJob#onResourceReady

@Override
public void onResourceReady(Resource<R> resource, DataSource   dataSource) {this.resource = resource;this.dataSource = dataSource;MAIN_THREAD_HANDLER.obtainMessage(MSG_COMPLETE, this).sendToTarget();
}private static class MainThreadCallback implements Handler.Callback{...@Overridepublic boolean handleMessage(Message message) {EngineJob<?> job = (EngineJob<?>) message.obj;switch (message.what) {case MSG_COMPLETE:// 核心代码job.handleResultOnMainThread();break;...}return true;}
}

从以上源码可知,通过主线程Handler对象进行切换线程,然后在主线程调用了handleResultOnMainThread这个方法。

@Synthetic
void handleResultOnMainThread() {...//noinspection ForLoopReplaceableByForEach to improve perffor (int i = 0, size = cbs.size(); i < size; i++) {ResourceCallback cb = cbs.get(i);if (!isInIgnoredCallbacks(cb)) {engineResource.acquire();cb.onResourceReady(engineResource, dataSource);}}...
}

这里又通过一个循环调用了所有ResourceCallback的方法,让我们回到步骤9处Engine#load方法的关注点8这行代码,这里对ResourceCallback进行了注册,在步骤8出SingleRequest#onSizeReady方法里的engine.load中,我们看到最后一个参数,传入的是this,可以明白,engineJob.addCallback(cb)这里的cb的实现类就是SingleRequest。接下来,让我们看看SingleRequest的onResourceReady方法。

22、SingleRequest#onResourceReady

/*** A callback method that should never be invoked directly.*/
@SuppressWarnings("unchecked")
@Override
public void onResourceReady(Resource<?> resource, DataSource   dataSource) {...// 从Resource<Bitmap>中得到Bitmap对象Object received = resource.get();...onResourceReady((Resource<R>) resource, (R) received, dataSource);
}private void onResourceReady(Resource<R> resource, R resultDataSource dataSource) {...try {...if (!anyListenerHandledUpdatingTarget) {Transition<? super R> animation =animationFactory.build(dataSource, isFirstResource);// 核心代码target.onResourceReady(result, animation);}} finally {isCallingCallbacks = false;}notifyLoadSuccess();
}

在SingleRequest#onResourceReady方法中又调用了target.onResourceReady(result, animation)方法,这里的target其实就是我们在into方法中建立的那个BitmapImageViewTarget,看到ImageViewTarget类,我们并没有发现onResourceReady方法,但是我们从它的子类ImageViewTarget中发现了onResourceReady方法,从这里我们继续往下看。

23、ImageViewTarget#onResourceReady

public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z>
implements Transition.ViewAdapter {...@Overridepublic void onResourceReady(@NonNull Z resource, @Nullable       Transition<? super Z> transition) {if (transition == null || !transition.transition(resource, this))   {// 核心代码setResourceInternal(resource);} else {maybeUpdateAnimatable(resource);}}...private void setResourceInternal(@Nullable Z resource) {// Order matters here. Set the resource first to make sure that the         Drawable has a valid and// non-null Callback before starting it.// 核心代码setResource(resource);maybeUpdateAnimatable(resource);}// 核心代码protected abstract void setResource(@Nullable Z resource);
}

这里我们在回到BitmapImageViewTarget的setResource方法中,我们终于看到Bitmap被设置到了当前的imageView上了。

public class BitmapImageViewTarget extends ImageViewTarget<Bitmap> {...@Overrideprotected void setResource(Bitmap resource) {view.setImageBitmap(resource);}
}

到这里,我们的分析就结束了,从以上的分析可知,Glide将大部分的逻辑处理都放在了最后一个into方法中,里面经过了20多个分析步骤才将请求图片流、解码出图片,到最终设置到对应的imageView上。

最后,这里给出一份我花费了数个小时绘制的完整Glide加载流程图,非常珍贵,大家可以仔仔细细再把Glide的主体流程在梳理一遍。

image

五、总结

到此,Glide整个的加载流程分析就结束了,可以看到,Glide最核心的逻辑都聚集在into()方法中,它里面的设计精巧而复杂,这部分的源码分析非常耗时,但是,如果你真真正正地去一步步去深入其中,你也许在Android进阶之路上将会有顿悟的感觉。目前,Android主流三方库源码分析系列已经对网络库(OkHttp、Retrofit)和图片加载库(Glide)进行了详细的源码分析,接下来,将会对数据库框架GreenDao的核心源码进行深入的分析,敬请期待~

参考链接:

1、Glide V4.8.0源码

2、从源码的角度理解Glide的执行流程

3、glide源码分析

赞赏

如果这个库对您有很大帮助,您愿意支持这个项目的进一步开发和这个项目的持续维护。你可以扫描下面的二维码,让我喝一杯咖啡或啤酒。非常感谢您的捐赠。谢谢!


Contanct Me

● 微信:

欢迎关注我的微信:bcce5360

● 微信群:

微信群如果不能扫码加入,麻烦大家想进微信群的朋友们,加我微信拉你进群。

● QQ群:

2千人QQ群,Awesome-Android学习交流群,QQ群号:959936182, 欢迎大家加入~

About me

很感谢您阅读这篇文章,希望您能将它分享给您的朋友或技术群,这对我意义重大。

希望我们能成为朋友,在 Github、掘金上一起分享知识。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. workBook导出excel

    相关依赖<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.15</version> </dependency> <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxm…...

    2024/3/29 13:01:03
  2. 五种方法加密python代码

    Python越来越热门了,2019年3月TIOBE编程语言排行榜上,Python更是罕见的击败了“霸榜三巨头”之一的C++,挤进前三。 Python优点很多,比如简单易学,代码量少,能做的事很多等等,和其他语言一样,Pyhton也有一些不可掩盖的缺点,版本不兼容,运行效率不高等等。 其中一个缺点…...

    2024/5/3 3:36:51
  3. Linux系统调优(系统优化)

    目录关闭selinux设置linux服务器时间同步系统最小化原则利用sudo控制用户对系统命令的使用权限设定运行级别为3精简开机系统自启动关闭防火墙更改ssh服务器端远程登录的配置linux中文显示设置 关闭selinux 设置linux服务器时间同步 系统最小化原则 利用sudo控制用户对系统命令的…...

    2024/5/3 1:28:42
  4. 猎头职场:即将步入中年你焦虑吗?

    猎头职场:即将步入中年你焦虑吗?对于一些中年职场人活着正准备步入中年的职场总会有焦虑的时段,想辞职,但怕自己出去能力不够,找不到更好地工作;努力工作,又感觉竞争压力大,牛人很多,升不上去,就像悬在半空中的瓶子,不知道要晃到哪里。 搞懂职场底层逻辑 底层逻辑,就…...

    2024/3/29 13:01:00
  5. freeradius安装与测试

    freeradius官网 freeradius的github 安装办法 非源码安装 例如ubuntu: For Bionic Beaver (18.04), add to your apt source list (/etc/apt/sources.list): deb http://packages.networkradius.com/releases/ubuntu-bionic bionic mainsudo apt-key adv --keyserver keys.gnu…...

    2024/3/29 13:00:59
  6. 家校 H5 部署说明

    家校 H5 部署说明 适用人群 开发、测试、运维 开发须知 开发中平时我们采用 npm run dev 进行启动,在转测前夕需使用npm run build && npm run pm2:prod 进行模仿测试、现网的项目部署方式,防止项目跑不起来造成转测失败。项目介绍 wave 是一个基于 Nuxt 框架的公众号…...

    2024/4/11 3:27:05
  7. 海龟交易法则和右侧交易

    先讲讲什么是右侧交易,我拿鼠标画个图讲解,不要嫌糙,搞懂就行。这图看明白了吧,左就是最低值出现前提前交易,右就是最低值出现后滞后交易。一开始我想当然的觉得左侧交易是王道,后来我认为右侧交易才更有把握赚到钱,但是随着交易经验的增加,如今的我觉得每一个人的性格…...

    2024/4/13 4:39:57
  8. MPLS第一次学习

    面向连接的技术:MPLS.多协议标签交换(英语:Multi-Protocol Label Switching,缩写为MPLS)是一种在开放的通信网上利用标签引导数据高速、高效传输的新技术。多协议的含义是指MPLS不但可以支持多种网络层层面上的协议,还可以兼容第二层的多种数据链路层技术。 (如果想了解大…...

    2024/5/3 2:10:25
  9. 互联网公司常用架构模式梳理

    一、管理和监控1.1、大使模式:创建代表消费者服务或应用程序发送网络请求的帮助服务进程外的代理服务(很多框架层面的事情可以以软件框架的形式寄宿在进程内,也可以以独立的代理形式做一个网络中间件)。这里的大使模式意思就是这么一个网络代理进程,用于和远端的服务进行通…...

    2024/3/29 13:00:57
  10. 速读原著-黑客入门(黑客的分类和行为)

    一. 黑客的分类和行为 以我的理解,“黑客”大体上应该分为“正”、“邪”两类,正派客依靠自己掌握的知识帮助系统管理员找出系统中的漏洞并加以完善,而邪派黑客则是通各种黑客技能对系统进行攻击、入侵或者做其他一些有害于网络的事情,因为邪派黑客所事的事情违背了《黑客守…...

    2024/3/29 7:35:20
  11. 股票小常识

    文章目录融资融券融资余额融资余额的意义 融资融券又称证券信用交易 投资者向有融资融券业务资格的证券公司 提供担保物 借钱买证券(融资交易) 借证券卖出(融券交易)包括券商对投资者的融资、融券和金融机构对券商的融资、融券 修订前的证券法禁止融资融券的证券信用交易。…...

    2024/4/24 10:53:59
  12. [你必须知道的.NET]第十九回:对象创建始末(下)

    本文将介绍以下内容:对象的创建过程内存分配分析内存布局研究接上回[第十八回:对象创建始末(上)],继续对对象创建话题的讨论>>>2.2 托管堆的内存分配机制引用类型的实例分配于托管堆上,而线程栈却是对象生命周期开始的地方。对32位处理器来说,应用程序完成进程…...

    2024/3/29 7:35:17
  13. 速读原著-黑客入门(网络安全术语解释)

    网络安全术语解释 一、协议: 网络是一个信息交换的场所,所有接入网络的计算机都可以通过彼此之间的物理连设备行息交换,这种物理设备包括最常见的电缆、光缆、无线WAP和微波等,但是单纯拥有这些物理设备并不能实现信息的交换,这就好像人类的身体不能缺少大脑的支配一样,信…...

    2024/3/29 7:35:16
  14. SpringBoot中.yml和.properties常用配置(下)

    原文:https://www.jianshu.com/p/8ec39786634a 文章目录THREAD DUMP ENDPOINT(ThreadDumpEndpoint)HEALTH INDICATORSHTTP TRACING(HttpTraceProperties)METRICSDESTOOLS(DevToolsProperties)//热部署TESTING PROPERTIES THREAD DUMP ENDPOINT(ThreadDumpEndpoint) ma…...

    2024/3/29 7:35:16
  15. 从程序角度谈谈游戏策划的重要性

    程序员对策划的普遍看法 其他工种对游戏策划的重要性是怎么看待的我不太清楚,但是程序员群体对于策划的看法大多是不够尊重的,这跟策划的水平和出身有很大关系,国内的策划大部分都是野路子出身,真正经过正规训练的不多,像程序员和美术都是经过长期而严格的训练的,弱一些的…...

    2024/3/29 7:35:14
  16. 操作系统中的进程间相互作用

    在一个计算机系统中存在着多个进程,这些进程之间可能有逻辑上的关系,也可能没有逻辑上的关系。进程之间无论是否存在逻辑上的关系,由于它们都要共享或竞争一个计算机系统中的资源,所以不可避免地会互相发生作用。本节专门研究进程间的相互作用。一、相关进程和无关进程在一…...

    2024/3/29 7:35:15
  17. 带你彻底掌握 Lambda 表达式(上)

    更多技术文章,欢迎关注我的微信公众号:码不停蹄的小鼠松(微信号:busy_squirrel),也可扫下方二维码关注获取最新文章哦~说明:由于 Lambda 表达式涉及的周边知识点实在太多,因此拆分为上、下两篇文章讲解,本篇为上篇,下篇随后放出,大家可在我公众号中查找。目录介绍:…...

    2024/3/29 7:35:12
  18. python深度学习读书笔记(一)

    一 从数据中学习表示 机器学习的要素输入数据点 预期输出的示例 衡量算法效果好坏的方法机器学习和深度学习的核心问题在于有意义的变换数据,换句话说在于学习输入数据的有用表示 学习指的是寻找更好数据表示的自动搜索过程二 深度学习的工作原理 深度学习:学习数据表示的多级…...

    2024/3/29 13:00:55
  19. Spring Boot中配置.yml和.proerties

    原文:https://www.jianshu.com/p/b4e606c1ffdd #JEST (Elasticsearch HTTP客户端)(JestProperties) spring.elasticsearch.jest.connection-timeout = 3s #连接超时。 spring.elasticsearch.jest.multi-threaded = true #是否从多个执行线程启用连接请求。 spring.elast…...

    2024/3/29 13:00:54
  20. 【Java】泛型

    重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。本文更新不及时,请到原地址查看原文。 原地址:【Java】泛型。 一、泛型简介 泛型听名知意,它是和Java中数据类型有点关系的东西,它本什么并不是某个类型。为了更好的使用它,必…...

    2024/3/29 13:00:52

最新文章

  1. 跳表(Java 实现)

    public abstract class SkipListUtil {/*** 按照规则生成随机层数&#xff0c;层数越大&#xff0c;概率越低** return*/public static int generateRandomLevel() {int result SkipListConfig.MIN_LEVEL;Random random new Random();while (random.nextInt(SkipListConfig.M…...

    2024/5/3 4:25:17
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. LeetCode-46. 全排列【数组 回溯】

    LeetCode-46. 全排列【数组 回溯】 题目描述&#xff1a;解题思路一&#xff1a;回溯。回溯三部曲解题思路二&#xff1a;0解题思路三&#xff1a;0 题目描述&#xff1a; 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案…...

    2024/5/2 11:58:51
  4. git总结

    建议配合gitlab实操 git分区&#xff1a;工作区(修改的地方)&#xff0c;暂存区(add)&#xff0c;本地仓库(commit) 常用命令 初次使用配置 git config --global user.name "xxx" git config --global user.email "xxxqq.com" 这2个是针对整个主机的全局…...

    2024/5/2 3:26:16
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/2 9:28:15
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57