news 2026/9/10 2:55:46

Android进阶工程师34讲:从原理到优化的知识体系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android进阶工程师34讲:从原理到优化的知识体系

我一直有个习惯,就是看技术资料的时候喜欢把核心思路单独抄出来,不是复制粘贴,而是用自己的话重新写一遍。以前零散记在各个地方,后来发现Android这块东西太杂,从UI到系统框架、从性能优化到构建流程,每一块拎出来都能写一本书。去年我下定决心做了一套完整的整理,大概持续了几个月,断断续续把笔记归拢成三十几讲,也就是我手里的这套“Android进阶工程师34讲笔记”。这次就把它拿出来,结合我自己做实际项目时的体会,把里面的内容框架、踩坑记录、观察思路都展开聊聊。对正在准备进阶或者面试的人来说,这套笔记的参考价值应该是直给的。

最初想得很简单:把日常开发里遇到的知识点系统性过一遍。结果一上手就发现,Java/Kotlin 基础、四大组件、View 机制、线程与进程、网络与存储、性能优化、Gradle 与构建、Jetpack 全家桶、AMFrameworks 涉及的系统服务、跨端方案、车载等新场景……每一个方向都能细化出十几个真实问题。硬做的话就是做成一本工具书,但那不是我想要的。我需要的是一套“能回答为什么”的笔记,而不是一堆名词的堆叠。

1. 整套笔记的内容框架与学习路线

1.1 为什么是34讲,三条主线怎么拆

很多朋友问我,你这个34讲是不是拍脑袋定的数字。其实不是,我的划分依据是三条主线。

第一条线是“应用层开发进阶”,就是把日常写代码这件事做得更扎实。包括 Kotlin 语言深水区、泛型与协程、Jetpack(Lifecycle、ViewModel、Room、WorkManager)、MVVM 架构、Compose 声明式 UI、自定义 View 绘制流程、事件分发机制、动画机制、多线程与并发处理、网络层封装、图片加载与缓存。这些内容对应的是“能不能写出高质量、好维护、不崩的应用”。

第二条线是“系统与框架层认知”,也就是跳出应用本身,去看 Android 系统是怎么运作的。包括 APK 打包与安装流程、Activity 启动流程、AMS 与 WMS 的职责边界、Handler 与 Looper 机制、Binder 通信原理、AIDL 的使用、进程与线程模型、匿名共享内存、系统服务注册与获取、四大组件的工作过程。这部分的难点在于源码量大、抽象度高,但一旦打通,很多应用层的问题就会迎刃而解。

第三条线是“工程效率与性能优化”,覆盖从开发到上线的完整链路。包括 Gradle 构建优化与 AGP 版本适配、R8 混淆与资源压缩、依赖冲突排查、APK 瘦身、启动速度优化、布局层级优化、内存泄漏排查、卡顿与掉帧分析、Crash 收集与定位、多模块化拆分、组件化通信。还有一部分专门针对新趋势,比如 Android 14 权限变化、动态图标主题适配、车载项目开发、以及如何在 Android 上用火焰图做性能剖析。

每条主线大概分到十几讲,加起来就是三十几讲。这样做的好处是:无论你平时做业务还是做系统,都能在前面找到跟自己相关的部分,不必从头到尾啃完。

1.2 学习顺序的建议:别贪多、按场景推进

笔记整理完之后,我给它排了一个推荐的阅读顺序。这个顺序不是按难度排的,而是按“工作场景”排的。

如果你是一名三年以内的应用层开发,我建议先看第二条线里的“Handler 与 Looper”“Binder 与 AIDL”“Activity 启动流程”,因为这类问题在面试和实际排障中出现的频率极高,而且理解了原理之后,很多应用层的技术决策会变得清晰。之后再去看第一条线的协程、MVVM 和 Compose,这是当前写业务的主流方式。

如果你已经在做性能优化或者架构相关的工作,那直接跳到第三条线,尤其是 APK 瘦身、启动优化、内存检测这三讲。这三讲里有大量我实际测试过的数据和配置,比网上零散的资料要连贯得多。

