Activity
singleTask:单例,taskAffinity:指定 Activity 要压入的 task
singleInstance:单例且独立 task
allowTaskReparenting 赋予 Activity 在各个 Task 中间转移的特性
Activity 的 onSaveInstance 方法何时调用?它跟onPause、onStop的调用顺序如何?
Binder 传输限制通常 1 M,不同厂商不同版本会有区别。
- 非必须的字段添加 transient 禁止序列化
- 转 json 传输
startActivity的具体过程
执行到 Instrumentation#execStartActivity
通过 ActivityTaskManager.getService() 获取 ATMS(Binder)代理,执行其 startActivity
- 在系统启动的时候, 有从 init 到 zygote 到 system_server 的一套流程
构建一个 ActivityStarter ,根据请求参数进行启动
- 权限检查,处理异常
- 封装 ActivityRecord,处理更新 task 栈帧信息
判断目标进程是否已存在
- 已存在:那么目标 ActivityThread 已经启动且 ATMS 是持有其内部 Binder(ApplicationThread)代理的。开启事务,通过 ActivityThread 的 Binder 代理,发送事务执行消息,最终执行 handleLaunchActivity
- 反射创建 Activity 对象
- 调用 Activity#attach
- 构建 PhoneWindow,将 Activity 与其关联,并持有 WindowManagerImpl 的引用
- 调用 Instrumentation#callActivityOnCreate 执行 Activity 的 onCreate 生命周期
- 不存在:ATMS 通过 startProcessAsync 发送异步消息通知 AMS 启动进程
- 这里是通过 PooledLambda 构建了一个 Message,再通过 mH.sendMessage 发送消息,但是我在 mH 中没有看到相关处理逻辑,进入到 PooledLambda 中发现,其实是通过 Message.obtain().setCallback 生成的 Message,所以这里其实是使用的 Message 中的 Runnable x消息分发触发 run 方法从而执行的传入的 ActivityManagerInternal::startProcess 方法,最终由实现了该接口的 AMS 的内部类 LocalService 执行
- AMS.LocalService#startProcess -> ProcessList#startProcess -> Zygote 创建进程,执行applicationInit,调用 ActivityThread.main。这里 AMS 将持有 ActivityThread 的 Binder 代理
- prepareMainLooper
- 构建 ActivityThread,执行 attach 方法,
- 通过 getService 调用 AMS#attachApplication,将 ApplicationThread(Binder)传入
- AMS 通过 ApplicationThread 调用 bindApplication 又回到 ActivityThread
- ActivityThread 中执行 sendMessage(H.BIND_APPLICATION, data),实际执行 handleBindApplication
- 构建 Instrumentation 和 Application,Instrumentation#callApplicationOnCreate
- Looper.loop()
- 已存在:那么目标 ActivityThread 已经启动且 ATMS 是持有其内部 Binder(ApplicationThread)代理的。开启事务,通过 ActivityThread 的 Binder 代理,发送事务执行消息,最终执行 handleLaunchActivity
AMS 继续执行 Activity 的启动处理
Flag,launchMode,taskAffinity
Activity#setContentView的具体过程
①PhoneWindow是何时创建的,它的作用是什么?
创建 Activity 时调用其 attach 方法中创建的,…
②setContentView中传递的资源文件是如何变成View对象的?
LayoutInflater.from(mContext).inflate(resId, contentParent);
③布局文件对应的View对象是添加到哪里的?
DecorView 中的 contentParent 中
④Activity的布局是何时显示出来的?
只有执行完 onResume 之后 Activity 中的内容才是屏幕可见状态。onCreate 阶段只是初始化了 Activity 需要显示的内容,而在 onResume 阶段才会将 PhoneWindow 中的 DecorView 真正的绘制到屏幕上。
⑤ViewRootImpl是何时初始化的?它的作用是什么?
⑥Choreography了解么?作用是什么?
初始化阶段
- AppCompatDelegateImpl#setContentView
- 执行 PhoneWindow#getDecorView(),构建 DecorView(FrameLayout),从DecorView 中获取 mContentParent
- 将 resId 通过 inflate 转化成 View 添加到 mContentParent 中
绘制阶段
执行 ActivityThread#handleResumeActivity,调用 WindowManager.addView
实际调用 WindowManagerGlobal#addView
构建 ViewRootImpl,调用其 setView 方法(将 DecorView 添加到 WMS)
- requestLayout() - 执行完整的 View 树的渲染操作。调用此方法后 ViewRootImpl 所关联的 View 也执行 measure - layout - draw 操作,确保在 View 被添加到 Window 上显示到屏幕之前,已经完成测量和绘制操作。
- checkThread() 检查线程是否合法,需与 ViewRootImpl 一致,即 UI 线程
- scheduleTraversals() - 方法中添加了同步屏障,实际执行 performTraversals(),这个方法就是真正的开始 View 绘制流程:measure –> layout –> draw 。
- performLayout()
- measureHierarchy() - performMeasure() - mView.measure()
- host.layout() – host = mView
- performDraw()
- draw(fullRedrawNeeded)
- mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this) – 硬件加速绘制(GPU)
- drawSoftware() – 软件绘制(CPU)
- mView.draw(canvas)
- surface.unlockCanvasAndPost(canvas) – 将 Canvas 中的内容提交给 SurfaceFlinger 进行合成处理。
- draw(fullRedrawNeeded)
- performLayout()
- 调用 mWindowSession.addToDisplayAsUser,mWS = WindowManagerGlobal.getWindowSession()
- getWindowSession - 通过 ipc 调用 WMS#openSession 获取 Session,调用其 addToDisplayAsUser,将 ViewRootImpl 弱引用存储,整个给到 WMS
- 实际调用 WMS#addWindow,完成后面的添加动作
- 设置输入管道,用来接收屏幕触摸事件
- requestLayout() - 执行完整的 View 树的渲染操作。调用此方法后 ViewRootImpl 所关联的 View 也执行 measure - layout - draw 操作,确保在 View 被添加到 Window 上显示到屏幕之前,已经完成测量和绘制操作。
ViewRootImpl 中有一个非常重要的对象 Surface,之所以说 ViewRootImpl 的一个核心功能就是负责 UI 渲染,原因就在于在 ViewRootImpl 中会将我们在 draw 方法中绘制的 UI 元素,绑定到这个 Surface 上。如果说 Canvas 是画板,那么 Surface 就是画板上的画纸,Surface 中的内容最终会被传递给底层的 SurfaceFlinger,最终将 Surface 中的内容进行合成并显示的屏幕上。
硬件加速绘制
并不是所有的 2D 绘制操作都支持硬件加速
View 视图被抽象成 RenderNode 节点,View 中的绘制操作都会被抽象成一个个 DrawOp,每个 DrawOp 有对应的 OpenGL 绘制命令。
遍历 View 递归构建 DrawOp,根据 Canvas 将所有的 DrawOp 进行缓存操作。所有的 DrawOp 对应的 OpenGL 命令构建完成之后,就需要使用 RenderProxy 向 RenderThread 发送消息,请求 OpenGL 线程进行渲染。整个渲染过程是通过 GPU 并在不同线程绘制渲染图形,因此整个流程会更加顺畅。
Invalidate
这个方法跟 requestLayout 的区别在于,它不一定会触发 View 的 measure 和 layout 的操作,多数情况下只会执行 draw 操作。
measure 方法中有个强制布局的标志位,只有当这个标志位为 true 才会执行 onMeasure,requestLayout 中会设置,但是 invalidate 不会。
当调用 invalidate 方法时,如果 View 的位置并没有发生改变,则 View 不会触发重新布局的操作。
postInvalidate
invalidate 是在 UI 线程调用,postInvalidate 是在非 UI 线程调用。
事件分发,滑动冲突
触摸事件通过驱动层 WMS 到达 DecorView 的 dispatchTouchEvent,执行 mWindow.cb.dispatchTouchEvent,这里的 cb 就是 Activity(在 Activity 的 attach 中设置),再从 Activity 的 dispatchTouchEvent 到 PhoneWindow.superDispatchTouchEvent,再到 DecorView.superDispatchTouchEvent
从 ViewGroup#dispatchTouchEvent 开始
- 判断 ViewGroup 是否拦截(down 事件 或者 TouchTarget 不为空),通过 onInterceptTouchEvent 判断
- 不拦截,事件分发给子 View 继续处理
- 前提是 down 事件
- 遍历子 View,View 坐标范围符合且不在动画状态
- dispatchTransformedTouchEvent 将事件分发给 View# dispatchTouchEvent ,如果 View 消费事件,赋值给 TouchTarget
- 如果有子 View 消费事件,将后续事件直接交其处理
- 这里有个有趣的事情,就是如果在后续事件中 onInterceptTouchEvent 返回 true,子 View 将会收到 cancel 事件。这也就是滑动冲突的解决思路。
- 如果没有,最终会执行自身的 onTouchEvent 进行处理
View# dispatchTouchEvent
- 优先判断 TouchListener ,处理 onTouch
- 其次 onTouchEvent
动画
帧动画 : 顺序播放图片
补间动画 : 只需要定义动画开始与结束两个关键帧,并指定动画变化的时间与方式等 。 只能给View加,不能给对象加,并且不会改变对象的真实属性。
属性动画 : 补间动画增强版本。 任意 Java 对象,不再局限于 View。 分为ObjectAnimator和ValueAnimator。
ValueAnimator 类是先改变值,然后手动赋值给对象的属性从而实现动画;是间接对对象属性进行操作;
ObjectAnimator 类是先改变值,然后自动赋值给对象的属性从而实现动画;是直接对对象属性进行操作;
插值器:根据时间流逝的百分比计算出当前属性值改变的百分比。
估值器:根据当前属性改变的百分比来计算改变后的属性值。
要控制动画速率的变化,就得去自定义插值器或估值器,或者使用关键帧Keyframe
对象来实现。Keyframe
让我们可以指定某个属性百分比时对象的属性值。
- 过渡动画 :Activity 或 View 转场动画
View 绘制
onMeasure、onLayout、onDraw
测量,确定最终宽高
- EXACTLY 固定宽高(设定具体值或者 match_parent)
- AT_MOST 自适应
- UNSPECIFIED 父容器对当前 View 无限制( 比如ScrollView,它的子View可以随意设置大小,无论多高,都能滚动显示 )
MeasureSpec
MeasureSpec是父控件提供给子View的一个参数,作为设定自身大小参考,只是个参考,要多大,还是View自己说了算
onMeasure和onLayout为何会执行两次或多次?
当
ViewRootImpl
添加View
时,就会测量多次ViewRootIpml 在进行测量、布局、绘制前会进行一次预测量,已得到最佳的窗口显示效果。
在预测量过程中进行两次协商,以得到最佳的显示尺寸,两次协商不成则直接使用窗口的尺寸进行一次预测量。
进行真正的测量,会调用一次。
Bitmap
宽 * 高 * 1个像素所需字节数
修改图片加载的 BitmapFactory.Options
- 将存储方式设置为 Bitmap.Config.RGB_565,1像素 = 2字节
- 实现 Bitmap 采样压缩(inSampleSize,每隔 inSampleSize 个像素进行一次采集)
Bitmap 复用(Options.inBitmap),inMutable 需设置 true
BitmapRegionDecoder 图片分片显示
Bitmap 缓存
- LruCache
RecyclerView的特点和缓存
1. RecyclerView 是如何经过测量、布局,最终绘制到屏幕上,其中大部分工作是通过委托给 LayoutManager 来实现的。不同的 LayoutManager 会有不同风格的布局显示,这是一种策略模式
2. RecyclerView 的缓存复用机制,主要是通过内部类 Recycler 来实现。
4 级缓存中依次查找
第一级缓存 mAttachedScrap&mChangedScrap
- 两个 ArrayList,这两者主要用来缓存屏幕内的 ViewHolder(下拉刷新)
第二级缓存 mCachedViews
- 缓存移除屏幕之外的 ViewHolder
- 刚移除屏幕,可能立刻用到,但又不能缓存所有,所以默认 2,可修改,先进先出
第三级缓存 ViewCacheExtension
- 抽象类,开发者自己实现
第四级缓存 RecycledViewPool
缓存屏幕外的 ViewHolder
mCachedViews 中淘汰出来的 ViewHolder 会先缓存到 RecycledViewPool 中
缓存到 RecycledViewPool 时,会将内部的数据清理,因此从 RecycledViewPool 中取出来的 ViewHolder 需要重新调用 onBindViewHolder 绑定数据 (同 LV 复用 convertView)
多个 RV 之间可以共享一个 RecycledViewPool
需要注意的是,RecycledViewPool 是根据 type 来获取 ViewHolder,每个 type 默认最大缓存 5 个。因此多个 RecyclerView 共享 RecycledViewPool 时,必须确保共享的 RecyclerView 使用的 Adapter 是同一个,或 view type 是不会冲突的。
动态代理的实现 –todo
OOM和内存泄漏
内存溢出:内存不够用
内存泄漏:本该释放的对象没有释放
内存抖动:频繁 GC,比如在 onDraw 里面频繁的创建对象
包体积如何优化
删除无用代码和资源
图片压缩,某些资源考虑 CDN 网络下载
删除不常用 abi,考虑只保留 arm-v7 和 64 位
代码混淆
部分功能可考虑插件化(不同主题或字体等)
ThreadLocal干嘛的?用法和原理
用于线程隔离做数据存储的工具类。
ThreadLocal 是指线程的本地变量,我们可以通过 ThreadLocal 去设计只有线程内部才可以访问的变量,该变量是与其他线程所隔离的。 可以理解为一种类似于 Map 的存储结构,它的 key 是当前线程。
我们一般是在理解 Handler 跨进程机制时会接触到 ThreadLocal。
ThreadLocal 有个内部类 ThreadLocalMap,ThreadLocalMap 又有个继承于 WeakReference 的内部类 Entry 的数组,用来实际存储,Thread 这个类里面有个成员变量 threadLocals 就是 ThreadLocalMap。
我们在创建 Looper 之后会让 Looper 与当前所在线程关联,就是通过获取当前线程的 ThreadLocalMap,然后再将其放到 Entry 里面。取的时候也是根据当前线程获取其 ThreadLocalMap 再从 Entry 中取。
jvm:运行时数据分区;类加载过程;GCRoot,垃圾回收算法。–todo
优化 –todo
Retrofit – doing
核心思想: Retrofit
通过动态代理我们定义的 service
接口代理对象,调用其方法时就会转移到InvocationHandler
对象的 invoke
方法中。
- 动态代理获取服务
- 对注解参数进行解析封装
- 提供转换接口(RxJava、Gson)
Rxjava –doing
LeakCanary
实现原理
LeakCanary 中对内存泄漏的检测基于 WeakReference 和 ReferenceQueue。
在构建 WeakReference 对象时传入 ReferenceQueue,当 WeakReference 中传入的对象可以被回收时,会将 WeakReference 对象添加到 ReferenceQueue 中,倘若 WeakReference 中的对象无法被回收时,不会将 WeakReference 对象添加到 ReferenceQueue 中。这样便可以检测到,应该被回收的对象,却没有出现在 ReferenceQueue 中,这些对象就是造成内存泄漏的元凶。
检测时机
向主线程 MessageQueue 中插入了一个 IdleHandler,IdleHandler 只会在主线程空闲时才会被 Looper 从队列中取出并执行。因此能够有效避免内存检测工作占用 UI 渲染时间。
单例、观察者、装饰者,工厂 –todo
单例模式
饿汉模式:类加载时就初始化好了
懒汉模式:调用 getInstance 的时候才进行初始化
- DCL:加同步锁两次判空
策略模式和桥接模式的区别 –todo
插件化和热更新原理
插件化主要涉及2个技术点,类加载和反射。
用法包括增量和和修改。
增量场景
通过 DexClassLoader 加载插件 apk 或者 dex 包,然后通过反射去调用其中的类进行使用。
修改场景(热更新)
这里涉及到一个 双亲委托
的概念。其实就是 ClassLoader 在 getClass 的时候交给自己的 parent 去做,一层一层往上递归从缓存中找,找不到再从顶层往下去看哪个 ClassLoader 可以进行加载然后返回。
ClassLoader 中有个 dexElements 的数组,加载的时候就是遍历这个数组然后进行加载。热更新的实现就是将修改后的类打包成 dex 之后,加载获取其 dexElements,然后将其与原 app 的 ClassLoader 的 dexElements 进行合并,将插件的 dexElements 放在前面,这样就可以优先加载修改后的类。