含泪刷128道面试题,50万字2022最新Android11位大厂面试专题(七)
前言
面试专题前面的百度篇,腾讯篇,阿里篇,京东篇,bilibili篇,网易篇,字节篇,小红书,小米九大板块已经更新完了,还剩下2个专题~持续更新中。
1.12W字;2022最新Android11位大厂面试专题(一)百度篇
2.12W字;2022最新Android11位大厂面试专题(二)阿里篇
3.12W字;2022最新Android11位大厂面试专题(三)腾讯篇
4.面霸养成记;50万字Android面试文档(四五)字节,京东篇
5.面霸养成记;50万字Android面试文档(六七)网易,Bilibili篇
6.面霸养成记;50万字Android面试文档(八九)小红书,小米篇
一共50W字的文档,面试专题12W字只是一小部分,字数限制,分几篇更。
关注公众号:Android苦做舟
提前解锁 《整套50W字Android体系PDF》,让学习更贴近未来实战。
总共囊括:
1.腾讯Android开发笔记(33W字)
2.2022最新Android十一位大厂面试专题(12W字)
3.音视频经典面试题(6W字)
4.Jetpack全家桶
5.Android 性能监控框架Matrix
6.JVM
7.车载应用开发
共十一模块,今天来更新第十专题携程篇
十丶携程
1.Glide讲一讲
①基本使用流程
Glide最基本的使用流程就是下面这行代码,其它所有扩展的额外功能都是以其建造者链式调用的基础上增加的。
GlideApp.with(context).load(url).into(iv);
其中的GlideApp是注解处理器自动生成的,要使用GlideApp,必须先配置应用的AppGlideModule模块,里面可以为空配置,也可以根据实际情况添加指定配置。
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
@Override
public 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的总体框架有一个初步的了解。
从GlideApp.with这行代码开始,内部主线执行流程如下。
GlideApp#with
return (GlideRequests) Glide.with(context);
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、获取前面应用中带注解的GlideModule
GeneratedAppGlideModule 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;
}
GlideBuilder#build
@NonNull
Glide build(@NonNull Context context) {
// 创建请求图片线程池sourceExecutor
if (sourceExecutor == null) {
sourceExecutor = GlideExecutor.newSourceExecutor();
}
// 创建硬盘缓存线程池diskCacheExecutor
if (diskCacheExecutor == null) {
diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
}
// 创建动画线程池animationExecutor
if (animationExecutor == null) {
animationExecutor = GlideExecutor.newAnimationExecutor();
}
if (memorySizeCalculator == null) {
memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
}
if (connectivityMonitorFactory == null) {
connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
}
if (bitmapPool == null) {
// 依据设备的屏幕密度和尺寸设置各种pool的size
int size = memorySizeCalculator.getBitmapPoolSize();
if (size > 0) {
// 创建图片线程池LruBitmapPool,缓存所有被释放的bitmap
// 缓存策略在API大于19时,为SizeConfigStrategy,小于为AttributeStrategy。
// 其中SizeConfigStrategy是以bitmap的size和config为key,value为bitmap的HashMap
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
// 创建对象数组缓存池LruArrayPool,默认4M
if (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);
}
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);
}
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)。
GlideApp#with小结
-
初始化各式各样的配置信息(包括缓存,请求线程池,大小,图片格式等等)以及glide对象。
-
将glide请求和application/SupportFragment/Fragment的生命周期绑定在一块。
with方法的执行流程
③load(url)源码详解
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则为设置的url
this.model = model;
// 记录url已设置
isModelSet = true;
return this;
}
可以看到,load这部分的源码很简单,就是给GlideRequest(RequestManager)设置了要请求的mode(url),并记录了url已设置的状态。
load方法的执行流程
④into(iv)源码详解
真正复杂的地方要开始了。
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或bitmap
return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
}
GlideContext#buildImageViewTarget
return imageViewTargetFactory.buildTarget(imageView, transcodeClass);
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
,接下来继续深入。
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对象(将制定的资源加载进对应的Target
return 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里面做了什么。
RequestManager#track
// 分析2
void track(@NonNull Target<?> target, @NonNull Request request) {
// 加入一个target目标集合(Set)
targetTracker.track(target);
requestTracker.runRequest(request);
}
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);
}
}
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,
// 最终的核心处理位于onSizeReady
onSizeReady(overrideWidth, overrideHeight);
} else {
// 根据imageView的宽高算出图片的宽高,最终也会走到onSizeReady
target.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
,可以猜到(因为后面只做了预加载缩略图的处理),真正的请求就是从这里开始的,咱们进去一探究竟~
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类了,感觉距离成功不远了,继续~
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/sourceExecutor
GlideExecutor executor =
decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}
可以看到,最终Engine(引擎)类内部会执行到自身的start方法,它会根据不同的配置采用不同的线程池使用diskCacheExecutor/sourceUnlimitedExecutor/animationExecutor/sourceExecutor
来执行最终的解码任务decodeJob。
DecodeJob#run
runWrapped();
private void runWrapped() {
switch (runReason) {
case INITIALIZE:
stage = getNextStage(Stage.INITIALIZE);
// 关注点1
currentGenerator = 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);
}
}
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;
}
DecodeHelper#getLoadData
List<LoadData<?>> getLoadData() {
if (!isLoadDataSet) {
isLoadDataSet = true;
loadData.clear();
List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model) ;
//noinspection ForLoopReplaceableByForEach to improve perf
for (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;
}
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) {
// 关注点5
modelCache.put(model, 0, 0, model);
url = model;
}
}
int timeout = options.get(TIMEOUT);
// 注意,这里创建了一个DataFetcher的实现类HttpUrlFetcher
return 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的时间。
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()
方法获取到了最终的图片流。
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());
// 核心代码
// 将解码任务分发给LoadPath
return 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.
// 核心代码
// 将解码任务分发给LoadPath
return path.load(
rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
} finally {
rewinder.cleanup();
}
}
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 perf
for (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;
}
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 perf
for (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的类型分发给不同的解码器Decoder
result = 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
,接下来让我们来看看这个解码器内部的解码流程。
ByteBufferBitmapDecoder#decode
/**
* Decodes {@link android.graphics.Bitmap Bitmaps} from {@link java.nio.ByteBuffer ByteBuffers}.
*/
public class ByteBufferBitmapDecoder implements ResourceDecoder<ByteBuffer, Bitmap> {
...
@Override
public 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,它是一个压缩器,主要是对流进行解码,压缩,圆角等处理。
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对象进行了发布。
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 {
...
}
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{
...
@Override
public 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 perf
for (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
方法。
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
,看到BitmapImageViewTarget
类,我们并没有发现onResourceReady
方法,但是我们从它的子类ImageViewTarget
中发现了onResourceReady
方法,从这里继续往下看。
ImageViewTarget#onResourceReady
public abstract class ImageViewTarget<Z> extends ViewTarget<ImageView, Z>
implements Transition.ViewAdapter {
...
@Override
public 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> {
...
@Override
protected void setResource(Bitmap resource) {
view.setImageBitmap(resource);
}
}
到这里,我们的分析就结束了,从以上的分析可知,Glide将大部分的逻辑处理都放在了最后一个into方法中,里面经过了20多个分析步骤才将请求图片流、解码出图片,到最终设置到对应的imageView上。
⑤完整Glide加载流程图
可以看到,Glide最核心的逻辑都聚集在
into()
方法中,它里面的设计精巧而复杂,这部分的源码分析非常耗时,但是,如果你真真正正地去一步步去深入其中,你也许在Android进阶之路上将会有顿悟的感觉。
2.OKhttp拦截器
在这篇文章中,我们将学习如何使用 OkHttp 拦截器。我们还将看到我们可以使用它的真实用例以及我们如何使用它来充分利用它。在 Android 中,我们有很多可以使用 OkHttp 拦截器完成的用例。
今天,我们将通过以下部分来掌握它:
- 什么是拦截器?
- 拦截器的类型。
- 如何在 OkHttpClient 中添加拦截器?
- 创建拦截器。
- 使用拦截器的真实用例。
①什么是拦截器?
根据文档,拦截器是一种强大的机制,可以监视、重写和重试 API 调用。所以基本上,当我们做一些 API 调用时,我们可以监控调用或执行一些任务。
简单来说,Interceptor 就像机场安检过程中的安检人员。他们检查我们的登机牌,在上面盖章,然后让我们通过。
我们可以使用拦截器来做很多事情,例如,集中监控 API 调用。通常,我们需要为每个网络调用添加记录器,但是通过使用拦截器,我们可以集中添加一个记录器,这将适用于所有网络调用。另一个用例可以缓存网络调用的响应以构建离线优先应用程序,我们将在本博客后面详细了解它。
②拦截器的类型
我们有两种类型的拦截器,如下所示:
- 应用程序拦截器:这些是添加在应用程序代码(我们编写的代码)和 OkHttp 核心库之间的拦截器。这些是我们使用
addInterceptor()
添加的。 - **网络拦截器:**这些是添加在 OkHttp 核心库和服务器之间的拦截器。这些可以使用
addNetworkInterceptor()
添加到 OkHttpClient。
③如何在 OkHttpClient 中添加拦截器?
在构建OkHttpClient对象时,我们可以添加如下拦截器:
fun myHttpClient(): OkHttpClient {
val builder = OkHttpClient().newBuilder()
.addInterceptor(/*our interceptor*/)
return builder.build()
}
在这里,在addInterceptor
中,我们传递了我们创建的拦截器。现在,让我们看看如何创建拦截器。
3.1创建拦截器
要创建拦截器,我们需要通过实现 Interceptor 接口来创建一个类,如下所示:
class MyInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
/**
* Our API Call will be intercepted here
*/
}
}
在这里,在intercept()
中,我们可以在其中执行我们想要的任何操作。
要使用拦截器,我们可以像下面这样使用:
fun myHttpClient(): OkHttpClient {
val builder = OkHttpClient().newBuilder()
.addInterceptor(MyInterceptor())
return builder.build()
}
我们可以在addInterceptor()
中添加 MyInterceptor。
现在,让我们讨论更多可以使用拦截器的真实用例。
3.2.使用拦截器的真实用例
以下是 Android 中的常见用例:
- 集中记录错误
首先,我们需要创建ErrorInterceptor,如下所示:
class ErrorInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
val response = chain.proceed(request)
when (response.code()) {
400 -> {
//Show Bad Request Error Message
}
401 -> {
//Show UnauthorizedError Message
}
403 -> {
//Show Forbidden Message
}
404 -> {
//Show NotFound Message
}
// ... and so on
}
return response
}
}
- 首先,我们从
chain.request()
获取请求** - 然后,我们通过在
chain.proceed(request)
中传递请求来获取服务器返回的响应 - 现在,我们可以检查响应代码并执行操作。
- 我们可以通过接口或使用 RxJava、EventBus 等将错误传递给视图。
- 假设我们收到401 错误,即未经授权,那么我们可以执行一个操作来清除应用程序数据/注销用户或我们想要执行的任何操作。
现在,要在 OkHttpClient 中使用这个 ErrorInterceptor,我们可以像下面这样添加:
.addInterceptor(ErrorInterceptor())
这就是我们如何使用拦截器创建集中式错误记录器的方法。
OkHttp 有一个内置的记录器,对于调试非常有用。
注意: 如果我们想记录 URL 重定向的详细信息,请考虑使用addNetworkInterceptor()
在网络层使用拦截器。
- 缓存响应
如果我们想缓存 API 调用的响应,这样如果我们再次调用 API,响应就会从 Cache 中出来。
假设我们有从客户端到服务器的 API 调用,并且从服务器启用了Cache-Control标头 ,那么 OkHttp Core 将尊重该标头并缓存从服务器发送的响应一段时间。
但是如果没有从服务器启用 Cache-Control 怎么办。我们仍然可以使用拦截器缓存来自 OkHttp 客户端的响应。
只需看上图。在这里,我们要做的是,在进入 OkHttp Core 之前,我们必须拦截 Response 并添加 header(Cache-Control),所以它会被视为响应(带有 Cache-Control header)已经到来来自服务器,OkHttp Core 会尊重并缓存响应。
我们将创建一个拦截器,如下所示:
class CacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response: Response = chain.proceed(chain.request())
val cacheControl = CacheControl.Builder()
.maxAge(10, TimeUnit.DAYS)
.build()
return response.newBuilder()
.header("Cache-Control", cacheControl.toString())
.build()
}
}
在这里,我们有一个 CacheControl 用于为 Cache-Control 提供标头。
最后,我们可以添加如下:
.addNetworkInterceptor(CacheInterceptor())
在这里,如果我们看到,我们没有使用addInterceptor()
而是使用addNetworkInterceptor()
的用例。这是因为在这种情况下,操作发生在网络层。
但是,在构建离线优先应用程序时,我们需要考虑一些重要的事情。
只有当互联网可用时才会返回缓存的响应,因为 OkHttp 就是这样设计的。
- 当 Internet 可用并且数据被缓存时,它会从缓存中返回数据。
- 即使数据被缓存并且互联网不可用,它也会返回错误“没有互联网可用”。
现在要做什么?
除了上述之外,我们还可以在应用层使用以下ForceCacheInterceptor (CacheInterceptor,仅当未从服务器启用时)。要在代码中实现,我们将创建一个 ForceCacheInterceptor,如下所示:
class ForceCacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val builder: Request.Builder = chain.request().newBuilder()
if (!IsInternetAvailable()) {
builder.cacheControl(CacheControl.FORCE_CACHE);
}
return chain.proceed(builder.build());
}
}
我们可以在 OkHttpClient 中添加拦截器,如下所示:
.addNetworkInterceptor(CacheInterceptor()) // only if not enabled from the server
.addInterceptor(ForceCacheInterceptor())
在这里,我们使用 addInterceptor()
而不是 addNetworkInterceptor()
将 ForceCacheInterceptor 添加到 OkHttpClient,因为我们希望它在应用程序层上工作。
- 集中添加诸如访问令牌之类的标头
假设我们必须进行 API 调用,并且必须在所有 API 调用中添加授权标头。我们可以单独使用它,也可以使用拦截器集中它。
class AuthTokenInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
.header("Authorization", "AuthToken")
val request = requestBuilder.build()
return chain.proceed(request)
}
}
- 首先,我们从本地存储(如 SharedPreference)中获取标头的令牌。
- 在这里,我们使用
chain.request()
拦截我们从应用程序触发的原始请求,并将其设置为originalRequest
。 - 然后,我们通过添加带有进行网络调用所需的键和值的Header来再次构建请求。
- 然后,我们将再次构建请求并使用**chain.proceed(request)**通过传递具有 Authorization 标头的新请求来返回响应。
这就是我们可以集中所有 API 调用中常见的 Header 的方式。现在要在 OkHttpClient 中使用它,我们将执行以下操作:
.addInterceptor(AuthTokenInterceptor())
让我们转到另一个用例。
- 在单个位置刷新访问令牌
假设我们有一个用例,当我们在错误拦截器中收到401 错误时,我们需要刷新身份验证令牌,因为我们有一个未经授权的错误。我们可以使用以下方法做到这一点:
override fun intercept(chain: Interceptor.Chain): Response {
val accessToken = //our access Token
val request = chain.request().newBuilder()
.addHeader("Authorization", accessToken)
.build()
val response = chain.proceed(request)
if (response.code() == 401) {
val newToken: String = //fetch from some other source
if (newToken != null) {
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", newToken)
.build()
return chain.proceed(newRequest)
}
}
return response
}
- 如果我们得到
response.code()
为401即**未授权,**我们将在此处刷新令牌,然后通过添加新标头来修改请求并向服务器发出新请求。
注意: 在刷新 Access Token 时,另一种更灵活的方法是使用 OkHttp 的 Authenticator 接口。
现在,让我们转到另一个用例。
- 在 Android 端启用 Gzip
Gzip 用于数据压缩。在 Android 中,我们也可以使用 Gzip 来使用拦截器进行压缩。
因此,在得到响应时,OkHttp 会自动尊重头部(内容编码)并解压缩数据并返回,但是假设当我们必须将压缩数据发送到服务器时,我们必须编写自己的拦截器。
要使用拦截器,我们可以使用如下:
.addInterceptor(GzipRequestInterceptor())
所以,这些是真正的用例,我们如何在我们的 Android 应用程序中使用拦截器。我们可以用拦截器做很多事情。让我们开始充分利用它。
3.get和post请求区别
- get请求发送数据的时候,数据会挂在URI的后面,并且在URI后面添加一个“?”,"?"后面是数据。这样会导致发送的数据回显在浏览器的地址栏上。(get请求在“请求行”上发送数据)
- post请求发送数据的时候,在请求体当中发送。不会回显到浏览器的地址栏上。也就是说post发送的数据,在浏览器地址栏上看不到。(post在“请求体”当中发送数据)
- get请求只能发送普通的字符串。并且发送的字符串长度有限制,不同的浏览器限制不同。这个没有明确的规范。
- get请求无法发送大数据量。
- post请求可以发送任何类型的数据,包括普通字符串,流媒体等信息:视频、声音、图片。
- post请求可以发送大数据量,理论上没有长度限制。
- get请求在W3C中是这样说的:get请求比较适合从服务器端获取数据。
- post请求在W3C中是这样说的:post请求比较适合向服务器端传送数据。 可能好多人,一看到说get请求会把内容显示在地址栏上,就是不安全的
4.项目里面内存优化
基础知识回顾(看前面文章JVM详解):
jVM内存模型,除了程序计数器以外,别的都会出现 OOM。
JAVA对象的生命周期,创建、运行、死亡。 GC对象可回收的判定:可达性分析。GC root(除了堆里的对象,虚拟机栈里的引用、方法区里的引用)。 强软弱虚四种引用。 GC回收算法:复制算法、标记清楚算法、标记整理算法
①App内存组成以及限制
Android
给每个App
分配一个VM
,让App运行在dalvik
上,这样即使App
崩溃也不会影响到系统。系统给VM
分配了一定的内存大小,App
可以申请使用的内存大小不能超过此硬性逻辑限制,就算物理内存富余,如果应用超出VM
最大内存,就会出现内存溢出crash
。
由程序控制操作的内存空间在heap
上,分java heapsize
和native heapsize
- Java申请的内存在
vm heap
上,所以如果java
申请的内存大小超过VM
的逻辑内存限制,就会出现内存溢出的异常。 - native层内存申请不受其限制,
native
层受native process
对内存大小的限制
总结:
app运行在虚拟机上,手机给虚拟机分配内存是固定的,超出就oom。
分配的内存大部分都是在堆上。分为java堆和native层的堆。
java层的堆超过VM给的就oom。理论上native层无限制,但是底层实现native process
对内存大小是有限制的。
②查看系统给App分配的内存限制
不同手机给app分配的内存大小其实是不一样大的。
- 通过cmd指令 adb shell cat /system/build.prop
2.通过代码activityManager.getMemoryclass();
ActivityManager activityManager =(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)
activityManager.getMemoryClass();//以m为单位
这些限制其实在 AndroidRuntime.cpp
的startVm里,我们改不了,手机厂商可以改
int androidRuntime:startvm(javavm** pjavaVm,Jnienv** penv,bool zygote)
{
/*
*The default staring and maximum size of the heap. larger
*valuse should be specified in a product property ovrride.
*/
parseRuntimeoption("dalvik.vm.heapstarsize",heapstartsizeoptsbuf,"-xms","4m");
parseRuntimeoption("dalvik.vm.heapsize",heapsizeoptsbuf,"-xmx","16m")
}
- 修改platform/dalvik/+eclair-relese/vm/init.c
gDvm.heaosizestart = 2 *1024* 1024;
//spec says 16MB;too big for us.
gDvm.heapsizeMax = 16 *1024* 1024;
//spec says 75% physical mem
③Android低内存杀进程机制
默认五个级别:空进程、后台进程、服务进程、可见进程、前台进程 所以在保活里有一个做法就是给app提升优先级,给他整成前台、可见这样的。
AMS里有一个oom_adj
,会给各个进程进行评分,数字越大越容易被回收,前台进程是0,系统进程是负数,别的是正数
④内存三大问题
1、内存抖动
profiler -> 内存波动图形呈 锯齿张、GC导致卡顿。
原因是内存碎片很严重。因为android虚拟机的GC算法是标记清楚算法,所以频繁的申请内存、释放内存会让内存碎片化很严重,连续的内存越来越少,让GC非常频繁,导致了内存抖动。
案例:在自定义View onDraw()
里new对象
2、内存泄漏 在当前应用周期内不再使用的对象被GC Roots引用,导致不能回收,使实际可使用内存变小。内存泄露如果越来越严重的话,最终会导致OOM。 案例:context被长生命周期的东西引用。没有释放listener
3、内存溢出 即OOM,OOM时会导致程序异常。Android设备出厂以后,java虚拟机对单个应用的最大内存分配就确定下来了,超出这个值就会OOM。 案例:加载大图、内存泄露、内存抖动
除了程序计数器以外 别的JVM部分都会OOM
⑤常见分析工具
-
Memory Analyzer Tools --- MAT
MAT是Eclipse下的内存分析工具。 用法:用cmd指令或者android studio 自带的profiler 截取一段数据,然后下载下来。得到一个
.hprof
然后用AMT打开,就能看预测泄露地方,有一个图表,基本没用。还有看哪个线程调用这个对象、深浅堆等等。挺难用的。
2.android studio自带的profiler
谷歌官网:developer.android.google.cn/studio/prof…
官网超级详细,还有视频,直接看官网的就行。
选中一段区域,就能查看这段区域内存被具体哪个对象用了多少等等。 我感觉还是用来看大势的,大的内存上涨、下落、起伏图。
3.LeakCanary 超级推荐LeakCanary!!!永远滴神 具体内存泄露的检测,细节还得用LeakCanary。
集成:
build.gradle
里添,跟集成第三方一样。
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
然后直接跑APP,跑完以后一顿点,点完以后会给推送。写了发现几个对象有泄露。点一下推送蓝会自动下载下来。
比如这就是我的问题,期初我认为是activity的context没有释放,其实是在dialog里使用了动画animation,但是动画的listener没有释放。
⑥Android内存泄漏常见场景以及解决方案
1、资源性对象未关闭 对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。例如Bitmap 等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。
2、注册对象未注销 例如BraodcastReceiver、EventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。
3、类的静态变量持有大数据对象 尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。
4.单例造成的内存泄漏 优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封 装,然后,在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。
5、非静态内部类的静态实例 该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的内存资源 不能正常回收。此时,我们可以将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如 果需要使用Context,尽量使用Application Context,如果需要使用Activity Context,就记得用完后置 空让GC可以回收,否则还是会内存泄漏。
6、Handler临时性内存泄漏 Message发出之后存储在MessageQueue中,在Message中存在一个target,它是Handler的一个引用,Message在Queue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的, 则会导致Activity或者Service不会被回收。并且消息队列是在一个Looper线程中不断地轮询处理消息, 当这个Activity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message 持有Handler实例的引用,Handler又持有Activity的引用,所以导致该Activity的内存资源无法及时回 收,引发内存泄漏。解决方案如下所示: 1、使用一个静态Handler内部类,然后对Handler持有的对象(一般是Activity)使用弱引用,这 样在回收时,也可以回收Handler持有的对象。 2、在Activity的Destroy或者Stop时,应该移除消息队列中的消息,避免Looper线程的消息队列中 有待处理的消息需要处理 (Handler那篇有讲)
7、容器中的对象没清理造成的内存泄漏 在退出程序之前,将集合里的东西clear,然后置为null,再退出程序
8、WebView WebView都存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。我们可以为 WebView开启一个独立的进程,使用AIDL与应用的主进程进行通信,WebView所在的进程可以根据业 务的需要选择合适的时机进行销毁,达到正常释放内存的目的。
9、使用ListView时造成的内存泄漏 在构造Adapter时,使用缓存的convertView。
10、Bitmap 80%的内存泄露都是Bitmap造成的,Bitmap有Recycle()方法,不用时要及时回收。但是如果遇到要用Bitmap直接用Glide就完事了,自己写10有8.9得出错。
5.LeakCanary原理及分析
①LeakCanary简单使用
LeakCanary用来检测内存泄漏的工具,从源码看目前支持activity和fragment
导包
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'//debug包,直接在通知栏显示内存泄漏信息
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'//
自定义application,然后再onCreate()
方法里面添加LeakCanary.install()
方法
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (LeakCanary.isInAnalyzerProcess(this)) {
return
}
LeakCanary.install(this) //引用LeakCanary
}
}
②LeakCanary流程简单分析
研究LeakCanary之前先明确几个问题:
1.LeakCanary是用来检测内存泄露的,那么什么是内存泄漏。
内存泄漏简单说就是该回收的内存没有被回收。那么导致内存没有被回收的原因,肯定是这块内存还被引用,导致回收不了。
举个最常见的new关键字创建对象,简单说一下:
Person per = new Person();
new Person()
会去创建一个对象,然后jvm给该对象在堆内存里分配一块地址。
Person per这里的per是一个引用,会在栈里分配一块内存,引用存储的是一个地址,该地址是句柄的地址,而句柄是一种结构,分别存储 实例指针和类型指针 这两种指针,(实例指针是指向堆中的对象实例,而类型指针指向的是在方法区中该对象所属类型)。当要访问对象时,先通过引用访问句柄,再通过句柄访问对象实例以及对象类型信息。句柄是存储在堆中的,如果使用这种方式,那么就会从堆中分出一块内存用作句柄池。
了解过jvm我们都知道,引用分为强、软、弱、虚四种引用。 强软弱虚四种引用的特点是什么呢?我们引用一段《深入理解java虚拟机》中的原话:
强引用是最传统的“ 引用 ” 的定义,是指在程序代码之中普遍存在的引用赋值,即类似 “
Object obj=new Object()
” 这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。 Student s=new Student();
软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内 存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。在 JDK 1.2 版之后提供了 SoftReference 类来实现软引用。 Student s=new Student(); SoftReference soft = new SoftReference(prime) ; s= null;
弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只 能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只 被弱引用关联的对象。在 JDK 1.2 版之后提供了 WeakReference 类来实现弱引用。 Student s=new Student(); WeakReference weakCounter = new WeakReference(s); s= null;
虚引用也称为 “ 幽灵引用 ” 或者 “ 幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的 存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚 引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在 JDK 1.2版之后提供 了 PhantomReference 类来实现虚引用。 Student s=new Student(); PhantomReference phantom = new PhantomReference(s); s= null;
所以LeakCanary通过弱引用是否被回收来检测是否有内存泄漏,即如果弱引用对象没有被回收,那肯定就是发生了内存泄漏。因为弱引用存活不到垃圾回收之后。
2.思考一个问题,activity被销毁之后就会被立即回收吗,答案是不一定会,因为要等垃圾回收机制启动之后,才有可能被回收,不一定会立即被回收。所以弱引用什么时候用呢。LeakCanary是这么处理的,监听activity的生命周期,在activity的onDestroy之后去创建一个该activity的弱引用。至于怎么监听的生命周期以及其原理什么,我们在单独的文章里讨论,这里不多赘述。
3.现在activity的弱引用有了,那么怎么知道这个弱引用有没有被回收呢。这里先简单介绍一下Reference
和ReferenceQueue
Reference类有两个构造方法
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
一个带ReferenceQueue一个不带
ReferenceQueue
,区别就是带ReferenceQueue
的方法,垃圾回收之后会把Reference放到queue里面。
所以LeakCanary
是这么处理的,把activity对应的引用object和一个ReferenceQueu
e组成WeakReference
, WeakReference
是Reference
的子类
垃圾回收之后通过queue的poll()
方法取出这个WeakReference
。
现在应用有很多activity,应用退出后怎么判断所有的activity都被回收了呢,
LeakCanary这么处理,监听activity的onDestroy()
方法时,本来是直接将activity的引用和ReferenceQueue
组成一个WeakReference,
优化一下,用一个随机生成的key,和activity的引用object以及ReferenceQueue
组成一个叫KeyedWeakReference
,KeyedWeakReference
是WeakReference
的子类。然后将key存到set集合里。
触发检测的时候,先从ReferenceQueue
里面拿到KeyedWeakReference
以及KeyedWeakReference
的key参数。然后从set集合里面移除相应的key。最后判断set集合是否有对应的key,如果是空那就没有内存泄漏。如果不为空是不是就一定有内存泄漏了呢,答案是不一定,因为可能没有触发GC操作。
所以在这个基础上,如果set集合有对应的key,那就再手动触发一次GC操作,再从ReferenceQueue
里面拿到KeyedWeakReference
以及KeyedWeakReference
的key参数。然后从set集合里面移除相应的key。最后判断set集合是否有对应的key,如果这时候还不为空,那说明确实发生了内存泄漏。
4.就是什么时候去触发内存泄漏的检测。LeakCanary是这么处理的,通过android消息机制,即Handler的IdleHandler
IdleHandler 说白了,就是 Handler 机制提供的一种,可以在 Looper 事件循环的过程中,当出现空闲的时候,允许我们执行任务的一种机制。 既然 IdleHandler 主要是在 MessageQueue 出现空闲的时候被执行,那么何时出现空闲? MessageQueue 是一个基于消息触发时间的优先级队列,所以队列出现空闲存在两种场景。 MessageQueue 为空,没有消息; MessageQueue 中最近需要处理的消息,是一个延迟消息(when>currentTime),需要滞后执行;
当没有消息要处理时,触发内存泄漏检测
总结一下LeakCanary实现的难点: 1.怎么判断内存泄漏(涉及到activity生命周期的监听,弱引用,ReferenceQueue的特性) 2.什么时候触发内存泄漏检测(涉及到消息机制,IdleHandler的特性) 3.怎么判断所有的activity都被回收(涉及到set集合的特点)
③LeakCanary相关代码流程分析
3.1 入口
入口在application的onCreate()
方法里面创建LeakCanary.install(this)
override fun onCreate() {
super.onCreate()
if (LeakCanary.isInAnalyzerProcess(this)) {
return
}
LeakCanary.install(this)
}
3.2 activity生命周期的监听如何实现
通过ActivityRefWatcher#instal
l方法里
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
//通过application的registerActivityLifecycleCallbacks()方法注册生命周期监听
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
callback的实现也很简单,只关注onActivityDestroyed()
方法,拿到activity的引用
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
3.3 弱引用以及ReferenceQueue的使用技巧
RefWatcher#watch()
方法里面
//watchedReference就是3.2中对应的activity
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();//创建一个random的key值
retainedKeys.add(key);//retainedKeys是一个set集合
//将watchedReference(activity的引用),key,queue组合成KeyedWeakReference,KeyedWeakReference是WeakReference的子类
//垃圾回收之后,queue里面就会有KeyedWeakReference对象,这是Reference的特性。子类也有这个特性。
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);//queue就是ReferenceQueue
ensureGoneAsync(watchStartNanoTime, reference);//准备完毕了,等待触发内存泄漏检测
}
3.4 触发内存泄漏检测
也就是idleHandler怎么用的:
3.3的技巧组合完毕之后呢,进入ensureGoneAsync
方法,首先通过watchExecutor.execute()
方法,去执行IdlerHandler操作。
真正判断内存泄露的是ensureGone()
方法
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
execute方法就是教你怎么实现一个Idlehandler,最终run()
方法的回调是在postToBackgroundWithDelay()
方法里。
@Override public void execute(@NonNull Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run(); //run方法的回调
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
3.5 具体判断有没有内存泄漏
具体思路是先遍历一遍ReferenceQueue,拿到对应的key之后清除set集合对应的key,如果集合不包含key就没有内存泄漏。 如果集合不为空,就手动触发一次GC,然后再去遍历一遍ReferenceQueue,拿到对应的key之后清除set集合对应的key,再去判断集合是否有key,没有可以就没有内存泄漏,有key就是内存泄漏。
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();//1.遍历ReferenceQueue
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) { //2.gone()方法判断集合是否包含当前reference的key
return DONE;
}
gcTrigger.runGc();//3.手动GC
removeWeaklyReachableReferences();//4.GC完毕后再检查一遍
if (!gone(reference)) {//5.如果还是有reference的key,说明出现内存泄露了。
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
heapdumpListener.analyze(heapDump);//6.输出内存泄漏信息。
}
return DONE;
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key); //刷新set集合,将回收的key清理掉。
}
}
6.Bitmap高效加载
①Bitmap(位图):
指一张图片,常见格式:.png、.jpg等
②必要性
直接加载大容量的高清Bitmap很容易出现显示不完整、内存溢出OOM的问题(如报错:java.lang.OutofMemoryError
)
③核心思想
为了解决这个问题,就出现了Bitmap的高效加载策略。其核心思想: 假设通过ImageView来显示图片,很多时候ImageView并没有原始图片的尺寸那么大,这个时候把整个图片加载进来后再设置给ImageView,显然是没有必要的,因为ImageView根本没办法显示原始图片。这时候就可以按一定的采样率来将图片缩小后再加载进来,这样图片既能在mageView显示出来,又能降低内存占用从而在一定程度上避免OOM,提高了Bitmap加载时的性能。
④工具类
1.BitmapFactory类提供的四种加载图片的方法:
注意:
decodeFile()
和decodeResource()
又间接调用decodeStream()
。
最终对应着BitmapFactory类的几个native方法;
2.BitmapFactory.Options
的参数
1.inSampleSize
参数:即采样率,同时作用于宽/高
- 取值规定: 应为2的指数,如1、2、4... 否则系统会向下取整并选择一个最接近2的指数来替代,如3被2替代。
- 变化规则: 当inSampleSize=1,采样后大小不变。 当inSampleSize=k>1,采样后图片会缩小。具体规则:宽高变为原图的1/k, 像素变为原图的1/k^2, 占用内存大小变为原图的1/k^2。
注意:根据图片宽高的 实际大小&需要大小,而计算出的缩放比尽可能取最小,避免由于缩小的过多,导致在控件中不能铺满而被拉伸至模糊。
2.inJustDecodeBounds
参数:
- 值为true:BitmapFactory只加载图片的原始宽高信息,而不真正加载图片到内存;
- 值为false:BitmapFactory真正加载图片到内存。
注意: BitmapFactory获取的图片宽高信息和图片的位置以及程序运行的设备有关,会导致获取到不同的结果; xxhdpi获取图片的大小是mdpi中的三倍; Bitmap计算规则 = 整个Bitmap占用的就是宽度 * 高度 * 4 byte(ARGB_8888格式的一个像素占用 4byte)一个1024*1024的图片占4MB大小。
- 加载流程:
- 将
BitmapFactory.Options.inJustDecodeBounds
参数设为true并加载图片。 - 从
BitmapFactory.Options
中取出图片的原始宽高信息,对应outWidth和outHeight参数。 - 根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize。
- 将
BitmapFactory.Options.inJustDecodeBounds
参数设为false,然后重新加载图片。
/**
* 对一个Resources的资源文件进行指定长宽来加载进内存, 并把这个bitmap对象返回
*
* @param res 资源文件对象
* @param resId 要操作的图片id
* @param reqWidth 最终想要得到bitmap的宽度
* @param reqHeight 最终想要得到bitmap的高度
* @return 返回采样之后的bitmap对象
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
//1.设置inJustDecodeBounds=true获取图片尺寸
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res,resId,options);
//3.计算缩放比
options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);
//4.再设为false,重新从资源文件中加载图片
options.inJustDecodeBounds =false;
return BitmapFactory.decodeResource(res,resId,options);
}
/**
* 一个计算工具类的方法, 传入图片的属性对象和想要实现的目标宽高. 通过计算得到采样值
* @param options 要操作的原始图片属性
* @param reqWidth 最终想要得到bitmap的宽度
* @param reqHeight 最终想要得到bitmap的高度
* @return 返回采样率
*/
private static int calculateInSampleSize(BitmapFactory.Options options, int reqHeight, int reqWidth) {
//2.height、width为图片的原始宽高
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
if(height>reqHeight||width>reqWidth){
int halfHeight = height/2;
int halfWidth = width/2;
//计算缩放比,是2的指数
while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){
inSampleSize*=2;
}
}
return inSampleSize;
}
7.大型APP启动优化
腾讯开发笔记PDF中有详细介绍
8.渲染布局优化
经过上面的一顿操作,发现占时间大块的少不了setContentView
。说明布局渲染视图还是挺费时的。
①减少层级
自定义Viewmeasure、layout、draw这三个过程,都需要对整个视图树自顶向下的遍历,而且很多情况都会多次触发整个遍历过程(Linearlayout 的 weight等),所以如果层级太深,就会让整个绘制过程变慢,从而造成启动速度慢、卡顿等问题。 而onDraw在频繁刷新时可能多次触发,因此 onDraw更不能做耗时操作,同时需要注意内存抖动。对于布局性能的检测,依然可以使用systrace与traceview按 照绘制流程检查绘制耗时函数。
工具Layout Inspector DecorView开始。content往下才是自己写的布局。
重复的布局使用include。 一个布局+到另一个上,如果加上以后,有两一样的ViewGroup,可以把被加的顶层控件的ViewGroup换成merge ViewStub:失败提示框等。需要展示的时候才创建,放在那不占位置。
②过度渲染
一块区域内(一个像素),如果被绘制了好几次,就是过度渲染。 过度绘制不可避免,但是应该尽量的减少。 手机->开发者模式->GPU 过度绘制 打开以后能看到不同颜色的块,越红就说明过度绘制的越严重。对于严重的地方得减少过度绘制。
1.移除布局中不需要的背景。 2.降低透明度。 3.使视图层次结构扁平化。
③布局加载优化
异步加载布局,视情况而用。
new AsyncLayoutInflater(this)
.inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
setContentView(view);
//......
}
});
9.ViewStub懒加载
①使用场景
当一块布局 有时候不需要展现 有时候需要展现 或者根据不同场景需要展现不同样式 通常的解决方案就是:就是把可能用到的View先写在布局里,再初始化其可见性都设为View.GONE,然后在代码中根据数据动态的更改它的可见性。
虽然这样的实现,逻辑简单而且控制起来比较灵活;但是也存在一定的缺点耗费资源。
因为即使把View的初始可见View.GONE但是在Inflate布局的时候View仍然会被Inflate,故而:
(1)仍然会创建对象;
(2)仍然会被实例化;
(3)仍然会被设置属性从而导致耗费内存等资源。
此时可以使用懒加载去处理 减少资源消耗和初始化时间
②使用方法
先在布局中使用ViewStub 当作占位符 (1)ViewStub一个是直接继承于View的类。
(2)实质上是一个宽高都为 0 的不可见 View。
注意设置宽高不要使用match_parent
否则会将整个页面都覆盖
在需要让其显示的地方 为其注入对应的布局文件 使用setLayoutResource(R.layout)
方法
注意: 注入对应的布局后 需要调用inflate()
方法 才会生效
一般会定义一个View对象 View v = viewStub.inflate();
接收注入后的对象
后续需要修改使用layout内的布局时 只需要使用v.findViewById(R.id.)
找到内部布局
对 inflate()
操作也只能进行一次,因为 inflate()
的时候是其指向的布局文件替换掉当前 标签。之后, 原来的布局文件中就没有 标签了。因此,如果多次 inflate()
操作,会报错:ViewStub must have a non-null ViewGroup viewParent
。
还可以设定 Visibility 为 VISIBLE 或 INVISIBLE,也会触发 inflate()
。 但是这里只会在首次使用 setVisibility()
会加载要渲染的布局文件。再次使用只是单纯的设置可见性。
10.MVP和MVVM,谈谈你对它们的了解
(第三章第1题)
11.概说23种设计模式
设计模式概念
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石脉络,如同大厦的结构一样。
即12字真言:设计模式是设计经验的总结
通俗的说,就是解决问题的方法,是前辈们归纳总结出来的便于人们理解使用,增加代码的可维护性、可复用性、可扩展性。
设计模式的几个分类
创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。 结构型模式:把类或对象结合在一起形成一个更大的结构。 行为型模式:类和对象如何交互,及划分责任和算法。
如下图所示:
每个模式分类中有哪些关键点?
- 单例模式: 不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责
- 简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。
- 工厂方法: 定义一个具体功能相关的接口,由子类来决定这么实现。
- 抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类。
- 建造者模式:封装一个复杂对象的构建过程,并可以按步骤构造。
- 原型模式: 将一个对象作为原型,通过复制的方式,克隆出多个和目标实例类似的新实例。
- 适配器模式:将一个类的方法接口转换成客户希望的另外一个接口。
- 组合模式:将对象组合成树形结构以表示“”部分-整体“”的层次结构。
- 装饰模式:动态的给对象添加新的功能。
- 代理模式:为其他对象提供一个代理以便控制这个对象的访问。
- 亨元模式:通过共享技术来有效的支持大量细粒度的对象。
- 外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
- 桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
- 模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
- 解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
- 策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
- 状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
- 观察者模式:对象间的一对多的依赖关系。
- 备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
- 中介者模式:用一个中介对象来封装一系列的对象交互。
- 命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
- 访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
- 责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
- 迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
23种设计模式
1.单例模式
单例模式,它的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。
单例模式具备典型的3个特点:
1、只有一个实例。 2、自我实例化。 3、提供全局访问点。
因此当系统中只需要一个实例对象或者系统中只允许一个公共访问点,除了这个公共访问点外,不能通过其他访问点访问该实例时,可以使用单例模式。
单例模式的主要优点就是节约系统资源、提高了系统效率,同时也能够严格控制客户对它的访问。也许就是因为系统中只有一个实例,这样就导致了单例类的职责过重,违背了“单一职责原则”,同时也没有抽象类,所以扩展起来有一定的困难。其UML结构图非常简单,就只有一个类,如下图:
2.工厂方法模式
作为抽象工厂模式的孪生兄弟,工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,也就是说工厂方法模式让实例化推迟到子类。
工厂方法模式非常符合“开闭原则”,当需要增加一个新的产品时,我们只需要增加一个具体的产品类和与之对应的具体工厂即可,无须修改原有系统。同时在工厂方法模式中用户只需要知道生产产品的具体工厂即可,无须关系产品的创建过程,甚至连具体的产品类名称都不需要知道。虽然他很好的符合了“开闭原则”,但是由于每新增一个新产品时就需要增加两个类,这样势必会导致系统的复杂度增加。其UML结构图:
3.抽象工厂模式
所谓抽象工厂模式就是提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。他允许客户端使用抽象的接口来创建一组相关的产品,而不需要关系实际产出的具体产品是什么。这样一来,客户就可以从具体的产品中被解耦。它的优点是隔离了具体类的生成,使得客户端不需要知道什么被创建了,而缺点就在于新增新的行为会比较麻烦,因为当添加一个新的产品对象时,需要更加需要更改接口及其下所有子类。其UML结构图如下:
4.建造者模式
对于建造者模式而已,它主要是将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。适用于那些产品对象的内部结构比较复杂。
建造者模式将复杂产品的构建过程封装分解在不同的方法中,使得创建过程非常清晰,能够让我们更加精确的控制复杂产品对象的创建过程,同时它隔离了复杂产品对象的创建和使用,使得相同的创建过程能够创建不同的产品。但是如果某个产品的内部结构过于复杂,将会导致整个系统变得非常庞大,不利于控制,同时若几个产品之间存在较大的差异,则不适用建造者模式,毕竟这个世界上存在相同点大的两个产品并不是很多,所以它的使用范围有限。其UML结构图:
5.原型模式
在我们应用程序可能有某些对象的结构比较复杂,但是我们又需要频繁的使用它们,如果这个时候我们来不断的新建这个对象势必会大大损耗系统内存的,这个时候我们需要使用原型模式来对这个结构复杂又要频繁使用的对象进行克隆。所以原型模式就是用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
它主要应用与那些创建新对象的成本过大时。它的主要优点就是简化了新对象的创建过程,提高了效率,同时原型模式提供了简化的创建结构。UML结构图:
模式结构
原型模式包含如下角色:
Prototype
:抽象原型类ConcretePrototype
:具体原型类Client
:客户类
6.适配器模式
在我们的应用程序中我们可能需要将两个不同接口的类来进行通信,在不修改这两个的前提下我们可能会需要某个中间件来完成这个衔接的过程。这个中间件就是适配器。所谓适配器模式就是将一个类的接口,转换成客户期望的另一个接口。它可以让原本两个不兼容的接口能够无缝完成对接。
作为中间件的适配器将目标类和适配者解耦,增加了类的透明性和可复用性。
适配器模式包含如下角色:
Target
:目标抽象类Adapter
:适配器类Adaptee
:适配者类Client
:客户类
7.桥接模式
如果说某个系统能够从多个角度来进行分类,且每一种分类都可能会变化,那么我们需要做的就是讲这多个角度分离出来,使得他们能独立变化,减少他们之间的耦合,这个分离过程就使用了桥接模式。所谓桥接模式就是讲抽象部分和实现部分隔离开来,使得他们能够独立变化。
桥接模式将继承关系转化成关联关系,封装了变化,完成了解耦,减少了系统中类的数量,也减少了代码量。
桥接模式包含如下角色:
Abstraction
:抽象类RefinedAbstraction
:扩充抽象类Implementor
:实现类接口ConcreteImplementor
:具体实现类
8.组合模式
组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。它定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。
虽然组合模式能够清晰地定义分层次的复杂对象,也使得增加新构件也更容易,但是这样就导致了系统的设计变得更加抽象,如果系统的业务规则比较复杂的话,使用组合模式就有一定的挑战了。
模式结构
组合模式包含如下角色:
Component
: 抽象构件Leaf
: 叶子构件Composite
: 容器构件Client
: 客户类
9.装饰模式
我们可以通过继承和组合的方式来给一个对象添加行为,虽然使用继承能够很好拥有父类的行为,但是它存在几个缺陷:
一、对象之间的关系复杂的话,系统变得复杂不利于维护。 二、容易产生“类爆炸”现象。 三、是静态的。在这里我们可以通过使用装饰者模式来解决这个问题。
装饰者模式,动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。虽然装饰者模式能够动态将责任附加到对象上,但是他会产生许多的细小对象,增加了系统的复杂度.
模式结构
装饰模式包含如下角色:
Component
: 抽象构件ConcreteComponent
: 具体构件Decorator
: 抽象装饰类ConcreteDecorator
: 具体装饰类
10.外观模式
我们都知道类与类之间的耦合越低,那么可复用性就越好,如果两个类不必彼此通信,那么就不要让这两个类发生直接的相互关系,如果需要调用里面的方法,可以通过第三者来转发调用。外观模式非常好的诠释了这段话。外观模式提供了一个统一的接口,用来访问子系统中的一群接口。它让一个应用程序中子系统间的相互依赖关系减少到了最少,它给子系统提供了一个简单、单一的屏障,客户通过这个屏障来与子系统进行通信。通过使用外观模式,使得客户对子系统的引用变得简单了,实现了客户与子系统之间的松耦合。但是它违背了“开闭原则”,因为增加新的子系统可能需要修改外观类或客户端的源代码。
外观模式包含如下角色:
Facade
: 外观角色SubSystem
:子系统角色
11.亨元模式
在一个系统中对象会使得内存占用过多,特别是那些大量重复的对象,这就是对系统资源的极大浪费。享元模式对对象的重用提供了一种解决方案,它使用共享技术对相同或者相似对象实现重用。享元模式就是运行共享技术有效地支持大量细粒度对象的复用。系统使用少量对象,而且这些都比较相似,状态变化小,可以实现对象的多次复用。这里有一点要注意:享元模式要求能够共享的对象必须是细粒度对象。享元模式通过共享技术使得系统中的对象个数大大减少了,同时享元模式使用了内部状态和外部状态,同时外部状态相对独立,不会影响到内部状态,所以享元模式能够使得享元对象在不同的环境下被共享。同时正是分为了内部状态和外部状态,享元模式会使得系统变得更加复杂,同时也会导致读取外部状态所消耗的时间过长。
享元模式包含如下角色:
Flyweight
: 抽象享元类ConcreteFlyweight
: 具体享元类UnsharedConcreteFlyweight
: 非共享具体享元类FlyweightFactory
: 享元工厂类
12.代理模式
代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的引用。它使得客户不能直接与真正的目标对象通信。代理对象是目标对象的代表,其他需要与这个目标对象打交道的操作都是和这个代理对象在交涉。
代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的,同时也在一定程度上面减少了系统的耦合度。
代理模式包含如下角色:
Subject
: 抽象主题角色 Proxy
: 代理主题角色 RealSubject
:真实主题角色
13.访问者模式
访问者模式俗称23大设计模式中最难的一个。除了结构复杂外,理解也比较难。在我们软件开发中我们可能会对同一个对象有不同的处理,如果我们都做分别的处理,将会产生灾难性的错误。对于这种问题,访问者模式提供了比较好的解决方案。访问者模式即表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式。同时我们还需要明确一点那就是访问者模式是适用于那些数据结构比较稳定的,因为他是将数据的操作与数据结构进行分离了,如果某个系统的数据结构相对稳定,但是操作算法易于变化的话,就比较适用适用访问者模式,因为访问者模式使得算法操作的增加变得比较简单了。
访问者模式包含如下角色:
Vistor
: 抽象访问者ConcreteVisitor
: 具体访问者Element
: 抽象元素ConcreteElement
: 具体元素ObjectStructure
: 对象结构
14.模板模式
有些时候我们做某几件事情的步骤都差不多,仅有那么一小点的不同,在软件开发的世界里同样如此,如果我们都将这些步骤都一一做的话,费时费力不讨好。所以我们可以将这些步骤分解、封装起来,然后利用继承的方式来继承即可,当然不同的可以自己重写实现嘛!这就是模板方法模式提供的解决方案。
所谓模板方法模式就是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板方法模式就是基于继承的代码复用技术的。在模板方法模式中,我们可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中。也就是说我们需要声明一个抽象的父类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法让子类来实现剩余的逻辑,不同的子类可以以不同的方式来实现这些逻辑。所以模板方法的模板其实就是一个普通的方法,只不过这个方法是将算法实现的步骤封装起来的。
模板方法模式包含如下角色:
AbstractClass
: 抽象类ConcreteClass
: 具体子类
15.策略模式
我们知道一件事可能会有很多种方式来实现它,但是其中总有一种最高效的方式,在软件开发的世界里面同样如此,我们也有很多中方法来实现一个功能,但是我们需要一种简单、高效的方式来实现它,使得系统能够非常灵活,这就是策略模式。
所以策略模式就是定义了算法族,分别封装起来,让他们之前可以互相转换,此模式然该算法的变化独立于使用算法的客户。
在策略模式中它将这些解决问题的方法定义成一个算法群,每一个方法都对应着一个具体的算法,这里的一个算法我就称之为一个策略。虽然策略模式定义了算法,但是它并不提供算法的选择,即什么算法对于什么问题最合适这是策略模式所不关心的,所以对于策略的选择还是要客户端来做。客户必须要清楚的知道每个算法之间的区别和在什么时候什么地方使用什么策略是最合适的,这样就增加客户端的负担。
同时策略模式也非常完美的符合了“开闭原则”,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。但是一个策略对应一个类将会是系统产生很多的策略类。
策略模式包含如下角色:
Context
: 环境类Strategy
: 抽象策略类ConcreteStrategy
: 具体策略类
16.状态模式
在很多情况下我们对象的行为依赖于它的一个或者多个变化的属性,这些可变的属性我们称之为状态,也就是说行为依赖状态,即当该对象因为在外部的互动而导致他的状态发生变化,从而它的行为也会做出相应的变化。对于这种情况,我们是不能用行为来控制状态的变化,而应该站在状态的角度来思考行为,即是什么状态就要做出什么样的行为。这个就是状态模式。
所以状态模式就是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
在状态模式中我们可以减少大块的if…else语句,它是允许态转换逻辑与状态对象合成一体,但是减少if…else语句的代价就是会换来大量的类,所以状态模式势必会增加系统中类或者对象的个数。
同时状态模式是将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。但是这样就会导致系统的结构和实现都会比较复杂,如果使用不当就会导致程序的结构和代码混乱,不利于维护。
状态模式包含如下角色:
Context
: 环境类State
: 抽象状态类ConcreteState
: 具体状态类
17.观察者模式
何谓观察者模式?观察者模式定义了对象之间的一对多依赖关系,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。
在这里,发生改变的对象称之为观察目标,而被通知的对象称之为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,所以么可以根据需要增加和删除观察者,使得系统更易于扩展。所以观察者提供了一种对象设计,让主题和观察者之间以松耦合的方式结合。
观察者模式包含如下角色:
Subject
: 目标ConcreteSubject
: 具体目标Observer
: 观察者ConcreteObserver
: 具体观察者
18.备忘录模式
后悔药人人都想要,但是事实却是残酷的,根本就没有后悔药可买,但是也不仅如此,在软件的世界里就有后悔药!备忘录模式就是一种后悔药,它给我们的软件提供后悔药的机制,通过它可以使系统恢复到某一特定的历史状态。
所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它实现了对信息的封装,使得客户不需要关心状态保存的细节。保存就要消耗资源,所以备忘录模式的缺点就在于消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
备忘录模式包含如下角色:
Originator
: 原发器Memento
: 备忘录Caretaker
: 负责人
19.中介者模式
租房各位都有过的经历吧!在这个过程中中介结构扮演着很重要的角色,它在这里起到一个中间者的作用,给我们和房主互相传递信息。在外面软件的世界里同样需要这样一个中间者。在我们的系统中有时候会存在着对象与对象之间存在着很强、复杂的关联关系,如果让他们之间有直接的联系的话,必定会导致整个系统变得非常复杂,而且可扩展性很差!在前面我们就知道如果两个类之间没有不必彼此通信,我们就不应该让他们有直接的关联关系,如果实在是需要通信的话,我们可以通过第三者来转发他们的请求。同样,这里我们利用中介者来解决这个问题。
所谓中介者模式就是用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。在中介者模式中,中介对象用来封装对象之间的关系,各个对象可以不需要知道具体的信息通过中介者对象就可以实现相互通信。它减少了对象之间的互相关系,提供了系统可复用性,简化了系统的结构。
在中介者模式中,各个对象不需要互相知道了解,他们只需要知道中介者对象即可,但是中介者对象就必须要知道所有的对象和他们之间的关联关系,正是因为这样就导致了中介者对象的结构过于复杂,承担了过多的职责,同时它也是整个系统的核心所在,它有问题将会导致整个系统的问题。所以如果在系统的设计过程中如果出现“多对多”的复杂关系群时,千万别急着使用中介者模式,而是要仔细思考是不是您设计的系统存在问题。
Mediator
: 抽象中介者ConcreteMediator
: 具体中介者Colleague
: 抽象同事类ConcreteColleague
: 具体同事类
20.迭代器模式
对于迭代在编程过程中我们经常用到,能够游走于聚合内的每一个元素,同时还可以提供多种不同的遍历方式,这就是迭代器模式的设计动机。在我们实际的开发过程中,我们可能会需要根据不同的需求以不同的方式来遍历整个对象,但是我们又不希望在聚合对象的抽象接口中充斥着各种不同的遍历操作,于是我们就希望有某个东西能够以多种不同的方式来遍历一个聚合对象,这时迭代器模式出现了。
何为迭代器模式?所谓迭代器模式就是提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露其内部的表示。迭代器模式是将迭代元素的责任交给迭代器,而不是聚合对象,我们甚至在不需要知道该聚合对象的内部结构就可以实现该聚合对象的迭代。
通过迭代器模式,使得聚合对象的结构更加简单,它不需要关注它元素的遍历,只需要专注它应该专注的事情,这样就更加符合单一职责原则了。
迭代器模式包含如下角色:
Iterator
: 抽象迭代器ConcreteIterator
: 具体迭代器Aggregate
: 抽象聚合类ConcreteAggregate
: 具体聚合类
21.解释器模式
所谓解释器模式就是定义语言的文法,并且建立一个解释器来解释该语言中的句子。解释器模式描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发的编译器中。它描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
解释器模式包含如下角色:
AbstractExpression
: 抽象表达式TerminalExpression
: 终结符表达式NonterminalExpression
: 非终结符表达式Context
: 环境类Client
: 客户类
22.命令模式
有些时候我们想某个对象发送一个请求,但是我们并不知道该请求的具体接收者是谁,具体的处理过程是如何的,们只知道在程序运行中指定具体的请求接收者即可,对于这样将请求封装成对象的我们称之为命令模式。所以命令模式将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。同时命令模式支持可撤销的操作。
命令模式可以将请求的发送者和接收者之间实现完全的解耦,发送者和接收者之间没有直接的联系,发送者只需要知道如何发送请求命令即可,其余的可以一概不管,甚至命令是否成功都无需关心。同时我们可以非常方便的增加新的命令,但是可能就是因为方便和对请求的封装就会导致系统中会存在过多的具体命令类。
命令模式包含如下角色:
Command
: 抽象命令类ConcreteCommand
: 具体命令类Invoker
: 调用者Receiver
: 接收者Client
:客户类
23.责任链模式
职责链模式描述的请求如何沿着对象所组成的链来传递的。它将对象组成一条链,发送者将请求发给链的第一个接收者,并且沿着这条链传递,直到有一个对象来处理它或者直到最后也没有对象处理而留在链末尾端。
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止,这就是职责链模式。在职责链模式中,使得每一个对象都有可能来处理请求,从而实现了请求的发送者和接收者之间的解耦。同时职责链模式简化了对象的结构,它使得每个对象都只需要引用它的后继者即可,而不必了解整条链,这样既提高了系统的灵活性也使得增加新的请求处理类也比较方便。但是在职责链中我们不能保证所有的请求都能够被处理,而且不利于观察运行时特征。
职责链模式包含如下角色:
Handler
: 抽象处理者ConcreteHandler
: 具体处理者Client
: 客户类
12.JVM内存模型
- Java堆(新生代(Eden,from,to);老年代(15岁,大对象))
- 方法区(永久代;元空间)
- 虚拟机栈(栈帧(局部变量表、操作数栈、动态连接、方法返回))
- 本地方法栈(和虚拟机栈类似,但是是为native服务)
- 程序计数器(cpu上下文切换)
13.垃圾回收gc讲一讲
①为什么要回收
我们知道,程序在运行的时候,为了提高性能,大部分数据都是会加载到内存中进行运算的,有些数据是需要常驻内存中的,但是有些数据,用过之后便不会再需要了,我们称这部分数据为垃圾数据。举个例子:有些数据new了之后,经过两行的运算就结束生命周期了,这个就算是垃圾数据。
为了防止内存被使用完,我们需要将这些垃圾数据进行回收,即需要将这部分内存空间进行释放,所以Java 虚拟机(JVM)提供了一种自动回收内存的机制 (GC)。
②简介
本文所介绍的垃圾回收(GC)是由 Java 虚拟机(JVM)垃圾回收器提供的一种对内存回收的一种机制,它一般会在内存空闲或者内存占用过高的时候对那些没有任何引用的对象不定时地进行回收。
所以由上述讨论我们很容易就会有如下的疑问,下文也会依照这几点疑问来进行深入探讨:
③JVM 内存模型
JVM 内存大致分为 线程私有区域 和 线程共享区域,并且其主要由5个区域组成,见下图:
由上图可以看出,右边3个:虚拟机栈、本地方法栈和程序计数器,这三个区域是线程私有的。比如栈帧的生命周期是和线程关联的,即随线程而生,随线程而死。
(栈帧:也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构,每一次函数的调用,都会在调用栈(call stack)上维护一个独立的栈帧(stack frame)每个独立的栈帧一般包括:
- 函数的返回地址和参数
- 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量
- 函数调用的上下文
虚拟机栈其实就是用来描述 Java 方法执行的,所以每个方法执行的时候都会创建一个栈帧,每个栈帧都包含:局部变量、操作数栈、动态链接、方法出口,当方法执行完成之后,对应的栈帧便会出栈。所以它的内存分配是具备确定性的。
虚拟机栈分配的内存具有确定性。私有线程区也就是右边3块的内存不需要太过关注,线程没了就清理掉了。
左边两块:方法区和堆内存,则是我们需要重点关注的对象。因为这两个区域主要存放对象、数组等不具有确定性的数据,例如创建对象,每个方法运行的过程中创建的对象的数量是不确定的,即占用的内存是不确定的,可能不需要创建对象,也可能会创建很多对象,所以我们需要一套合理的内存管理机制来对这两个区域进行维护,因此,垃圾回收就应运而生了,并且这两个区域也是垃圾回收器进行垃圾回收的最重要的内存区域。
我们再来对堆内存和方法区进行一下划分,因为 JVM 是采用分代回收的算法,即根据对象的生命周期进行区分并进行分代存储和回收,其主要分为年轻代、老年代、持久代,见下图:
堆内存主要由年轻代和老年代组成,而方法区主要存储持久代的数据,详细的细节在下文讲回收算法的时候会细说。
注意:从 JDK 1.8 开始,永久代已经被移除了,取而代之的是元空间(Meta Space),它和服务器的内存相关联,本文暂不赘述。
④内存中的垃圾
定义:程序在运行过程中会创建对象,但是当方法执行完成或当这个对象使用完毕之后,它便被定义为了“垃圾”。
判定一个对象是否是“垃圾”,即判定一个对象的存活与否,常见的算法有两种:引用计数法 和 根搜索算法。
引用计数算法(Reference Counting Collector) 一个对象被创建之后,系统会给这个对象初始化一个引用计数器,当这个对象被引用了,则计数器 +1,而当该引用失效后,计数器便 -1,直到计数器为 0,意味着该对象不再被使用了,则可以将其进行回收了。
这种算法其实很好用,判定比较简单,效率也很高,但是却有一个很致命的缺点,就是它无法避免循环引用,即两个对象之间循环引用的时候,各自的计数器始终不会变成 0,所以 引用计数算法 只出现在了早期的 JVM 中,现在基本不再使用了。
根搜索算法(Tracing Collector) 根搜索算法的中心思想,就是从某一些指定的根对象(GC Roots)出发,一步步遍历找到和这个根对象具有引用关系的对象,然后再从这些对象开始继续寻找,从而形成一个个的引用链(其实就和图论的思想一致),然后不在这些引用链上面的对象便被标识为引用不可达对象,也就是我们说的“垃圾”,这些对象便需要回收掉。这种算法很好地解决了上面 引用计数算法 的循环引用的问题了。
算法的核心思想是很简单的,就是标记不可达对象,然后交由 GC 进行回收,但是有一个点是很重要的,那就是 何为根对象(GC Roots)?
根对象,一般有如下几种:
- 虚拟机栈中引用的对象(栈帧中的本地变量表);
- 方法区中常量引用的对象;
- 方法区中静态属性引用的对象;
- 本地方法栈中 JNI(Native 方法)引用的对象;
- 活跃线程。
但其实,上述算法只是一个算法的中心思想,实际执行过程是比这个复杂的,另外,GC 判断对象是否可达其实看的还是强引用。
- 进行根搜索的时候,是需要暂停所有线程的,即执行一次 STW(Stop The World),最主要的目的是防止上述的对象图在算法运行的过程中有变化从而影响算法的准确性。
- 线程暂停的时间长短,取决于对象的多少,和堆内存的大小无关。
- 宣告一个对象的“死亡”其实不仅仅通过上述的算法计算,而是需要经历两次的标记,本文暂不进行赘述。
回收算法 除了需要上文研究的标记“垃圾对象”的算法,我们也需要“清理垃圾”的 回收算法。
常用的回收算法一般有:标记-清除算法、标记-整理算法、复制算法,以及系统自动进行判定使用的 适应性算法。
1、标记 - 清除算法(Tracing Collector) 标记-清除 算法是最基础的收集算法,它是由 标记 和 清除 两个步骤组成的。
标记的过程其实就是上面的 根搜索算法 所标记的不可达对象,当所有的待回收的“垃圾对象”标记完成之后,便进行第二个步骤:统一清除。
该算法的优点是当存活对象比较多的时候,性能比较高,因为该算法只需要处理待回收的对象,而不需要处理存活的对象。
但是缺点也很明显,就是在执行完 标记-整理 之后,由于将“垃圾对象”回收掉了,所以原本连续使用的内存块便会变得不连续,这样会导致内存块上面会出现很多小单元的内存区域,这些小单元的内存区域只能够存放比较小的对象,而比较大的对象是无法直接存储的。
即原本空闲 1M 的内存区域,有可能会出现无法直接存放 0.9M 大小的对象。
2、标记 - 整理算法(Compacting Collector) 上述的 标记-清除 算法会产生内存区域使用的间断,所以为了将内存区域尽可能地连续使用, 标记-整理 算法应运而生。
标记-整理 算法也是由两步组成,标记 和 整理。
第一步的 标记 动作也是使用的 根搜索算法,但是在标记完成之后的动作却和 标记-清除算法 天壤之别,该算法并不会直接清除掉可回收对象 ,而是让所有的对象都向一端移动,然后将端边界以外的内存全部清理掉。
该算法所带来的最大的优势便是使得内存上面不会再有碎片问题,并且新对象的分配只需要通过简单的指针碰撞便可完成。
3、复制算法(Copying Collector) 无论是标记-清除算法还是垃圾-整理算法,都会涉及句柄的开销或是面对碎片化的内存回收,所以,复制算法 出现了。
复制算法将内存区域均分为了两块(记为S0和S1),而每次在创建对象的时候,只使用其中的一块区域(例如S0),当S0使用完之后,便将S0上面存活的对象全部复制到S1上面去,然后将S0全部清理掉。
复制算法的优势是: ① 不会产生内存碎片; ② 标记和复制可以同时进行; ③ 复制时也只需要移动栈顶指针即可,按顺序分配内存,简单高效; ④ 每次只需要回收一块内存区域即可,而不用回收整块内存区域,所以性能会相对高效一点。
但是缺点也是很明显的:可用的内存减小了一半,存在内存浪费的情况。
所以 复制算法 一般会用于对象存活时间比较短的区域,例如 年轻代,而存活时间比较长的 老年代 是不适合的,因为老年代存在大量存活时间长的对象,采用复制算法的时候会要求复制的对象较多,效率也就急剧下降,所以老年代一般会使用上文提到的 标记-整理算法。
4、适应性算法(Adaptive Collector) 适应性算法 其实不是一种单独的回收算法,他只是一种智能选择回收算法的机制,也就是该算法会根据堆内存具体的使用情况而自动选用更适合当前情况的回收算法。
5、分代回收 分代回收 并不是一种垃圾回收算法,它是上述各种垃圾回收算法的一个落地应用方案。
因为上述各个算法都有各自的优势,我们在内存的使用过程中,有些对象存活时间长,有些对象存活时间短,有些对象甚至一直存活着,所以根据对象的存活周期,我们将内存区域分为三大块:年轻代、老年代 和 永久代,并且年轻代也继续细分为:Eden区、S0 和 S1。
1、各个内存区域的内存大小可以见上文中的内存模型图,当然,我们也可以给 JVM 传递参数来进行调整,这些内容本文也暂不赘述。 2、 Eden : S0 : S1 的默认比例为 8:1:1,为什么这么设计呢?其实 IBM 有专门的研究表明,年轻代中 98% 的对象都是朝生夕死的,所以只需要划分为一个较大的 Eden 区和两个较小的 Survivor 区即可,而且这样做的好处是只有 10% 的 Survivor 区会被浪费掉,这也是可以接受的。
下面简单介绍下各个内存区的 GC 过程:
- 对象首次创建进行内存分配的时候,首先会放置在 Eden 区,当 Eden 区放满了或者当该对象太大无法放进 Eden 区的时候,此时会对年轻代(Eden区 和 S0)进行一次 GC,将幸存下来的对象放置在 S1,然后清空掉 Eden区和 S0 区;(此时年轻代采用的是 复制算法)
- 在上面第一步中对年轻代进行垃圾回收的时候,同时会对幸存的对象进行标记,统计每个幸存对象经历的 GC 次数;
- 当 S1 区满了之后,或者年轻代的对象经历过指定次数的 GC 之后,这部分对象会被放置到老年代之中;
- 当老年代也满了之后,便会对老年代进行一次 GC;(老年代采用的是 标记-整理算法)
⑤垃圾回收器
好了,上文介绍过了 “垃圾”的识别算法 和 “垃圾”的回收算法,那么这些算法的执行者是谁呢?就是下文介绍的 垃圾回收器(GC) 了。
垃圾回收器的类型 在 Java 语言中,垃圾回收器按照执行机制来进行划分,主要分为四种类型:
- 串行垃圾回收器(Serial Garbage Collector);
- 并行垃圾回收器(Parallel Garbage Collector);
- 并发标记扫描垃圾回收器(CMS Garbage Collector);
- G1垃圾回收器(G1 Garbage Collector)。
上述四种垃圾回收器都是有各自的优缺点的,我们可以通过向 JVM 传递参数来指定其中一款垃圾回收器。
1、串行垃圾回收器(Serial Garbage Collector) 串行垃圾回收器会暂停所有的应用程序线程,并采用单独的的线程进行 GC。
适用于单 CPU、并且对应用程序的暂停时间要求不高的情况,所以不太适合当前的生产环境。
2、并行垃圾回收器(Parallel Garbage Collector) 并行垃圾回收器是 JVM 默认的垃圾回收器,相较于串行垃圾回收器而言性能稍有提升,它也是需要暂停所有的应用程序线程的,但是区别是它会使用多线程进行 GC。
所以并行垃圾回收器适用于多 CPU 的服务器、并且能接受短暂的应用暂停的程序。
3、并发标记扫描垃圾回收器(CMS Garbage Collector) CMS 回收器也是一种并行的垃圾回收器,它会采用多线程来进行扫描堆内存,标记需要清理的对象并将这些对象清理掉。
但是 CMS 它需要更多的 CPU 来保证程序的吞吐量,并且它保证了最短的回收停顿时间,所以,在服务器允许的情况下,为了达到更到的性能,我们应该使用 CMS 来代替默认的 并行垃圾回收器。
4、G1 垃圾回收器(G1 Garbage Collector) G1 垃圾回收器是在 JDK1.7 中才正式引入的一款垃圾回收器,“科技在进步,所以一般越是先进的技术一般会更好用并且会替代陈旧的技术”,好了,玩笑归玩笑,但是 G1 的引入,目的就是为了取代 CMS 的。
不要被上面 G1 的示意图误导, G1 并没有将内存进行物理划分,它只是将堆内存划分为一个个的 Region,但是也是属于分代垃圾回收器,G1 仍然会区分年轻代和老年代,并且年轻代仍然会有 Eden 区和 Survivor 区。
这么做的目的是保证 G1 回收器在有限的时间内可以获得尽可能高的回收效率。
2、HotSpot 虚拟机(HotSpot VM)提供的几种垃圾收集器 HotSpot VM 提供了 7 种垃圾收集器,分别为:
- Serial
- PraNew
- Parallel Scavenge
- Serial Old
- Parallel Old
- CMS
- G1
其中,1、2、3 种适合年轻代内存区的垃圾回收,4、5、6种适合老年代内存区的垃圾回收,并且它们之间是两两组合来进行使用的,详见下图:
⑥垃圾回收的时机
垃圾回收分为两种,Full GC 和 Scavenge GC。
Full GC 发生在整个堆内存中,而 Scavenge GC 仅仅发生在年轻代的 Eden 区,所以我们应该尽可能地减少 Full GC 的次数,当然,对于 JVM 的调优,很多情况下也是在想办法对 Full GC 进行调优。
因为 GC 是可能会对应用程序造成影响的,所以触发 GC 也是有一定的条件的,例如:
- 当应用程序空闲时,GC 有可能会被调用,因为 GC 运行线程的优先级是相对较低的,所以当线程忙的时候,它是不会运行的,当然,内存不足的情况除外;
- 堆内存不足的时候,GC 会被调用。例如创建对象的时候,若此时内存不足,则会触发 GC 用来给这个对象分配合适的内存,当进行完一次 GC 之后内存还是不足,则会继续进行第二次 GC,若第二次 GC 之后内存还是不足,则一般会提示 “out of memory”异常;
小 Tip: System.gc() 方法会显示触发 Full GC,但是它只是对 JVM 的一个 GC 请求,至于何时触发,还是由 JVM 自行判断的。
GC 的调用开销是比较大的,所以我们需要有针对性地进行调优,一般有如下方案:
- 不要显式调用 System.gc()。此函数虽然是建议 JVM 进行 GC,但很多情况下它会触发 GC,从而增加 GC 的频率;
- 尽量减少临时对象的使用。在方法结束后,临时对象便成为了垃圾,所以减少临时变量的使用就相当于减少了垃圾的产生,从而减少了GC的次数;
- 对象不用时最好显式置为 Null。一般而言,为 Null 的对象都会被作为垃圾处理,所以将不用的对象显式地设为 Null 有利于 GC 收集器对垃圾的判定;
- 尽量使用 StringBuilder 来代替 String 的字符串累加。因为 String 的底层是 final 类型的数组,所以 String 的增加其实是建了一个新的 String,从而产生了过多的垃圾;
- 允许的情况下尽量使用基本类型(如 int)来替代 Integer 对象。因为基本类型变量比相应的对象占用的内存资源会少得多;
- 合理使用静态对象变量。因为静态变量属于全局变量,不会被 GC 回收;
⑦其它
JVM 的 GC,它就像能看到也能感受到的真实存在的事物,但是当我们去伸手够它的时候,此时它又是虚无缥缈般的存在,处理它的时候还需要格外地谨慎。
因为它的不确定性,所以我们不应该去假定 GC 触发的时间,也不要去使用类似 System.gc() 这样显示调用 GC 的方法,这些都是得不偿失的。
最需要注意的是我们的编程习惯和编程态度,良好的编程习惯能够帮助我们规避掉很多内存方面的问题,包括但不仅限于内存泄露等。
最后,由于垃圾回收器众多,在特定的情况下,我们是可以指定使用垃圾回收器的类型的,例如使用:-X:+UseG1GC 来指定使用 G1 垃圾回收器。
一共50W字的文档,面试专题12W字只是一小部分,字数限制,分几篇更。
关注公众号:Android苦做舟
提前解锁 《整套50W字Android体系PDF》,让学习更贴近未来实战。
总共囊括:
1.腾讯Android开发笔记(33W字)
2.2022最新Android十一位大厂面试专题(12W字)
3.音视频经典面试题(6W字)
4.Jetpack全家桶
5.Android 性能监控框架Matrix
6.JVM
7.车载应用开发
转载自:https://juejin.cn/post/7147970341246074887