作者:Jingle_zhang

第三方App使用Jetpack等开源框架非常流行,在Gradle文件简单指定即可。然而ROM内置的系统App在源码环境下进行开发,与第三方App脱节严重,采用开源框架的情况并不常见。但如果系统App也集成了Jetpack或第三方框架,开发效率则会大大提高。

前言

系统App开发者,很少采用Jetpack 以及第三方框架的原因主要有几点:

  1. 导入麻烦:有的框架过于庞大,可能依赖的库比较多,编译文件的构建比较繁琐,没有gradle那么智能

  2. 功能单一:系统App注重功能性,业务逻辑较少,依赖庞大库文件的场景不多

  3. license风险:引用第三方框架的话,需要特别声明license ,会尽量避免采用

但对于功能复杂,架构庞大的系统App而言,集成第三方框架显得尤为必要。比如Android系统里最核心的App SystemUI,就采用了知名的DI框架Dagger2 。Dagger2的引入使得功能庞杂的SystemUI管理各个依赖模块变得游刃有余。

SystemUI将Dagger2集成的方式给了我启发,探索和总结了Android 源码中如何配置Jetpack 以及第三方库,希望能够帮到大家。

源码编译说明

与Gradle不同,源码环境里的编译构建都是配置在.mk或者.bp文件里的,配置起来较为繁琐。

.bp文件::Android.bp是用来替换Android.mk的配置文件,它使用Blueprint框架来解析。Blueprint是生成、解析Android.bp的工具,是Soong的一部分。Soong则是专为Android编译而设计的工具,Blueprint只是解析文件的形式,而Soong则解释内容的含义,最终转换成Ninja文件。下文bp 就是指.bp的文件

**注意:**以下基于Android 11上进行的演示,Android 10及之前部分Jetpack框架没有集成进源码,需留意

gradle切换到bp

gradle和bp的对比

看一个使用aar和注解库的例子。

看一个AndroidStudio(以下简称AS)下build.gradle 文件里包的导入代码:

dependencies {
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    //room
    def room_version = "2.3.0"
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
}

ROM环境里的编译依赖.bp 配置如下:

android_app {
    ......
    static_libs: [        "androidx.appcompat_appcompat",        "com.google.android.material_material",        "androidx-constraintlayout_constraintlayout",        "androidx.room_room-runtime",    ],
    plugins: ["androidx.room_room-compiler-plugin"],
    ......
}

导入关键字的差异

依赖文件里的导入关键字:

在AS和AOSP里面导入包的关键字有些差异,又分为两种情况。

build.gradle .bp
代码库 implementation static_libs
注解使用的库 annotationProcessor plugins

引入库文件(libs):比较常见。引入的方式有多种。下文会讲具体的几种方式。

引入注解库:比较流行,源码中使用比较繁琐,下文会重点讲解。

库文件的导入规则

眼尖的同学已经看出规律了

如:implementation ‘androidx.appcompat:appcompat:1.2.0’

bp 文件中:androidx.appcompat_appcompat,将“:” 改为 “”即可,不需要加版本号。其实就是group 与 name 中间用“”连接,基本上符合上述规则,当然也有特殊

注解库的导入规则

如今框架流行注解编程。

gradle 配置:annotationProcessor “androidx.room:room-compiler:$room_version”

bp 中就需要使用到plugins,对应配置plugins: [“androidx.room_room-compiler-plugin”]

根据jar 包的规则,那plugin 命名应该是“:” 改为 ”_" version+“-plugin” 。

SystemUI 使用Dagger2配置 plugins: [“dagger2-compiler-2.19”],所以命名规则并不是上文猜测的那样。

那如何确定Jetpack框架的名称呢?

确定Jetpack框架的名称

源码编译,所有的内容和都在源码中,都需要在源码环境中寻找。

以Room 为例

在prebuilts/sdk/current/androidx/Android.bp 配置了引入jar包 中有如下配置

android_library {
    name: "androidx.room_room-runtime",//名称
    ......
    manifest: "manifests/androidx.room_room-runtime/AndroidManifest.xml",//配置manifast
    static_libs: [//两个room库文件,三个依赖的库文件
        "androidx.room_room-runtime-nodeps",
        "androidx.room_room-common",
        "androidx.sqlite_sqlite-framework",
        "androidx.sqlite_sqlite",
        "androidx.arch.core_core-runtime",
    ],
}

插件配置在prebuilts/sdk/current/androidx/JavaPlugins.bp

java_plugin {
    name: "androidx.room_room-compiler-plugin",//名称
    static_libs: [//1个room库文件,1个依赖的库文件
        "androidx.room_room-compiler",
        "kotlin-reflect"
    ],
    processor_class: "androidx.room.RoomProcessor",//需要指定处理的类
}

