跳至主要內容

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

guodongAndroid大约 11 分钟

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

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

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

uni-app x demo:uniappx-offline-demoopen in new window

android template project:uniappx-offline-for-android-templateopen in new window

准备

本文档基于以下工具:

  • 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,如下图:

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

  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,新建完成后,项目结构如下图:

其中:

  • 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,如下图:

  • 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,新建完成后,项目结构如下图:

配置 uniappx 模块

基础库配置

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

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

SDK官方下载地址open in new window

  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

拷贝完成后如下图:

由于引入了 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:*~"
}

配置完成后如下图:

配置 Gradle 插件

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

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

拷贝完成如下图:

然后,修改项目根目录的 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 插件模块中。其他子项目不需要配置。

配置完成后如下图:

修改项目的 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

修改完成后如下图:

修改 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 中。

修改完成后如下图:

拷贝资源文件

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

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

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

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

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

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

拷贝 kt 文件

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

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

添加到主模块(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,如下图:

  • 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 模块的结构如下图所示:

修改 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'
    

修改完成后如下图:

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

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

根据config.json配置应用

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

复制资源

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

添加到 uniappx 模块

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

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

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

添加完成后如下图所示:

小结

配置 uts 插件至此完成。

配置内置模块

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

本文以 video 组件为例

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

拷贝本地依赖库

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

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

拷贝完成后如下图:

线上依赖库

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

组件注册

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

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"/>

添加完成后如下图:

小结

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

Bugs & FixBug

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

复现步骤

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

debug {
    applicationIdSuffix = ".debug"
}

添加完成后如下图:

此时,运行程序会发现 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"

修改完成后如下图: