文章目录
- 一. Android知识
- 1. 打包、编译、虚拟机、启动流程、签名
- 2. Context
- 3. 消息机制
- 3.1 Handler原理的四大组件
- 3.2 IdleHandler原理使用
- 3.3 HandlerThread使用及原理
- 3.4 一个线程有几个Looper?几个MessageQueue?常见线程与Looper的关系?
- 3.5 一个线程几个Handler?如何处理正确的Msg
- 3.6 MessageQueue的数据结构?如何保证线程安全
- 3.7 主线程和子线程new Handler的差异
- 3.8 Handler.postDelayed()消息时间是否准确?实现原理
- 3.9 MessageQueue中没有消息的时候会发生什么?为什么Looper.loop不会阻塞主线程?
- 3.10 为什么Handler死循环不会卡死?和ANR有什么区别
- 3.11 Handler机制中的三种消息类型及如何理解屏障消息
- 3.12 Handler为什么内存泄漏?如何解决
- 3.13 如何创建Message?理由?
- 3.14 IntentService理解
- 4.四大组件
- 4.1 Serverview种类及启动
- 4.2 广播的原理及注册方式
- 4.3 ContentProvider原理及作用
- 4.4 Fragment生命周期
- 4.5 Activity生命周期
- 4.6 Activity的LaunchMode
- 4.7 BroadcastReceiver和LocalBroadcastReceiver区别
- 4.8 Activity和Context 的 startActivity的区别
- 4.9 onSaveInstanceState和onRestoreInstanceState的区别
- 4.10 广播常见面试题
- 4.11 怎么在Service中创建Dialog对话框
- 4.12 显示Intent与隐式Intent的区别
- 4.13 横竖屏切换时Activity的生命周期
- 4.14 onDestroy方法是在Activity finish之后立即执行的吗
- 4.15 需要在Activity之间传递大量的数据怎么办
- 5. View相关
- 6. 多线程
- 7. Jetpack
- 8. Bitmap
- 9. 进程
- 10. 动画
- 11. 渲染
- 12. 数据库
- 13. ANR
- 14. 内存泄漏
- 15. Binder
- 16. Framework
- 17. 性能优化
- 18. 其他
- 二. Kotlin语言
前言: 该笔记为初步整理的小程序笔记,内容类型较多
一. Android知识
1. 打包、编译、虚拟机、启动流程、签名
1.1 APK打包流程
1.2 jar和aar区别
- JAR:适合Java库或工具类的打包,不包含Android资源。主要用于打包Java类文件、资源文件和辅助文件。
- AAR:适合Android库项目,包含完整的Android组件和资源,可以直接集成到Android应用中。除了包含Java类和资源文件外,还支持Android特有的资源(如布局、清单文件等)
1.3 Android虚拟机
- Dalvik 虚拟机
目标:在早期设备(CPU 性能弱、内存小)上实现快速启动和节省空间。
执行方式:JIT(Just-In-Time,即时编译)
只存储一份 .dex 字节码。 - ART 虚拟机
(1) 初始 ART(Android 5.0 ~ 6.0):纯 AOT
执行方式:AOT(Ahead-Of-Time,预编译)
安装:应用安装时,一个名为 dex2oat 的工具会将整个应用的 .dex 文件全部编译成本地机器码
运行:应用启动时,直接执行已经编译好的机器码,无需任何实时编译
(2) 现代 ART(Android 7.0 至今):混合模式(JIT + AOT)
首次安装:应用安装很快,因为不进行任何 AOT 编译。ART 此时更像 Dalvik,使用解释器来执行代码。
首次运行:ART 内置了一个 JIT 编译器。在应用运行时,JIT 会编译热点代码,并记录下哪些方法被频繁执行(生成方法简档)。这次运行可能不太流畅。
后台优化(空闲时):当设备充电且空闲时,系统会启动一个 后台作业。它会读取 JIT 记录的方法简档,然后只对那些频繁执行的方法进行 AOT 编译。
后续运行:应用再次启动时,直接运行已编译的“热点方法”,其他代码则解释执行或由 JIT 编译。这样既保证了运行效率,又控制了安装时间和空间占用。

1.4 Assert和res目录的区别