如果你是在准备进阶面试,那我的建议是:把三条线交叉着看,每次只挑一个专题深入。比如今天只看 AIDL,那么就把 Binder 原理、AIDL 使用、跨进程回调、常见异常全部过一遍,形成一个闭环。不要今天看 AIDL、明天跳去 Compose,那样知识是散的,过两天就忘了。

2. 核心细节解析与实操要点

2.1 Android 系统框架先破冰:AMS、Activity 与启动流程

很多人刚开始看 AMS(ActivityManagerService)都会懵,因为名字多、方法多、调用栈深。我在笔记里把它压缩成了一句话:AMS 是 Android 系统里负责管理 Activity 生命周期和任务栈的系统服务。它本身运行在 system_server 进程里,应用要启动 Activity 时,并不是自己直接 new 一个出来,而是通过 Binder 调用 AMS,由 AMS 决定这个 Activity 应该放在哪个任务栈、是否需要创建新进程、走什么生命周期。

这里有个非常容易忽略的细节:应用进程与 system_server 进程是两个不同的进程,它们之间的通信依赖 Binder。而 Binder 通信的每一次调用都涉及数据序列化、线程切换、内存映射,不像普通方法调用那样可以随意地把大对象传来传去。所以像 Intent 里的 Bundle,如果塞了特别大的数据,就很容易触发 TransactionTooLargeException。这个在实际业务里非常常见,特别是页面跳转时想把一个列表数据全部传给下一个页面的时候。

我自己在记这一讲的时候,特意做了一个对比实验:用 Intent 传递一个 1MB 的 Bitmap,结果运行时报错;换成传一个 URI 或者先用文件缓存再传路径,就完全没有问题。这个实验的结论我直接写进了笔记:“跨进程传大量数据时,先问自己——数据能不能不直接传?能不能传引用?”这个思维方式后来帮我解决了不少线上问题。

2.2 AIDL 的使用与 Binder 线程模型

AIDL(Android Interface Definition Language)是很多人觉得“会写代码但说不清楚原理”的一块内容。其实它做的事情很简单:帮你生成一套基于 Binder 通信的客户端与服务端接口代码。你自己要写的,只是接口定义和具体实现。

我在笔记里把 AIDL 的使用过程拆成四步:

  1. 定义 .aidl 接口文件,里面写方法签名。
  2. 在服务端实现接口(Stub 的子类),在 onBind 里返回该实例。
  3. 客户端通过 bindService 拿到 IBinder,调用 Stub.asInterface 转成接口。
  4. 通过接口调用方法。

但这里有一个坑特别值得说:AIDL 方法默认是阻塞式的,运行在调用方的 Binder 线程池里,不是主线程。所以如果你在客户端直接同步调用一个耗时较长的 AIDL 方法,不会导致主线程直接崩溃,但可能会让调用线程卡住。如果在主线程调用,就会触发 ANR。

我曾经在一个项目里遇到“客户端调服务端接口时 UI 卡住”的问题,查了很久最后发现是服务端接口里做了一个网络请求,导致 Binder 调用耗时接近两秒,而客户端又在主线程里同步等待。解决办法有两种:一是把 AIDL 调用放到子线程;二是用 oneway 方式声明接口,让调用方不等待服务端返回,异步执行。但 oneway 不能有返回值,所以适合“通知型”的任务,不适合“请求-响应型”的任务。

另外,AIDL 里的回调也是可以的。服务端可以持有客户端的 IInterface 对象,在状态变化时调用。这里要注意:回调对象需要做 RemoteCallbackList 管理,否则服务端保存的客户端对象会泄漏,或者客户端已经销毁却还在被回调,最终导致 DeadObjectException。

2.3 Jetpack Compose 与声明式 UI 的思维转变

Compose 这块我是自己踩了很深的坑才慢慢适应过来的。早期的 Android UI 以 View 体系为主,你要手动 setText、addView、notifyDataSetChanged,数据和界面之间是“命令式”的关系。而 Compose 的逻辑是:界面是状态的一个函数,状态变了,界面就自动重组(Recompose)。

