AI 替代不了你什么?

1,482 阅读9分钟

000.jpg

“这个问题 AI 三秒就能解决。”

如果你也这样想过,那不妨听听我最近的真实故事。

胸有成竹

这!

20251219-141313.jpg

是一块 RockChip 3528 的开发版,它能完美的运行 Android 13 系统,从图中也可以看到,这块板子的接口相当齐全。

我外接了一个显示器,一个 USB 调试线,以及一块鼠标(是的,它可以用鼠标,因为它的 Andorid 系统是基于 TV 版本的)

而我的任务,就是在这块板子上集成一系列的 App,并针对这块板子修改 Android 系统以满足商业需求。

实际上大概在 7 年前,我做过类似的开发,当时的项目目标是:让一块 Firefly 的板子连接车辆上一系列传感器,并开发出一系列的 App,做一个类似智能驾舱的系统,去广州的车展做推广,以期待车厂能够跟我们合作。

我的开发任务大概有 200 多条,为了起个好头,我选了个“看起来”最简单的任务——“修改系统的 dpi220”。

当我拿到这块板子的时候,有一系列的配套文档以及系统的源码,所以我在文档的帮助下,花了半天时间配置好了系统环境,并尝试第一次编译。

1.png

虽然我的电脑配置不低(拥有 20 核心 CPU 的笔记本),但是第一次编译还是花去了 3 小时。

成功刷机之后,系统启动起来,还是有点小激动的。

2.png

此时,还有 2 小时我就下班了,我心想,赶紧改好,早点回家。

于是我问了一下我的 AI 助手,AOSP 源码怎么修改系统的分辨率。

AI 给的回复大致是:在当前中源码的 device.mk 中更改 ro.sf.lcd_density 属性即可。

于是乎,我全局检索了一下 mk 文件,祈求找到 ro.sf.lcd_density

这里,我用到了第一个很有用的命令行:

grep -rn "ro.sf.lcd_density" --include "*.mk"

该命令行会对当前递归目录下的所有 mk 文件进行查找,如果匹配,就会打印行号:

3.jpg

经过我的甄别,我觉得应该就是 device/rockchip/rk3528/device.mk 文件了,为了方便,我把所有 ro.sf.lcd_density 出现的地方都改成了 220

保存,编译。

10 分钟后,刷机,等待,启动。

adb 输入 adb -d shell wm density,输出如下:

Physical density: 320
Override density: 240

嘶~~~~~~~~~~~

一定是我编译的问题,我把源码中的 out 目录删了,重新编译。

删除 out 目录等于重新编译。类似 clean 之后 build

3 小时过去了,离我下班时间过去了一个小时。

刷机,等待,启动。

adb 检查:

Physical density: 320
Override density: 240

嘶~~~~~~~~~~~嘶~~~~~~~~~~~

下班回家!

当天我想了一晚上,为什么会不起作用呢?

我在 Google 和 AI 上面疯狂寻找答案,我整理了一下大致有几种解决方案:

  1. 看看是不是其他构建文件(mkbp)更改了 ro.sf.lcd_density
  2. TV 版本的系统比较特殊,会自动映射 dpi,也就是说,220 不是一个标准的 dpi,会自动映射到临近的 240
  3. ro.sf.lcd_density 如果不起作用的话,说明系统给了一个默认值,可以搜索类似 default_density 的参数看看。

此时的我,已经成竹在胸,我不信三管齐下改不好?

明天再说!

步入泥沼

第二天开完早会后,我着手实施上面三个措施。

我先把我的源码丢给了 cursor,让其分析哪些地方可以更改 dpi

cursor 是借的同事的,这样做其实很不道德,分析整个工程需要消耗 token,不过公司报销,所以我就不心疼了。

同时另一边,我着手开始实施第一第二策略。

我先在所有的构建文件中查找 ro.sf.lcd_density,发现并没有修改的地方,而 Java 文件中,都是读取这个属性,也没有修改的地方。

