别只背 app制作教程,手写实现源码才懂底层逻辑 别只背 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?评论区聊聊你的解决方案,我们一起避坑。