Android面试笔记

记录下最近面试准备阶段做的笔记,也对一些知识点进行总结。内容整体来说偏重点,部分基础知识点未覆盖。如果想一些更详细的面试指南可以看下这篇。
https://blog.csdn.net/qq_29966203/article/details/105455615

Android

NDK

https://blog.csdn.net/afei__/article/details/81290711

ClassLoader

https://juejin.cn/post/6844903621109252109

Android ClassLoader主要负责将dex文件中的类加载到虚拟机中。

ClassLoader是一个抽象类,子类有BootClassLoader,它是ClassLoader的一个内部类,主要负责加载Framework层级需要的类。

另一个子类是BaseDexClassLoader,而PathClassLoader、DexClassLoader继承自BaseDexClassLoader。

PathClassLoader加载系统类和应用程序的类。如果是加载应用程序类,则会加载data/app/目录下的dex文件以及包含dex的apk文件或jar文件。

DexClassLoader可以加载自定义的dex文件以及包含dex的apk文件或jar文件,也支持从SD卡进行加载。

创建ClassLoader时可以传入一个父加载器,如果不传会默认使用BootClassLoader作为父加载器,而PathClassLoader的父加载器这是BootClassLoader。
ClassLoader中重要的方法是loadClass(String name),采用双亲委派的方式加载对应的类。

双亲委派:

  1. 从当前ClassLoader出发查看是否已经加载过此类,如果加载过直接返回,如果没加载过,委托父加载器进行加载
  2. 父加载会执行相同的操作,直到根加载器
  3. 如果根加载器加载过直接返回,如果没加载过,到相应路劲加载,加载成功者返回;如果加载失败则返回子加载器进行加载
  4. 子加载重复相同的操作,知道当前ClassLoader,如果加载成功直接返回,加载失败抛出ClassNotFoundException

虚拟机

https://blog.csdn.net/qq_41701956/article/details/81664921

JVM内存模型、垃圾回收机制

https://github.com/interviewandroid/AndroidInterView/blob/master/android/traked.md

binder源码:

https://zhuanlan.zhihu.com/p/35519585

binder是Android提供的进程间通信的方式。一个进程空间分为用户空间和内核空间,由于进程隔离,进程间的用户空间是不共享的,所以传递数据只能通过内核空间,Binder提供实现这一过程的驱动。Binder基于C/S框架,是连接client进程、service进程、ServiceManager进程的桥梁。

Binder IPC实现原理

  1. 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
  2. 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
  3. 发送方进程通过系统调用 copyfromuser() 将数据 copy到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
    在这里插入图片描述

通信过程:

  1. 首先,一个进程使用 BINDERSETCONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
  2. Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
  3. Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。

image
Binder 通信中的代理模式:
我们已经解释清楚 Client、Server 借助 Binder 驱动完成跨进程通信的实现机制了,但是还有个问题会让我们困惑。A 进程想要 B 进程中某个对象(object)是如何实现的呢?毕竟它们分属不同的进程,A 进程 没法直接使用 B 进程中的 object。

前面我们介绍过跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。

当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。
image

handler源码分析(完成)

Handler消息机制主要有三部分构成:

  • Handler:负责发生消息,处理消息
  • MessageQueue:消息队列,用于管理消息
  • Looper:循环从消息队列中取消息,交由handler处理

一般在创建Handler之前会调用Looper.prepare()为当前线程创建Looper,在Looper中会创建一个消息队列,然后创建Handler,可以指定Looper或获取当前进程的Looper,最后会调用Looper.loop()开启循环从消息队列中取消息。如果消息队列中有消息就交由handler进行处理,如果没消息就会阻塞或者delay时间没到就会阻塞等待delay时间,当delay时间到了或者有新的消息进行唤醒。Handler发送消息就是将消息按delay时间的先后插入队列。

https://mp.weixin.qq.com/s/vCnftbD3z07X79gHj30Kiw

性能优化

https://juejin.im/post/6844903943558938638

https://jsonchao.github.io/2019/12/29/%E6%B7%B1%E5%85%A5%E6%8E%A2%E7%B4%A2Android%E5%86%85%E5%AD%98%E4%BC%98%E5%8C%96/

包括四个部分内存优化、布局优化、启动速度优化、电量优化

内存优化:

内存主要的问题有内存抖动、内存泄漏、内存溢出

内存抖动

频繁创建回收对象,造成频繁GC,GC会阻塞造成卡顿。一般使用Profiler中Memory或CPU模块,即可找到内存抖动的地方。重点查看循环或频繁调用的地方。

