(源码分析)Glide(图片异步加载缓存库)发起request获取resource

本文主要是介绍(源码分析)Glide(图片异步加载缓存库)发起request获取resource,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

工作流程:

既然Glide的功能这么强大,那么就一定要学习下源码,看看内部是怎样工作的。

Glide.with((Fragment) t).load(url).into(imageView);

为了好理解,将上面一行代码进行分解:

RequestManager requestManager = Glide.with((Fragment) t);DrawableTypeRequest drawableTypeRequest = requestManager.load(url);//class DrawableTypeRequest<ModelType> extends DrawableRequestBuilder<ModelType> Target<GlideDrawable> target = DrawableRequestBuilder.into(imageView);

这几个类,我们一起了解下。

Glide:单例,提供使用(BitmapRequestBuilder)构建请求、包含engine,BitmapPool,DiskCache,MemoryCache的简单的静态方法。
Engine:启动下载和管理当前正在使用或者缓存的资源。
BitmapPool:接口类型,允许用户重用bitmap 对象。实现类 LruBitmapPool :复用bitmap,使用最近最少使用策略保证池子的大小。
RequestManager:用于管理和启动Glide请求的类,可以使用activity,fragment连接其生命周期方法来智能的中止、启动和重新发起请求。通过实例化一个新对象,或者利用Activity和Fragment的生命周期处理,在Fragment或者Activity中,使用Glide的静态load方法。

DrawableTypeRequest : 创建一个下载请求来下载drawable或者Bitmap drawable。

Target:接口类型,将加载的资源填充到target对象身上。实现类 ImageViewTarget ViewTarget GlideDrawableImageViewTarget等等。

那就从Glide的with()方法开始看。with方法可以接收context、activity、fragment类型。

    /*** Begin a load with Glide by passing in a context.* 通过传递一个上下文对象来开始Glide。* 传入的如果是fragment/Activity,那么这个请求的生命周期就跟fragment/Activity生命周期一样* 如果是上下文是application,那么请求的声明周期就是应用级别的。* @see #with(android.app.Activity)* @see #with(android.app.Fragment)* @see #with(android.support.v4.app.Fragment)* @see #with(android.support.v4.app.FragmentActivity)** @param context Any context, will not be retained.* @return A RequestManager for the top level application that can be used to start a load.*/public static RequestManager with(Context context) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(context);}@TargetApi(Build.VERSION_CODES.HONEYCOMB)public static RequestManager with(Activity activity) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(activity);}public static RequestManager with(Fragment fragment) {RequestManagerRetriever retriever = RequestManagerRetriever.get();return retriever.get(fragment);}

如果传入的是Activity或者Fragment,那么是怎么保证生命周期的?点进去看一下

public RequestManager get(Context context) {if (context == null) {throw new IllegalArgumentException("You cannot start a load on a null Context");} else if (Util.isOnMainThread() && !(context instanceof Application)) {if (context instanceof FragmentActivity) {return get((FragmentActivity) context);} else if (context instanceof Activity) {return get((Activity) context);} else if (context instanceof ContextWrapper) {return get(((ContextWrapper) context).getBaseContext());}}return getApplicationManager(context);
}

 

先判断如果不是在主线程,或者是application类型那么直接return getApplicationManager(context); 以activity类型为例,那么会调到下面的方法。

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public RequestManager get(Activity activity) {if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {return get(activity.getApplicationContext());} else {assertNotDestroyed(activity);android.app.FragmentManager fm = activity.getFragmentManager();return fragmentGet(activity, fm);}
}

 如果是后台线程的话,继续创建 application级别的 requestmanager则会执行到fragmentGet(activity, fm);

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)RequestManager fragmentGet(Context context, android.app.FragmentManager fm) {RequestManagerFragment current = getRequestManagerFragment(fm);RequestManager requestManager = current.getRequestManager();if (requestManager == null) {requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());current.setRequestManager(requestManager);}return requestManager;}

方法内部会创建一个RequestManagerFragment对象 current,是一个fragment对象。     

requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode());

将fragment的ActivityFragmentLifecycle 给requestManager。这样在RequestManager内部,通过 添加一个

 addListener(LifecycleListener listener)

就可以保持与fragment的生命周期保持同步。

