给前端开发者的 uni-app x 离线打包 Android 详解

4,626 阅读10分钟

重要的事情只说一次: 首先查看官方文档

本文也是基于官方文档,同时本文对笔者认为官方文档描述不清的地方进行了修订与增幅。

若尚有不明或描述不当之处欢迎留言反馈。

uni-app x demo:uniappx-offline-demo

android template project:uniappx-offline-for-android-template

准备

本文档基于以下工具:

  • HBuilder X:4.24

  • Android Studio: Koala | 2024.1.1 Patch 1,以下简称为:AS

  • Gradle:8.7

  • Android Gradle Plugin:8.5.1,简称: AGP

  • Kotlin:1.9.0

新建 Android 项目

打开 AS,点击顶部的 New Project –> Phone And Tablet –> No Activity,如下图:

0.new-project.png.png

点击 Next 进入项目配置界面:

1.configure-new-project.png

  1. Name:取一个高大上或随意的项目名称,
  2. Package name:项目的包名,自己随意哈,
  3. Save location:项目存储路径,自己随意哈,
  4. Language:项目开发语言,(必须)选择 Kotlin
  5. Minumum SDK:uni-app x 最低支持的版本为 API 21,所以需要选择 API 21 及以上的版本,
  6. Build configuration language 选择 Kotlin DSL(build.gradle.kts)

最后,点击 Finish,新建完成后,项目结构如下图:

2.new-project-structure.png

其中:

  • app 是主模块,它下面的 build.gradle.kts 是主模块的编译配置文件,主要包含子项目构建所使用的任务,
  • gradle 文件夹下面的 gradle-wrapper.properties 文件中配置了项目使用的 Gradle 版本,其中的 lib.versions.toml 是项目使用的第三方库的配置信息,
  • 接下来的 build.gradle.kts 是项目级别的编译配置文件,主要包含项目构建所使用的任务
  • 接下来的 gradle.properties 是项目级别的 Gradle 的配置文件,
  • 最下面的 settings.gradle.kts 是项目级别的配置文件,主要用于告诉 Gradle 如何组织与处理项目和子项目。
  1. 以上补充了官方文档没有新建 Android 项目的空白,
  2. 接下来的文档基于官方文档进行修订。

新建 uniappx 模块

点击 File --> New --> New Module –> Android Library,如下图:

3.new-uniappx-module.png

  • Module name:建议设置为 uniappx
  • Package name:自己随意哈,
  • Language:必须 Kotlin
  • Bytecode Level:字节码级别,默认即可,
  • Minumum SDK:uni-app x 最低支持的版本为 API 21,所以需要选择 API 21 及以上的版本,
  • Build configuration language 建议选择 Groovy DSL(build.gradle)
    • 新建项目时使用的是 Kotlin DSL(build.gradle.kts),其实没有太大区别,笔者比较喜欢 Kotlin DSL
    • 其实也可以选择 Kotlin DSL(build.gradle.kts),但这里我们跟随官方文档。

最后,点击 Finish,新建完成后,项目结构如下图:

4.uniapx-module-structure.png

配置 uniappx 模块

基础库配置

如下图在项目根目录下新建 libs 目录:

5.new-libs-directory.png

然后,将以下共 19aar 拷贝至 libs 目录下:

SDK官方下载地址

  1. uts-runtime-release.aar
  2. android-gif-drawable-1.2.28.aar
  3. app-common-release.aar
  4. app-runtime-release.aar
  5. breakpad-build-release.aar
  6. dcloud-layout-release.aar
  7. framework-release.aar
  8. uni-exit-release.aar
  9. uni-getAccessibilityInfo-release.aar
  10. uni-getAppAuthorizeSetting-release.aar
  11. uni-getAppBaseInfo-release.aar
  12. uni-getSystemSetting-release.aar
  13. uni-openAppAuthorizeSetting-release.aar
  14. uni-prompt-release.aar
  15. uni-storage-release.aar
  16. uni-getDeviceInfo-release.aar
  17. uni-getSystemInfo-release.aar
  18. uni-rpx2px-release.aar
  19. uni-theme-release.aar

拷贝完成后如下图:

6.copy-libs.png

由于引入了 kux-request 插件,所以上图中多了一个 uni-network-release.aar

修改 uniappx 模块和主模块(app)的编译配置文件