听起来很美,但实际写的时候有几个关键点必须注意。

第一,Composable 函数不是普通函数,它可以被随时重新执行。所以千万不要在 Composable 函数里直接写耗时操作、IO 操作,也不要把大对象作为局部变量频繁创建,否则重组时会反复执行,造成卡顿。

第二,状态谁持有。用remember { mutableStateOf(...) }可以保存本地状态,用rememberSaveable可以应对配置变更时的恢复。但是跨页面或跨模块共享状态,就需要 ViewModel 或者官方推荐的 StateFlow。我在笔记里专门做了一张状态提升的说明图,把“哪个状态放在哪个层级”这个问题拆成了三个判断条件:这个状态是否会被兄弟组件使用?是否会被父组件使用?是否会和页面生命周期绑定?

第三,Compose 与传统 View 的互操作。现有项目不太可能一天改成纯 Compose,所以AndroidViewComposeView这两个桥接组件特别重要。我在一个项目里把原来自定义的图表 View 嵌到了 Compose 页面里,方法很简单,就是通过AndroidView(factory = { context -> MyChartView(context) })来创建,但是要注意属性更新时要在update回调里做,不要每次都重新 create。

笔记里我还记录了一个实测数据:一个普通列表页在 Compose 版本的帧率与 XML 版本相差不大,但在初次合成时,Compose 的内存占用会稍高一些,因为它需要额外的布局和组合数据结构。对低端机来说,如果列表非常长,建议使用LazyColumn配合key来提高复用效率,这一点和 RecyclerView 的 ViewHolder 复用是一个道理。

2.4 R8、AGP 与构建优化:别等上架才重视

很多人对 R8 的理解停留在“混淆工具”这一层。其实 R8 在 Android Gradle Plugin 3.4.0 之后就完全取代了 ProGuard,扮演的角色是“压缩、优化、混淆、脱糖”四合一。默认情况下,release 构建就会开启 R8,它会移除未使用的代码、对类名和方法名进行混淆、内联一些方法、裁剪未被反射调用的类。

这里必须强调一个日常开发特别注意的地方:R8 开启后一定要配置 keep 规则。特别是使用 Gson/泛型、反射、JNI、注解处理生成的类、以及第三方 SDK 里通过反射加载的类,如果没配 keep,轻则运行时崩溃,重则功能静默失效。我遇到过最典型的是某个扫码 SDK 在 release 包上点击扫码直接闪退,打开堆栈才发现是 SDK 内部通过反射获取了被混淆掉的类名。

构建优化这块,我建议每位开发都做几件事:

  • org.gradle.jvmargs里的堆内存调到合理的值,比如-Xmx4g
  • 开启org.gradle.parallel=trueorg.gradle.caching=true
  • 在大型多模块项目里,使用configuration-on-demand来缩短配置阶段时间。
  • 依赖版本统一管理,用 Version Catalog(Gradle 7.0+)替代原来的 ext 变量。

我笔记里整理过一个实际项目从冷构建 5 分钟降到 2 分钟左右的例子,主要改动就是上面这几项。但有一个很容易被忽视的坑:如果模块之间的依赖关系没有理清,开启并行构建可能会导致某些任务执行顺序变化,进而引发编译失败。所以并行构建不是救命稻草,前提是你的模块边界清晰。

AGP 版本适配也是个大话题。比如 Android Studio Hedgehog(2023.1.1 Patch 2)以及它对应的 AGP 8.x 版本,对 Gradle JDK 版本就有明确要求。如果本地 JDK 版本过老或过新,会直接报Unsupported class file major version之类的错误。我在笔记里记录了一个完整的环境匹配表:Gradle 8.2+ 配 JDK 17,AGP 8.1/8.2 配 Gradle 8.0/8.2,Android Studio 版本要高于 AGP 要求的最低版本。

2.5 性能剖析:Android 火焰图与卡顿定位