/*** 接口监听Fragment和Activity的生命周期事件。*/
public interface LifecycleListener {/***当Activity或者fragment的onStart方法执行的时候调用。*/void onStart();/*** 当Activity或者fragment的onStop方法执行的时候调用。*/void onStop();/***当Activity或者fragment的onDestroy方法执行的时候调用。*/void onDestroy();
}

在RequestManagManager中

    /***重新开始失败或者暂停的requests.*/@Overridepublic void onStart() {resumeRequests();}/*** 停止request*/@Overridepublic void onStop() {pauseRequests();}/***取消请求,清楚回收资源*/@Overridepublic void onDestroy() {requestTracker.clearRequests();}

以上过程保证了Glide发起的request,与Activity/Fragment的生命周期保持一致,当Activity/Fragment销毁时,取消请求,这样能及时回收资源。

接下来再看下load方法。

  /*** 返回一个来下载drawable或者Bitmap drawable的request对象。** @see #fromString()* @see #load(Object)** @param string A file path, or a uri or url handled by {@link com.bumptech.glide.load.model.UriLoader}.*/public DrawableTypeRequest<String> load(String string) {return (DrawableTypeRequest<String>) fromString().load(string);}

在我们介绍Glide 的功能时,设置的占位图,加载失败展示图,动画效果等都是由DrawableRequestBuilder来完成的。并且包含了具体的发起请求步骤。

	Glide.with((Activity) t).load(url).placeholder(holderId).error(errorId).dontAnimate().dontTransform().into(imageView);

继承自GenericRequestBuilder,它是一个泛型类,可以处理设置选项和初始加载。可以看下它的成员变量

public class GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> implements Cloneable {protected final Class<ModelType> modelClass;protected final Context context;protected final Glide glide;protected final Class<TranscodeType> transcodeClass;//用于跟踪、取消和重新启动正在进行的、已完成的和失败的请求的类。protected final RequestTracker requestTracker;//生命周期protected final Lifecycle lifecycle;//磁盘缓存策略,只保存处理后的结果。private DiskCacheStrategy diskCacheStrategy = DiskCacheStrategy.RESULT;//设置的transformationprivate Transformation<ResourceType> transformation = UnitTransformation.get();//占位图idprivate int placeholderId;//失败图idprivate int errorId;...

以上通过load()方法,将网络,或者本地的资源,封装成一个GenericRequestBuilder 实例。

接下来是最后一个方法into(), 是绑定target对象。将前面要展示的资源,与要显示的载体绑定。这一步也是不可或缺的,一起分析下绑定流程。

    @Overridepublic Target<GlideDrawable> into(ImageView view) {return super.into(view);}

直接看下父类是怎么生成target对象的。

    /*** 设置资源加载的ImageView,会取消一切当前存在的加载,释放任何先前已经加载到View上的资源,以便可以复用。* @see Glide#clear(android.view.View)** @param view 取消view身上之前设置的的下载,加载新的下载。* @return Target 是传入的view的封装。*/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) {//如果设置过ImageView的scaleTypeswitch (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));}

 

执行到最下面一行,继续跟进去。

return into(glide.buildImageViewTarget(view, transcodeClass));

glide.buildImageViewTarget(view, transcodeClass)  对View做了一些封装,比如 在开始下载的时候,先展示默认图。这里就不一起看了,可以自己点进去看看,继续主流程。

    /*** 设置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())");}//先判断当前target有没有被设置过requestRequest previous = target.getRequest();if (previous != null) {//复用的target,释放资源。previous.clear();requestTracker.removeRequest(previous);previous.recycle();}//给target重新设置requestRequest request = buildRequest(target);target.setRequest(request);//添加声明周期方法,保证fragment声明周期回调时,request做出相应的处理。lifecycle.addListener(target);//开启请求requestTracker.runRequest(request);return target;}

这里要注意,对复用类型的 target首先判断之前有没有设置过request。设置的方式实际上是通过view.setTag()来实现的。

因此,在我们使用Glide的时候, 要注意,不要自己调用 view.setTag() 否则会报错 。

java.lang.IllegalArgumentException: You must not call setTag() on a view Glide is targeting

接下来就重点看下请求怎么发起的,request的发起,暂停,移除,恢复request都由RequestTrackerjia 间接管理。

    public void runRequest(Request request) {requests.add(request);if (!isPaused) {request.begin();} else {pendingRequests.add(request);}}

实际上调用的就是request.begin()

    @Overridepublic void begin() {startTime = LogTime.getLogTime();if (model == null) {onException(null);return;}status = Status.WAITING_FOR_SIZE;if (Util.isValidDimensions(overrideWidth, overrideHeight)) {//指定过宽度,长度。onSizeReady(overrideWidth, overrideHeight);} else {//先获取长度target.getSize(this);}//未下载完成,展示占位图。if (!isComplete() && !isFailed() && canNotifyStatusChanged()) {target.onLoadStarted(getPlaceholderDrawable());}if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("finished run method in " + LogTime.getElapsedMillis(startTime));}}

不论是重新指定过宽高还是使用target的默认宽高,最终都会回调到onSizeReady。

    /*** 回调方法,永远不能直接调用。*/@Overridepublic void onSizeReady(int width, int height) {if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime));}if (status != Status.WAITING_FOR_SIZE) {return;}status = Status.RUNNING;width = Math.round(sizeMultiplier * width);height = Math.round(sizeMultiplier * height);ModelLoader<A, T> modelLoader = loadProvider.getModelLoader();final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height);if (dataFetcher == null) {onException(new Exception("Failed to load model: \'" + model + "\'"));return;}ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder();if (Log.isLoggable(TAG, Log.VERBOSE)) {logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime));}loadedFromMemoryCache = true;//交给Engine去获取资源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));}}