内存泄漏

对象被持有导致无法释放或不能按照对象正常的生命周期进行释放。典型场景:

  1. 经常出现static变量(使用弱引用来解决)
  2. 线程(使用弱引用来解决)
  3. 内部类(静态内部类+弱引用来解决)
  4. Handler(静态内部类+弱引用来解决)
  5. 资源未及时关闭,像Cursor、File、Stream
  6. 系统服务、监听器未注销/移除

常用工具:profiler、LeakCanary来进行检查

内存溢出

OOM可以大致归为以下3类:

  1. 线程数太多
  2. 打开太多文件
  3. 内存不足

当APP占用的内存加上我们申请的内存资源超过了虚拟机的最大内存时就会抛出。主要出现再大量的图片、音频、视频处理。针对bitmap,控制图片的大小,压缩大图,高效处理,加载合适属性的图片

https://mp.weixin.qq.com/s/GFnWqYmEih6LMBLU52jTmw

UI优化

过深的View层级会导致测量阶段耗时较多,复杂的依赖和约束关系可能导致多次测量。整体的一个原则就是,尽量减少子view的数量和layout的嵌套。常用的方式:

  1. 采用ConstraintLayout减少布局嵌套
  2. ViewStub延迟初始化
  3. include布局时善用merge标签,减少嵌套
  4. 减少Overdraw过度绘制
卡顿优化

页面卡顿当然和内存、ui布局有很大关系。比如内存过高频繁触发GC;布局层级过深,子view太多,导致绘制耗时。都会导致页面卡顿,所以卡顿优化也不能忽视内存优化和ui优化。另外可能导致卡顿的地方有:

  1. 在主线程中做耗时操作,轻微的可能出现页面卡顿,严重导致出现ANR(ANR出现:activity如果5s内无响应事件(屏幕触摸事件或者键盘输入事件)。BroadcastReceiver如果在10s内无法处理完成。Service如果20s内无法处理完成。)
  2. 开线程太多,导致主线程的时间片被压缩
启动速度优化

https://juejin.cn/post/6844903958113157128

启动分为冷启动和热启动,冷启动需要启动进程、application、Activity初始化等。所以更耗时。优化方向:

  1. 主线程不要做耗时操作
  2. Application的创建过程中尽量少的进行耗时操作;能延迟初始化的可以进行延迟;可以使用三方框架如Jetpack StartUp来优化初始化逻辑
  3. 减少布局的层次,并且生命周期回调的方法中尽量减少耗时的操作。
apk大小优化

主要措施:

  1. 移除未使用的资源。使用lint检查无效资源然后进行删除
  2. 减少支持密度,一般就xxhdpi、hdpi
  3. 尽量使用shape、矢量图
  4. 使用webp

Android系统启动过程

http://gityuan.com/2016/02/01/android-booting/

Activity启动过程

https://blog.csdn.net/u012267215/article/details/91406211

  1. Launcher通知AMS要启动新的Activity(在Launcher所在的进程执行)
  • Activity.startActivity会调用Instrumentation.execStartActivity。Instrumentation是一个工具类,一个app只有一个实例,用来创建activity以及生命周期的调用。
  • Instrumentation通过binder调用ActivityManagerService进行Activity创建
  1. ActivityManagerService接收到应用进程创建Activity的请求之后会执行初始化操作,解析启动模式,保存请求信息等一系列操作
  2. ActivityManagerService保存完请求信息之后会将当前系统栈顶的Activity执行onPause操作,并且IApplication进程间通讯告诉应用程序继承执行当前栈顶的Activity的onPause方法;
  3. 执行完栈顶的Activity的onPause方法之后会通过Binder执行进程间通讯告诉ActivityManagerService,栈顶Actiity已经执行完成onPause方法
  4. ActivityManagerService会继续执行启动Activity的逻辑,这时候会判断需要启动的Activity所属的应用进程是否已经启动,若没有启动则首先会启动这个Activity的应用程序进程;
  5. ActivityManagerService会通过socket与Zygote继承通讯,并告知Zygote进程fork出一个新的应用程序进程,然后执行ActivityThread的mani方法;
  6. 在ActivityThead.main方法中执行初始化操作,创建一个Handler,并执行loop开始循环,然后通知ActivityManagerService执行进程初始化操作;
  7. ActivityManagerService将执行创建Activity的通知告知ActivityThread,然后Instrumentation通过反射机制创建出Activity对象,并执行Activity的onCreate方法,onStart方法,onResume方法;
  8. ActivityThread执行完成onResume方法之后告知ActivityManagerService onResume执行完成,开始执行栈顶Activity的onStop方法;