性能优化这讲,我花了很多时间整理工具和思路。很多人遇到卡顿第一反应是看 Logcat 或者随便加打印,但我更推荐的流程是:先复现,再用工具采样,最后定位热点。

Android Studio 自带的 CPU Profiler 已经很好用了,但如果你想要更细致地看函数调用耗时占比,火焰图是不可或缺的工具。Android 上生成火焰图的常见路径有两种:

  • simpleperf采样,生成 perf.data,再用report.py转成火焰图 HTML。
  • 用 Android Studio 的 CPU Profiler 导出 Call Chart 或 Flame Chart。

我在笔记里记录了 simpleperf 的核心命令,大致长这样:

# 在设备上执行采样 adb shell simpleperf record -p <pid> -o /data/local/tmp/perf.data --duration 10 # 拉取数据 adb pull /data/local/tmp/perf.data # 在主机上生成报告 python report.py --format html -i perf.data -o flamegraph.html

但采样工具只能告诉你哪段函数耗时高,不能直接告诉你“为什么耗时高”。比如你看到inflate方法占了很大比例,说明布局 XML 解析和 View 创建开销大;看到measure/layout耗时高,说明视图层级过深或约束过多;看到 GC 相关方法频繁出现,说明分配了太多短生命周期对象。

我之前排查过一个卡顿案例:页面里有一个动态图标动画,在低端机上明显掉帧。用火焰图采样后发现,耗时主要在ImageView.setImageDrawableDrawable.invalidateSelf上,原因是动画每一帧都重新创建了一个 Drawable 对象。后来把动画改成复用同一个 Drawable,只更新 bitmap 内容,帧率立刻稳定了。

这类问题靠眼睛看是看不出来的,必须用数据说话。这也是我在笔记里把“火焰图”单独拿出来强调的原因,用工具提效,比盲目优化靠谱得多。

3. 实操过程与核心环节实现

3.1 搭建一套可复用的实验工程

整理笔记的时候,我特意建了一个独立的实验工程,专门用来跑各种源码验证和性能测试。这个工程的包名结构是分模块的,比如frameworkuiperformancebuildtools各对应一个 module。好处是验证某个知识点时不污染主项目,所有代码都有回头查看的现场。

工程环境我当时用的是 Android Studio Hedgehog(2023.1.1 Patch 2)和 AGP 8.2 的组合。为什么要用这个组合?因为 Hedgehog 对 Compose 和 AGP 8.x 的支持已经很稳定,而且 JDK 17 是标配,能同时满足 Kotlin 2.x 和 AGP 8.x 的要求。如果你还在用 Android Studio 4.1.3 那个年代的工具链,那我建议还是跟上时代,因为很多 API 和构建工具已经做了向后兼容的适配,老版本跑起来反而容易出现奇奇怪怪的问题。

搭建工程时有个小技巧:在每个 module 的build.gradle.kts里,把一些通用依赖统一放到根项目的libs.versions.toml里管理。这样以后查版本号、升版本都方便,不需要到处翻文件。

3.2 MVVM 代码实例:从 XML 到 Compose 的双版本

我在笔记里专门整理了一个 MVVM 的示例,同一个界面分别用 XML + ViewBinding 和 Compose 写了一遍。两种写法背后的架构思路是一致的:界面层只负责展示状态,事件由 ViewModel 接收,数据由 Repository 提供。

用 XML + ViewBinding 的写法大致是这样的:

class MainViewModel : ViewModel() { private val _uiState = MutableStateFlow(MainUiState()) val uiState: StateFlow<MainUiState> = _uiState.asStateFlow() fun loadData() { viewModelScope.launch { _uiState.value = _uiState.value.copy(isLoading = true) val data = repository.fetchData() _uiState.value = _uiState.value.copy(data = data, isLoading = false) } } }

Activity 里通过viewModel.uiState.collectLatest { ... }来刷新 UI。这种写法核心在于状态是单向流动的,数据变更只能从 ViewModel 发出,UI 不会自己改数据。如果业务逻辑里出现“在 Activity 里直接改 ViewModel 的字段”,那架构就崩塌了。