接下来执行到engine.laod() 方法。这个方法是根据传递的参数去开启资源加载。

流程如下:
    1.从memorycache中检索资源,如果内存中有缓存资源,使用内存中的缓存。
     2.检索当前使用的活跃资源(ActiveResources),如果检索到。使用当前活跃的。
    ActiveResources也是内存缓存策略中的,只要是正在使用的缓存,都是加入到ActiveResource中的,ActiveResource核心是维护了一个弱引用的Map来储存对应的缓存数据
     3.从当前正在进行加载的任务集合中查找,如果前面已经添加进加载队列,那么直接使用。
    4.开始一个新的下载。
         
    内存缓存策略:使用了两级内存缓存,MemoryCache和ActiveResource,前者默认为一个LruResourceCache,
    后者是一个Map弱引用,引用了从MemoryCache中读取过的资源和从网络、硬盘下载和转换出的资源。
    加载图片时先使用MemoryCache,如果没有找到则尝试从ActiveResource中获取资源。如果还是没有再从磁盘、网络获取资源。

   /*** Starts a load for the given arguments. Must be called on the main thread.*根据给定的参数去开启资源加载,这个方法工作在主线程。*/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();//根据签名、宽高、transformation等等信息对要加载的资源生成一个唯一的key。EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(),loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(),transcoder, loadProvider.getSourceEncoder());//1.从内存中获取,isMemoryCacheable:是否允许内存缓存策略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;}//2.从activeResources中获取 isMemoryCacheable:是否允许内存缓存策略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;}//3.是否之前已经添加了该任务。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);}//4.执行到这里,说明要新建任务了。engineJobFactory.build()EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,transcoder, diskCacheProvider, diskCacheStrategy, priority);//将任务添加到集合中。 Map<Key, EngineJob> jobsjobs.put(key, engineJob);engineJob.addCallback(cb);//启动任务start();engineJob.start(runnable);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Started new load", startTime, key);}return new LoadStatus(cb, engineJob);}
    /***1.从内存中获取。*/private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {//不允许内存缓存 直接返回if (!isMemoryCacheable) {return null;}//尝试从内存中获取EngineResource<?> cached = getEngineResourceFromCache(key);if (cached != null) {//从内存中获取到cached.acquire();//加入到activeResources集合。activeResources = new HashMap<Key, WeakReference<EngineResource<?>>>();activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));}return cached;}/***2.从activeResources中获取。*/private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {//不允许内存缓存 直接返回if (!isMemoryCacheable) {return null;}EngineResource<?> active = null;WeakReference<EngineResource<?>> activeRef = activeResources.get(key);if (activeRef != null) {//从weakreference中 获取active = activeRef.get();if (active != null) {//没有被回收。active.acquire();} else {activeResources.remove(key);}}return active;}

如果缓存中没有,接下来会创建下载任务,开启任务。engineJobFactory.build()创建1个 EngineJob。然后启动

       EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable);DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation,transcoder, diskCacheProvider, diskCacheStrategy, priority);EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority);jobs.put(key, engineJob);engineJob.addCallback(cb);engineJob.start(runnable);

 

