第30章:Android Framework实战项目

说实话,到了这个阶段,你已经把Framework的理论啃得差不多了。但光有理论,就像只会背菜谱的厨师——真让你炒个菜,可能连火候都掌握不好。这一章,我们就来点真格的。

我会带你走一遍完整的实战流程:从自定义系统服务开始,到系统级应用开发,再到Bug修复和日志分析,最后聊聊CTS兼容性测试。这些内容,都是我这些年踩坑踩出来的经验。

30.1 自定义系统服务开发

为什么要自己写系统服务?说白了,有些功能需要全局访问权限,或者需要在系统启动时就运行。比如,你想做一个系统级的屏幕时间管理功能,普通App根本做不到。

30.1.1 服务接口定义

先定义AIDL接口。这是系统服务的“门面”。

// IMyCustomService.aidl
package com.example.framework;

interface IMyCustomService {
    String getServiceName();
    void registerCallback(IMyCustomCallback callback);
    void unregisterCallback(IMyCustomCallback callback);
    int getServiceStatus();
}

我个人习惯把回调接口单独放一个文件:

// IMyCustomCallback.aidl
package com.example.framework;

oneway interface IMyCustomCallback {
    void onStatusChanged(int newStatus);
}
小提示:oneway关键字表示异步调用,不会阻塞客户端。如果你的回调不需要返回值,建议加上。

30.1.2 服务实现类

接下来是核心实现。这里要注意线程安全,因为系统服务可能被多个进程同时调用。

public class MyCustomService extends IMyCustomService.Stub {
    private static final String TAG = "MyCustomService";
    private final Context mContext;
    private final RemoteCallbackList<IMyCustomCallback> mCallbacks
            = new RemoteCallbackList<>();
    private int mServiceStatus = 0;

    public MyCustomService(Context context) {
        mContext = context;
    }

    @Override
    public String getServiceName() {
        return "MyCustomService";
    }

    @Override
    public void registerCallback(IMyCustomCallback callback) {
        mCallbacks.register(callback);
    }

    @Override
    public void unregisterCallback(IMyCustomCallback callback) {
        mCallbacks.unregister(callback);
    }

    @Override
    public int getServiceStatus() {
        return mServiceStatus;
    }

    private void notifyStatusChanged(int newStatus) {
        mServiceStatus = newStatus;
        int count = mCallbacks.beginBroadcast();
        for (int i = 0; i < count; i++) {
            try {
                mCallbacks.getBroadcastItem(i).onStatusChanged(newStatus);
            } catch (RemoteException e) {
                Log.e(TAG, "Callback error", e);
            }
        }
        mCallbacks.finishBroadcast();
    }
}
注意:RemoteCallbackList的beginBroadcast和finishBroadcast必须成对调用。我曾经因为忘记调用finishBroadcast,导致回调列表泄漏,系统服务内存暴涨。排查了整整两天才找到原因。

30.1.3 注册到SystemServer

服务写好了,得让系统知道它的存在。在SystemServer.java中添加:

private void startMyCustomService() {
    try {
        ServiceManager.addService("my_custom_service", new MyCustomService(this));
        Log.i(TAG, "MyCustomService registered");
    } catch (Throwable e) {
        Log.e(TAG, "Failed to register MyCustomService", e);
    }
}

然后在startOtherServices()方法中调用它。嗯,这里要注意启动顺序——如果你的服务依赖其他服务,一定要等它们初始化完成。

30.2 系统级应用开发

系统级应用和普通App最大的区别是什么?权限。系统应用可以调用很多普通App碰不到的API。比如修改系统设置、控制状态栏、管理通知等。

30.2.1 Settings应用扩展

假设我们要在设置里加一个“开发者选项”的入口。先写一个Fragment:

public class CustomDeveloperSettingsFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_custom_developer, container, false);
        // 初始化UI
        return view;
    }

    private void initViews(View view) {
        SwitchPreference enableFeature = view.findViewById(R.id.enable_feature);
        enableFeature.setOnPreferenceChangeListener((preference, newValue) -> {
            boolean enabled = (boolean) newValue;
            // 调用系统服务
            IBinder binder = ServiceManager.getService("my_custom_service");
            IMyCustomService service = IMyCustomService.Stub.asInterface(binder);
            try {
                service.setFeatureEnabled(enabled);
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to set feature", e);
            }
            return true;
        });
    }
}

30.2.2 SystemUI定制

SystemUI是Android的“脸面”。状态栏、通知栏、导航栏都在这里。我做过一个项目,需要在状态栏显示自定义图标,表示当前网络加密状态。

核心思路是:在SystemUI的PhoneStatusBarPolicy中监听状态变化,然后更新图标。

public class CustomStatusBarPolicy {
    private final Context mContext;
    private final StatusBarIconController mIconController;

    public CustomStatusBarPolicy(Context context) {
        mContext = context;
        mIconController = LocalServices.getService(StatusBarIconController.class);
    }

