(源码分析)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

相关文章

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

如何利用Java获取当天的开始和结束时间

《如何利用Java获取当天的开始和结束时间》:本文主要介绍如何使用Java8的LocalDate和LocalDateTime类获取指定日期的开始和结束时间,展示了如何通过这些类进行日期和时间的处... 目录前言1. Java日期时间API概述2. 获取当天的开始和结束时间代码解析运行结果3. 总结前言在J

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

Python利用PIL进行图片压缩

《Python利用PIL进行图片压缩》有时在发送一些文件如PPT、Word时,由于文件中的图片太大,导致文件也太大,无法发送,所以本文为大家介绍了Python中图片压缩的方法,需要的可以参考下... 有时在发送一些文件如PPT、Word时,由于文件中的图片太大,导致文件也太大,无法发送,所有可以对文件中的图

java获取图片的大小、宽度、高度方式

《java获取图片的大小、宽度、高度方式》文章介绍了如何将File对象转换为MultipartFile对象的过程,并分享了个人经验,希望能为读者提供参考... 目China编程录Java获取图片的大小、宽度、高度File对象(该对象里面是图片)MultipartFile对象(该对象里面是图片)总结java获取图片

Java通过反射获取方法参数名的方式小结

《Java通过反射获取方法参数名的方式小结》这篇文章主要为大家详细介绍了Java如何通过反射获取方法参数名的方式,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、前言2、解决方式方式2.1: 添加编译参数配置 -parameters方式2.2: 使用Spring的内部工具类 -

Java如何获取视频文件的视频时长

《Java如何获取视频文件的视频时长》文章介绍了如何使用Java获取视频文件的视频时长,包括导入maven依赖和代码案例,同时,也讨论了在运行过程中遇到的SLF4J加载问题,并给出了解决方案... 目录Java获取视频文件的视频时长1、导入maven依赖2、代码案例3、SLF4J: Failed to lo

Java实战之自助进行多张图片合成拼接

《Java实战之自助进行多张图片合成拼接》在当今数字化时代,图像处理技术在各个领域都发挥着至关重要的作用,本文为大家详细介绍了如何使用Java实现多张图片合成拼接,需要的可以了解下... 目录前言一、图片合成需求描述二、图片合成设计与实现1、编程语言2、基础数据准备3、图片合成流程4、图片合成实现三、总结前

使用Java实现获取客户端IP地址

《使用Java实现获取客户端IP地址》这篇文章主要为大家详细介绍了如何使用Java实现获取客户端IP地址,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 首先是获取 IP,直接上代码import org.springframework.web.context.request.Requ