例如这里,frameworks\base\core\java\android\util\DisplayMetrics.java

3.png

于是乎,我看向了第二个策略,检查是否会自动映射。

其实这个很难,我的同事在另一套开发板源码上找到了做 dpi 映射的代码:

4.png

很不幸的是,我这儿没有类似的代码。

于是我转向搜索上面源码中 DisplayMetrics.getDeviceDensity() 的使用,经过我大部分的筛查,也没有什么实际作用。

现在,我只能等待 cursor 的结果了。

等待 cursor 分析完毕之后,它告诉我两个地方可以更改:

  • 一个是上面提到的 ro.sf.lcd_density
  • 另一个就是在一个神奇的文件 device/rockchip/common/tv/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xm 中,修改 def_display_density 声明。

好的,第一个方案已经尝试过了,现在直接看第二个,涉及到的文件是这样的:

5.png

这个和普通的Android资源文件是类似的,这里清晰的显示了,def_display_density240

不管那么多了,改了再说!

保存,编译~~~,刷机,等待,启动。

Physical density: 320
Override density: 240

嘶~~~~~~~~~~~嘶~~~~~~~~~~~嘶~~~~~~~~~~~

明天再说!

迎来曙光

“修改系统的 dpi220”这条需求我已经干到第三天了,我还没有放弃,我继续跟踪 def_display_density 的使用,发现在文件 frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java 有所使用,相关代码是这样的:

if ("box".equals(SystemProperties.get("ro.target.product")))
                 loadStringSetting(stmt, Settings.Secure.DISPLAY_DENSITY_FORCED,R.string.def_display_density);

这段代码引起了我的警觉,因为我的编译命令中,有一条是这样写的:

lunch rk3528_box-userdebug

rk3528_box,嗯,我这个可能确实是个 box。于是我通过 adb 命令检查一下:

6.png

这下证明,上面那段代码起作用了,于是我继续跟踪 Settings.Secure.DISPLAY_DENSITY_FORCED 的引用。

首先,在 frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java 中发现如下代码:

/**
 * @param userId the ID of the user
 * @return the forced display density for the specified user, if set, or
 *         {@code 0} if not set
 */
private int getForcedDisplayDensityForUserLocked(int userId) {
    String densityStr = Settings.Secure.getStringForUser(mContext.getContentResolver(),
            Settings.Secure.DISPLAY_DENSITY_FORCED, userId);
    if (densityStr == null || densityStr.length() == 0) {
        densityStr = SystemProperties.get(DENSITY_OVERRIDE, null);
    }
    if (densityStr != null && densityStr.length() > 0) {
        try {
            return Integer.parseInt(densityStr);
        } catch (NumberFormatException ex) {
        }
    }
    return 0;
}

这段代码简单来说就是如果 Settings.Secure.DISPLAY_DENSITY_FORCED 有值,就会直接使用它的结果。

这里其实就很奇怪了,在 DatabaseHelper 中,已经设置了它的值为默认值 def_display_density 了。

我们继续看,在 frameworks/base/services/core/java/com/android/server/wm/DisplayWindowSettings.java 有这样的代码:

