“这个问题 AI 三秒就能解决。”
如果你也这样想过,那不妨听听我最近的真实故事。
胸有成竹
这!
是一块 RockChip 3528 的开发版,它能完美的运行 Android 13 系统,从图中也可以看到,这块板子的接口相当齐全。
我外接了一个显示器,一个 USB 调试线,以及一块鼠标(是的,它可以用鼠标,因为它的 Andorid 系统是基于 TV 版本的)
而我的任务,就是在这块板子上集成一系列的 App,并针对这块板子修改 Android 系统以满足商业需求。
实际上大概在 7 年前,我做过类似的开发,当时的项目目标是:让一块 Firefly 的板子连接车辆上一系列传感器,并开发出一系列的 App,做一个类似智能驾舱的系统,去广州的车展做推广,以期待车厂能够跟我们合作。
我的开发任务大概有 200 多条,为了起个好头,我选了个“看起来”最简单的任务——“修改系统的 dpi 为 220”。
当我拿到这块板子的时候,有一系列的配套文档以及系统的源码,所以我在文档的帮助下,花了半天时间配置好了系统环境,并尝试第一次编译。
虽然我的电脑配置不低(拥有 20 核心 CPU 的笔记本),但是第一次编译还是花去了 3 小时。
成功刷机之后,系统启动起来,还是有点小激动的。
此时,还有 2 小时我就下班了,我心想,赶紧改好,早点回家。
于是我问了一下我的 AI 助手,AOSP 源码怎么修改系统的分辨率。
AI 给的回复大致是:在当前中源码的 device.mk 中更改 ro.sf.lcd_density 属性即可。
于是乎,我全局检索了一下 mk 文件,祈求找到 ro.sf.lcd_density。
这里,我用到了第一个很有用的命令行:
grep -rn "ro.sf.lcd_density" --include "*.mk"
该命令行会对当前递归目录下的所有 mk 文件进行查找,如果匹配,就会打印行号:
经过我的甄别,我觉得应该就是 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 上面疯狂寻找答案,我整理了一下大致有几种解决方案:
- 看看是不是其他构建文件(
mk,bp)更改了ro.sf.lcd_density。 - TV 版本的系统比较特殊,会自动映射
dpi,也就是说,220不是一个标准的dpi,会自动映射到临近的240。 ro.sf.lcd_density如果不起作用的话,说明系统给了一个默认值,可以搜索类似default_density的参数看看。
此时的我,已经成竹在胸,我不信三管齐下改不好?
明天再说!
步入泥沼
第二天开完早会后,我着手实施上面三个措施。
我先把我的源码丢给了 cursor,让其分析哪些地方可以更改 dpi。
cursor 是借的同事的,这样做其实很不道德,分析整个工程需要消耗 token,不过公司报销,所以我就不心疼了。
同时另一边,我着手开始实施第一第二策略。
我先在所有的构建文件中查找 ro.sf.lcd_density,发现并没有修改的地方,而 Java 文件中,都是读取这个属性,也没有修改的地方。
例如这里,frameworks\base\core\java\android\util\DisplayMetrics.java:
于是乎,我看向了第二个策略,检查是否会自动映射。
其实这个很难,我的同事在另一套开发板源码上找到了做 dpi 映射的代码:
很不幸的是,我这儿没有类似的代码。
于是我转向搜索上面源码中 DisplayMetrics.getDeviceDensity() 的使用,经过我大部分的筛查,也没有什么实际作用。
现在,我只能等待 cursor 的结果了。
等待 cursor 分析完毕之后,它告诉我两个地方可以更改:
- 一个是上面提到的
ro.sf.lcd_density。 - 另一个就是在一个神奇的文件
device/rockchip/common/tv/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xm中,修改def_display_density声明。
好的,第一个方案已经尝试过了,现在直接看第二个,涉及到的文件是这样的:
这个和普通的Android资源文件是类似的,这里清晰的显示了,def_display_density 为 240。
不管那么多了,改了再说!
保存,编译~~~,刷机,等待,启动。
Physical density: 320
Override density: 240
嘶~~~~~~~~~~~嘶~~~~~~~~~~~嘶~~~~~~~~~~~
明天再说!
迎来曙光
“修改系统的 dpi 为 220”这条需求我已经干到第三天了,我还没有放弃,我继续跟踪 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 命令检查一下:
这下证明,上面那段代码起作用了,于是我继续跟踪 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 对应的值。
我隐约看见了曙光!继续跟踪,发现了下面这样一个调用链条:
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;
}
...
广播???
好吧,我看看是谁发送的这个广播,这个广播的 action 是 com.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
终于成功了。
总结
非常感谢你坚持读到这里,这个看似简单的需求——“修改系统的 dpi 为 220”,让我花费了三天时间。
这期间其实我一直在用 AI 解决很多问题,例如:
- 我告诉它我想要的效果,让它帮我生成一段
shell命令; - 我请教它 AOSP 源码中很多类的作用;
- 我问它 AOSP 镜像是如何构建出来;
- 我请教他为什么构建工具既有
mk,也有bp文件; - 很多很多,但凡我不懂的,我都是先问 AI。
我似乎明白了,为什么现在不需要 stackoverflow 了。
但我依然花了 3 天时间来完成这个需求,为什么这么久?
因为真正的工程,从来不是“查文档 + 改参数”这么线性。它交织着历史遗留、厂商定制、产品逻辑,甚至是一段为特定 App 临时加上的代码。这些,目前的 AI 无法预知,也无法推理。
AI 代替不了你对系统的直觉,代替不了你在无数个深夜 debug 后形成的“肌肉记忆”,更代替不了你站在业务目标前,判断“该往哪里用力”的决策力。
当然,我们不必恐惧 AI 取代程序员,真正该警惕的,是把思考外包给 AI 的惰性。
所以:
AI 能写代码,但写不出你的洞察;
它能加速执行,但决定不了方向。
而那个方向,永远只属于——你。