Launcher.startActivity最终都会通过binder通知AMS创建Activity,AMS进行创建任务栈等工作。然后判断应用程序进程是否存在,不存在AMS通过Socket向Zygote请求创建进程。
进程创建时同时会创建Binder线程池(这样进程就可以进行跨进程通信),和运行ActivityThread,创建ApplicationThread、Looper、Handler并开启消息循环。ApplicationThread是一个binder,运行在binder线程中。应用程序创建完成后会通知AMS,AMS在通过ApplicationThread发生创建Activity的消息,最后通过Handler发生到主线程。首先通过反射创建Activity->初始化上下文->创建applicktion->执行applicktion生命周期->activity.attach->执行activity生命周期。

view绘制流程及原理

Activity.setContentView会直接调用window.setContentView,window实际上是PhoneWindow对象,再activity.attach中创建。window.setContentView会先创建一个DecorView是一个framelayout,然后再里面添加contentview,titleview,setContentView会放在contentview中。WindowManager.add(DecorView)是会创建RootViewImpl,有内部类W、RootViewHandler,W类是一个Binder主要接受WMS发送的事件,然后通过handler切换到主线程执行。接受到显示事件时,最后会调用performTraversals然后performMeasure、performLayout、performDraw。

https://www.jianshu.com/p/0d00cb85fdf3

  • 以Activity为例,setContentView其实是调用PhoneWindow.setContentView,再内部会创建一个DecorView,
    • DecorView是窗口的根容器,setContentView会被添加到DecorView的ContentView中。在ActivityThread的handleResumeActivity()方法中,会调用WindowManager的addView()方法,
    • 将把DecorView添加到WindowManagerGlobal中,并会创建ViewRootImpl,事件传递、view绘制刷新都是通过ViewRootImpl来实现的,通过ViewRootImpl.setView方法把ViewRootImpl和DecorView关联起来,并且调用ViewRootImpl.requestLayout,发起首次绘制。其实调用
    • requestLayout并不会立即进行绘制,而是会监听屏幕刷新信号,在刷新下一帧的时候才会进入绘制流程。View的整个绘制流程可以分为measure、layout、draw
    • 三个阶段:
    • measure阶段会递归遍历子view,计算出控件树中的各个控件的显示大小
    • layout阶段会递归遍历子view,确定出控件树中的各个控件的显示位置
    • draw阶段会先绘制背景,通过onDraw()绘制自身内容,通过dispatchDraw()绘制子View;
    • 最后通过gpu渲染,放入显示缓存区中,显示设备按固定刷新频率从显示缓存区中取出数据进行显示

事件传递

https://www.jianshu.com/p/e99b5e8bd67b

KeyEvent分发

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FBmR6KGI-1651902982150)(20200103181956561.jpeg)]

  1. 起点在ViewRootImpl接收到native发送的事件后调用DecorView.dispatchKeyEvent->Activity.dispatchKeyEvent->PhoneWindow.superDispatchKeyEvent
  2. PhoneWindow.superDispatchKeyEvent->DecorView.superDispatchKeyEvent->ViewGroup.dispatch
  3. 循环调用具有焦点的View,调用View.dispatchKeyEvent
  4. View如果设置了OnKeyListener,调用OnKeyListener.onKey,返回true进行消费停止传递,返回false调用OnKeyDown、OnKeyUp
  5. OnKeyDown、OnKeyUp返回true进行消费停止传递,返回false回滚到Activity执行OnKeyDown、OnKeyUp
  6. OnKeyDown、OnKeyUp返回true进行消费,返回false进行焦点分发
焦点分发

焦点分发起点在ViewRootImpl中,第一是在首次布局后调用DecorView.restoreDefaultFocus()。第二在按键分发后,如果事件都未消费就会进行焦点分发

DecorView.restoreDefaultFocus()

  1. DecorView.restoreDefaultFocus->ViewGroup.requestFocus(View.FOCUS_DOWN,null)

  2. ViewGroup.requestFocus根据焦点分发策略,是当前优先还是子View优先

  3. 如果是当前优先调用View.requestFocus将当前view设置未焦点

  4. 如果是子View优先根据子view添加顺序调用requestFocus找到首个可以获取焦点的view设置焦点