void setForcedDensity(DisplayInfo info, int density, int userId) {
    if (info.displayId == Display.DEFAULT_DISPLAY) {
        final String densityString = density == 0 ? "" : Integer.toString(density);
        Settings.Secure.putStringForUser(mService.mContext.getContentResolver(),
                Settings.Secure.DISPLAY_DENSITY_FORCED, densityString, userId);
    }
    ...

意思是说可以通过 DisplayWindowSettings.setForcedDensity 去更改 Settings.Secure.DISPLAY_DENSITY_FORCED 对应的值。

我隐约看见了曙光!继续跟踪,发现了下面这样一个调用链条:

6.jpg

onCommand 内部调用了 runDisplayDensity,以此类推。我本来想表达被谁调用的意思,所以箭头朝上了.

onCommand 函数,是专门处理 adb wm 命令的,

@Override
public int onCommand(String cmd) {
    if (cmd == null) {
        return handleDefaultCommands(cmd);
    }
    final PrintWriter pw = getOutPrintWriter();
    try {
        switch (cmd) {
            case "size":
                return runDisplaySize(pw);
            case "density":
                return runDisplayDensity(pw);
...

也就是说,这个系统启动起来之后,有个地方调用了 wm density 去修改了系统的 dpi

那就继续跟踪 wm density

果然,在 /home/xpg/workspace/rk3528_dev/rk3528/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java 中:

public static void setDensity(String density){
    String cmd = "wm density " +density;
    try {
            Log.d(TAG, "zxLog "+cmd);
            Runtime.getRuntime().exec(cmd);
    } catch (Exception e) {
            e.printStackTrace();
    }
}

现在,我看看到底是谁调用了这个 setDensity

我觉得我已经临门一脚了。

BroadcastReceiver mDreamReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
                    String top_app = SystemProperties.get("sys.lmk_top_app", "");
                    if("com.open.disney".equals(intent.getAction()) && top_app.equals("com.disney.disneyplus")){
                            if(setDensityOnce == 1){
                                    setDensityOnce = 0;
                                    setDensity("320");
                            }
                            return;
                    }else if("com.open.disney.back".equals(intent.getAction()) && !top_app.equals("com.disney.disneyplus")){
                            if(setDensityOnce == 0){
                                    setDensityOnce = 1;
                                    setDensity("240"); // 在这里起作用的!
                            }
                            return;
                    }
                    ...

广播???

好吧,我看看是谁发送的这个广播,这个广播的 actioncom.open.disney.back,很简单。

/home/xpg/workspace/rk3528_dev/rk3528/frameworks/base/core/java/android/app/Activity.java 中:

private void checkWmDensity_back(final View view) {
    view.postDelayed(new Runnable() {
        @Override
        public void run() {
            if (!isFinishing() && view != null) {
                try {
                    sendBroadcast(new Intent("com.open.disney.back"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }, 2000);
}

protected void onResume() {
    ...
    checkWmDensity_back(findViewById(android.R.id.content));
    ...
}

好的,我们成功定位到源头了,在 Activity onResume 的时候,会调用 checkWmDensity_back 发送这个广播。

改法也就简单了,我把 PhoneWindowManager 中起作用的 setDensity("240") 改成了 setDensity("220")

保存,编译~~~,刷机,等待,启动。

Physical density: 320
Override density: 220

终于成功了。

总结

非常感谢你坚持读到这里,这个看似简单的需求——“修改系统的 dpi220”,让我花费了三天时间。

这期间其实我一直在用 AI 解决很多问题,例如:

  • 我告诉它我想要的效果,让它帮我生成一段 shell 命令;
  • 我请教它 AOSP 源码中很多类的作用;
  • 我问它 AOSP 镜像是如何构建出来;
  • 我请教他为什么构建工具既有 mk,也有 bp 文件;
  • 很多很多,但凡我不懂的,我都是先问 AI。

我似乎明白了,为什么现在不需要 stackoverflow 了。

但我依然花了 3 天时间来完成这个需求,为什么这么久?

因为真正的工程,从来不是“查文档 + 改参数”这么线性。它交织着历史遗留、厂商定制、产品逻辑,甚至是一段为特定 App 临时加上的代码。这些,目前的 AI 无法预知,也无法推理。

AI 代替不了你对系统的直觉,代替不了你在无数个深夜 debug 后形成的“肌肉记忆”,更代替不了你站在业务目标前,判断“该往哪里用力”的决策力。

当然,我们不必恐惧 AI 取代程序员,真正该警惕的,是把思考外包给 AI 的惰性。

所以:

AI 能写代码,但写不出你的洞察;
它能加速执行,但决定不了方向。

而那个方向,永远只属于——