1.5 APK安装过程
- 复制 APK 文件
路径:APK 会被复制到系统的应用目录(如 /data/app//),通常以 .base.apk 命名 - 解析应用信息
解析 AndroidManifest.xml:系统通过 PackageManagerService(PMS)解析 APK 中的清单文件
生成 Package 对象 - 优化代码(如 AOT 编译)
将 APK 中的 classes.dex 转换为更高效的 OAT 格式(机器码),提升运行速度
AOT(Ahead-Of-Time)编译:安装时编译(默认行为),或 JIT(Just-In-Time) 运行时编译(Android 7.0+ 混合模式) - 注册应用
注册四大组件
更新应用列表 - 安装完成

1.6 Android系统启动流程

1.7 APP启动流程(Activity的冷/热启动流程)
1.8 谈谈你对安卓签名的理解
-
为什么需要签名
在安装Apk时,同样需要确保Apk来源的真实性,以及Apk没有被第三方篡改;
方法就是开发者对Apk进行签名:在Apk中写入一个“指纹”。指纹写入以后,Apk中有任何修改,都会导致这个指纹无效,Android系统在安装Apk进行签名校验时就会不通过,从而保证了安全性 -
数字摘要
数字摘要是将任意长度的消息变成固定长度的短消息,它类似于一个自变量是消息的函数,也就是Hash函数。 -
数字证书
接收方必须要知道发送方的公钥和所使用的算法。如果数字签名和公钥一起被篡改,接收方无法得知,还是会校验通过。如何保证公钥的可靠性呢?答案是数字证书,数字证书是身份认证机构颁发的 -
签名和校验的简略过程

- 签名和校验完整过程

2. Context
2.1 Android Context的认识
2.2 ApplicationContext和ActivityContext的区别

2.3 为什么Diaglog不能用Apllication的Context
2.4 Context的数量
Service个数 + Activity个数 + 1(Application对应的Context实例)
2.5 四大组件可以像普通Java类一样,采用new的方式实例化吗
- Android应用模型是基于组件的应用设计模式
- 组件的运行要有一个完整的Android工程环境,要有它们各自的上下文环境Context
3. 消息机制
3.1 Handler原理的四大组件
3.2 IdleHandler原理使用
3.3 HandlerThread使用及原理
3.4 一个线程有几个Looper?几个MessageQueue?常见线程与Looper的关系?


3.5 一个线程几个Handler?如何处理正确的Msg
- Handler的数量无限制;
- 通过msg.target进行区分。具体原理如下:Handler在sendMessageAtTime时,会把自身填入msg.target,然后在Looper.loop()不断从MessageQueue中获取Message处理的时候,会根据msg.target去调用对应的dispatchMessage,对应的msg.target就是前面的handler
3.6 MessageQueue的数据结构?如何保证线程安全
- 数据结构:单链表,按执行时间排序。根据 when 时间插入到合适位置
- 安全机制:单线程访问 + synchronized + native epoll
3.7 主线程和子线程new Handler的差异
- Android 主线程(UI线程)在启动时已经自动调用了 Looper.prepare() 和 Looper.loop()。所以在主线程中直接创建 Handler 是合法的
- 子线程中必须手动调用 Looper.prepare() 和 Looper.loop()才能创建 Handler 并启动消息循环
3.8 Handler.postDelayed()消息时间是否准确?实现原理
- 不准确,原因如下:
主线程繁忙:如果主线程正在处理耗时操作(如复杂动画、密集计算),消息即使到时间了,也得等主线程空闲才能执行
消息队列顺序:所有消息按时间顺序排队执行。如果前面有未处理的同步屏障消息或耗时任务,后续消息会被阻塞。
系统时钟精度: 底层依赖 SystemClock.uptimeMillis(),精度为毫秒级,但受系统调度影响 - 实现原理:
当你调用 postDelayed(5000),消息的 when(触发时间)被设置为:
msg.when = SystemClock.uptimeMillis() + 5000;
- 场景应用
适用场景:UI 动画、简单延时操作(如按钮防抖)
不适用场景:音视频同步、高频实时任务
3.9 MessageQueue中没有消息的时候会发生什么?为什么Looper.loop不会阻塞主线程?
问题1:
- 主线程进入休眠:
当 MessageQueue 为空时,Looper.loop() 会调用底层 epoll_wait,让主线程进入休眠状态,不占用 CPU 资源。 - 等待唤醒:
休眠期间,主线程通过 epoll 监听文件描述符(如输入事件、新消息插入等),直到被唤醒。
问题2:
- 源码的 ActivityThread 类中执行Looper.loop();的main函数,也就是主线程的入口
- 核心机制:epoll 的休眠唤醒机制。
- 休眠:当队列为空时,线程通过 epoll_wait 进入休眠,释放 CPU。 nativePollOnce方法
- 唤醒:当新消息入队或外部事件(如触摸、传感器)触发时,通过写入文件描述符唤醒线程。 nativeWake
3.10 为什么Handler死循环不会卡死?和ANR有什么区别
3.11 Handler机制中的三种消息类型及如何理解屏障消息

3.12 Handler为什么内存泄漏?如何解决
- Handler 内部持有 Activity 的引用,如果 Activity 销毁时,Handler 还在等待处理消息(比如延迟消息),就会导致 Activity 无法被回收(内存泄漏)。
- 方案 1:静态内部类 + 弱引用(官方推荐)
- 方案 2:在 onDestroy 中移除所有消息
3.13 如何创建Message?理由?
3.14 IntentService理解
4.四大组件
4.1 Serverview种类及启动

4.2 广播的原理及注册方式
- 设计模式和模型
- 两种注册方式特点和应用场景
- 普通/系统/有序/app应用内广播
- 如何将全局广播变成局部广播

4.3 ContentProvider原理及作用
4.4 Fragment生命周期
-
生命周期概述


-
Activity加入Fragment生命周期

4.5 Activity生命周期

4.6 Activity的LaunchMode


4.7 BroadcastReceiver和LocalBroadcastReceiver区别
- 应用场景
BroadcastReceiver用于应用之间的传递消息;LocalBroadcastReceiver用于应用内部之间传递消息 - 安全
BroadcastReceiver:由于可以接收系统和跨应用广播,因此可能存在安全风险。
LocalBroadcastReceiver:仅限于应用内部通信,更加安全,不会暴露给其他应用 - 注册方式
BroadcastReceiver:可以通过 AndroidManifest.xml 静态注册,也可以在代码中动态注册。
LocalBroadcastReceiver:只能通过代码动态注册,不能在 AndroidManifest.xml 中声明。 - 原理
BroadcastReceiver 是跨应用广播,利用Binder机制实现。
LocalBroadcastReceiver 是应用内广播,利用Handler实现,利用了IntentFilter的match功能
4.8 Activity和Context 的 startActivity的区别
- 使用不同
- 1.在Activity中跳转到其他的Activity时,两种使用方法是一样的:
this.startActivity(intent);
context.startActivity(intent);
- 2.从非 Activity (例如从其他Context中)启动Activity则必须给intent设置Flag:FLAG_ACTIVITY_NEW_TASK:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ;
mContext.startActivity(intent);
- 使用场景
Activity.startActivity():activity内部启动其他activity;使用隐式intent启动系统或者其他应用的activty;需要将activty放入当前任务栈
Context.startActivity():BroadcastReceivcer/Service/Application中启动activity,需要创建新任务栈 - 原理分析
添加链接描述
1.对于Activity,
Context是抽象类,它的startActivity函数是抽象方法:
ContextWrapper类只是调用了Context的实现:
ContextThemeWrapper中没有实现此方法
Activity中:调用自身的方法
在Activity中无论是使用哪一种startActivity方法都会调用到Activity自身的方法,所以是一样的。
2.对于非Activity
在其他的Context子类,例如ContextImpl.java中的实现,会检查有没有设置Flag:FLAG_ACTIVITY_NEW_TASK
4.9 onSaveInstanceState和onRestoreInstanceState的区别
- 当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁 一个Activity时,onSaveInstanceState会被调用
- onSaveInstanceState调用具体场景
(1)当用户按下HOME键时,可能调用(视系统是否销毁 Activity)
(2)长按HOME键,选择运行其他的程序时,可能调用(视系统是否销毁 Activity)
(3)从activity A中启动一个新的activity时,可能调用(视系统是否销毁 Activity)
(4)屏幕方向切换时,例如从竖屏切换到横屏时,一定调用 - onRestoreInstanceState()调用时机
onRestoreInstanceState 只在特定情况下被调用,主要发生在 Activity 被系统销毁并重新创建时(例如屏幕旋转、系统资源不足时杀死后台 Activity 等) - 不会调用 ONRESTOREINSTANCESTATE 的情况:
第一次启动 Activity。
用户主动调用 finish() 关闭 Activity 后再次启动。
使用 startActivity() 正常启动一个新的 Activity。 - 大部分项目未使用这两个方法的核心原因
现代架构组件的替代:ViewModel 成为主流
对 “触发场景” 的依赖过强,可靠性不足(调用时机完全由系统控制)
数据类型与容量限制,适用场景狭窄(采用Bundle存储限制了应用场景)
业务场景迁移:从 “临时状态” 到 “持久化”
4.10 广播常见面试题
- 程序A能否收到程序B的广播
能,使用全局广播 BroadCastRecevier 能进行跨进程通信。 - 广播是否可以请求网络
不可以,广播默认在主线程中。 - 广播引起 anr 的时间限制是多少
10s - 广播传输的数据是否有限制,是多少,为什么要限制?
用 Intent 传递数据,实际上走的是跨进程通信(IPC),跨进程通信需要把数据从内核 copy 到进程中,每一个进程有一个接收内核数据的缓冲区,默认是 1 M。如果一次传递的数据超过限制,就会出现异常。
不同厂商表现不一样有可能是厂商修改了此限制的大小,也可能同样的对象在不同的机器上大小不一样。
传递大数据,不应该用 Intent;考虑使用 ContentProvider 或者直接匿名共享内存。简单情况下可以考虑分段传输。
4.11 怎么在Service中创建Dialog对话框
- 为什么不能直接在 Service 中创建 Dialog?
Dialog 必须依附于 Activity 的 Window(见Context章节) - 方案1:
启动一个透明的 Activity 来“伪装”后台弹窗,需注意权限和版本限制。(Android 10+ 限制后台启动 Activity,需要申请权限) - 方案2:
获取Diaglog对象后,设置类型
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//8.0新特性
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY );
} else {
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST);
}
需要增加权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
- 方案3:
使用通知(Notification) :更符合 Android 设计规范,用户干扰小
val notification = NotificationCompat.Builder(this, "channel_id")
.setContentTitle("提示")
.setContentText("来自Service的提醒!")
.setSmallIcon(R.drawable.ic_notification)
.build()
startForeground(1, notification)
4.12 显示Intent与隐式Intent的区别

