Android编译过程详解之一
Android编译过程详解之二
Android编译过程详解之三
Android.mk解析
ActivityNotFoundException
最近将Android从4.4移植到5.1时,添加一个从拨号界面输入*#360*#进入battery info查看界面的功能时(如感兴趣,详情见Android电池监控系统(bms)之一电池系统架构),activity跳转部分代码如下:
1 | else if(input.equals(BATTERY_INFO)) |
遇到如下问题:
1 | ActivityNotFoundException : Unable to find explicit activity class; have you declared this activity in your AndroidManifest.xml? |
我将流程检查了一遍又一遍,都完全没有问题,Google了很久也无结果。后灵光一现:是否Setting这个apk根本就没有安装成功,所以才导致找不到Activity。
于是,我首先通过命令 adb install -r Setting.apk 手动安装,但总是提示安装失败,这时已有些小小兴奋,因为感觉自己快找到原因了。
然后,我将apk push进手机相应文件系统路径,重启,通过logcat打印log,后发现关键信息,如下:
1 | Failed to parse /system/priv-app/Settings: Signature mismatch for shared user : SharedUserSetting{2a5b4702 android.uid.system/1000} |
通过此log信息可知:系统没有能成功安装此apk,原因是app签名不匹配。 这样就找到了问题的根源。 向应用软件部同事了解情况后得知,是因为此项目客户指定Signature。
所以,接下来我就有两种选择:
- 自己整编整个系统,然后刷机,但是整编时间太长,所以放弃。
- 将修改代码给出软件同事,让其帮忙编一apk。(我选择了此方式,然后测试OK了)
当然,这种问题主要是做系统级APP,需要用到root权限或运行于系统进程时时才会遇到,其他一般都是如下几个情况:
- 如log中提示,没有在AndroidManifest.xml中定义此Activity。
- 包名或者类名书写错误,不统一。
- 自己定义的包名或者类名与系统自带类重复。
Android.mk解析
因为上面问题时由签名引起的,所以就深入看了一下什么地方指定签名,后发现在APP目录中的Android.mk中制定。Android.mk将source打包为如下几种modules:
1. APK程序
一般的Android程序,编译打包生成apk文件
2. JAVA库
java类库,编译打包生成jar文件
3. C\C++应用程序
可执行的C\C++应用程序
4. C\C++静态库
编译生成C\C++静态库,并打包成.a文件,静态库则可被链接到动态库。
5. C\C++动态库
编译生成共享库(动态链接库),并打包成.so文, 只有动态库才能被install or copy到apk。
在 Android Build 系统中,编译以模块(而不是文件)作为单位,每个模块都有一个唯一的名称,一个模块的依赖对象只能是另外一个模块,而不能是其他类型的对象。对于已经编译好的二进制库,如果要用来被当作是依赖对象,那么应当将这些已经编译好的库作为单独的模块。对于这些已经编译好的库使用 BUILD_PREBUILT 或 BUILD_MULTI_PREBUILT。例如:当编译某个 Java 库需要依赖一些 Jar 包时,并不能直接指定 Jar 包的路径作为依赖,而必须首先将这些 Jar 包定义为一个模块,然后在编译 Java 库的时候通过模块的名称来依赖这些 Jar 包。
下面为Qualcomm Settings中的Android.mk (带“Lee:”为我自己加入以详解Android.mk):
1 | //通常以如下两行开头 |
编译类型的说明
eng
- 默认类型,该编译类型适用于开发阶段。
- 安装包含 eng, debug, user,development 标签的模块
- 安装所有没有标签的非APK模块
- 安装所有产品定义文件中指定的APK模块
user
- 该编译类型适合用于最终发布阶段。
- 安装所有带有 user 标签的模块
- 安装所有没有标签的非 APK 模块
- 安装所有产品定义文件中指定的 APK 模块,APK 模块的标签将被忽略
userdebug
该编译类型适合用于debug阶段。该类型和user一样,另:
- 会安装包含debug标签的模块
- 编译出的系统具有root访问权限
build/core/config.mk中已经定义好了各种类型模块的编译方式。所以要执行编译,只需通过常量的方式引入对应的 Make 文件即可。详见Android编译过程详解之二,例如,要编译一个 APK 文件,只需要在 Android.mk 文件中,加入“include $(BUILD_PACKAGE)。
除此以外,Build 系统中还定义了一些便捷的函数以便在 Android.mk 中使用,如下:
1 | $(call my-dir):获取当前文件夹路径。 |
LOCAL_CERTIFICATE
分析了Android.mk,再来详细说说我之前问题相关的一个属性:LOCAL_CERTIFICATE ,用于指定签名是使用的key,如不指定默认testkey。
分析这个属性就先得谈谈此apkAndroidManifest.xm了中的sharedUserId属性说起,如下:
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
通过将sharedUserId配置为”android.uid.system”,即让程序运行在系统进程,而运行在系统进程则需要目标系统的platform key,Android.mk中的 LOCAL_CERTIFICATE := platform 即是声明相应签名key文件。key文件的源码路径在 build\target\product\security。通过这样处理的apk则只能在自己编译的系统里面才能使用,如若装到其他Android系统会提示:”Package … has no signatures that match those in shared user android.uid.system”。
另,android:sharedUserId属性不仅仅可以把apk放到系统进程中,也可以配置多个APK运行在一个进程中,这样可以共享数据,就会很有用处。就像我上面的Settings.apk。
在Android.mk中,LOCAL_CERTIFICATE可设置的值如下:
- LOCAL_CERTIFICATE := platform
- LOCAL_CERTIFICATE := shared
- LOCAL_CERTIFICATE := media
然后,需要在APK源码的AndroidManifest.xml文件中的manifest节点添加如下内容: - android:sharedUserId=”android.uid.system”
- android:sharedUserId=”android.uid.shared”
- android:sharedUserId=”android.media”
在Android源码的build/target/product/security/目录下有如下的4对KEY:- media.pk8与media.x509.pem;
- platform.pk8与platform.x509.pem;
- shared.pk8与shared.x509.pem;
- testkey.pk8与testkey.x509.pem;
其中,”.pk8”文件为私钥,”.x509.pem”文件为公钥,这需要了解非对称加密方式。