public void start(EngineRunnable engineRunnable) {this.engineRunnable = engineRunnable;future = diskCacheService.submit(engineRunnable);
}

 磁盘线程通过submit提交,后最终执行EngineRunnable 的run方法

    @Overridepublic void run() {if (isCancelled) {return;}Exception exception = null;Resource<?> resource = null;try {//获取资源resource = decode();} catch (Exception e) {if (Log.isLoggable(TAG, Log.VERBOSE)) {Log.v(TAG, "Exception decoding", e);}exception = e;}//如果已经取消任务,回收资源。if (isCancelled) {if (resource != null) {resource.recycle();}return;}//拿到资源后回调if (resource == null) {onLoadFailed(exception);} else {onLoadComplete(resource);}}

主要看下怎么获取资源的,即 decode()方法

    private Resource<?> decode() throws Exception {if (isDecodingFromCache()) {//从缓存中return decodeFromCache();} else {//从数据源中return decodeFromSource();}}

这里是从数据源获取,我们直接看decodeFromSource();

    private Resource<?> decodeFromSource() throws Exception {return decodeJob.decodeFromSource();}
    /***对返回的resource 添加transformed 。* @throws Exception*/public Resource<Z> decodeFromSource() throws Exception {Resource<T> decoded = decodeSource();return transformEncodeAndTranscode(decoded);}

   

 private Resource<T> decodeSource() throws Exception {Resource<T> decoded = null;try {long startTime = LogTime.getLogTime();//请求资源。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;}


    
 

   @Overridepublic 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.}}//使用的是HttpUrlConnectionurlConnection = 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) {//200 请求成功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());}}
    //返回inputStreamprivate 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封装成 Bitmap资源。private Resource<T> decodeFromSourceData(A data) throws IOException {final Resource<T> decoded;if (diskCacheStrategy.cacheSource()) {//缓存源数据decoded = cacheAndDecodeSourceData(data);} else {long startTime = LogTime.getLogTime();//包装数据。decoded = loadProvider.getSourceDecoder().decode(data, width, height);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Decoded from source", startTime);}}return decoded;}

 

    再回到前面的decodeFromSource()方法,将 resource 进行缓存。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();Resource<Z> result = transcode(transformed);if (Log.isLoggable(TAG, Log.VERBOSE)) {logWithTimeAndKey("Transcoded transformed from source", startTime);}return result;}

这样,从发起request到获取到resource的流程就完成了。整个流程就是对最开始一张图的分析。

 

再来总结一下:

1.with(context):传入上下文对象,Glide内部 根据上下文的类型,来创建RequestManager。request的生命周期,受传入的context参数影响,如果传递的是application类型的,那么request的生命周期跟随应用走,如果是activity,或者fragment,那么生命周期与传入activity,或者fragment的生命周期保持一致。如果Activity/Fragment 被销毁,request 也会被cancel。

2.load(url): 根据url资源封装一个来返回drawable或者Bitmap drawable的request对象。

3.into(target):传入要承载resource 的target对象。先判断target有没有被复用过,如果复用过,先回收target身上设置过的request info,然后重新设置新的request info.设置的方式是通过view.setTag()所以,使用Glide加载图片的时候,我们不要自己调用setTag()方法,否则会报错。java.lang.IllegalArgumentException: You must not call setTag() on a view Glide is targeting。
设置完request info 后,会启动request,获取resource,获取的策略优先级是memorycache->activeRearource->diskcache-> netWork。
    

或许你还有疑问,后面会继续看以下几个问题。

Glide 中线程池用大小?
Bitmap如何复用的? Bitmappool。
发起request 拿到的 resource 如何缓存的。?
Glide缓存机制 内存 磁盘?
Glide中的设计模式?

https://muyangmin.github.io/glide-docs-cn/
​​​​​​​​​​​​​​http://www.paincker.com/glide-study​​​​​​​
https://blog.csdn.net/u012124438/article/details/73612492
https://www.cnblogs.com/android-blogs/p/5735655.html
https://blog.csdn.net/guolin_blog/article/details/53939176
https://segmentfault.com/a/1190000014853308

 谢谢认真观读本文的每一位小伙伴,衷心欢迎小伙伴给我指出文中的错误,也欢迎小伙伴与我交流学习。

 欢迎爱学习的小伙伴加群一起进步:230274309 。

这篇关于(源码分析)Glide(图片异步加载缓存库)发起request获取resource的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1066538

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。