學得懂的 Android Framework 教程——玩轉 AOSP 之系統 App 原始碼新增
1. 如何新建一個系統 App 專案
使用 Android Studio 新建一個空專案 FirstSystemApp,包名設定為 com.yuandaima.firstsystemapp
,語言選擇 Java。後面為敘述方便稱該專案為 as 專案。
接著在 Jelly/Rice14
目錄下建立如下的目錄和檔案:
接著將 as 專案中的 res 檔案下的資原始檔拷貝到 Jelly/Rice14/FirstSystemApp/res
中,把 as 專案中的 MainActivity.java 拷貝到 Jelly/Rice14/FirstSystemApp/src/com/yuandaima/firstsystemapp
中。
接著修改已新增的 AndroidManifest.xml 檔案:
```xml
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.FirstSystemApp">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>
```
接著修改已新增的 Android.bp 檔案:
```json android_app { name: "FirstSystemApp",
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
manifest: "AndroidManifest.xml",
sdk_version: "current",
certificate: "platform",
product_specific: true,
//依賴
static_libs: ["androidx.appcompat_appcompat",
"com.google.android.material_material",
"androidx-constraintlayout_constraintlayout"],
} ```
至此我們的系統 App 就建立好了。
接著在我們的 Product 中新增這個App,修改 device/Jelly/Rice14/Rice14.mk
:
```Makefile
新增以下內容
PRODUCT_PACKAGES += FirstSystemApp ```
接著編譯系統,啟動虛擬機器,開啟 app:
bash
source build/envsetup.sh
lunch Rice14-eng
make -j16
emulator
2. 系統 App 與 普通 App 的差異
2.1 系統 App 可以使用更多的 api
當我們在 Android.bp 中配置了:
platform_apis: true,
sdk_version: "",
當 platform_apis 為 true 時,sdk_version 必須為空。這種情況下我們的 app 會使用平臺 API 進行編譯而不是 SDK,這樣我們的 App 就能訪問到非 SDK API 了。關於 SDK API 和非 SDK API 的內容可以參考官方文件
2.2 系統 App 的簽名
AOSP 內建了 APK 簽名檔案,我們可以在 Android.bp 中通過 certificate 配置系統 app 的簽名檔案,certificate 的值主要有一下幾個選項:
- testkey:普通 APK,預設情況下使用
- platform:該 APK 完成一些系統的核心功能。經過對系統中存在的資料夾的訪問測試,這種方式編譯出來的 APK 所在程序的 UID 為system
- shared:該 APK 需要和 home/contacts 程序共享資料
- media:該 APK 是 media/download 系統中的一環
- PRESIGNED:表示 這個 apk 已經簽過名了,系統不需要再次簽名;
2.3 系統 App 能使用更多的許可權
當 Android.bp 中的 privileged 被配置為 true 時,我們的系統 App 在新增特許許可權許可名單後,能使用 signature 和 signatureOrSystem 級別的許可權,而普通 App 是不能使用這些許可權的。
在後續的系統許可權相關的分享中會介紹如何給系統 App 新增特許許可權許可名單
3. 系統 App 新增依賴
1. 新增 AOSP 中已有的庫
在 FirstSystemApp 的 Android.bp 中我們添加了很多依賴:
```json static_libs: ["androidx.appcompat_appcompat", "com.google.android.material_material", "androidx-constraintlayout_constraintlayout"],
```
在 AOSP 中, 很多常用的庫均以預編譯模組的方式新增到系統原始碼中。比如常用的 AndroidX 庫定義在 prebuilts/sdk/current/androidx
目錄下。這些庫通過 prebuilts/sdk/current/androidx/Android.bp
引入。比如 recyclerview 庫的引入方式如下:
json
android_library {
name: "androidx.recyclerview_recyclerview",
sdk_version: "31",
apex_available: [
"//apex_available:platform",
"//apex_available:anyapex",
],
min_sdk_version: "14",
manifest: "manifests/androidx.recyclerview_recyclerview/AndroidManifest.xml",
static_libs: [
"androidx.recyclerview_recyclerview-nodeps",
"androidx.annotation_annotation",
"androidx.collection_collection",
"androidx.core_core",
"androidx.customview_customview",
],
java_version: "1.7",
}
可以看到引入的是一個 android_library
,名字叫 androidx.recyclerview_recyclerview
。maifest 檔案在 manifests/androidx.recyclerview_recyclerview/
目錄下,進入這個目錄只有一個 AndroidManifest.xml
檔案,其內容如下:
```xml
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="28" />
```
很奇怪,並沒有看到 RecyclerView 庫的原始碼,也沒有看到 aar 庫檔案。我們接著看 Android.bp 中的依賴,其中一項是 androidx.recyclerview_recyclerview-nodeps
,我們在 Android.bp
中看一下它的引入方式:
json
android_library_import {
name: "androidx.recyclerview_recyclerview-nodeps",
aars: ["m2repository/androidx/recyclerview/recyclerview/1.1.0-alpha07/recyclerview-1.1.0-alpha07.aar"],
sdk_version: "current",
min_sdk_version: "14",
static_libs: [
"androidx.annotation_annotation",
"androidx.collection_collection",
"androidx.core_core",
"androidx.customview_customview",
],
}
這裡看到了,它的 aar 庫在這裡: m2repository/androidx/recyclerview/recyclerview/1.1.0-alpha07/recyclerview-1.1.0-alpha07.aar
繼續查閱我們可以發現,prebuilts/tools/common/m2
目錄下引入了大量的三方庫。
總結一下,當我們的系統 App 需要引入一個庫的時候,通常會在 prebuilds 目錄下查詢:
- androidx 相關庫引入,先在 prebuilts/sdk/current/androidx 下尋找配置好的 bp 檔案
- 其他庫引入,先在 prebuilts/tools/common/m2 下尋找尋找配置好的 bp 檔案
都沒有,就得自己引入了
2. 自己給 AOSP 新增庫
java 庫原始碼引入
這部分參考之前分享的玩轉 AOSP 之自定義模組新增的新增自定義模組之 Java 庫和可執行程式章節
java 庫以 jar 包形式引入
這部分參考之前分享的玩轉 AOSP 之預編譯模組新增的JAR 包章節
Android 庫原始碼引入
在 device/Jelly/Rice14
目錄下建立如下的檔案和資料夾
其中 MyCustomView.java
是一個用於演示的沒有具體功能的自定義 View:
```java package com.yuandaima.firstsystemandroidlibrary;
import android.content.Context; import android.util.AttributeSet; import android.view.View;
import androidx.annotation.Nullable;
public class MyCustomView extends View { public MyCustomView(Context context) { super(context); }
public MyCustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
} ```
AndroidManifest.xml
的內容如下:
```xml
```
Android.bp
的內容如下:
```json android_library {
//......
//依賴
static_libs: ["androidx.appcompat_appcompat",
"com.google.android.material_material",
"androidx-constraintlayout_constraintlayout",
"FirstSystemAndroidLibrary"],
} ```
修改一下 MainActivity
,在 App 裡使用我們的自定義 View:
```java package com.yuandaima.firstsystemapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.yuandaima.firstsystemandroidlibrary.MyCustomView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyCustomView myView = new MyCustomView(this);
}
} ```
接著編譯系統,啟動虛擬機器,開啟 app:
bash
source build/envsetup.sh
lunch Rice14-eng
make -j16
emulator
這樣我們的庫就算引入完畢了。
Android 庫以 aar 包形式引入
更多的時候 Android 庫是以 aar 包的形式引入。
假設我們的 SourceApp 需要引入 lottie 這個動畫庫。
首先我們這裡下載好 lottie 庫的 aar 打包檔案。
在 device/Jelly/Rice14
目錄下建立如下的目錄結構:
bash
liblottie/
├── Android.bp
└── lottie-5.2.0.aar
其中 Android.bp 的內容如下:
bash
android_library_import {
name: "lib-lottie",
aars: ["lottie-5.2.0.aar"],
sdk_version: "current",
}
然後我們修改 FirstSystemApp 中的 Android.bp 引入這個庫:
```json
static_libs: ["androidx.appcompat_appcompat",
"com.google.android.material_material",
"androidx-constraintlayout_constraintlayout",
"FirstSystemAndroidLibrary",
"lib-lottie"],
```
這樣就可以在 App 中使用 lottie 庫了
JNI 專案
Android 10 下,Android.bp(soong) 方式對 JNI 的支援有點問題,所以我們只有用 Android.mk 來演示了。Android 13 下 Android.bp (soong) 是完美支援 JNI 的。
在 device/Jelly/Rice14
目錄下新增如下的檔案與資料夾:
jni/Android.mk 內容如下:
```Makefile LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
This is the target being built.
LOCAL_MODULE:= myjnilib
All of the source files that we will compile.
LOCAL_SRC_FILES:= \ native.cpp
All of the shared libraries we link against.
LOCAL_LDLIBS := -llog
No static libraries.
LOCAL_STATIC_LIBRARIES :=
LOCAL_CFLAGS := -Wall -Werror
LOCAL_NDK_STL_VARIANT := none
LOCAL_SDK_VERSION := current
LOCAL_PRODUCT_MODULE := true
include $(BUILD_SHARED_LIBRARY)
```
jni/native.cpp 的內容如下:
```c++ / * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. /
define LOG_TAG "simplejni native.cpp"
include
include
include "jni.h"
define ALOGV(...) android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS)
define ALOGD(...) android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS)
define ALOGI(...) android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS)
define ALOGW(...) android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS)
define ALOGE(...) android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS)
static jint add(JNIEnv /env/, jobject /thiz*/, jint a, jint b) { int result = a + b; ALOGI("%d + %d = %d", a, b, result); return result; }
static const char *classPathName = "com/example/android/simplejni/Native";
static JNINativeMethod methods[] = { {"add", "(II)I", (void*)add }, };
/ * Register several native methods for one class. / static int registerNativeMethods(JNIEnv env, const char className, JNINativeMethod* gMethods, int numMethods) { jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
ALOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
ALOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
/ * Register native methods for all classes we know about. * * returns JNI_TRUE on success. / static int registerNatives(JNIEnv* env) { if (!registerNativeMethods(env, classPathName, methods, sizeof(methods) / sizeof(methods[0]))) { return JNI_FALSE; }
return JNI_TRUE; }
// ----------------------------------------------------------------------------
/ * This is called by the VM when the shared library is first loaded. /
typedef union { JNIEnv env; void venv; } UnionJNIEnvToVoid;
jint JNI_OnLoad(JavaVM vm, void /reserved/) { UnionJNIEnvToVoid uenv; uenv.venv = NULL; jint result = -1; JNIEnv* env = NULL;
ALOGI("JNI_OnLoad");
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed");
goto bail;
}
env = uenv.env;
if (registerNatives(env) != JNI_TRUE) {
ALOGE("ERROR: registerNatives failed");
goto bail;
}
result = JNI_VERSION_1_4;
bail: return result; } ```
SimpleJNI.java 的內容如下:
```java
package com.example.android.simplejni;
import android.app.Activity; import android.os.Bundle; import android.widget.TextView;
public class SimpleJNI extends Activity { /* Called when the activity is first created. / @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv = new TextView(this); int sum = Native.add(2, 3); tv.setText("2 + 3 = " + Integer.toString(sum)); setContentView(tv); } }
class Native { static { // The runtime will add "lib" on the front and ".o" on the end of // the name supplied to loadLibrary. System.loadLibrary("simplejni"); }
static native int add(int a, int b);
}
```
最外面的 Android.mk 的內容如下:
```Makefile TOP_LOCAL_PATH:= $(call my-dir)
Build activity
LOCAL_PATH:= $(TOP_LOCAL_PATH) include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := JNIApp
LOCAL_JNI_SHARED_LIBRARIES := myjnilib
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_SDK_VERSION := current
LOCAL_DEX_PREOPT := false
LOCAL_PRODUCT_MODULE := true
include $(BUILD_PACKAGE)
============================================================
Also build all of the sub-targets under this one: the shared library.
include $(call all-makefiles-under,$(LOCAL_PATH)) ```
AndroidManifest.xml 的內容如下:
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.simplejni">
<application android:label="Simple JNI">
<activity android:name="SimpleJNI">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
最後在 device/Jelly/Rice14/Rice14.mk
中新增:
Makefile
PRODUCT_PACKAGES += helloworld \
JNIApp \
編譯並執行虛擬機器就可以看到 JNIApp 了:
JNIApp 連結自定義庫
我們這裡嘗試修改 JNIApp,讓其引用到我們的 libmymath 庫。
修改 JNIApp/jni/Android.mk:
```Makefile
新增以下內容
LOCAL_SHARED_LIBRARIES := libmymath ```
修改 JNIApp/jni/native.cpp:
```c++
include "my_math.h"
static jint add(JNIEnv /env/, jobject /thiz*/, jint a, jint b) { int result = a + b; result = my_add(result, result); ALOGI("%d + %d = %d", a, b, result); return result; }
```
然後編譯系統,發現報以下錯誤:
bash
error: myjnilib (native:ndk:none:none) should not link to libmymath (native:platform)
可以看出是編譯平臺不一致導致的,修改 JNIApp/jni/Android.mk:
```Makefile
下面這行註釋掉即可
LOCAL_SDK_VERSION := current
```
最後重新編譯,執行虛擬機器即可
參考資料
- android系統原始碼中新增app原始碼(原始碼部署移植)
- AOSP 預置 APP
- Android Framework 常見解決方案(15)android內建可解除安裝APP整合方案
- Android Framework 常見解決方案(02)android系統級APP整合方案
- 在AOSP編譯時,新增預編譯apk
- Android系統預製可自由解除安裝apk
- Soong Modules Reference
- Jetpack太香了,系統App也想用,怎麼辦?
- Android.bp 檔案中引入aar、jar、so庫正確編譯方法(值得收藏)
- Jetpack太香了,系統App也想用,怎麼辦?
- AOSP: Creating a System Application