别只背 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.xmlManifestParser manifest = parseManifest(packageName);// 2. 找到 android:name="com.example.MyApp"String appName = manifest.getApplicationClassName();// 3. 反射实例化 Application 对象// 注意:这里不是 new MyApplication(),而是通过 ClassLoaderApplication 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. 查找当前 ActivityActivityClientRecord 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 ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();private MessageQueue mQueue;// 获取当前线程的 Looperpublic 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 Queue<MiniMessage> 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 ThreadLocal<MiniLooper> 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?评论区聊聊你的解决方案,我们一起避坑。