4.13 横竖屏切换时Activity的生命周期
- 不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行1次,切竖屏时会执行1次
- 设置Activity的android:configChanges="orientation"或者android:configChanges="orientation|keyboardHidden"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次也会执行onConfigurationChanged方法
- 设置Activity的android:configChanges="orientation|keyboardHidden|screenSize"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法。切屏切记要加上screenSize,否则4.0版本以上生命周期不生效
Android 3.2(API 13)开始,新增了 screenSize 配置参数,横竖屏切换不仅改变 orientation,还会改变 screenSize。
4.14 onDestroy方法是在Activity finish之后立即执行的吗
不是。
Activity 的 onStop/onDestroy 是依赖 IdleHandler 来回调的,正常情况下当主线程空闲时会调用。但是由于某些特殊场景下的问题(比如在启动第二个activity时,进行大量动画的场景,源源不断的向主线程消息队列塞消息),导致主线程迟迟无法空闲,onStop/onDestroy 也会迟迟得不到调用但这并不意味着 Activity 永远得不到回收,系统提供了一个兜底机制,当 onResume() 回调 10s 之后,如果仍然没有得到调用,会主动触发。
4.15 需要在Activity之间传递大量的数据怎么办
添加链接描述
小数据使用Intent即可:Intent 传递数据的大小是有限制的,它大概能传的数据是1M-8K,原因是Binder锁映射的内存大小就是1M-8K
一般大数据的存储不适宜使用SP, MMKV,DataStore。
无非就是数据持久化或者内存共享
- LruCache
- 持久化(sqlite、file等)
- 匿名共享内存:Android 上层提供了一些内存共享工具类,就是基于 Ashmem 来实现的,比如 MemoryFile、 SharedMemory。
- 用ViewModel和LiveData,repository
5. View相关
5.1 Window,Activity,DecorView,ViewRoot之间的关系
5.2 事件分发机制
-
事件分发对象是谁
MotionEvent -
三个方法返回不同的值对应处理方式
dispatchTouchEvent(MotionEvent ev)
onInterceptTouchEvent(MotionEvent ev)
onTouchEvent(MotionEvent ev) -
整体流程图
5.3 View的绘制流程(View测量,布局及绘制原理)
-
从上到下还是从下到上
DecorView–>ViewGroup(—>ViewGroup)–>View ,按照这个流程从上往下,依次measure(测量),layout(布局),draw(绘制) -
MeasureSpec类型
MeasureSpec由两部分组成,一部分是测量模式,另一部分是测量的尺寸大小。
EXACTLY:精确尺寸(如 match_parent 或具体数值)。
AT_MOST:最大尺寸(如 wrap_content)。
UNSPECIFIED:未指定(如 ScrollView 的子 View)。 -
MeasureSpec如何确定的
DecorView:其确定是通过屏幕的大小,和自身的布局参数LayoutParams。
其他View(包括ViewGroup):通过父布局的MeasureSpec和自身的布局参数LayoutParams。 -
自定义View为什么要重写onMeasure方法
默认 onMeasure 不会处理 AT_MOST 模式,需要开发者手动设置尺寸。 -
每个View都需要实现onLayout方法吗
只有自定义布局(即继承自ViewGroup的类)时,才需要重写onLayout方法来定义子View的布局逻辑
5.4 Merge和ViewStub的作用
当你使用 include 引入一个布局文件时,如果被引入的布局最外层是一个 FrameLayout 或其他布局容器,可以使用 merge 替代它,这样在引入时就不会额外增加一层 ViewGroup。merge 只能在布局文件的根节点使用,并且通常与 include 一起使用。