修改 uniappx 模块的 build.gradle

  • 将以下依赖信息添加到 uniappx 模块 build.gradledependencies 闭包中:

    compileOnly fileTree(include: ['*.aar'], dir: '../libs')
    
    implementation("androidx.core:core-ktx:1.8.0")
    implementation("androidx.recyclerview:recyclerview:1.0.0")
    implementation("androidx.appcompat:appcompat:1.0.0")
    implementation("androidx.exifinterface:exifinterface:1.3.6")
    implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0@aar")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    implementation("androidx.viewpager2:viewpager2:1.1.0-beta02")
    implementation("androidx.annotation:annotation:1.1.0")
    implementation("androidx.core:core:1.1.0")
    implementation("com.google.android.material:material:1.4.0")
    implementation("com.alibaba:fastjson:1.2.83")
    implementation("com.facebook.fresco:fresco:3.1.3")
    implementation("com.facebook.fresco:middleware:3.1.3")
    implementation("com.facebook.fresco:animated-gif:3.1.3")
    implementation("com.facebook.fresco:webpsupport:3.1.3")
    implementation("com.facebook.fresco:animated-webp:3.1.3")
    implementation("com.github.bumptech.glide:glide:4.9.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
    implementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.10")
    implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.10")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
    implementation("com.squareup.okhttp3:okhttp:3.12.12")
    implementation("com.github.getActivity:XXPermissions:18.0")
    
  • 添加 aaptOptions 配置

    aaptOptions 配置添加到 android 闭包中:

    aaptOptions {
        additionalParameters '--auto-add-overlay'
        ignoreAssetsPattern '!.svn:!.git:.*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~'
    }
    

修改主模块(app)的 build.gradle.kts

aaptOptions 配置添加到 android 闭包中:

// 可能会提示 aaptOptions 已废弃,可以使用下面的 androidResources 代替,也可以都配置上
aaptOptions {
    additionalParameters += "--auto-add-overlay"
    ignoreAssetsPattern = "!.svn:!.git:.*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~"
}

androidResources {
    additionalParameters += "--auto-add-overlay"
    ignoreAssetsPattern = "!.svn:!.git:.*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~"
}

配置完成后如下图:

7.configure-dependencies-and-aaptOptions.png

配置 Gradle 插件

与创建 libs 目录类似,在项目根目录下创建 plugins 目录,然后拷贝以下共 2jar 文件至 plugins 目录:

  1. uts-kotlin-compiler-plugin-0.0.1.jar
  2. uts-kotlin-gradle-plugin-0.0.1.jar

拷贝完成如下图:

8.copy-plugins.png

然后,修改项目根目录的 build.gradle.kts 文件,在顶部添加 Gradle 插件依赖:

buildscript {
    dependencies {
        classpath(files("plugins/uts-kotlin-compiler-plugin-0.0.1.jar"))
        classpath(files("plugins/uts-kotlin-gradle-plugin-0.0.1.jar"))
    }
}

最后,修改 uniappx 模块的 build.gradle 文件,在顶部的 plugins 闭包中添加以下代码:

id 'io.dcloud.uts.kotlin'

注意:io.dcloud.uts.kotlin 仅需要配置到 uniappx 模块和 android uts 插件模块中。其他子项目不需要配置。

配置完成后如下图:

9.configure-gradle-plugin.png

修改项目的 settings.gradle.kts 和 gradle.properties 文件

修改 settings.gradle.kts

在项目根目录下的 settings.gradle.kts 中添加 jitpackmaven 仓库地址和本地 Gradle 插件的路径配置。代码如下:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()

        // for uni-app x
        maven {
            url = uri("https://jitpack.io")
        }

        flatDir {
            dirs("./plugins/")
        }
    }
}

修改 gradle.properties

在项目根目录下的 gradle.properties 中添加如下内容:

android.useAndroidX=true
android.enableJetifier=true

修改完成后如下图:

10.modify-settings.gradle-and-gradle.properties.png

修改 AndroidManifest.xml 文件

修改主模块(app)的 AndroidManifest.xml

  • 添加 activity

    将以下代码拷贝至 application 节点下:

    <activity
    	android:name="io.dcloud.uniapp.UniAppActivity"
    	android:configChanges="orientation|keyboard|keyboardHidden|smallestScreenSize|screenLayout|screenSize|mcc|mnc|fontScale|navigation|uiMode"
    	android:exported="true"
    	android:label="@string/app_name"
    	android:screenOrientation="portrait"
    	android:theme="@style/UniAppX.Activity.DefaultTheme"
    	android:windowSoftInputMode="adjustResize"
    	tools:replace="android:label,android:exported,android:theme,android:configChanges,android:windowSoftInputMode,android:screenOrientation">
    
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    
    </activity>
    
  • 添加或修改 application

    application 节点的 android:name 修改为 io.dcloud.uniapp.UniApplication

    注意:如果需要自定义 application,必须继承自 UniApplication