Compose 版本更直接,Composable 函数直接观察 StateFlow:

@Composable fun MainScreen(viewModel: MainViewModel = viewModel()) { val uiState by viewModel.uiState.collectAsState() when { uiState.isLoading -> LoadingView() uiState.error != null -> ErrorView(uiState.error) else -> ContentList(uiState.data) } }

这两种写法我都在笔记里给出了完整代码,但比代码更重要的是对几个关键问题的思考:

  • ViewModel 为什么要继承?不继承行不行?——不继承其实也可以,但你就失去了 ViewModelStore 自动管理生命周期的能力,容易在配置变更后丢数据。
  • StateFlow 和 LiveData 怎么选?——LiveData 是 Android 平台感知生命周期的,StateFlow 是纯 Kotlin 的,更适合在非 UI 层使用。如果你在 Repository 里返回 LiveData,那就等于让数据层依赖了 Android 框架,不利于单元测试。
  • 协程的viewModelScope为什么能在 ViewModel 销毁时自动取消?——因为 viewModelScope 绑定了 ViewModel 的 onCleared 回调,onCleared 触发时内部 Job 会取消。

3.3 AIDL 完整实现与跨进程回调

AIDL 这讲我写了一个具体场景:音乐播放器应用,客户端是 UI,服务端是后台播放服务,需要跨进程拿到播放状态和进度。直接在同一个进程里用接口当然方便,但需求要求播放服务分离,所以必须上 AIDL。

流程是这样的:先建一个IPlayerService.aidl,声明两个方法,一个获取播放状态,一个注册回调;再建一个IPlayerCallback.aidl,声明一个方法,用于服务端通知客户端进度变化。

服务端实现:

class PlayerService : Service() { private val callbackList = RemoteCallbackList<IPlayerCallback>() private val binder = object : IPlayerService.Stub() { override fun getStatus(): PlayerStatus = currentStatus override fun registerCallback(cb: IPlayerCallback) { callbackList.register(cb) } override fun unregisterCallback(cb: IPlayerCallback) { callbackList.unregister(cb) } } override fun onBind(intent: Intent): IBinder = binder }

客户端调用:

private val connection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { playerService = IPlayerService.Stub.asInterface(service) playerService?.registerCallback(callback) } } bindService(Intent(this, PlayerService::class.java), connection, Context.BIND_AUTO_CREATE)

这里有个很容易踩的坑:RemoteCallbackList并不是线程安全的,而且它的registerbeginBroadcast/finishBroadcast必须配套使用。广播回调时,示例代码一般是:

val n = callbackList.beginBroadcast() for (i in 0 until n) { callbackList.getBroadcastItem(i).onProgressChanged(progress) } callbackList.finishBroadcast()

忘记调用finishBroadcast会导致后续注册/解绑异常,甚至死锁。我刚开始写的时候踩过一次,服务端收到回调后回调没触发,日志也没报错,排查了很久才意识到是 beginBroadcast 没配对。

3.4 Android 14 权限变化与动态图标适配

如果想要让你的应用适配 Android 14(API 34),有几个重点必须落实。这一讲我特别整理了权限变化和图标适配。

Android 14 在权限方面最明显的变化是:部分权限(比如媒体部分访问权限)更细分,同时系统限制了后台启动 Activity 的场景。如果你的应用有“后台推送唤起页面”的需求,在 Android 14 上可能不稳定,需要走通知点击或其他合规方式。

动态图标方面,官方一直在推 Adaptive Icon,它由背景层和前景层组成,系统可以基于不同设备做出不同的遮罩效果。如果你做的是支持主题化图标(Themed Icons)的应用,需要额外提供 monochrome 图层。我在笔记里给的建议是:颜色尽量使用系统提供的语义色,不要硬编码纯黑纯白,这样在浅色/深色模式下才能自适应。