按键分发

  1. 根据按键获取按键方向

  2. 获取当前焦点View,如果有焦点View调用焦点View的focusSearch

  3. view中focusSearch会循环调用mParent.focusSearch,最后会调用到ViewRootImpl.focusSearch

  4. ViewRootImpl.focusSearch使用FocusFinder单例FocusFinder.getInstance().findNextFocus寻找下一个获取焦点的view

  5. FocusFinder首先根据当前焦点View自定义的焦点查找规则,就是再xml中设置的nextFocusLeft、nextFocusRight获取View

  6. 自定义的焦点查找规则未获取下一个焦点View,调用addFocusables将可以获取焦点的view添加到列表中,根据算法从可获取焦点的view列表中选择按键方向最近的View作为下一个焦点veiw

  7. 最后调用下一个焦点View.requestFocus来获取焦点,依次调用mParent.requestChildFocus,设置mFocused,mFocused是当前包含焦点view的ViewGroup或焦点View

  8. 回调onFocusChanged

RecyclerView

缓存机制

四级缓存,Scrap 缓存列表、cache缓存列表、用户缓存(ViewCacheExtension) 、RecyclerViewPool。Scrap 缓存列表(mChangedScrap、mAttachedScrap)mChangedScrap 和 mAttachedScrap 只在布局阶段使用,其他时候它们是空的。ViewCacheExtension是由开发者设置的,默认情况下为空,一般我们也不会设置。我们一般能控制的就是cache缓存和RecyclerViewPool。

mCachedViews:用于保存最新被移除(remove)的ViewHolder,已经和RecyclerView分离的视图;它的作用是滚动的回收复用时如果需要新的ViewHolder时,精准匹配(根据position/id判断)是不是原来被移除的那个item;如果是,则直接返回ViewHolder使用,不需要重新绑定数据;如果不是则不返回,再去mRecyclerPool中找holder实例返回,并重新绑定数据。这一级的缓存是有容量限制的,最大数量为2。

RecyclerPool:是一个终极回收站,真正存放着被标识废弃(其他池都不愿意回收)的ViewHolder的缓存池,如果上述mAttachedScrapmChangedScrapmCachedViewsmViewCacheExtension都找不到ViewHolder的情况下,就会从mRecyclerPool返回一个废弃的ViewHolder实例,但是这里的ViewHolder是已经被抹除数据的,没有任何绑定的痕迹,需要重新绑定数据。它是根据itemType来存储的,是以SparseArray嵌套一个ArraryList的形式保存ViewHolder的,不同itemType的默认最多缓存5个

https://www.jianshu.com/p/3e9aa4bdaefd

https://www.cnblogs.com/jimuzz/p/14040674.html

mvc、mvp、mvvm

MVC:比较早期的框架,mode、view、control,三者之间都存在依赖,view状态改变,通过 control调用mode更新数据,mode数据更新后又通知view更新ui。由于Android view的功能较弱,我们经常会将事件监听和数据监听放在control中,这样control代码就变得很臃肿。
MVP:再mvc基础上进行改进,用presenter替换control,彻底解耦view和model。view状态改变,通过接口调用presenter,presenter再调用model进行数据更新,model更新后通知presenter,presenter再通过view的接口更新ui。
由于presenter和view直接通过接口进行依赖,界面复杂了接口就变得很多,而且presenter需要手动更新ui,代码也会变得臃肿。
MVVM:模型和MVP一样,用ViewMode替换presenter,彻底解耦view和model。只是viewmodel和view通过databanding进行视图和试图模型的双向绑定。ui和状态的更新通过databanding来进行。减少接口和代码复杂性。

组件化

三方库

插件化、热修复

https://github.com/interviewandroid/AndroidInterView/blob/master/android/plugin.md

ARouter源码分析(完成)

https://blog.csdn.net/JALLV/article/details/105256217

RxJava源码分析

RXJava是一个一个响应式扩展库,使用可观察的序列将异步和基于事件的程序组合在一起。通俗的讲就是一个异步处理库。要理解主要从两部分,一是订阅流程、二是线程切换。

订阅流程:

被观察者(Observable)、观察者(Observer),被观察者内部有一个订阅方法(subscribe)方法,在订阅时会创建一个发射器(Emitter),将被观察者、观察者关联在一起,被观察者通过Emitter向下游发送事件,Emitter会把事件发送给观察者。常用的一些操作符如map、flatMap就是通过组合的方式创建新的被观察者、观察者,在订阅的时候会先执行上游的被观察者,新的观察者接收到事件后进行相应的处理后,在向下游观察者传递。