5.5 更新UI的方式

- runOnUiThread
如果你在子线程中执行耗时操作后需要更新 UI,可以使用 Activity 提供的 runOnUiThread() 方法: - View.post()
适用于需要在 View 完成布局或测量之后执行某些操作,例如获取 View 的宽高、位置等信息。
其底层依赖于 Handler 和 MessageQueue

5.6 布局优化的三种标签
include:在当前布局中引另一个布局文件,实现布局复用
merge:用于 合并布局,避免引入额外的父布局层级
ViewStub:在布局加载时不加载其引用的布局资源,直到需要时才动态加载,用于实现延迟加载
5.7 SurfaceView的理解
资料1
资料2: 读书笔记-自定义控件开发入门与实战
-
view和surfaceView的区别
-
普通view和它的宿主窗口共享一个绘图表面(Surface),SurfaceView虽然也在View的树形结构中,但是它有属于自己的绘图表面,Surface内部持有一个Canvas,可以利用这个Canvas绘制
-
View适用主动更新,SurfaceView 适用被动更新,如频繁的刷新
-
View在UI线程更新,在非UI线程更新会报错,当在主线程更新view时如果耗时过长也会出错, SurfaceView在子线程刷新不会阻塞主线程,适用于界面频繁更新、对帧率要求较高的情况
-
SurfaceView底层利用双缓存机制,绘图时不会出现闪烁问题。
-
双缓冲机制
前台缓冲区:用于显示当前的画面。
后台缓冲区:用于绘制下一帧的内容。
当后台缓冲区的绘制完成后,两个缓冲区会进行交换,后台缓冲区的内容变成前台缓冲区的内容,从而显示到屏幕上。这种交换操作通常是快速且原子化的,避免了绘制过程中的画面撕裂问题。 -
TextureView
基于硬件加速的 Layer,将内容绘制到一个 SurfaceTexture 上。
所有绘制操作在 UI 线程中进行,不支持双缓冲。
更适合用于需要变换(如旋转、缩放)或叠加在其他 UI 上的场景(如相机预览)

5.8 滑动冲突如何解决
- 外部拦截法
由父容器决定是否拦截事件:onInterceptTouchEvent
在ACTION_MOVE中动态决定是否拦截
ViewPager 嵌套 RecyclerView - 内部拦截法
由子View决定是否允许父容器拦截:requestDisallowInterceptTouchEvent
横向滑动子 View 嵌套在纵向滑动父容器
5.9 如何优化自定义View
减少 onDraw() 中的耗时操作
- 避免在 onDraw() 中创建对象:如 Paint、Path、Rect 等对象应提前初始化。
- 避免频繁调用 invalidate():只刷新需要更新的区域,使用 invalidate(Rect dirty)。
- 减少绘制层级:尽量避免在 onDraw() 中进行复杂的路径绘制或多次绘制叠加
避免过度绘制(Overdraw) - 减少不必要的背景绘制,如父容器已设置背景,子视图不需要重复绘制。
- 使用 canvas.quickReject() 判断是否需要绘制某区域
合理使用 onLayout() 和 onMeasure() - 在自定义布局时,避免在 onLayout() 和 onMeasure() 中进行复杂计算。
- 重写 onMeasure() 时,确保返回合适的 MeasureSpec,避免无限循环或不合理的尺寸请求。
使用 Canvas 的状态保存与恢复 - 使用 canvas.save() 和 canvas.restore() 管理画布状态,避免重复设置参数
6. 多线程
6.1 子线程发消息到主线程进行更新ui方式
6.2 AsyncTask
抽象类,异步类(Android11后已废弃)
- 常用方法
onPreExecute():主线程,用于准备操作(如显示进度条)。
doInBackground(Params…):子线程,执行耗时任务。
onProgressUpdate(Progress…):主线程,用于更新进度。
onPostExecute(Result):主线程,用于处理结果并更新 UI。
onCancelled():主线程,任务被取消时调用。 - 代替方案
推荐使用协程、WorkManager、ExecutorService 等替代方案。
6.3 LRU缓存
- LRU(Least Recently Used,最近最少使用)缓存是一种常见的缓存淘汰策略,其核心思想是:当缓存空间满时,优先淘汰最近最少使用的数据。
- LRU 缓存通常基于 哈希表 + 双向链表 实现
- Android 提供了 LruCache 和 DiskLruCache 两个类,分别用于内存缓存和磁盘缓存

6.4 Thread / AsyncTask / IntentService的使用场景与特点

6.5 子线程为什么不能更新UI?如果想实现子线程更新UI,如何实现(从子线程“切换”到主线程去执行)
-
源码理解:
ViewRootImp的setView
requestLayout
checkThread -
为什么在onCreate里面的子线程去更新UI的不报错(如果放置个button再去点击就报错)
ANDROID 的 UI 线程检查机制并不是在所有时刻都严格生效;
ViewRootImpl的初始化有关,因为在onCreate的时候此时View还没被绘制出来,ViewRootImpl还未创建出来,它的创建是在activity.handleResumeActivity的调用到windowManager.addView(decorView)时候,如前面说的ViewRootImpl才被创建起来
添加链接描述 -
如何在子线程中更新UI(从子线程“切换”到主线程去执行)

