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):介绍了属性的类型和赋值方式,以及如何覆盖属性的值。

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

相关文章
|
15天前
|
存储 数据库 Android开发
安卓Jetpack Compose+Kotlin,支持从本地添加音频文件到播放列表,支持删除,使用ExoPlayer播放音乐
为了在UI界面添加用于添加和删除本地音乐文件的按钮,以及相关的播放功能,你需要实现以下几个步骤: 1. **集成用户选择本地音乐**:允许用户从设备中选择音乐文件。 2. **创建UI按钮**:在界面中创建添加和删除按钮。 3. **数据库功能**:使用Room数据库来存储音频文件信息。 4. **更新ViewModel**:处理添加、删除和播放音频文件的逻辑。 5. **UI实现**:在UI层支持添加、删除音乐以及播放功能。
|
15天前
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
16天前
|
存储 Android开发 Kotlin
开发安卓app OKhttp下载后使用MediaPlayer播放
在Android Jetpack Compose应用程序中,要使用OkHttp下载远程音频文件并在本地播放,你需要完成以下几个步骤: 1. **添加依赖**:确保`build.gradle`文件包含OkHttp和Jetpack Compose的相关依赖。 2. **下载逻辑**:创建一个`suspend`函数,使用OkHttp发起网络请求下载音频文件到本地。 3. **播放逻辑**:利用`MediaPlayer`管理音频播放状态。 4. **Compose UI**:构建用户界面,包含下载和播放音频的按钮。
|
1天前
|
Java 开发工具 Android开发
详细解读Android开发DNK开发将.c文件打包成os
详细解读Android开发DNK开发将.c文件打包成os
|
6天前
|
Android开发
Android Gradle开发—脚本实现自动打包后复制一份APK文件,并修改APK名称,到指定目录作备份
Android Gradle开发—脚本实现自动打包后复制一份APK文件,并修改APK名称,到指定目录作备份
18 0
|
6天前
|
缓存 大数据 Android开发
Android 巧用putBinder方法传递大文件
Android 巧用putBinder方法传递大文件
14 0
|
6天前
|
开发工具 Android开发
Android 代码自定义drawble文件实现View圆角背景
Android 代码自定义drawble文件实现View圆角背景
15 0
|
7天前
|
安全 Java Android开发
安卓开发中的新趋势:Kotlin与Jetpack的完美结合
【6月更文挑战第20天】在不断进化的移动应用开发领域,Android平台以其开放性和灵活性赢得了全球开发者的青睐。然而,随着技术的迭代,传统Java语言在Android开发中逐渐显露出局限性。Kotlin,一种现代的静态类型编程语言,以其简洁、安全和高效的特性成为了Android开发中的新宠。同时,Jetpack作为一套支持库、工具和指南,旨在帮助开发者更快地打造优秀的Android应用。本文将探讨Kotlin与Jetpack如何共同推动Android开发进入一个新的时代,以及这对开发者意味着什么。
|
2天前
|
Java 开发工具 Android开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
在移动应用开发的广阔天地中,Android和iOS两大平台各自占据着半壁江山。本文将深入探讨这两个平台在开发过程中的关键差异点,包括编程语言、开发工具、用户界面设计、性能优化以及市场覆盖等方面。通过对这些关键因素的比较分析,旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和目标受众做出明智的平台选择。
|
2天前
|
编解码 Android开发 iOS开发
深入探索Android与iOS开发的差异与挑战
【6月更文挑战第24天】在移动应用开发的广阔舞台上,Android和iOS两大操作系统扮演着主角。它们各自拥有独特的开发环境、工具集、用户基础及市场策略。本文将深度剖析这两个平台的开发差异,并探讨开发者面临的挑战,旨在为即将踏入或已在移动开发领域奋斗的开发者提供一份实用指南。
22 13