修改 uniappx 模块的 AndroidManifest.xml

  • 添加 appid

    application 节点(如没有 application 节点可以自行创建)下添加 meta-data 节点,并将 android:name 设置为 DCLOUD_UNI_APPIDandroid:value 设置为你自己应用的 appid

    <application>
        <!--   value 替换成你应用的appid    -->
        <meta-data
    				android:name="DCLOUD_UNI_APPID"
    				android:value="替换成你应用的appid" />
    </application>
    

    注意:如果 uni-app x 项目(此处指 HBuidler X 中的项目)根目录下有 AndroidManifest.xml 文件,你需要按照 xml 文件的结构将内容拷贝到 uniappx 模块的 AndroidManifest.xml 中。

修改完成后如下图:

11.configure-android-manifest.png

拷贝资源文件

如下图所示,在 uniappx 模块的 main 目录上右键点击 –> New –> Folder –> AssetFolder,在接下来弹出的界面中直接点击 Finish

12.new-assets-folder.png

然后,在新建的 assets 文件夹下再新建 apps 文件夹,此过程与新建 libs 目录类似,

接下来,在 HBulder X 中导出资源文件,选择项目,然后点击:发行 –> 原生App-本地打包 –> 生成本地打包App资源

13.hbulderx-gen-local-resources.png

导出成功之后会在项目的 unpackage/resources 目录下生成资源文件:

14.hbulderx-gened-local-resources.png

最后,将 app-android 目录下与 appid 对应的目录拷贝到新建的 assets/apps 文件夹下:

15.copy-gen-resoures-to-assets.png

注意:apps 文件夹下的 __UNI__appid 必须与 AndroidManifest.xmlDCLOUD_UNI_APPID 保持一致。

拷贝 kt 文件

需要将 unkackage/resource/app-android/uniappx/app-android/src/ 目录下的所有文件拷贝到 uniappx 模块的 src/main/java 下:

16.copy-kt-files.png

新建模块时的包名可以删除。

添加到主模块(app)

最后,将 uniappx 模块添加到主模块中,修改主模块的 build.gradle.kts 文件,在 dependencies 闭包中添加如下代码:

implementation(fileTree(mapOf("include" to listOf("*.aar"), "dir" to "../libs")))
implementation(project(":uniappx"))

小结

至此,如果你的项目没有依赖第三方插件,官方的内置模块或者官方的扩展模块,就可以运行了。

配置 uts 插件

说明:每个插件都需要在 AS 中新建一个子模块。

本文以 kux-request 插件为例

新建 android uts 插件模块

与新建 uniappx 模块类似,点击 File --> New --> New Module –> Android Library,如下图:

17.new-android-uts-plugin-module.png

  • Module name:建议与 uts 插件名称保持一致,本文设置为 kux-request
  • Package name:自己随意哈,
  • Language:必须 Kotlin
  • Bytecode Level:字节码级别,默认即可,
  • Minumum SDK:uni-app x 最低支持的版本为 API 21,所以需要选择 API 21 及以上的版本,
  • Build configuration language 建议选择 Groovy DSL(build.gradle)
    • 新建项目时使用的是 Kotlin DSL(build.gradle.kts),其实没有太大区别,笔者比较喜欢 Kotlin DSL
    • 其实也可以选择 Kotlin DSL(build.gradle.kts),但这里我们跟随官方文档。

创建完成后,kux-request 模块的结构如下图所示:

18.kux-request-module-structure.png

修改 android uts 插件模块的 build.gradle

  • 添加 gradle 插件

    修改 kux-request 模块的 build.gradle 文件,在顶部的 plugins 闭包中添加以下代码:

    id 'io.dcloud.uts.kotlin'
    
  • 添加依赖

    将以下依赖信息添加到 kux-request 模块 build.gradledependencies 闭包中:

    compileOnly fileTree(include: ['*.aar'], dir: '../libs')
    compileOnly fileTree(include: ['*.aar'], dir: './libs')
    compileOnly "com.alibaba:fastjson:1.2.83"
    compileOnly "androidx.core:core-ktx:1.10.1"
    compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
    compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
    

修改完成后如下图:

19.configure-kux-request.png

说明:如果插件依赖其他 uts 插件,建议优先将依赖的 uts插件 配置成 android uts插件模块。然后在当前 android uts插件模块build.gradle 中添加依赖的插件模块:

implementation project(':uts-依赖的android uts插件模块')

根据config.json配置应用

说明:kux-request 插件的 config.json 文件没有特殊配置,本文忽略,如有需要可以参考官方文档,官方文档本节的介绍比较全面。

复制资源

说明:本节与 uniappx 模块的资源拷贝类似,可以回顾参考 uniappx 模块的步骤,同时参考官方文档

添加到 uniappx 模块

注意:本节与官方文档不一致

android uts 插件模块的依赖添加到 uniappx 模块的 build.gradledependencies 闭包中:

// kux-request 为示例,实际中你需要将 kux-request 替换成自己的模块名称
implementation project(':kux-request')

添加完成后如下图所示:

20.add-kux-request-to-uniappx.png