7. Jetpack
7.1 LiveData的生命周期是怎么监听的
- LiveData 通过 LifecycleObserver 接口与 LifecycleOwner(如 Activity 或
Fragment)进行绑定,从而感知其生命周期状态 - LiveData 内部会创建一个 LifecycleBoundObserver,它实现了 LifecycleEventObserver接口
- LifecycleBoundObserver 监听 LifecycleOwner 的生命周期事件(如
ON_START、ON_STOP、ON_DESTROY) - **粘性事件机制:**LiveData 会在观察者变为活跃状态(如 onStart())时,自动发送最后一次的数据给观察者。这是 LiveData 的“粘性”特性,但也可能导致意外交付旧数据。
7.2 OkHttp相关
7.2.1 OKHttp 请求的整体流程是怎样的?
- 通过建造者模式构建OKHttpClient与 Request
- OKHttpClient通过newCall发起一个新的请求
- 通过分发器维护请求队列与线程池,完成请求调配
- 通过五大默认拦截器完成请求重试,缓存处理,建立连接等一系列操作
- 得到网络请求结果

7.2.2 OKHttp 分发器是怎样工作的?
Dispatcher 主要维护了以下结构:
最大并发请求数(maxRequests):默认为64,表示最多同时执行64个请求。
每个主机的最大请求数(maxRequestsPerHost):默认为5,防止对单一主机发起过多请求。
线程池(ExecutorService):用于执行异步请求的线程池。
就绪队列(readyAsyncCalls):存放等待执行的异步请求。
运行队列(runningAsyncCalls):存放当前正在运行的异步请求。
当调用 enqueue() 方法发起一个异步请求时,Dispatcher 会检查当前运行的请求数是否已达到上限。如果没有,则将其提交给线程池执行;否则,将其放入就绪队列中等待。
synchronized void enqueue(AsyncCall call) {
//请求数最大不超过64,同一Host请求不能超过5个
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
7.2.3 OKHttp拦截器是怎样工作的?
OKHttp 使用责任链模式(Chain of Responsibility)来组织多个拦截器,每个拦截器都有机会处理请求和响应。整个流程如下:
- 创建请求:用户创建一个 Request 对象。
- 进入拦截器链:请求进入拦截器链,依次经过应用拦截器(Application Interceptor)、OkHttp 内部拦截器(如缓存拦截器、桥接拦截器、重定向拦截器等)、网络拦截器(Network Interceptor)
- 执行网络请求:最终由网络层执行请求并获取响应。
- 响应返回:响应从网络层返回,并依次经过拦截器链的各个拦截器。
- 返回给用户:最终的响应返回给用户代码
拦截器的典型用途: - 添加统一的请求头(如 User-Agent、Authorization)
- 日志记录(如请求 URL、响应状态码)
- 缓存控制
- 请求重试逻辑
- 数据压缩/解压
- 耗时统计
7.2.4 OKHttp如何复用TCP连接?
- 工作原理:
当一个 HTTP 请求完成后,OKHttp 并不会立即关闭底层的 TCP 连接,而是将其放入连接池中缓存一段时间。当下次有相同目标主机的请求时,OKHttp 会尝试从连接池中取出已存在的连接来复用,而不是重新建立连接。 - 实现细节:
基于 Address 的匹配:
OKHttp 根据请求的 Address(包括主机名、端口、SSL 套接字工厂、代理等)来判断是否可以复用已有连接。
连接池(ConnectionPool):
连接池内部维护了一个 Deque,用于保存空闲的连接。默认最大空闲连接数为 5,每个连接默认保持 1 分钟。
Keep-Alive 支持:
OKHttp 默认支持 HTTP/1.1 的 Keep-Alive 机制,服务器可以通过 Connection: keep-alive 和 Keep-Alive: timeout=xx 来控制连接的保持时间。
自动清理空闲连接:
连接池内部有一个后台线程定期清理超时的空闲连接,避免资源泄露。
7.2.5 OKHttp框架中用到了哪些设计模式?
构建者模式:OkHttpClient 与 Request 的构建都用到了构建者模式
外观模式:OkHttp使用了外观模式,将整个系统的复杂性给隐藏起来,将子系统接口通过一个客户端 OkHttpClient 统一暴露出来
责任链模式: OKHttp 的核心就是责任链模式,通过5个默认拦截器构成的责任链完成请求的配置
享元模式: 享元模式的核心即池中复用, OKHttp 复用 TCP 连接时用到了连接池,同时在异步请求中也用到了线程池
7.3 Retrofit相关
7.3.1 Retrofit 是如何工作的?
Retrofit 通过 Java 动态代理模式,将定义的接口转换为可执行的网络请求。具体流程如下:
- 用户定义一个接口并使用注解(如 @GET、@POST)描述请求方式和参数。
- Retrofit 使用 create() 方法生成接口的动态代理对象。
- 调用接口方法时,Retrofit 将注解信息解析为 HTTP 请求,并交给 OkHttp 执行。
- OkHttp 返回响应后,Retrofit 使用 Converter(如 Gson、Moshi)将响应体转换为 Java 对象。
7.3.2 Retrofit 如何支持 RxJava?
Retrofit 可以配合 RxJava2CallAdapterFactory 或 RxJava3CallAdapterFactory 来返回 Observable、Single、Completable 等响应式类型。
OkHttpClient client = new OkHttpClient.Builder().build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build();
7.3.3 Retrofit 中的 Converter 和 CallAdapter 有什么作用?
Converter:负责将请求体或响应体转换为特定格式(如 JSON、XML)。
常见的有 GsonConverterFactory、MoshiConverterFactory、ScalarsConverterFactory 等。
CallAdapter:将 Call< T > 转换为其他类型的返回值,如 Observable< T >、LiveData< T >、CompletableFuture< T> 等。
通过 addCallAdapterFactory() 添加,如 RxJava3CallAdapterFactory、LiveDataCallAdapterFactory。
8. Bitmap
8.1 Bitmap回收问题
- 手动调用 RECYCLE() 方法(适用于 API < 26)
在 Android 8.0(API 26)之前,Bitmap 的像素数据是存储在 Native 层 的,Java 层只是引用。因此需要手动调用 recycle() 来释放 Native 内存。 - 自动回收(API >= 26,ANDROID 8.0 及以上)
从 Android 8.0 开始,Bitmap 的像素数据被移到了 Java 堆,由垃圾回收机制自动管理,不再需要手动调用 recycle()。
8.2 如何计算Bitmap占用内存的大小;怎么保证不内存溢出
- 计算公式:
内存大小 = 宽 × 高 × 每个像素占用的字节数 - 保证内存不溢出
使用 INSAMPLESIZE 缩放加载图片
使用 BITMAP.CONFIG 选择合适格式
及时回收不再使用的 BITMAP
9. 进程
9.1 Android进程间的通信方式(IPC)

9.2 Android进程的优先级

9.3 多进程通信可能出现的问题

10. 动画
10.1 动画分类

10.2 属性动画的核心类,实现原理
- 核心类

- 实现原理
- 定义动画时长和帧率:系统根据动画的持续时间(duration)和刷新频率(默认 10ms)生成动画帧。
- 计算动画进度(Fraction):根据时间推进,计算当前动画完成的百分比(0.0 ~ 1.0)。
- 通过插值器(Interpolator)调整动画节奏:例如 AccelerateInterpolator 会让动画开始慢、结束快。
- 通过估值器(Evaluator)计算当前属性值: 例如:从 0 到 100,当前进度为 0.5,插值后可能为
0.3(取决于插值器),估值器将 0.3 转换为实际值 30。 - 通过反射或属性 setter 设置目标对象的属性值: 若目标对象有属性的 setter 方法(如
setTranslationX()),则调用该方法更新属性。
10.3 有一个Button,进行平移后,移动后的点击事件分析
如果你对一个 Button 使用 平移动画(无论是补间动画还是属性动画),视觉上按钮是移动了,但点击事件仍然作用于原来的位置。这是因为这两种动画本质上都不会修改 View 的布局位置(left, top, right, bottom),只是改变了它的绘制位置。
- 解决方案:
ObjectAnimator animator = ObjectAnimator.ofFloat(button, "translationX", 0f, 200f);
animator.setDuration(500);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// 更新布局参数
ViewGroup.MarginLayoutParams params =
(ViewGroup.MarginLayoutParams) button.getLayoutParams();
params.leftMargin += 200;
button.setLayoutParams(params);
button.setTranslationX(0f); // 可选:重置 translation
}
});
animator.start();
11. 渲染
11.1 硬件加速和软件加速
-
软件绘制流程
invalidate触发:View调用invalidate()
遍历视图树 : 从根view开始遍历需要重绘的View
CPU绘制:调用View.onDraw发明回复;通过Skia库进行软件绘制;结果写入Bitmap缓冲区 -
硬件加速流程
invalidate触发:View调用invalidate()
DisplayList构建:将绘制操作记录为显示列表;不立即执行实际绘制
RenderThread处理:专用渲染线程处理显示列表;转换为OpenGL ES/Direct3D指令 -
开启硬件加速
1.view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
2.可以在 AndroidManifest.xml 文件中为整个应用或某个 Activity 设置是否启用硬件加速
3.Android 3.0(API 11)开始默认启用硬件加速。
4.低版本(如 Android 4.x)可能存在兼容性问题,建议根据需求动态控制。 -
硬件加速的注意事项
Canvas 操作支持不全: 并非所有 Canvas 操作都支持硬件加速,尤其是在 Android 早期版本中。例如:drawPicture() ; Canvas.clipPath() 在某些设备上不支持
GPU 图层(Layer)占用内存:使用 setLayerType(View.LAYER_TYPE_HARDWARE, null) 会为 View 创建一个 GPU 图层,占用额外内存。
11.2 简单说下Vsync的作用
- 其核心功能是同步屏幕刷新率与应用的绘制操作,以提升显示流畅性并避免画面撕裂
- 同步 GPU/CPU 绘制与屏幕刷新
屏幕的刷新频率通常是 60Hz(每秒 60 次),VSync 信号每 16.6 毫秒触发一次,通知系统开始绘制下一帧。这样可以确保绘制操作与屏幕刷新同步。 - 避免画面撕裂(Tearing)
如果绘制速度与屏幕刷新不同步,可能会出现“画面撕裂”现象(即一帧画面显示了两个不同帧的部分内容)。VSync 可以避免这种情况。 - 大Jank

