Android源代码定制:MK文件执行顺序|属性覆盖

简介: Android源代码定制:MK文件执行顺序|属性覆盖

前言

Android的构建系统中,有一些机制和工具可以帮助我们实现源代码的定制和优化,例如:

  • mk文件:这是一种用于描述构建规则和依赖关系的Makefile。可以在mk文件中定义一些变量和指令,来控制源代码的编译和打包过程。
  • 属性(sysprop):这是一种用于控制系统行为和配置的系统属性。我们可以在不同的地方定义和赋值属性,来影响系统的运行时状态。

在本文中,我将介绍这些机制和工具的基本概念和用法,并且通过一些实例来展示如何使用它们来实现Android源代码的定制和优化。还将介绍如何调试我们的修改是否生效,以及如何查看我们的修改的详细信息。

1. 如何确定mk文件的执行顺序

在Android的构建系统中,xxx.mk文件是一种用于描述构建规则和依赖关系的Makefile。mk文件可以通过include-include指令来包含其他mk文件,从而形成一个复杂的构建图。mk文件的执行顺序是由构建图中的依赖关系决定的,而不是由文件名或目录结构决定的。

构建系统会从根目录下的build/make/core/main.mk开始执行,然后根据不同的目标平台和产品选择相应的mk文件。例如我选择了rk3568_s作为目标项目,那么构建系统会执行以下mk文件:

  • build/make/core/main.mk
  • build/make/target/product/core.mk
  • device/rockchip/rk356x/rk3568_s/rk3568_s.mk
  • device/rockchip/rk356x/BoardConfig.mk
  • device/rockchip/common/BoardConfig.mk
  • vendor/customize/customize.mk

每个mk文件都可能包含其他mk文件,形成一个嵌套的结构。可以通过在mk文件中添加$(warning)$(info)指令来打印一些信息,从而观察它们的执行顺序。在调试过程中,在rk3568_s.mkcustomize.mk中添加了以下代码:

$(warning Enter rk3568_s > device.mk is included $(TARGET_PRODUCT))
 
ifeq ($(TARGET_PRODUCT),rk3568_s)
$(warning Enter rk3568_s)
endif
$(warning Enter customize > customize.mk is included $(TARGET_PRODUCT))
 
ifeq ($(filter $(TARGET_PRODUCT),rk3568_s_beta rk3568_s),)
# 这里是当TARGET_PRODUCT不是rk3568_s_beta或rk3568_s时执行的代码
else
# 这里是当TARGET_PRODUCT是rk3568_s_beta或rk3568_s时执行的代码
$(warning Enter $(MY_CUSTOM_PATH)/rk3568_s_beta)
endif

这样当我执行lunch rk3568_s-userdebug时,会看到以下输出:

device/rockchip/rk356x/rk3568_s/rk3568_s.mk:17: warning: Enter rk3568_s > device.mk is included rk3568_s
device/rockchip/rk356x/rk3568_s/rk3568_s.mk:20: warning: Enter rk3568_s
vendor/customize/customize.mk:3: warning: Enter customize > customize.mk is included rk3568_s
vendor/customize/customize.mk:12: warning: Enter ./vendor/customize/rk3568_s_beta

这说明了rk3568_s.mk先于customize.mk被执行,并且它们都被正确地识别了目标产品,除了确定顺序外,还可以通过在mk文件中添加$(warning)$(info)指令来打印 来确认有些模块 是否编译,是否进入了我们预期的编译条件内 懂我意思吧 而不是傻傻的改完就刷机验证,很多内容可以在编译期间就可以知道结果了。

2. 如何覆盖PRODUCT_PROPERTY_OVERRIDES属性

在Android中,有一些系统属性(sysprop)是用于控制系统行为和配置的。例如,ro.sf.lcd_density属性用于指定屏幕密度(dpi)。这些属性可以在不同的地方被定义和赋值,例如:

  • 在设备相关的mk文件中,使用PRODUCT_PROPERTY_OVERRIDES += <property>=<value>指令。
  • 在设备相关的Java代码中,使用SystemProperties.set(<property>, <value>)方法。
  • 在设备相关的C/C++代码中,使用property_set(<property>, <value>)函数。
  • 在命令行中,使用setprop <property> <value>命令。

当一个属性在多个地方被定义和赋值时,它们可能会产生冲突和覆盖。为了避免这种情况,Android的构建系统提供了一些规则和工具来检查和处理属性的冲突和覆盖。

属性的赋值可以分为两种类型:强制可选。强制赋值是指在mk文件中直接使用PRODUCT_PROPERTY_OVERRIDES += <property>=<value>指令的赋值,它表示这个属性必须被设置为这个值。可选赋值是指在mk文件中使用PRODUCT_PROPERTY_OVERRIDES += ?<property>=<value>指令的赋值,它表示这个属性可以被设置为这个值,但也可以被其他强制赋值覆盖。