注意:AS 开发 并不需要配置 “processor_class”,我反编译了room-compiler,找到了RoomProcessor.java.(AS 为什么不需要指定,我这里我就不研究了)

看下图,META-INF/services/javax.annotation.processing.Processor 文件中配置了RoomProcessor.java(就按照这个文件配置就可以了)

Jetpack太香了,让开发效率提升了不少

如何确定源码中哪些jetpack 库可以使用呢?

在Android.bp 中搜索,或者看androidx目录下包含了什么

prebuilts/sdk/current/androidx/m2repository/androidx$ ls

Jetpack太香了,让开发效率提升了不少

导入第三方的开源框架

以上讲的是引入Jetpack相关jar包,其他常见的是否包含呢?如Glide,它是不属于androidx 的

第三方库,Android 源码中整理就不算好了,使用比较乱。下面我梳理下

导入下载的jar包

大家最常用的,把 jar 包 放到 libs,就可以了(当然,比较简单,与其他库关联较少可以采用此种方式)

java_import {
    name: "disklrucache-jar",
    jars: ["disklrucache-4.12.0.jar"],
    sdk_version: "current",
}
android_library_import {
   name: "glide-4.12.0",
   aars: ["glide-4.12.0.aar"],
   sdk_version: "current",
}
android_library_import {
   name: "gifdecoder-4.12.0",
   aars: ["gifdecoder-4.12.0.aar",],
   sdk_version: "current",
}
android_library_import {
   name: "exifinterface-1.2.0",
   aars: ["exifinterface-1.2.0.aar",],
   sdk_version: "current",
}

android_app {
    ......
    static_libs: [
        "disklrucache-jar",
        "glide-4.12.0",
        "gifdecoder-4.12.0",
        "exifinterface-1.2.0"
    ],
}

导入AOSP内置的jar包

常用第三方放在了prebuilts/tools/common/m2/repository/下面包含了很多库文件,如Glide,Okhttp,但比较尴尬的是,.bp文件并没有写好。应用需要自己编写,编写方式可以参考上文。

以后google应该会把 external 下 的整合到这个里面,可以关注下prebuilts/tools/common/m2/repository 中Android.bp文件的变化。

如:prebuilts/maven_repo/bumptech/Android.bp

java_import {
    name: "glide-prebuilt",
    jars: [
        "com/github/bumptech/glide/glide/4.8.0/glide-4.8.0.jar",
        "com/github/bumptech/glide/disklrucache/4.8.0/disklrucache-4.8.0.jar",
        "com/github/bumptech/glide/gifdecoder/4.8.0/gifdecoder-4.8.0.jar",
    ],
    jetifier: true,
    notice: "LICENSE",
}

Android.bp 直接用"glide"了

static_libs: [
        "glide-prebuilt"
 ],

导入jar包源码

external 下面 很多第三方库的源码,如Glide的源码,目录为external/glide/

android_library {
    name: "glide",
    srcs: [
        "library/src/**/*.java",
        "third_party/disklrucache/src/**/*.java",
        "third_party/gif_decoder/src/**/*.java",
        "third_party/gif_encoder/src/**/*.java",
    ],
    manifest: "library/src/main/AndroidManifest.xml",
    libs: [
        "android-support-core-ui",
        "android-support-compat",
        "volley",
    ],
    static_libs: [
        "android-support-fragment",
    ],
    sdk_version: "current",
}
复制代码

App 的Android.bp 直接用"glide"了

static_libs: [
        "glide"
 ],

以上三种方式都是引入 Android 中源码存在的。不存在怎么办,Android源码 不像 AS,连上网,配置下版本号就可以下载。

内置新的Jetpack框架

引入第三方库文件方式,方式一:aar包导入。就可以。但这里不讨论,找些复杂的,包含annotationProcessor(bp 中的plugin) 。Hilt 是 Google 相对较新的框架。

Hilt基于Dagger2开发,又针对Android进行了专属的DI优化。

所以在导入Dagger2和它的依赖文件之外还需要导入Hilt专属的一堆库和依赖文件。

1. 获取框架的库文件

一般来说AS里导入完毕的目录下即可获取到对应的库文件,路径一般在 :C:\Users\xxx.gradle\caches\modules-2\files-2.1\com.google.dagger\hilt-android

2. 确定额外的依赖文件

为什么需要额外的依赖文件?

完全依赖AS开发可能不知道,导入的包的同时可能引入其他的包。

如Hilt的是在dagger2基础上开发,当然会引入Dagger2,

使用注解,需要javax.annotation包。