适配 Android 14 时还有一点:如果你的应用要读取Android/data目录下的文件,传统做法file:///storage/emulated/0/Android/data/...会被严格限制。Android 11 起就已经收紧了访问权限,14 更是把这条路基本堵死。这个时候应该走 MediaStore 或者 SAF(Storage Access Framework)来访问用户选择的文件,而不是试图拼路径访问应用专属目录之外的数据。

4. 常见问题与排查技巧实录

整理这套笔记的过程中,我遇到了不少看似蹊跷的问题。这里挑几个有代表性的,写一下我当时是怎么排查和解决的,希望能帮你少走弯路。

4.1 Could not load compiled classes for settings file

这个报错是很多人在切换 Gradle 版本或者升级 Android Studio 后遇到的。报错内容类似:

Could not load compiled classes for settings file 'D:\android\coffee\settings.gradle.kts'.

第一眼以为是 settings 文件写错了,但其实这个报错绝大多数情况是 Gradle 缓存损坏或版本不一致导致的。我的解决办法是:

  • 先关掉 Android Studio 和所有 Java/ Gradle 进程。
  • 删除项目根目录下的.gradle文件夹。
  • 删除用户目录下~/.gradle/caches里跟该项目相关的缓存(如果空间不够,直接全清也行)。
  • 重新打开项目,让它重新同步。

如果还不行,再检查一下 JDK 版本是否匹配 AGP 要求。用java -version查看当前 JDK,如果版本是 11 或更低,而 AGP 8.x 需要 JDK 17,那就需要去 Project Structure 里改 SDK 的 JDK 路径。

4.2 Android Studio 虚拟设备无效

虚拟设备(AVD)无法启动或者显示“Invalid”是比较常见的问题。我遇到过三种情况:

第一种是系统镜像下载不完整,AVD 列表里能看到设备,但一点启动就闪退。解决办法是在 SDK Manager 里把对应的 system-image 重新下载一遍,或者换一个 API level 的镜像试试。

第二种是硬件加速问题。Windows 上如果没装 Intel HAXM 或者 AEHD,x86 镜像跑不起来,会报x86 emulation currently requires hardware acceleration。解决办法是根据你的 CPU 型号安装对应的加速器,现代 Android Studio 会弹窗引导,但有时候需要手动去 SDK Manager 里装。

第三种是内存和磁盘空间不足。AVD 默认分配的内存可能不够,特别是 API 30+ 的镜像,开机就很慢,甚至黑屏。可以在 AVD 配置里把 RAM 调到 2GB 以上,同时把内部存储空间设大一些。

实在不行,我建议直接真机调试。真机的真实性和稳定性都优于模拟器,而且很多性能相关的问题,模拟器上根本复现不出来。

4.3 i2c-tools 在 Android 上的使用限制

这个点尤其针对做硬件或车载项目的朋友。i2c-tools 是一套经典的 I2C 总线调试工具,但 Android 系统默认不带这些工具。如果你的设备已经 root,可以尝试把编译好的 i2cdetect、i2cget、i2cset push 到/system/xbin/data/local/tmp,并用chmod +x赋予执行权限。但要注意,I2C 设备节点通常需要通过 SELinux 策略才能访问,即便你是 root 用户,也可能被限制。常见的做法是临时切换到 permissive 模式,或者给对应的 domain 添加 allow 规则。

我在实际用的时候发现,Android 上 I2C 调试比 Linux 发行版麻烦得多,因为每家的内核配置不一样,/dev/i2c-N 的设备节点编号也不同。建议先ls /dev/i2c-*确认存在哪些节点,再用 i2cdetect 扫地址。如果扫不到设备,先确认外设地址是否正确、上拉电阻是否焊好,再排查软件问题。硬件问题用再好的工具也查不出来,这一点很真实。

4.4 网络请求无法访问某些应用的数据目录

很多开发者习惯直接用file:///storage/emulated/0/Android/data/com.baidu.searchbox/files/download/...这类路径去访问别的应用目录下的文件,结果发现在高版本 Android 上根本打不开。原因就是 Android 对应用专属目录的访问权限收紧了。

