date: 2017-02-16 11:00 status: public title: ‘添加有源码apk到系统目录下编译(使应用拥有系统应用权限)’ tags: build —​

需求

当我们的应用想要使用一些系统应用才能使用的功能时该怎么办呢?如何让我们的应用“变成”系统应用?
现在我写了一个测试demo,用于打开GSP,但是2.3(还是2.1?)以后不允许三方应用直接打开location开关。那我们该怎么做呢?
先不管应用系统(system app)的限制,我们照常写app。权限该添加的添加,即使出现下面的提示:

然后安装运行,应用可能强退或者操作无响应。 大概log会提示

Caused by: java.lang.SecurityException: Permission denial: writing to settings requires:android.permission.WRITE_SECURE_SETTINGS

没关系,下面我们有三种方法让这个应用可用。

  1. 给应用系统签名
  2. 不带源码直接放apk,mm编译
  3. 带源码mm编译

生成的apk放到system/app 或者priv-app目录下,让系统认为这是一个系统应用。
第1个方法最快,第2,3可以应用到预置apk的需求中。
下面我们分别说一下具体的步骤

1. 给应用系统签名

说起来简单也不简单。说简单因为就一条命令:

java -jar signapk.jar platform.x509.pem platform.pk8 app-debug.apk ControlCenter_signed.apk

文件目录:
signapk.jar: android/out/host/linux-x86/framework/signapk.jar
另外两个文件在 android/build/target/product/security/
除了platform签名以后还有 testkey media shared releasekey。
系统级别的签名使用的是platform来签名(此时使用android:sharedUserId=”android.uid.system”才有用)。
但是使用的时候要注意,一定要使用和目标系统匹配的文件来签名。
比如我的源码目中有两组platform签名的文件,我第一次签名搞错了(我没发现有另一组在devices目录下的文件),所有才有了后面两种方法的尝试。
之后我重新尝试另一组签名文件,签名成功后是可以符合期望工作的。

2.  不带源码直接放apk,mm编译

其实这一步也可以叫做“预置不带源码的apk”。
1) 使用这种方法我们要为apk新建一个目录(一般自己的应用预置在packages/apps目录下,三方的可能在Vendor/third-party下),
2) 把apk放到目录下
3) 编写一个对应的Android.mk文件。
这里我们以googel translate.apk的预置为例,并以这个为参考编写我们自己的Android.mk。
下面是Translate目录下的Android.mk部分内容:

# Translate
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Module name should match apk name to be installed
LOCAL_MODULE := Translate
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_BUILT_MODULE_STEM := package.apk
include $(BUILD_PREBUILT)

这里LOCAL_CERTIFICATE := PRESIGNED,表示保留原签名,如果我们想让apk拥有和系统一样的签名则需要改成

LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true

4)之后在目录下mm编译。把生成的文件push到手机上就可以用了。
5)目前为止我们只是拿到系统应用的权限,如果需要在整包编译的时候把这个apk也编译进去的话,需要修改对应的编译系统的.mk文件,添加这个模块

PRODUCT_PACKAGES += \
Books \
Bugle \
GoogleCamera \

3. 带源码mm编译

加入现在已经有一个使用Android Studio编写的app了,我们想要把它的源码放到Android系统目录下编译,该如何做呢?
1) 跟2差不多,还是先建目录已packages/app/目录下面为例,新建一个ControlCenter的目录,
2) 然后把源码全部放进去,然后把AndroidMainfest.xml复制到根目录。
如果不做清理的话,目录就是这个样子的

这里提醒一下,如果要保持Android Studio Project的目录格式的话,在用mm编译前要把build目录,test和androidTest目录删掉才能成功编译。
如果有强迫症的话也可以删除其他文件,改成下面的样子。