Dagger2,javax.annotation 在Gradle 自动下载好的,非项目中明确配置的,我们称之为依赖包。

使用Gradle 自动下载,都会有pom 文件。“dependency”,表示需要依赖的jar 包,还包含了版本号等

如:hilt-android-2.28-alpha.pom

`<dependency>`
  `<groupId>com.google.dagger</groupId>`
  `<artifactId>dagger</artifactId>` //依赖的dagger2
  `<version>2.28</version>`//dagger2的版本
`</dependency>`
`<dependency>`
  `<groupId>com.google.dagger</groupId>` 
  `<artifactId>dagger-lint-aar</artifactId>`
  `<version>2.28</version>`
`</dependency>`
`<dependency>`
  `<groupId>com.google.code.findbugs</groupId>`
  `<artifactId>jsr305</artifactId>`
  `<version>3.0.1</version>`
`</dependency>`
......

3. 导入需要的依赖文件

比如SystemUI,已经导入了一些文件,只要导入剩余的文件即可。

一般常用的 源码中都是存在的,决定copy 之前,可以看下先源码中是否存在,存在可以考虑使用。

当然也有例外,如Hilt 我依赖的是源码中dagger2是2.19 版本,编译中报错,没有找到dagger2 中的class,反编译jar确实不存在,使用2.28 的dagger 版本,问题就解决了。所以说可能存在库文件版本较老的情况。

以下就是新增的文件夹,其中manifests 后文中有讲。

    manifests/
    repository/com/google/dagger/dagger-compiler/2.28/
    repository/com/google/dagger/dagger-producers/2.28/
    repository/com/google/dagger/dagger-spi/2.28/
    repository/com/google/dagger/dagger/2.28/
    repository/com/google/dagger/hilt-android-compiler/
    repository/com/google/dagger/hilt-android/

4. 编写最终的bp文件

这一步就是把依赖的包,关联起来,根据上文的 pom 文件。

java_import {
name: "dagger2-2.28",
jars: ["repository/com/google/dagger/dagger/2.28/dagger-2.28.jar"],
host_supported: true,
}
java_import_host {
name: "dagger2-compiler-2.28-import",
jars: [
"repository/com/google/dagger/dagger-compiler/2.28/dagger-compiler-2.28.jar",
"repository/com/google/dagger/dagger-producers/2.28/dagger-producers-2.28.jar",
"repository/com/google/dagger/dagger-spi/2.28/dagger-spi-2.28.jar",
"repository/com/google/dagger/dagger/2.28/dagger-2.28.jar",
"repository/com/google/guava/guava/25.1-jre/guava-25.1-jre.jar",
"repository/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar",
"repository/com/google/dagger/dagger-google-java-format/1.6/google-java-format-1.6-all-deps.jar",
],
}
java_plugin {
    name: "dagger2-compiler-2.28",
    static_libs: [
        "dagger2-compiler-2.28-import",
        "jsr330",
    ],
    processor_class: "dagger.internal.codegen.ComponentProcessor",
    generates_api: true,
}
android_library_import {
    name: "hilt-2.82-nodeps",
    aars: ["repository/com/google/dagger/hilt-android/2.28-alpha/hilt-android-2.28-alpha.aar"],
    sdk_version: "current",
    apex_available: [
        "//apex_available:platform",
        "//apex_available:anyapex",
    ],
    min_sdk_version: "14",
    static_libs: [
        "dagger2-2.28",
        "jsr305",
        "androidx.activity_activity",
        "androidx.annotation_annotation",
        "androidx.fragment_fragment",
    ],
}
android_library {
    name: "hilt-2.82",
    manifest: "manifests/dagger.hilt.android/AndroidManifest.xml",
    static_libs: [
        "hilt-2.82-nodeps",
        "dagger2-2.28"
    ],
    ......
}
java_import_host {
    name: "hilt-compiler-2.82-import",
    jars: [
        "repository/com/google/dagger/dagger-compiler/2.28/dagger-compiler-2.28.jar",
        "repository/com/google/dagger/dagger-producers/2.28/dagger-producers-2.28.jar",
        "repository/com/google/dagger/dagger-spi/2.28/dagger-spi-2.28.jar",
        "repository/com/google/dagger/dagger/2.28/dagger-2.28.jar",
        "repository/com/google/guava/guava/25.1-jre/guava-25.1-jre.jar",
        "repository/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar",
        "repository/com/google/dagger/dagger-google-java-format/1.6/google-java-format-1.6-all-deps.jar",
        "repository/com/google/dagger/hilt-android-compiler/2.28-alpha/hilt-android-compiler-2.28-alpha.jar",
        "repository/javax/inject/javax.inject/1/javax.inject-1.jar"
    ],
}