在标号2由于CPU没有及时处理这一帧,导致显示器只能继续显示第一帧,即大Jank
- VSync,CPU, GPU 三者之间的协作流程
- CPU 阶段(UI Thread)
接收到 VSync 信号后,开始执行 onMeasure()、onLayout()、onDraw() 等操作。 - 生成 Display List(绘制命令列表)
GPU 阶段(Render Thread)
将 CPU 生成的 Display List 转换为真正的像素图像,渲染到帧缓冲区(Frame Buffer) - VSync 同步显示
屏幕在下一个 VSync 信号到来时,从帧缓冲区读取图像进行显示。如果渲染完成前屏幕刷新,就会出现卡顿或掉帧。

12. 数据库
12.1 SharedPrefreneces的apply和commit的区别

13. ANR
13.1 定义,不同组件发送ANR的时间
- 定义 ANR(Application Not Responding)
当 Android 应用的主线程(UI 线程)在规定时间内没有响应某个事件(如点击、启动组件、广播等),系统会认为应用无响应,弹出 ANR 对话框,提示用户“等待”或“关闭”。 - 不同组件发送ANR的时间

13.2 常见导致ANR的原因

14. 内存泄漏
14.1 内存泄漏的本质
对象不再被使用,但由于某些原因无法被垃圾回收器(GC)回收,导致内存被无效占用,最终可能引发内存溢出(OOM)或性能下降。
14.2 常见的GCRoot
在 Java 和 Android 的垃圾回收机制中,GC Roots 是垃圾回收器用来判断一个对象是否可被回收的起点。GC Root 是一组必须活跃的引用,从这些根节点出发,沿着引用链向下遍历,所有能被访问到的对象都被称为“可达对象”,不会被回收;而不能被访问到的对象则被认为是“不可达对象”,将被回收。

14.3 常见的内存泄漏例子

14.4 AndroidStudio用于监测内存泄漏的工具