当一个属性有多个强制赋值时,构建系统会检查它们是否有相同的值。如果有相同的值,那么构建系统会忽略可选赋值,并将这个属性设置为这个值。如果没有相同的值,那么构建系统会报错,并提示找到了重复的赋值。

其实我开始是不知道这么个问题的,因为早期调试Android源码的时候 大概记得可以在不同的MK定义PRODUCT_PROPERTY_OVERRIDES 从而形成覆盖属性, 但是根据我调试发现

假设我在Android源码device目录下 有个文件比如device.mk 定义了

PRODUCT_PROPERTY_OVERRIDES += ro.sf.lcd_density=240

然后vendor目录下又有一个自定义的custom.mk 又定义了

PRODUCT_PROPERTY_OVERRIDES += ro.sf.lcd_density=320

编译Android源码最终会编译使用240还是320?我开始理解的以为根据覆盖原则,在vendor/build.prop 应该只存在240 直接编译。。

编译报错:

error: found duplicate sysprop assignments:

ro.sf.lcd_density=240

ro.sf.lcd_density=320

资料显示:既然已经确定了vendor下的custom.mk是最后加载的,那么它的定义会覆盖之前的定义。但从Android 9 开始,构建系统不再允许同一个属性在多个地方被定义,因此会看到这个错误。

所以这就为啥 在device.mkcustom.mk中都使用了强制赋值:

PRODUCT_PROPERTY_OVERRIDES += ro.sf.lcd_density=240
PRODUCT_PROPERTY_OVERRIDES += ro.sf.lcd_density=320

执行make时,会看到以下错误:

error: found duplicate sysprop assignments:
ro.sf.lcd_density=240
ro.sf.lcd_density=320

这是因为构建系统不允许同一个属性有多个不同的强制赋值。

为了解决这个问题,有以下几种方法:

  • 修改其中一个强制赋值为可选赋值:如果想让一个强制赋值可以被另一个强制赋值覆盖,可以在属性名前加上一个问号(?),表示这是一个可选赋值。如果想让device.mk中的赋值可以被custom.mk中的赋值覆盖,可以修改device.mk中的代码为:
PRODUCT_PROPERTY_OVERRIDES += ?ro.sf.lcd_density=240

当执行make时,构建系统会忽略device.mk中的可选赋值,并将这个属性设置为custom.mk中的强制赋值(320)。

  • 修改构建工具来删除重复的强制赋值:如果不想修改mk文件中的代码,也可以修改构建系统中用于处理属性冲突和覆盖的工具。这个工具是一个py脚本,位于build/make/tools/post_process_props.py。它会读取所有定义的属性,并根据它们的类型和值来决定是否删除或保留它们。可以修改这个脚本来实现想要的逻辑。尝试了以下几种修改:
  • 去掉了重复属性的限制:注释掉了报错的代码,使得构建系统不再检查重复属性。这样当你执行make时,构建系统会保留所有定义的属性,并将它们写入到最终生成的build.prop文件中。例如:
rk_android12.0_sdk/out/target/product/rk3568_s/vendor$ grep -rn "ro.sf.lcd_density"
build.prop:91:ro.sf.lcd_density=240
build.prop:156:ro.sf.lcd_density=320
  • 这样做的问题是,当系统启动时,它会读取最后一个定义的属性作为最终的值,而忽略之前的定义。这可能会导致一些不一致和混乱。
  • 去掉了重复属性的最后一个:添加了一些代码,使得构建系统在遇到重复属性时,删除最后一个定义的属性,并保留第一个定义的属性。这样,当你执行make时,构建系统会删除custom.mk中的强制赋值,并将这个属性设置为device.mk中的强制赋值(240)。例如:
rk_android12.0_sdk/out/target/product/rk3568_s/vendor$ grep -rn "ro.sf.lcd_density"
  #build.prop:92:ro.sf.lcd_density=240
  # Removed by post_process_props.py because Duplicate property, keeping the first one
  build.prop:161:#ro.sf.lcd_density=320

这样做的问题是,不想让device.mk中的赋值覆盖custom.mk中的赋值,而是反过来,但结果却是注释掉了custom.mk的240。

按照正常理解 应该是custom.mk(240) 比device.mk(320) 后加载应该删除320才对?但是py脚本加载写入是倒序的?这不符合我的预期。

  • 去掉了重复属性的第一个:继续修改,使得构建系统在遇到重复属性时,删除第一个定义的属性,并保留最后一个定义的属性。这样,当你执行make时,构建系统会删除device.mk中的强制赋值,并将这个属性设置为custom.mk中的强制赋值(320)。例如:
# Removed by post_process_props.py because Duplicate property, keeping the last one
build.prop:92:#ro.sf.lcd_density=240
# Removed by post_process_props.py because Duplicate property, keeping the first one
#build.prop:161:ro.sf.lcd_density=320

这样做可以让custom.mk中的赋值覆盖device.mk中的赋值,而不用修改原生的device.mk文件中的代码 不管系统之前属性如何写的,只需要最终在我们的客制化mk文件中重新覆盖即可,不需要整个源码的去找。但是这样做的缺点是,可能会影响其他属性的赋值顺序和结果,因为改变了构建工具的默认行为。但根据我的测试暂时没什么影响。

总结

在本文中,介绍了Android源代码定制的一些机制和工具,包括:

  • mk文件:介绍了mk文件的结构和语法,以及如何确定mk文件的执行顺序。
  • 属性(sysprop):介绍了属性的类型和赋值方式,以及如何覆盖属性的值。

希望这篇文章能够对你有所帮助,如果你有任何问题或建议,请在评论区留言。谢谢!

相关文章
|
6月前
|
Android开发 开发者
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
本文详细介绍了如何通过自定义 `attrs.xml` 文件实现 Android 自定义 View 的属性配置。以一个包含 TextView 和 ImageView 的 DemoView 为例,讲解了如何使用自定义属性动态改变文字内容和控制图片显示隐藏。同时,通过设置布尔值和点击事件,实现了图片状态的切换功能。代码中展示了如何在构造函数中解析自定义属性,并通过方法 `setSetting0n` 和 `setbackeguang` 实现功能逻辑的优化与封装。此示例帮助开发者更好地理解自定义 View 的开发流程与 attrs.xml 的实际应用。
148 2
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
|
6月前
|
Java Android开发
Android studio中build.gradle文件简单介绍
本文解析了Android项目中build.gradle文件的作用,包括jcenter仓库配置、模块类型定义、包名设置及依赖管理,涵盖本地、库和远程依赖的区别。
575 19
|
6月前
|
存储 XML Java
Android 文件数据储存之内部储存 + 外部储存
简介:本文详细介绍了Android内部存储与外部存储的使用方法及核心原理。内部存储位于手机内存中,默认私有,适合存储SharedPreferences、SQLite数据库等重要数据,应用卸载后数据会被清除。外部存储包括公共文件和私有文件,支持SD卡或内部不可移除存储,需申请权限访问。文章通过代码示例展示了如何保存、读取、追加、删除文件以及将图片保存到系统相册的操作,帮助开发者理解存储机制并实现相关功能。
1579 2
|
9月前
|
移动开发 安全 Java
Android历史版本与APK文件结构
通过以上内容,您可以全面了解Android的历史版本及其主要特性,同时掌握APK文件的结构和各部分的作用。这些知识对于理解Android应用的开发和发布过程非常重要,也有助于在实际开发中进行高效的应用管理和优化。希望这些内容对您的学习和工作有所帮助。
870 83
|
ARouter Android开发
Android不同module布局文件重名被覆盖
Android不同module布局文件重名被覆盖
|
ARouter Android开发
Android不同module布局文件重名被覆盖
Android不同module布局文件重名被覆盖
772 0
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
存储 Android开发 Kotlin
开发安卓app OKhttp下载后使用MediaPlayer播放
在Android Jetpack Compose应用程序中,要使用OkHttp下载远程音频文件并在本地播放,你需要完成以下几个步骤: 1. **添加依赖**:确保`build.gradle`文件包含OkHttp和Jetpack Compose的相关依赖。 2. **下载逻辑**:创建一个`suspend`函数,使用OkHttp发起网络请求下载音频文件到本地。 3. **播放逻辑**:利用`MediaPlayer`管理音频播放状态。 4. **Compose UI**:构建用户界面,包含下载和播放音频的按钮。
|
Web App开发 Android开发
Android 使用 aria2c + 百度网盘助手下载百度云文件
首先表明,这是一篇我转载的文章 原为知乎专题 :https://zhuanlan.zhihu.com/p/26873167?group_id=846415883457949696 闲的无聊,突然想到自己在 Android 上下百度云的东西很麻烦,而且不想用山寨云什么的,如果能在 Android 也能实现 aria2c + 百度网盘助手的那一套就好了 于是试了一下,那当然是可以的 ( 首先,你去 aria2c 的 github-release** 页面,那里就有 aria2c for Android 的二进制包,都不用自己编译了多好。
2512 0

热门文章

最新文章