Jetpack太香了,让开发效率提升了不少

好吧,看到上图我傻眼了,11个。下文代码我只贴了一个,需要写11个,其他省略。

java_plugin {
    name: "hilt-compiler-2.82",
    static_libs: [
        "hilt-compiler-2.82-import",
        "jsr330",
    ],
    processor_class: "dagger.hilt.processor.internal.root.RootProcessor",
    generates_api: true,
}
    `static_libs: [`
        `"androidx-constraintlayout_constraintlayout",`
        `"androidx.appcompat_appcompat",`
        `"com.google.android.material_material",`
        `"androidx.room_room-runtime",`
        `"androidx.lifecycle_lifecycle-viewmodel",`
        `"androidx.lifecycle_lifecycle-livedata",`
        `"hilt-2.82",`
        `"jsr330"`
    `],`
    `plugins: ["androidx.room_room-compiler-plugin",`
              `"hilt-compiler-2.82",`
              `"hilt-compiler-2.82-UninstallModulesProcessor",`
              `"hilt-compiler-2.82-TestRootProcessor",`
              `"hilt-compiler-2.82-DefineComponentProcessor",`
              `"hilt-compiler-2.82-BindValueProcessor",`
              `"hilt-compiler-2.82-CustomTestApplicationProcessor",`
              `"hilt-compiler-2.82-AndroidEntryPointProcessor",`
              `"hilt-compiler-2.82-AggregatedDepsProcessor",`
              `"hilt-compiler-2.82-OriginatingElementProcessor",`
              `"hilt-compiler-2.82-AliasOfProcessor",`
              `"hilt-compiler-2.82-GeneratesRootInputProcessor",`
             `],`

public class MainActivity extends AppCompatActivity { ^ Expected @AndroidEntryPoint to have a value. Did you forget to apply the Gradle Plugin? [Hilt] Processing did not complete. See error above for details.

public class MainFragment extends BaseFragment { ^ Expected @AndroidEntryPoint to have a value. Did you forget to apply the Gradle Plugin? [Hilt] Processing did not complete. See error above for details.

public class AppApplication extends Application { ^ Expected @HiltAndroidApp to have a value. Did you forget to apply the Gradle Plugin? [Hilt] Processing did not complete. See error above for details.

虽然Hilt引入失败,但是整个过程我觉得有必要分享一下,给大家一些导入新框架的参考。

源码环境里集成开源框架的流程

Jetpack太香了,让开发效率提升了不少

常用开源框架的对照表

build.gradle Android.bp AOSP源码位置
androidx.appcompat:appcompat androidx.appcompat_appcompat /sdk/current/androidx/Android.bp
androidx.core:core androidx.core_core prebuilts/sdk/current/androidx/Android.bp
com.google.android.material:material com.google.android.material_material prebuilts/sdk/current/extras/material-design-x/Android.bp
androidx.constraintlayout:constraintlayout androidx-constraintlayout_constraintlayout prebuilts/sdk/current/extras/constraint-layout-x/Android.bp
androidx.lifecycle:lifecycle-livedata androidx.lifecycle_lifecycle-livedata prebuilts/sdk/current/androidx/Android.bp
androidx.lifecycle:lifecycle-viewmodel androidx.lifecycle_lifecycle-viewmodel prebuilts/sdk/current/androidx/Android.bp
androidx.recyclerview:recyclerview androidx.recyclerview_recyclerview prebuilts/sdk/current/androidx/Android.bp
androidx.annotation:annotation androidx.annotation_annotation prebuilts/sdk/current/androidx/Android.bp
androidx.viewpager2:viewpager2 androidx.viewpager2_viewpager2 prebuilts/sdk/current/androidx/Android.bp
androidx.room:room-runtime androidx.room_room-runtime prebuilts/sdk/current/androidx/Android.bp
glide glide-prebuilt prebuilts/maven_repo/bumptech/Android.bp
gson gson-prebuilt-jar prebuilts/tools/common/m2/Android.bp
Robolectric相关 Robolectric相关 prebuilts/tools/common/m2/robolectric.bp

经验总结

1、build.gradle 需要配置 额外插件的,如hilt、databinding viewbinding 不建议使用源码编译。

2、建议使用 AOSP 源码 中 bp 已经配置好的。这样就可以直接使用了。

3、jetpack 包引入或者androidx 引入,建议先prebuilts/sdk/current/androidx 下寻找配置好的bp 文件

4、非androidx ,建议先在prebuilts/tools/common/m2下寻找寻找配置好的bp 文件

5、文章中的例子都是prebuilts目录下配置,项目中使用,也可以配置在项目中,都是可以的。