线程切换:

subscribeOn时同样会通过组合的方式创建新的被观察者,在被订阅的时候会将上游的被观察者运行在我们指定的调度器上。一般使用的io调度器,内部有两类线程池,工作线程池(ThreadWorker),用于处理外部提交过来的任务;回收线程池,定期回收过期的工作线程池的线程池。还有一个工作线程池(ThreadWorker)缓存队列,执行任务时如果缓存队列中有工作线程池就直接使用,不存在就创建,在执行完后再接入缓存队列。回收线程池会定期检查缓存队列,如果过期了就销毁。整体来说就像一个Cache线程池。observeOn时会通过组合的方式创建新的观察者,上层传递的数据会再指定线程池上向下游传递。般使用的mainThread调度器,内部就是一个使用MainLooper的Handler

Retrofit源码分析(doing)

Retrofit是一个方便和快捷的网络请求框架,底层使用OkHttp实现网络请求。在使用Retrofit前我们一般会创建一个API接口类,包含网络请求方法的定义,通过注解设置接口请求方式(Post、Get),通过参数注解设置请求数据,请求头等。然后使用构建者模式创建一个Retrofit实例,通过Retrofit.create方法获取前面定义API接口实例,其实create返回的是一个动态代理类,在执行API接口方法时会进行拦截,执行 loadServiceMethod方法,内部通过反射获取方法注解、参数类型和参数注解信息,这些信息后面会被用来创建OkHttp的Request类,返回一个 HttpServiceMethod对象并调用它的 invoke方法;在invoke方法中根据API接口方法返回类型,使用CallAdapter对OkHttpCall进行封装,返回指定类型的封装类。执行OkHttpCall的 enqueue方法期间会根据之前解析方法上的注解,参数注解,拼成okhttp3.Request对象,并使用OktttpClient实例创建Call并执行的enqueue方法然后通过 Converter来解析返回的响应数据,并回调CallBack接口协程支持,在定义API接口时在方法中添加suspend关键字,在编译期会在方法最后一个参数位上增加一个Continuation对象,HttpServiceMethod中通过反射检查到最后一个参数没有注解并且是Continuation类型时,认为当前请求支持协程,在执行HttpServiceMethod.invoke方法时会将OkHttpCall请求过程和响应数据转换封装在协程中进行返回

Retrofit主要是用动态代理模式来实现的,对用户隐藏了网络请求的全部过程。使用Retrofit首先需要定义一个api接口,在通过Retrofit获取api接口实例,返回的就是一个动态代理类。调用接口方法时都会通过代理拦截,拦截时通过反射获取接口方法中的注解信息,通过这些信息创建OkHttp中的Request和Call,同时根据返回类型通过设置的CallAdapter对Call发起请求过程进行封装,例如支持rxjava,或把Call.execute放在在Observable的订阅方法中。同时使用Converter对网络返回的Response转换成返回的指定数据类型。

Okhttp源码解析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8lnyt23T-1651902982152)(okhttp.png)]

CacheInterceptor:缓存拦截器

首先讲一下http的缓存机制

  1. Cache-Control请求/响应头,缓存控制字段,可以说是控制http缓存的最高指令,要不要缓存也是它说了算。它有以下常用值
- no-store:所有内容都不缓存
- no-cache:缓存,但是浏览器使用缓存前,都会请求服务器判断缓存资源是否是最新,它是个比较高贵的存在,因为它只用不过期的缓存。
- max-age=x(单位秒) 请求缓存后的X秒不再发起请求,属于http1.1属性,与下方Expires(http1.0属性)类似,但优先级要比Expires高。
- s-maxage=x(单位秒) 代理服务器请求源站缓存后的X秒不再发起请求,只对CDN缓存有效(这个在后面会细说)
- public 客户端和代理服务器(CDN)都可缓存
- private 只有客户端可以缓存
  1. Expires响应头,代表资源过期时间,由服务器返回提供,GMT格式日期,是http1.0的属性,在与max-age(http1.1)共存的情况下,优先级要低。
  2. Last-Modified响应头,资源最新修改时间,由服务器告诉浏览器。
  3. if-Modified-Since请求头,资源最新修改时间,由浏览器告诉服务器(其实就是上次服务器给的Last-Modified,请求又还给服务器对比),和Last-Modified是一对,它两会进行对比。
  4. Etag响应头,资源标识,由服务器告诉浏览器。
  5. if-None-Match请求头,缓存资源标识,由浏览器告诉服务器(其实就是上次服务器给的Etag),和Etag是一对,它两会进行对比。