    public void updateCustomIcon(boolean active) {
        StatusBarIcon icon = new StatusBarIcon(
                "com.android.systemui",
                null,
                active ? R.drawable.ic_custom_active : R.drawable.ic_custom_inactive,
                0,
                0,
                "custom_icon"
        );
        mIconController.setIcon("custom_icon", icon);
    }
}
关键点:SystemUI的图标更新必须通过StatusBarIconController,直接修改View会导致状态不一致。我刚开始不懂,直接操作ImageView,结果图标闪烁不停。

30.3 Framework层Bug修复与日志分析

这部分是Framework工程师的“吃饭本领”。不会看日志,不会定位Bug,那就别谈什么系统开发了。

30.3.1 常用日志工具

日志类型 命令 用途
Main日志 logcat -b main 应用层日志
系统日志 logcat -b system 系统服务日志
事件日志 logcat -b events 系统事件(如Activity生命周期)
Crash日志 logcat -b crash 应用崩溃信息
Kernel日志 dmesg 内核日志

30.3.2 实战:定位ANR问题

有一次,用户反馈系统经常卡死。我拿到日志后,先看traces.txt:

adb shell cat /data/anr/traces.txt

找到关键信息:

----- pid 1234 at 2024-01-15 10:30:00 -----
Cmd line: system_server
...
"main" prio=5 tid=1 Blocked
  at com.example.framework.MyCustomService.getServiceStatus(MyCustomService.java:45)
  - waiting to lock <0x0c71f240> (a java.lang.Object)
  at com.android.server.am.ActivityManagerService.handleMessage(ActivityManagerService.java:2345)
  ...

看到没?主线程在等一个锁。我顺着锁的持有者找下去:

"Binder:1234_5" prio=5 tid=15 Blocked
  at com.example.framework.MyCustomService.notifyStatusChanged(MyCustomService.java:78)
  - waiting to lock <0x0c71f240> (a java.lang.Object)
  held by thread "Binder:1234_3"

真相大白了:两个Binder线程互相等待,形成了死锁。修复方案很简单,把notifyStatusChanged中的同步块缩小范围即可。

我的经验:遇到ANR,先看traces.txt。如果找不到,再看logcat中的ANR日志。实在不行,用systrace抓取CPU调度情况。我曾经用systrace发现一个低概率ANR,是因为CPU频率被限制导致的。

30.4 兼容性测试与CTS

CTS(Compatibility Test Suite)是Google用来保证Android兼容性的“大杀器”。你的设备要过CTS,才能预装Google服务。说白了,这是Android生态的“入场券”。

30.4.1 CTS测试流程

  1. 下载CTS测试包:从Google官方获取对应Android版本的CTS
  2. 准备测试环境:设备刷入user版本固件,恢复出厂设置
  3. 运行测试:./cts-tradefed run cts --plan CTS
  4. 分析结果:查看test_result.xml中的fail项

30.4.2 常见CTS失败原因

我遇到过最坑的一次,CTS测试WiFi相关用例全部失败。排查了三天,最后发现是WiFi驱动返回的MAC地址格式不对——Google要求大写字母,驱动返回了小写。

失败类型 常见原因 解决方案
硬件相关 驱动实现不符合规范 修改HAL层实现
权限相关 系统应用缺少权限 检查AndroidManifest.xml
API行为 自定义ROM修改了API行为 回退修改或添加兼容层
性能相关 响应时间超出阈值 优化代码或调整阈值

30.4.3 自定义服务的CTS兼容性

如果你添加了自定义系统服务,要注意两点:

  • 不要修改Google定义的API行为。比如,你不能让startActivity()的行为和标准不一样。
  • 自定义服务不要影响系统服务的正常运行。我见过有人把SystemServer的启动时间拖长了,导致CTS的启动时间测试失败。
避坑指南:我曾经在自定义服务中加了一个网络请求,结果CTS测试时设备没有网络,服务初始化失败,导致SystemServer崩溃。从那以后,我所有自定义服务的初始化都加了超时和降级逻辑。

30.5 本章知识体系

下面这张图,是我对本章内容的总结。你可以把它当作一个“实战地图”。

Framework实战项目知识体系 自定义系统服务 • AIDL接口定义 • Stub实现类(线程安全) • RemoteCallbackList管理回调 • ServiceManager注册 系统级应用开发 • Settings Fragment扩展 • SystemUI状态栏定制 • StatusBarIconController • 系统权限调用 Bug修复与日志分析 • logcat多buffer使用 • traces.txt ANR定位 • 死锁分析与修复 • systrace性能分析 CTS兼容性测试 • 测试流程与环境准备 • 常见失败原因分析 • 自定义服务兼容性 • 降级与超时处理 理论与实践结合,才是真正的Framework高手

好了,这一章的内容就到这里。记住,Framework开发没有捷径,多写代码、多分析日志、多踩坑,你才能真正成长。下次遇到问题,别急着问别人,先自己动手查日志、看源码——你会发现,很多问题其实没那么难。


公众号:蓝海资料掘金营,微信deep3321