文章目录
- 一. 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. 渲染
硬件加速和软件加速
- 基本概念区别:绘制执行者,绘制方式差异,优缺点
- 软件绘制流程四步曲
- 硬件加速流程四步曲
- 性能差异
- 使用场景及优化建议
简单说下Vsync的作用
12. 数据库
SharedPrefreneces的apply和commit的区别
13. ANR
定义,不同组件发送ANR的时间
常见导致ANR的原因
列举五个
14. 内存泄漏
强引用置为null,会不会被回收
内存泄漏的本质
常见的GCRoot
常见的内存泄漏例子
AndroidStudio用于监测内存泄漏的工具
自定义Handler如何避免内存泄漏问题
BlockCanary原理
LeakCanary原理
后续再深入理解
15. Binder
基本理解
架构/Binder驱动层/服务端/客户端/对象/引用计数
Binder机制的作用和原理
Binder框架中ServiceManager的作用
Binder工作流程
5步
Binder是如何实现跨进程通信的
Binder和AIDL的区别与联系
如何正确管理Binder对象的生命周期
如何处理Binder的死亡通知
如何优化跨进程数据传输的性能
Binder的安全性如何保障,有哪些机制用于权限控制
AIDL中的in out inout oneway关键字
16. Framework
从启动到主页面显示经历了哪些过程
AMS家族重要术语解释
AMS
ActivityThread
ApplicationThread
ApplicationThreadProxy
Instrumentation
ActivityStack
ActivityRecord
TaskRecord
Choregrapher的理解
WMS如何管理Window
17. 性能优化
布局优化
绘制优化
内存泄漏优化
响应速度优化
ListView/RecyleView以及Bitmap优化
线程优化
18. 其他
XML解析方式
- 常见的两种方式及使用场景
AndroidMainfest的作用与理解
Base64,MD5的理解
- 原理
- 是否为加密算法,是否可逆
Bunder传递对象为什么需要序列化?
Serialzable和Parcelable的使用场景区别
编译期注解和运行时注解
热修复原理
原理文章
PathClassLoader和DexClassLoader
一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找
那么这样的话,我们可以在这个dexElements中去做一些事情,比如,在这个数组的第一个元素放置我们的patch.jar,里面包含修复过的类,这样的话,当遍历findClass的时候,我们修复的类就会被查找到,从而替代有bug的类。
插件化原理
编译器注解之APT相关
APT和JavaPoet的一些技巧
Android推送服务的几种实现方式
APP电量优化如何做
Android混淆原理
ProGuard四个步骤












1178

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