14.5 自定义Handler如何避免内存泄漏问题
- 将 Handler 定义为静态内部类,并使用 WeakReference 持有外部类的引用。
- onDestroy 中 removeCallbacks
- 使用 Lambda 或静态方法避免隐式引用
private Handler handler = new Handler(Looper.getMainLooper());
private void startTask() {
handler.postDelayed(() -> {
// 这里不会持有外部类的引用
}, 5000);
}
14.6 BlockCanary原理

14.7 LeakCanary原理

WeakReference:弱引用的对象在下一次 GC 时会被回收,即使它被引用。
ReferenceQueue:当弱引用指向的对象被回收后,该弱引用会被加入到引用队列中。
- LeakCanary 利用这两个机制来检测对象是否被正确释放:
创建一个 WeakReference< SomeObject > 并关联一个 ReferenceQueue。
如果对象被回收,该弱引用会进入引用队列。
如果在一定时间内未进入引用队列,则认为该对象未被回收,可能存在内存泄漏。
15. Binder
15.1 Binder机制的作用和原理
- Binder机制的作用:
跨进程通信(IPC):让不同进程之间能够传递数据和调用方法。
对象引用管理:Binder机制支持在不同进程之间传递对象的引用,而不需要复制整个对象。
服务注册与获取:系统服务(如AMS、WMS等)通过Binder机制注册并供其他进程调用。
统一接口:提供统一的接口封装底层通信细节,简化开发。 - Binder机制的原理:
基于C/S架构:Binder通信模型中,一方作为服务端提供服务,另一方作为客户端发起请求。
Binder驱动:Binder机制的核心是在内核空间中的Binder驱动,它负责进程间的数据传输和对象管理。
IBinder接口:所有可以通过Binder传输的对象都必须实现IBinder接口。
Parcel数据包:Binder通信中传递的数据需要封装成Parcel格式,Parcel是一种轻量级的序列化机制。
Binder通信流程:
客户端通过Binder代理对象发起远程调用(RPC)。
Binder驱动将请求转发给服务端。
服务端处理请求并返回结果。
15.2 Binder工作流程
- 服务端注册服务:
服务端进程创建一个Binder本地对象(继承自Binder类),并实现相应的接口方法。
服务端通过ServiceManager或Context将Binder对象注册到系统中,供客户端查找和绑定。 - 客户端获取服务代理:
客户端通过ServiceManager.getService()或绑定服务(bindService())获取服务端的Binder引用。
系统返回的是一个Binder代理对象(Proxy),它实现了与服务端相同的接口。 - 客户端发起远程调用(RPC):
客户端调用Binder代理对象的方法。
Proxy将方法调用的参数打包成Parcel数据,并通过transact()方法发送请求。 - Binder驱动转发请求:
Binder驱动负责在内核空间中将客户端的请求转发给服务端。
驱动将请求从客户端进程复制到服务端进程。 - 服务端处理请求并返回结果:
服务端的Binder本地对象接收到请求后,解析Parcel数据并调用实际的方法。
执行完毕后,将结果封装成Parcel返回给客户端。 - 客户端接收返回结果:
客户端从Binder驱动接收到返回的Parcel数据,解包后得到服务端返回的结果。
15.3 Binder跨进程通信的核心组件:

15.4 Binder框架中ServiceManager的作用
在Binder框架中,ServiceManager 是一个非常关键的系统服务,它负责管理系统中的 Binder服务注册与查询,是Android跨进程通信(IPC)机制中用于服务管理的核心组件。
15.5 Binder和AIDL的区别与联系

15.6 如何正确管理Binder对象的生命周期
Binder 对象使用 引用计数(strong/weak references) 管理生命周期

15.7 如何处理Binder的死亡通知
- 每个 IBinder 对象可以注册一个或多个 死亡通知回调(DeathRecipient)。
- 当 Binder 对应的远程进程终止时,Binder 驱动会通知所有注册的 DeathRecipient。
- 客户端可以在这个回调中释放资源、重新连接服务或提示用户。
15.8 Binder的安全性如何保障,有哪些机制用于权限控制
- 基于 UID/GID 的权限控制
Android 是基于 Linux 内核的系统,每个应用在安装时都会被分配一个唯一的 UID。
Binder 在进行 IPC 调用时会验证调用者的 UID/GID,服务端可以根据调用者的 UID/GID 决定是否允许访问。 - 使用签名权限(signature permissions)
Android 支持自定义权限类型,其中 signature 级别的权限只允许与声明权限的应用具有相同签名的应用访问。这种机制可以有效防止未授权应用访问敏感服务 - Binder 驱动中的权限检查
Binder 驱动在内核空间中运行,负责 IPC 数据的传输和权限检查。
在 Binder 调用过程中,驱动会记录调用链的上下文信息(如 UID、PID),供服务端进行安全验证。
15.9 AIDL中的in out inout oneway关键字

16. Framework
16.1 从启动到主页面显示经历了哪些过程
- 点击应用图标
用户点击桌面上的应用图标,Launcher 进程通过 startActivity() 方法启动目标应用的主 Activity。 - AMS 接收启动请求
Launcher 通过 Binder 调用 ActivityManagerService(AMS)的 startActivity() 方法。
AMS 检查目标 Activity 是否已经存在、是否需要新建任务栈等。 - 创建应用进程
如果目标应用尚未运行,AMS 会通过 zygote 创建一个新的应用进程。
AMS 通过 Process.start() 向 Zygote 发起创建进程的请求。
Zygote fork 出一个新的进程作为应用进程。 - 应用进程初始化
新进程启动后,执行 ActivityThread 的 main() 方法。
创建 Looper 和 Handler,准备主线程的消息循环。
通过 Binder 向 AMS 发起 bindApplication() 请求,通知 AMS 应用进程已就绪。 - AMS 发送启动 Activity 指令
AMS 向应用进程发送 scheduleLaunchActivity() 消息。
应用进程接收到消息后,创建 Context、Instrumentation、Activity 实例。 - 执行生命周期方法
执行 onCreate()、onStart()、onResume() 生命周期方法。
加载布局文件(setContentView())、初始化 UI 组件、绑定数据等。 - Surface 创建与绘制
PhoneWindow 创建 DecorView,ViewRootImpl 与 SurfaceFlinger 通信创建 Surface。
执行 measure、layout、draw 三大流程,将 UI 绘制到屏幕上。
第一次绘制完成后,页面正式显示。 - 通知 AMS 页面已显示
应用进程通过 Binder 通知 AMS,主页面已成功显示。
16.2 AMS家族重要术语解释