强缓存: 不发起http请求,直接使用本地缓存,比如浏览器地址栏回车,使用浏览器的刷新按钮,在Expires或max-age生效的情况下,触发的都是强缓存。
协商性缓存(弱缓存): 在使用本地缓存前,先与服务器协商,核对缓存文件是否为最新。比如设置了cache-control=no-cache,不管你做任何操作,都会发起请求,这一类就是协商性缓存了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NqsztGuy-1651902982154)(http-cache.png)]

ConnectInterceptor:连接拦截器

总结下连接池中相关问题:

  • 连接池是为了解决频繁的进行建立Sokcet连接(TCP三次握手)和断开Socket(TCP四次分手)。

  • Okhttp的连接池支持最大5个链路的keep-alive连接,并且默认keep-alive的时间是5分钟。

  • 连接池ConnectionPool,它负责存储与清除的工作,存储是通过ArrayDeque的双端队列存储,删除交给了线程池处理cleanupRunnable的任务。

  • 在连接池中找连接的时候会对比连接池中相同host的连接。

  • 如果在连接池中找不到连接的话,会创建连接,创建完后会存储到连接池中。

  • 在把连接放入连接池中时,会把清除操作的任务放入到线程池中执行,删除任务中会判断当前连接有没有在使用中,有没有正在使用通过RealConnection的allocations集合的size是否为0来判断,如果不在使用中,找出空闲时间最长的连接,如果空闲时间最长的连接超过了keep-alive默认的5分钟或者空闲的连接数超过了最大的keep-alive连接数5个的话,会把存活时间最长的连接从连接池中删除。保证keep-alive的最大空闲时间和最大的连接数。

cleanupRunnable清理的详细流程

  1. 首先根据pruneAndGetAllocationCount方法判断该连接有没有被正在使用,如果是正在使用,则直接跳出该次循环,如果不是则把idleConnectionCount数加1。
  2. 算出当前空闲连接的已经存活的时间,然后找出存活最久的连接。
  3. 如果最久的连接大于设置的keep-alive设定或者正在使用的连接数超过了最大空闲连接数,则从连接池中移除,注意此时方法直接返回0了,继续下一次的清除操作。
  4. 如果还存在没有正在使用的连接,但是存活时间没超过keep-alive设置的时间并且个数没超过最大的个数,返回还要等多久的时间才能进行下次清除操作。
  5. 如果都是正在使用的连接,则直接返回keep-alive设置的时间,也就是5分钟。
  6. 如果连正在使用的连接都没有,则直接返回-1,说明不需要下次的清除操作。

Glide源码分析

设计一个图片加载需要考虑那些东西:

  1. 异步加载,线程切换
  2. 图片解码,支持多种格式png、jpg、webp、gif
  3. 缓存:内存缓存、本地缓存
  4. 防止OOM,指定图片大小、图片压缩
  5. 防止内存泄漏,软引用、弱引用,生命周期绑定
  6. Bitmap缓存池

源码分析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0ym1KHgM-1651902982156)(6963391-6171bfef4310dcbe.webp)]

https://www.jianshu.com/p/9bb50924d42a

https://juejin.cn/post/6970683481127043085#heading-6juejin.cn

缓存机制

Glide将它的缓存分为2个大的部分,一个是内存缓存,一个是硬盘缓存。其中内存缓存又分为2种,弱引用和Lrucache;磁盘缓存就是DiskLrucache,DiskLrucache算法和Lrucache差不多。

内存缓存

从弱引用取缓存,拿到的话,引用计数+1;从LruCache中拿缓存,拿到的话,引用计数也是+1,同时把LruCache缓存转移到弱应用缓存池中;从EngineJob去加载图片,拿到的话,引用计数也是+1,会把图片放到弱引用。反过来,一旦没有地方正在使用这个资源,就会将其从弱引用中转移到LruCache缓存池中。这也说明了正在使用中的图片使用弱引用来进行缓存,暂时不用的图片使用LruCache来进行缓存的功能。

磁盘缓存

Glide5大磁盘缓存策略
DiskCacheStrategy.DATA: 只缓存原始图片;
DiskCacheStrategy.RESOURCE:只缓存转换过后的图片;
DiskCacheStrategy.ALL:既缓存原始图片,也缓存转换过后的图片;对于远程图片,缓存 DATARESOURCE;对于本地图片,只缓存 RESOURCE
DiskCacheStrategy.NONE:不缓存任何内容;
DiskCacheStrategy.AUTOMATIC:默认策略,尝试对本地和远程图片使用最佳的策略。当下载网络图片时,使用DATA;对于本地图片,使用RESOURCE