是不是简洁了很多?
但是上面那种有个好处,既可以mm编译,又可以单独用Android Studio编译。看自己需求。
3) 新建Android.mk文件。贴一下Android.mk文件内容:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# 将res移动到这个应用的根目录
# LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
# 如果你是Android Studio的res目录
LOCAL_RESOURCE_DIR+= $(LOCAL_PATH)/app/src/main/res
# apk名字
LOCAL_PACKAGE_NAME := ControlCenter
# 系统签名
LOCAL_CERTIFICATE := platform
# 如果有使用到依赖
LOCAL_STATIC_JAVA_LIBRARIES:= \
android-support-v4
LOCAL_PRIVILEGED_MODULE := true
include $(BUILD_PACKAGE)

mm/mma编译

4)同样如果编整包的时候把这个也编译进去,要修改对应的.mk文件。
因为我只是写了个简单的demo,jni和lib等不在本次讨论之列。
注意:这里和上面有个区别,编译apk的时候我们用LOCAL_PACKAGE_NAME定义应用名,预置apk的时候使用LOCAL_MODULE定义模块名

期间遇到的问题

mm编译不过 错误提示:

packages/apps/ControlCenter/res/layout/activity_main.xml:14: error: Error: This attribute must be localized. (at 'text' with value 'wifi').

对应的代码:

<Switch
android:id="@+id/switch_wifi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="wifi"
android:onClick="onSwitchClicked" />

居然不允许直接使用字符,修改成@string后编译正常了。

总结

到此为止我们的需求已经通过三种方法实现了。
在有签名文件的情况下第一种方法最快捷。
第二种方法适合没有源码的情况,或者app已经稳定不需要修改的情况下。而且可以预置到系统中。
第三种方法适合有源码,而且这个app可能随时调整的情况。

扩展

一些Android.mk参数说明:

LOCAL_PATH:= $(call my-dir)
一个Android.mk file首先必须定义好LOCAL_PATH变量。它用于在开发树中查找源文件。
宏函数’my-dir’,由编译系统提供,用于返回当前路径(即包含Android.mk file文件的目录)。
# begin 在加入这个宏之前apk在/system/app下
# LOCAL_PRIVILEGED_MODULE := true 
# end 加入后apk在/system/priv-app下
# 这个宏控制的是apk可卸载,恢复出厂设置后无法恢复
# LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
# 这个宏控制系统给apk签名
LOCAL_CERTIFICATE := PRESIGNED
# 这个宏控制的是apk可卸载,恢复出厂设置后可以恢复
LOCAL_MODULE_PATH := $(TARGET_OUT)/vendor/operator/app
Android.mk中可以定义多个编译模块,每个编译模块都是以include $(CLEAR_VARS)开始,以include $(BUILD_XXX)结束(详解参考文末第二个链接)。
LOCAL_MODULE_TAGS := optional
解析:
LOCAL_MODULE_TAGS :=user eng tests optional
user:  指该模块只在user版本下才编译
eng:  指该模块只在eng版本下才编译
tests: 指该模块只在tests版本下才编译
optional:指该模块在所有版本下都编译
取值范围debug eng tests optional samples shell_ash shell_mksh。注意不能取值user,如果要预装,则应定义core.mk。

参考链接

  1. Android.mk文件格式 Android.mk文件格式_机器人的忧伤的博客-CSDN博客
  2. Android.mk简单分析 Android.mk简单分析 - …平..淡… - 博客园 (讲解了哪些是必选项,哪些是可选项)
  3. Android.mk编译APK范例  http://hubingforever.blog.163.com/blog/static/1710405792011656434982/ (编译各种apk的举例)
  4. 带有源码的apk预置到系统 http://blog.csdn.net/u013377887/article/details/53870803

推荐阅读

  1. Android.mk简介   http://hubingforever.blog.163.com/blog/static/171040579201152185542166/
  2. Android.mk文件语法规范(Android.mk File)Android.mk文件语法规范(Android.mk File)_眨巴眨巴的博客-CSDN博客_call my-dir (NDK jni)
  3. Android.mk文件语法规范 & 使用模板 Android.mk文件语法规范 & 使用模板 - Hecker385 - 博客园 (这是个译文,也有很多参数的解释)