學得懂的 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