磁盘缓存是在EngineJob中的DecodeJob任务中完成的,依次通过ResourcesCacheGeneratorDataCacheGeneratorSourceGenerator来获取缓存数据。ResourcesCacheGenerator获取的是转换过的缓存数据;DataCacheGenerator获取的是未经转换的原始的缓存数据;SourceGenerator是通过网络获取图片数据再按照按照缓存策略的不同去缓存不同的图片到磁盘上。

Glide缓存分为弱引用+ LruCache+ DiskLruCache,其中读取数据的顺序是:弱引用 > LruCache > DiskLruCache>网络;写入缓存的顺序是:网络 --> DiskLruCache–> 弱引用–>LruCache

内存缓存分为弱引用的和 LruCache ,其中正在使用的图片使用弱引用缓存,暂时不使用的图片用 LruCache缓存,这一点是通过 图片引用计数器(acquired变量)来实现的。

磁盘缓存就是通过DiskLruCache实现的,根据缓存策略的不同会获取到不同类型的缓存图片。它的逻辑是:先从转换后的缓存中取;没有的话再从原始的(没有转换过的)缓存中拿数据;再没有的话就从网络加载图片数据,获取到数据之后,再依次缓存到磁盘和弱引用。

BitmapPool

BitmapFactory.Options参数

inJustDecodeBounds:

如果将这个值置为true,那么在解码的时候将不会返回bitmap,只会返回这个bitmap的尺寸。这个属性的目的是,如果你只想知道一个bitmap的尺寸,但又不想将其加载到内存时。这是一个非常有用的属性。

inSampleSize:

这个值是一个int,当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4。

inPreferredConfig:

这个值是设置色彩模式,默认值是ARGB_8888,在这个模式下,一个像素点占用4bytes空间,一般对透明度不做要求的话,一般采用RGB_565模式,这个模式下一个像素点占用2bytes。如果inPreferredConfig不为null,解码器会尝试使用此参数指定的颜色模式来对图片进行解码,如果inPreferredConfig为null或者在解码时无法满足此参数指定的颜色模式,解码器会自动根据原始图片的特征以及当前设备的屏幕位深,选取合适的颜色模式来解码,例如,如果图片中包含透明度,那么对该图片解码时使用的配置就需要支持透明度,默认会使用ARGB_8888来解码。

inBitmap:

在 Android 3.0 上面引入了 BitmapFactory.Options.inBitmap 字段。如果设置了此选项,那么采用 Options 对象的解码方法会在加载内容时尝试重复使用现有位图。这样可以复用现有的 Bitmap,减少对象创建,从而减少发生 GC 的概率。不过,inBitmap 的使用方式存在某些限制。特别是在 Android 4.4(API 级别 19)之前,系统仅支持大小相同的位图。在 Android 4.4 之后的版本,只要内存大小不小于需求的 Bitmap 都可以复用。

Jetpack

DataBinding

https://juejin.cn/post/6844903549223059463#heading-15

如何避免findViewById

在初始化时,我们是通过DataBindingUtil.setContentView来建立layout与ViewBinding的联系

  1. 从view的tag中获取缓存,防止多次初始化
  2. 将view存储在bindings数组内,分为三种情况,与前面生成的ViewBinding内的view变量类型一致,一为根布局,tag以layout开头;二为设置了@{}的,tag以binding开头;三为设置了id属性的。
  3. 这部分判断如果是ViewGroup的话,则判断子View是不是include的,如果是的话,则使用DataBindingUtil.bind进行递归;如果不是include,则直接使用mapBindings进行递归。

通过这三个步骤,递归得到最后的bindings数组。如果设置了id的,就将view变量设置为public,这样就避免了findViewById的代码。这种方式从性能上比findViewById高效,因为databinding只需要遍历一次view数,而findViewById多次调用会遍历多次。

VM变化如何通知View

在binding.setViewModel(viewModel)绑定的过程中,就在Observable上加了监听,监听者就是WeakPropertyListener。当VM发生变化时,会通过mCallbacks.notifyCallbacks将变化发送出去。

V的变化如何同步到VM

在使用双向绑定后,binding.setViewModel(viewModel)绑定的过程中会添加View的事件监听,在事件回调里,将变动后的值赋值到VM上。这样,V的变化就自动同步到VM上了。

