1. 如何新建一个体系 App 项目

运用 Android Studio 新建一个空项目 FirstSystemApp,包名设置为 com.yuandaima.firstsystemapp,语言选择 Java。后面为叙说便利称该项目为 as 项目。

接着在 Jelly/Rice14 目录下创立如下的目录和文件:

学得懂的 Android Framework 教程——玩转 AOSP 之系统 App 源码添加

接着将 as 项目中的 res 文件下的资源文件拷贝到 Jelly/Rice14/FirstSystemApp/res 中,把 as 项目中的 MainActivity.java 拷贝到 Jelly/Rice14/FirstSystemApp/src/com/yuandaima/firstsystemapp 中。

接着修正已增加的 AndroidManifest.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yuandaima.firstsystemapp">
    <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>
</manifest>

接着修正已增加的 Android.bp 文件:

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

# 增加以下内容
PRODUCT_PACKAGES += FirstSystemApp

接着编译体系,发动虚拟机,打开 app:

source build/envsetup.sh
lunch Rice14-eng
make -j16
emulator 

学得懂的 Android Framework 教程——玩转 AOSP 之系统 App 源码添加

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 中咱们增加了许多依靠:

    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 库的引进办法如下:

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 version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="androidx.recyclerview" >
    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="28" />
</manifest>

很古怪,并没有看到 RecyclerView 库的源码,也没有看到 aar 库文件。咱们接着看 Android.bp 中的依靠,其间一项是 androidx.recyclerview_recyclerview-nodeps,咱们在 Android.bp 中看一下它的引进办法:

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 目录下创立如下的文件和文件夹

学得懂的 Android Framework 教程——玩转 AOSP 之系统 App 源码添加

其间 MyCustomView.java 是一个用于演示的没有详细功用的自界说 View:

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 version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.yuandaima.firstsystemandroidlibrary">
</manifest>

Android.bp 的内容如下:

android_library  {
    name: "FirstSystemAndroidLibrary",
    srcs: ["src/**/*.java"],
    resource_dirs: ["res"],
    manifest: "AndroidManifest.xml",
    sdk_version: "current",
    product_specific: true,
    //依靠
    static_libs: ["androidx.appcompat_appcompat",],
    java_version: "1.7",
    installable: true,
}

接着修正咱们的 FirstSystemApp 项目

Android.bp 增加依靠如下:

android_library  {
    //......
    //依靠
    static_libs: ["androidx.appcompat_appcompat",
                 "com.google.android.material_material",
                 "androidx-constraintlayout_constraintlayout",
                 "FirstSystemAndroidLibrary"],
}

修正一下 MainActivity,在 App 里运用咱们的自界说 View:

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:

source build/envsetup.sh
lunch Rice14-eng
make -j16
emulator 

这样咱们的库就算引进完毕了。

Android 库以 aar 包办法引进

更多的时分 Android 库是以 aar 包的办法引进。

假定咱们的 SourceApp 需要引进 lottie 这个动画库。

首先咱们这儿下载好 lottie 库的 aar 打包文件。

device/Jelly/Rice14 目录下创立如下的目录结构:

liblottie/
├── Android.bp
└── lottie-5.2.0.aar

其间 Android.bp 的内容如下:

android_library_import {
    name: "lib-lottie",
    aars: ["lottie-5.2.0.aar"],
    sdk_version: "current",
}

然后咱们修正 FirstSystemApp 中的 Android.bp 引进这个库:


    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 目录下增加如下的文件与文件夹:

学得懂的 Android Framework 教程——玩转 AOSP 之系统 App 源码添加

jni/Android.mk 内容如下:

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 的内容如下:

/*
 * 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 <android/log.h>
#include <stdio.h>
#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 的内容如下:


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 的内容如下:

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 的内容如下:

<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 中增加:

PRODUCT_PACKAGES += helloworld \
    JNIApp \

编译并运行虚拟机就能够看到 JNIApp 了:

学得懂的 Android Framework 教程——玩转 AOSP 之系统 App 源码添加

JNIApp 链接自界说库

咱们这儿测验修正 JNIApp,让其引用到咱们的 libmymath 库。

修正 JNIApp/jni/Android.mk:

# 增加以下内容
LOCAL_SHARED_LIBRARIES := libmymath

修正 JNIApp/jni/native.cpp:

#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;
}

然后编译体系,发现报以下错误:

error: myjnilib (native:ndk:none:none) should not link to libmymath (native:platform)

能够看出是编译渠道不一致导致的,修正 JNIApp/jni/Android.mk:

# 下面这行注释掉即可
# 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