
别只背 app制作教程,手写实现源码才懂底层逻辑
面试被问“App 启动流程”或“页面生命周期”,你卡壳了吗?很多人只会调 API,一旦深入原理就露馅。其实,手写实现核心模块,比死记硬背十遍 app制作教程 更有效。
入口定位:从 Manifest 到 Application 的真相
很多初学者盯着 UI 代码看,却忽略了 App 真正的“大脑”。在 Android 开发中,AndroidManifest.xml 不仅是配置清单,更是系统的入口索引。而 Application 类,则是全局状态的容器。
为什么面试常问这个?因为它是所有组件(Activity, Service, BroadcastReceiver)的父级环境。理解它,你就理解了 App 的“根”。
核心源码片段 1:Application 的初始化时机
// 伪代码:模拟 Android 框架加载 Application 的过程
public class AppLoader {
public static void loadApplication(String packageName) {
// 1. 读取 AndroidManifest.xml
ManifestParser manifest = parseManifest(packageName);
// 2. 找到 android:name=com.example.MyApp
String appName = manifest.getApplicationClassName();
// 3. 反射实例化 Application 对象
// 注意:这里不是 new MyApplication(),而是通过 ClassLoader
Application app = (Application) Class.forName(appName).newInstance();
// 4. 调用 attachBaseContext()
// 这一步在 onCretate() 之前,此时 mBase 已赋值
app.attachBaseContext(new ContextWrapper(app));
// 5. 调用 onCreate()
// 此时才能安全地初始化单例、注册全局监听
app.onCreate();
}
}
逐行解析:
第 3-4 行:系统并不直接 new 你的 Application。它通过反射 Class.forName 动态加载。这意味着,如果你忘了在 Manifest 中声明 android:name,系统会默认加载空实现,你的 onCreate() 根本不会被调用。
第 7-9 行:attachBaseContext 是容易被忽略的坑。此时 this 已经可用,但 getSystemService 可能还没完全就绪。很多第三方 SDK 喜欢在这里初始化,如果操作不当,极易导致 NPE。
第 11-12 行:onCreate() 是全局唯一的初始化点。所有单例、全局变量、线程池,都应该在这里启动。如果在这里做了耗时操作(如网络请求、数据库写入),会直接阻塞主线程,导致启动卡顿,甚至 ANR(Application Not Responding)。
核心片段:Activity 生命周期并非你想的那样
面试高频题:“Activity 的生命周期有哪些?”
背出 onCreate、onStart、onResume、onPause、onStop、onDestroy 的人一大把。但面试官追问:“屏幕旋转时,哪个方法会被调用几次?”
这就涉及到源码级的理解。Activity 的生命周期不仅仅是状态机,更是视图重建的过程。
核心源码片段 2:ActivityThread 处理生命周期事件
// 摘自 Android AOSP: ActivityThread.java (简化版)
public final class ActivityThread {
// 处理系统分发的生命周期事件
public void handleLaunchActivity(ActivityClientRecord r, Intent intent, Object customInstance) {
// 1. 创建 Activity 实例
Activity activity = performLaunchActivity(r, intent, customInstance);
// 2. 调用 onCreate()
// 注意:此时 View 还没加载,findViewById 会报错
activity.performCreate(savedInstanceState);
// 3. 调用 onStart()
activity.performStart();
// 4. 调用 onResume()
activity.performResume();
}
// 处理配置变更(如屏幕旋转)
public void handleRelaunchActivityLocally(IBinder token, Configuration config, Intent intent) {
// 1. 查找当前 Activity
ActivityClientRecord r = findActivity(token);
// 2. 销毁旧实例
// 触发 onDestroy()
destroyActivity(r);
// 3. 创建新实例
// 触发 onCreate(), onStart(), onResume()
handleLaunchActivity(r, intent, null);
}
}
逐行解析:
第 6-8 行:performLaunchActivity 是一个黑盒。它内部做了大量工作:加载布局 XML、绑定数据、设置主题。这里的关键是,Activity 实例的创建早于 View 的创建。
第 16-23 行:这是屏幕旋转时的真相。默认情况下,系统会销毁旧的 Activity 实例,然后重新创建一个新的。这就是为什么你在 onCreate() 里做的局部变量,旋转后全丢了。
常见误区:很多人认为旋转只是 onPause - onResume。错!除非你在 Manifest 中声明了 android:configChanges=orientation|screenSize,否则就是完整的销毁与重建。
设计思想:为什么是单线程模型?
Android 的 UI 更新必须在主线程(Main Thread)进行。这不是设计者的任性,而是线程安全的妥协。
核心问题:View 不是线程安全的
View 类的所有属性(位置、大小、颜色、文本)都没有加锁。如果在子线程中直接调用 textView.setText(Hello),会发生什么?
// 危险代码:在子线程更新 UI
new Thread(() - {
// 假设这是一个耗时操作
String data = fetchDataFromNetwork();
// 直接更新 UI,极大概率崩溃或显示错误
textView.setText(data);
}).start();
后果:
Crash:CalledFromWrongThreadException。
数据竞争:主线程正在读取 textView 的宽度,子线程正在修改其内容,导致计算错误。
源码级解决方案:Handler 机制
Android 通过 Handler + MessageQueue + Looper 机制,将子线程的消息投递到主线程执行。
核心源码片段 3:Handler 消息投递核心逻辑
// 简化版 Handler 源码
public class Handler {
private final Looper mLooper;
public Handler(Looper looper) {
mLooper = looper;
}
// 发送消息
public boolean sendMessage(Message msg) {
return mLooper.getQueue().enqueueMessage(msg, 0);
}
// 处理消息
public void handleMessage(Message msg) {
// 默认空实现,子类重写
}
}
// Looper: 消息循环
public class Looper {
private static final ThreadLocalLooper sThreadLocal = new ThreadLocal();
private MessageQueue mQueue;
// 获取当前线程的 Looper
public static Looper myLooper() {
return sThreadLocal.get();
}
// 启动消息循环
public static void loop() {
Looper me = myLooper();
for (;;) {
Message msg = me.mQueue.next(); // 阻塞等待消息
if (msg == null) {
// 退出循环
return;
}
// 关键:在主线程中执行消息处理
msg.target.dispatchMessage(msg);
}
}
}
逐行解析:
第 10 行:enqueueMessage 只是把消息放入队列,并不执行。执行权在 Looper。
第 23-25 行:ThreadLocal 是核心。每个线程(包括主线程)都有自己的 Looper。主线程在 ActivityThread 启动时就被赋予了 Looper。
第 33 行:next() 是阻塞调用。如果没有消息,线程会休眠,避免空转消耗 CPU。
第 39 行:dispatchMessage 最终会调用你重写的 handleMessage。此时,执行线程就是主线程。这就是为什么子线程可以“安全”更新 UI——因为更新动作最终发生在主线程。
手写简化版:实现一个迷你 Handler
为了真正理解,我们手写实现一个简化版的消息队列。
import java.util.LinkedList;
import java.util.Queue;
// 1. 定义消息
class MiniMessage {
int what;
Runnable callback;
MiniMessage(int what, Runnable callback) {
this.what = what;
this.callback = callback;
}
}
// 2. 消息队列
class MiniMessageQueue {
private final QueueMiniMessage queue = new LinkedList();
private boolean mQuitting = false;
private boolean mBlocked = true;
// 放入消息
public void enqueueMessage(MiniMessage msg) {
synchronized (this) {
queue.offer(msg);
mBlocked = false; // 唤醒等待中的线程
notifyAll();
}
}
// 取出消息(阻塞)
public MiniMessage next() {
synchronized (this) {
while (queue.isEmpty() !mQuitting) {
mBlocked = true;
try {
wait(); // 阻塞,直到有新消息或退出
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (mQuitting) return null;
return queue.poll();
}
}
public void quit() {
synchronized (this) {
mQuitting = true;
notifyAll();
}
}
}
// 3. Looper
class MiniLooper {
private static final ThreadLocalMiniLooper sThreadLocal = new ThreadLocal();
private MiniMessageQueue mQueue;
public MiniLooper() {
mQueue = new MiniMessageQueue();
sThreadLocal.set(this);
}
public static MiniLooper myLooper() {
return sThreadLocal.get();
}
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException(Can't call prepare() from within Looper.prepare());
}
sThreadLocal.set(new MiniLooper());
}
public static void loop() {
MiniLooper me = myLooper();
for (;;) {
MiniMessage msg = me.mQueue.next();
if (msg == null) {
return;
}
// 执行回调
msg.callback.run();
}
}
public MiniMessageQueue getQueue() {
return mQueue;
}
}
// 4. Handler
class MiniHandler {
private final MiniLooper mLooper;
public MiniHandler(MiniLooper looper) {
mLooper = looper;
}
public boolean post(Runnable r) {
MiniMessage msg = new MiniMessage(0, r);
return mLooper.getQueue().enqueueMessage(msg);
}
}
使用示例:
// 主线程
MiniLooper.prepare();
MiniHandler handler = new MiniHandler(MiniLooper.myLooper());
// 子线程发送任务
new Thread(() - {
handler.post(() - {
System.out.println(UI 更新在主线程执行: + Thread.currentThread().getName());
});
}).start();
// 启动循环
MiniLooper.loop();
设计思想总结:
线程隔离:通过 ThreadLocal 确保每个线程独立的消息循环。
生产者-消费者模型:子线程是生产者,主线程 Looper 是消费者。
同步机制:wait()/notifyAll() 保证线程安全与高效唤醒。
应用场景:从面试到实战
理解这些源码,对你有什么实际帮助?
启动优化:知道 Application.onCreate() 是启动关键路径,避免在其中做耗时操作。
内存泄漏排查:理解 Activity 重建机制,避免在 Activity 中持有 Context 引用。
多线程通信:掌握 Handler 原理,能自行实现跨线程通信,不依赖 runOnUiThread 的黑盒。
面试加分:当你能画出 MessageQueue 的阻塞与唤醒流程,面试官会眼前一亮。
避坑指南
不要在 onCreate 中启动线程池:应延迟到首次使用时初始化。
避免在 onDestroy 中做异步操作:此时 Activity 已销毁,回调可能引发 NPE。
谨慎使用 Thread.sleep:它会阻塞线程,导致消息队列堆积,引发 ANR。
与其他技术栈的对比
iOS:使用 RunLoop 机制,与 Android 的 Looper 异曲同工。
Web:使用 Event Loop,同样是单线程模型,通过宏任务/微任务队列管理。
共性:UI 更新必须串行化,避免并发冲突。
结尾互动
你在项目里踩过这个坑吗?比如,因 Activity 旋转导致数据丢失,或因 Handler 泄漏导致 OOM?评论区聊聊你的解决方案,我们一起避坑。