16.3 Choregrapher的理解
添加链接描述
Choreographer(中文可译为“编舞者”)是 Android 中用于协调 UI 渲染、输入事件、动画、绘制等操作的系统组件。它的核心作用是将多个异步任务(如动画、绘制请求)与系统的 VSync 信号 同步,以确保它们在每一帧中有序执行,从而提高 UI 的流畅性和性能。
- VSync(垂直同步)信号:来自系统底层(SurfaceFlinger),通常每 16ms 发送一次(对应 60Hz屏幕刷新率),表示屏幕准备刷新。
- Choreographer:每个线程(通常是主线程)拥有一个 Choreographer 实例,它负责监听 VSync信号,并在每一帧到来时触发回调。
- 你可以将 Choreographer 想象一个“舞蹈编排师”:
每个 VSync 信号就像一个节拍(16ms 一拍)。
Choreographer 负责在每个节拍到来时,安排不同的“舞者”(如动画、布局、绘制)按顺序上场。
如果某个“舞者”动作太慢(比如布局计算太久),就会错过节拍,导致掉帧。
16.4 WMS如何管理Window
WMS 是 Android 系统级别的服务,负责管理所有窗口的创建、布局、层级和焦点等核心功能。
- App 调用 WindowManager.addView()。
- 通过 Binder 调用 WMS 的 addWindow() 方法。
- WMS 验证权限、分配 token、计算布局。
- WMS 通知 SurfaceFlinger 创建 Surface。
- App 通过 ViewRootImpl 向 Surface 绘制内容。
17. 性能优化
17.1 布局优化
参考小程序的绘制会有这一小节 学一些话术
线上监控:Choreographer这个类,获取帧率实时
线下监控:采用AOP方式或者ARTHook

17.2 内存泄漏优化

17.3 内存抖动
内存抖动(Memory Churn)是指在短时间内频繁地分配和释放内存,导致 内存使用曲线呈现“抖动”状 的现象。
- 避免在循环体内创建对象
- 自定义View的onDraw方法会被频繁调用,不应该在这里频繁的创建对象
- 当需要大量使用Bitmap时,把它们缓存在数组或者容器中实现复用
- 对于能够复用的对象,使用对象池进行缓存
17.4 响应速度优化

17.5 ListView/RecyleView以及Bitmap优化

17.6 APP电量优化如何做

18. 其他
18.1 XML解析方式

18.2 AndroidMainfest的作用与理解

18.3 Base64,MD5的理解

18.4 Bunder传递对象为什么需要序列化? Serialzable和Parcelable的使用场景区别
-
Bundle 只能直接存储基本数据类型(如 int、String 等)以及实现了 Parcelable 或 Serializable 接口的对象。
-
Serialzable和Parcelable的使用场景区别

18.5 编译期注解和运行时注解
- 编译时处理,用于生成代码或检查错误
- 运行时通过反射获取注解信息并执行逻辑

18.6 编译器注解之APT相关
- APT 的工作原理
APT 是 Java 编译流程中的一个插件机制,它在编译阶段(即 .java → .class 之间)运行。流程如下: - Java 编译器(javac) 启动编译
- APT 找到所有注解处理器(AnnotationProcessor)
- 处理器扫描源码中使用了特定注解的类、方法、字段等元素
- 根据注解信息生成新的 Java 文件或其他资源文件
- 生成的代码与源码一起参与后续的编译流程
- APT 不会修改已有代码,而是生成新代码,然后这些代码会参与后续编译,最终一起打包进 .class 文件。
- 在Android中的应用

18.7 APT和JavaPoet
添加链接描述
APT(Annotation Processing Tool)是 Java 编译期注解处理工具,常用于在编译阶段解析注解并生成代码。JavaPoet 是 Square 开源的一个用于生成 Java 源码的库,它可以简化代码生成过程,是 APT 中常用的辅助工具。

18.8 Android混淆原理
- Java 编译为 .class 文件
- 转换为 .dex 文件前,R8/ProGuard 对 .class 进行处理
- 执行压缩(Shrinking):移除无用代码
- 执行优化(Optimization):简化逻辑、内联方法等
- 执行混淆(Obfuscation):重命名类、方法、字段
- 输出混淆后的 .dex 文件和映射文件(mapping.txt)
18.9 Android推送服务的几种实现方式
18.10 热修复原理
原理文章
PathClassLoader和DexClassLoader
一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找
那么这样的话,我们可以在这个dexElements中去做一些事情,比如,在这个数组的第一个元素放置我们的patch.jar,里面包含修复过的类,这样的话,当遍历findClass的时候,我们修复的类就会被查找到,从而替代有bug的类。
18.11 插件化原理
添加链接描述
添加链接描述
Android 插件化是一种将部分功能模块以插件形式集成到宿主 App 中的技术,插件可以按需加载,实现热更新、动态加载、模块解耦等目标。
- 🧠 插件化实现原理
- 类加载机制
使用 DexClassLoader 加载插件中的 .dex 文件或 .apk 文件
通过反射调用插件中的类和方法 - 资源访问机制
插件资源无法直接通过 R.xx.xxx 访问
需要创建一个新的 AssetManager 并添加插件资源路径
构造新的 Resources 对象访问资源 - 组件生命周期管理
插件中的 Activity 无法直接启动(没有在 AndroidManifest.xml 中注册)
代理 Activity 模式:宿主提供一个 StubActivity,插件通过反射调用其生命周期方法
Hook 技术:通过 Hook Instrumentation 或 AMS,欺骗系统启动插件 Activity














1361

被折叠的 条评论
为什么被折叠?