Android 11 之后,Android/dataAndroid/obb目录不再允许直接通过文件路径访问。如果想做文件分享,正确姿势有几种:

  • 使用FileProvider,把文件通过content://URI 分享给其他应用,这样接收方获得的是有权限的临时 URI,而不用直接访问路径。
  • 如果只是自己应用读取自己的外部存储文件,用context.getExternalFilesDir()获取专属目录即可。
  • 如果必须访问用户选择的文档,用系统文件选择器(ACTION_OPEN_DOCUMENT),拿到content://URI 后再做读写。

这个知识点我几乎每个项目都会强调,因为线上出现过太多“获取不到文件”“文件不存在”的反馈,追根溯源大多都是路径访问被限制的问题。现在做文件相关功能,最好统一封装一层存储抽象,不要直接到处拼路径。

5. 进阶工程师怎么消化整套笔记

5.1 笔记不是摘抄,而是回答“为什么”

我见过很多人的笔记做得像 API 文档,把一个方法的参数、返回值抄一遍,然后就没有然后了。这样的笔记写的时候很爽,回头看完全无感。

我自己记笔记有两个硬性要求。

第一,每一个重要概念必须写下“我自己的理解”,哪怕理解得不够准确,也要先写出来,后续再修正。比如 Binder 我一开始写的是“类似一种跨进程方法调用”,后面深入后改成“一种基于内核驱动的跨进程通信机制,客户端通过代理对象调用服务端方法,数据通过内核缓冲区拷贝一次完成传递”。从模糊到精确,这个精进过程本身就是学习。

第二,每个知识点必须配一个验证实验。就算只是写一个几十行的 demo 跑一遍,也比纯看书强十倍。我之前对协程的Dispatchers.IODispatchers.Default的理解一直是背定义,后来写了一个同时开启多个任务打印线程名的实验,才真正直观感受到两者切换线程的差异。笔记里这些实验记录,后来成了我编写技术分享文章的素材来源。

5.2 用回看和输出来检验掌握程度

笔记整理完不是终点。我会在每两周挑一个专题,不看笔记,在白纸上把核心流程画出来。比如“Activity 启动流程”,如果我能画出从 startActivity 到 AMS、再到 ApplicationThread、最后执行生命周期的完整主线,那说明这个知识点真的内化了。如果只能画一半,就翻笔记补缺,然后过两天再画一次。

另外,我还经常把某个专题写成小短文发到自己的博客或群里,目的是逼自己用通俗语言把复杂概念讲清楚。每次写的时候都会发现“原来这个细节我没搞明白”,然后回头再做实验。这个循环跑下来,对知识点的掌握会非常扎实。

这套笔记整理下来,我最大的感受是:Android 进阶并不缺资料,缺的是把零散资料串成体系、再结合实践验证的过程。不管是看源码还是做性能优化,最终都要回到“亲手做一遍”这个动作上。希望这篇整理思路能给你一点启发,让你在建立自己知识体系的时候少绕一些弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 2:52:02

Qt TCP通信深度解析:事件循环、粘包处理与生产级加固

简介&#xff1a;本资源是一套基于Qt框架实现TCP通信的完整客户端-服务器双工程示例&#xff0c;面向Qt初学者及网络编程入门者&#xff0c;解决跨平台TCP连接建立、数据收发与信号槽机制实践等核心问题。压缩包共20个文件&#xff0c;含4个关键cpp源码、2个头文件&#xff08;…

作者头像 李华
网站建设 2026/9/10 2:49:19

Pintos操作系统内核开发:GCC环境搭建与线程调试实战

简介&#xff1a;本资源是面向高校操作系统课程设计的Pintos内核实验完整实现方案&#xff0c;聚焦threads模块开发与验证&#xff0c;适用于计算机专业本科生及系统编程初学者。资源已通过全部27个make check测试用例&#xff0c;涵盖线程调度、同步原语、中断处理等核心机制&…

作者头像 李华
网站建设 2026/9/10 2:47:25

CANN/ge Session接口概述

简介 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前端的友好…

作者头像 李华