小结

配置 uts 插件至此完成。

配置内置模块

  1. 官方文档
  2. App端支持的内置模块列表:根据此文档查询 uni.xxxx API 对应哪个内置模块。

本文以 video 组件为例

App端支持的内置模块列表 中可以得到 video 组件包含在 uni-video 模块中,参考官方的 uni-video 模块的文档进行配置。

拷贝本地依赖库

将以下共 3aar 拷贝至项目根目录的 libs 目录下:

  1. uni-video-release.aar
  2. ijkplayer.aar
  3. videoplayer.aar

拷贝完成后如下图:

21.copy-video-module-aar.png

线上依赖库

注意:由于配置 uniappx 模块时已包含线上依赖库,所以 uni-video 模块不需要再配置,其他模块视情况而定。

组件注册

将以下代码添加到主模块(app)的 build.gradle.ktsandroid -> defaultConfig 闭包中,详见 根据configjson配置应用

buildConfigField("String", "UTSRegisterComponents", "\"[{\\\"name\\\":\\\"video\\\",\\\"class\\\":\\\"uts.sdk.modules.DCloudUniVideo.VideoComponent\\\"}]\"")

然后,可能还需要配置开启 buildConfig 功能,将以下代码添加到主模块(app)的 build.gradle.ktsandroid 闭包中:

buildFeatures {
    buildConfig = true
}

最后,修改主模块(app)的 AndroidManifest.xml 添加网络访问权限:

<uses-permission android:name="android.permission.INTERNET"/>

添加完成后如下图:

22.register-components-and-add-permission.png

小结

至此,uni-video 模块配置完成。

Bugs & FixBug

Bug1:组件注册与应用程序生命周期监听函数不生效

复现步骤

当你 根据configjson配置应用 中的 componentshooksClass 注册了 uts 组件和 uts 插件的应用程序生命周期监听函数后,又在主模块(app)的 build.gradle.ktsbuildType 闭包中添加了以下代码:

debug {
    applicationIdSuffix = ".debug"
}

添加完成后如下图:

23.bug1-add-applicaiton-id-suffix.png

此时,运行程序会发现 video 组件没有显示。

原因分析

这是因为组件没有注册,因为配置了 applicationIdSuffix 后程序的包名发生了变化,从原来的 com.guodong.uniappx.offline 变为了 com.guodong.uniappx.offline.debug,这将导致 context.getPackageName() API 返回修改后的包名,然而 BuildConfig.java 生成的包路径还是原来的 com.guodong.uniappx.offline,导致 UniSDKEngine 在初始化时根据 context.getPackageName() API 获取不到原来的包名而找不到 BuildConfig.java 文件,从而无法注册组件和应用程序生命周期监听函数,最终 video 组件无法显示。

问题修正

在主模块(app)中新建自定义 Application 类并继承 UniApplication,代码如下所示:

package com.guodong.uniappx.offline

import android.util.Log
import io.dcloud.uniapp.UniApplication
import io.dcloud.uniapp.UniSDKEngine
import io.dcloud.uniapp.ui.component.IComponent
import io.dcloud.uts.UTSAndroidHookProxy

class App : UniApplication() {

    private val TAG = "App"

    override fun onCreate() {
        super.onCreate()
        register()
    }

    private fun register() {
        try {
            registerComponents("video", "uts.sdk.modules.DCloudUniVideo.VideoComponent")
        } catch (e: Exception) {
            Log.e(TAG, "register: 无法注册 video 组件", e)
        }

        try {
            registerHooksClass("uts.sdk.modules.zlText.ZlTextHook")
        } catch (e: Exception) {
            Log.e(TAG, "register: 无法注册 ZlTextHook 生命周期监听函数", e)
        }
    }

    /**
     * "name": 对应 buildConfigField UTSRegisterComponents 配置中的 name
     * "className": 对应 buildConfigField UTSRegisterComponents 配置中的 class
     */
    @Throws(ClassNotFoundException::class)
    private fun registerComponents(name: String, className: String) {
        UniSDKEngine.registerUniComponent(name, Class.forName(className) as Class<out IComponent>)
    }

    /**
     * "className": 对应 buildConfigField UTSHooksClassArray 配置中的值
     */
    @Throws(ClassNotFoundException::class)
    private fun registerHooksClass(className: String) {
        // 注册应用程序生命周期监听函数
        val instance = Class.forName(className).newInstance()
        if (instance is UTSAndroidHookProxy) {
            instance.onCreate(this)
        }
    }
}

打开主模块(app)的 AndroidManifest.xml 文件,替换 application 标签的 android:name 为自定义的 Application 类:

- <application android:name="io.dcloud.uniapp.UniApplication"
+ <application android:name=".App"

修改完成后如下图:

24.bug1-fixed.png