ViewModel

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存,并且能方便的在Activity和Fragment之间、Fragment和Fragment之间进行数据传递

创建

new ViewModelProvider(ViewModelStoreOwner,Factory).get(ViewModel.class)

ViewMode一般通过ViewModelProvider的get方法获取,get方法接收对应ViewModel类的class对象作为参数。创建ViewModelProvider对象时接受两个参数,ViewModelStoreOwner和Factory,ViewModelStoreOwner用来获取ViewModelStore,ViewModelStore中缓存了已经创建的ViewModel,可通过ViewModel类信息来获取对应缓存实例;Factory用来创建ViewModel。Activity和Fragment都继承了ViewModelStoreOwner,内部包含ViewModelStore变量。获取ViewModel时会先看ViewModelStore中是否有对应的ViewModel,有就直接返回,不存在就通过Factory创建并添加到ViewModelStore中。所以同一个ViewModelStore中,相同的ViewModel类只会有一个实例,通过ViewModel可以在Activity与Fragment之间或Fragment与Fragment之间传递数据。

Activity旋转屏幕后ViewModel可以恢复数据

Activity中创建ViewModelStore时会先从NonConfigurationInstances 中获取,如果不存在直接创建。NonConfigurationInstances 有点类似于onSaveInstanceState中的Bundle,在旋转屏幕后ActivityThread执行performDestroy中调用activity.retainNoConfiguratuinInstances将ViewModelStore放到NonConfigurationInstances,然后将NonConfigurationInstances 对象放在ActivityClientRecord中,在重新创建Activity时ActivityThread执行到performLauncherActivity时,再从ActivityClientRecord中取出赋值给Activity,以此达到保持数据的目的。

ViewModel.onCleared()

activity 销毁时,判断如果是非配置改变导致的销毁,如果不是调用 getViewModelStore().clear()ViewModelStoreclear() 会遍历 所以的ViewModel并执行clear()

LiveData

Transformations、MediatorLiveData、onActive、onInactive、distinctUntilChanged

https://juejin.cn/post/6934710412751405064

Lifecycle

https://juejin.cn/post/6844904030037082126

LifecycleOwnerLifecycleObserverLifecycle

LifecycleOwner 是一个接口 , 接口通常用来声明具备某种能力。LifecycleOwner 的能力就是具有生命周期。典型的生命周期组件有 ActivityFragment 。当然,我们也可以自定义生命周期组件。LifecycleOwner 提供了 getLifecycle() 方法来获取其 Lifecycle 对象。

LifecycleObserver 是生命周期观察者,它是一个空接口。它没有任何方法,依赖 OnLifecycleEvent 注解来接收生命周期回调。

生命周期组件生命周期观察者 都有了,Lifecycle 就是它们之间的桥梁。

Lifecycle 是具体的生命周期对象,每个 LifecycleOwner 都会持有 Lifecycle 。通过 Lifecycle 我们可以获取当前生命周期状态,添加/删除 生命周期观察者等等。

关键点
  1. addObserver时会依次回调生命周期至最新生命周期
  2. 分发生命周期时通过ComponentActivity.onCreate时添加了一个ReportFragment,它是一个空Fragement专门用来感知生命周期变化,并回调Lifecycle
  3. LifecycleObserver之类LifecycleEventObserver、FullLifecycleObserver,也可以通过注解来定义LifecycleObserver子类
Room

Leakcanary

https://blog.csdn.net/xiaohanluo/article/details/78196755

JAVA

注解

https://juejin.cn/post/6844903879524483086

反射

https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html

JNI

线程池源码分析(完成)

https://github.com/interviewandroid/AndroidInterView/blob/master/android/thread1.md

ThreadLocal

https://segmentfault.com/a/1190000018399795

数据结构(List、Map、Set、Queue、LruCache、LinkedHashMap)

https://blog.csdn.net/qq_29966203/article/details/91040696

https://tech.meituan.com/2016/06/24/java-hashmap.html

Kotlin

协程

https://blog.csdn.net/suyimin2010/article/details/91125803

https://github.com/Kotlin-zh/KEEP/blob/master/proposals/coroutines.md

tcp/ip

https://github.com/interviewandroid/AndroidInterView/blob/master/android/https.md

HTTPS原理

socket

算法

设计模式

项目总结

项目介绍、遇到的问题、如何解决

Flutter

安全隐私合规

https://github.com/huage2580/PermissionMonitor

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值