Aplicación de Android práctica en el extranjero

Autor de este artículo: Siu Mai

En la actualidad, es una operación común en la industria de Internet que varias empresas nacionales vayan al extranjero para generar ingresos a través de APP. En los últimos dos años, el autor ha estado desarrollando el cliente Android de la primera aplicación en el extranjero de Cloud Music. Este artículo comparte algunas experiencias en el desarrollo de aplicaciones en el extranjero.

Cuando nos hicimos a la mar por primera vez, resumimos todos los aspectos de la adaptación al entorno exterior, incluidos

  • Muchos módulos comunes en el cliente necesitan admitir entornos en el extranjero. incluir aquí

    • Confirme el grado de soporte de algunos servicios de terceros para entornos en el extranjero, como Yunxin y Shengwang SDK
    • Empaquetado de la versión en el extranjero de algunas funciones comunes de la aplicación, como iniciar sesión, cargar archivos, empujar, compartir
    • Autoexamen de las funciones subyacentes de la biblioteca, soporte para políticas de estantes y algunas configuraciones de recursos.

    Nuestro objetivo es mantener el marco técnico original tanto como sea posible para desarrollar nuevas aplicaciones y no cambiar el marco técnico debido a cambios en el entorno operativo.

  • Publicación de canales y formatos para aplicaciones Android. Las aplicaciones de Android en el extranjero se lanzan principalmente en Google Play, aquí necesitamos admitir adicionalmente el formato aab (paquete de aplicaciones de Android) para la publicación.

Diseño de aplicaciones en el extranjero

Capa de implementación de biblioteca básica en el extranjero

Para el módulo básico, seguimos el principio de diseño de separación de la implementación de la interfaz Tomando la biblioteca subyacente de carga de archivos como ejemplo, tendremos 3 módulos que finalmente se etiquetarán como aar:

  • uploader_interface proporciona varias interfaces relacionadas con la carga de archivos
  • uploader_module uploader_interface La implementación específica de cada interfaz del módulo, por ejemplo, los archivos se cargan a través de la interfaz CDN de la plataforma intermedia.
  • uploader_module_oversea también es la implementación específica de cada interfaz en el módulo uploader_interface.La lógica de implementación cambia de cargar directamente a la interfaz CDN a cargar primero a Amazon Cloud y luego sincronizar la información de carga de Amazon Cloud a CDN.

Gracias a los principios de diseño anteriores, solo necesitamos proporcionar la implementación en el extranjero correspondiente del módulo básico. La API del módulo de interfaz todavía se llama en el código comercial. De esta manera, algunos códigos comerciales que dependen de la capa subyacente se pueden reutilizar directamente y, en segundo lugar, los estudiantes de desarrollo no necesitan estar familiarizados con otro conjunto de bibliotecas subyacentes. API.

Verificación de cumplimiento de la biblioteca subyacente

海外 APP 在 Google Play 作为主要分发渠道的情况下,隐私政策可能和国内略有不同。而一些底层库可能包括了一些不合规的代码,这部分需要进行排查,一般来说,遵循下面 2 个原则就不容易出现问题:

  • 底层库代码里面没有违规的 API 调用,例如和热修复这种动态代码下发的。Google Play 不允许相关功能
  • 底层库的依赖里不要包含海外环境用不到的功能。例如一些之前全公司 APP 都通用的三方服务的SDK被集成在了某个底层库,虽然海外没有使用相关功能,但是这些 SDK 非常有可能因为包括了动态下发 so 而被检查出来。

Google Play 隐私政策可以参考

support.google.com/googleplay/…

底层库资源

另一方面,对于比较简单的底层逻辑,我们一般情况也不会对其做接口与实现拆分,但是底层有可能会使用一些通用的资源,例如文案、图标等。如果我们把这些值作为变量设置进去,一方面底层库的改动比较大,另一方面初始化时候的设置也非常的繁琐。这里我们可以利用 Android 自身的资源合并策略。

如上图,底层库里面定义的 key1 字符串,我们在上层定义同名的字符串 key2, 最终在打包的时候,资源合并会保留 key2。所以也需要我们在设计底层库的时候避免直接使用字符串硬编码,以免不能灵活支持海外应用。

aab 文件与 Play Store 分发

app bundle 格式

使用 app bundle 格式当下在 Google Play 进行分发是唯一选择。

我们使用

./gradlew :app:bundleRelease
复制代码

构建我们的 app bundle 文件上传至 Google Play 后台进行发布。

但是由于 aab 文件并不能直接安装在设备上,所以在日常的测试、回归阶段,我们仍然是安装 apk 文件来进行,流程如下图:

从理论上来说,apk测试回归没有什么问题,aab 也就没什么问题。但是在日常实践,我们可能会有一些 Gradle Plugin 的 task 在 hook 一些编译任务的时候,忽略了 aab 的情况,从而导致一些运行时的错误。针对这种情况,在正式的 aab 文件发布前,我们还是有必要对其做一个快速的走查。

Google 官方也提供了方法让我们安装 aab 文件到设备上,使用 bundletool 工具根据 aab 文件生成 apks 文件,然后使用 adb install-multiple 命令安装:

java -jar bundletool.jar build-apks --bundle=${FILE_NAME} --output=${target_apks}
unzip target_apks
cd splits
adb install-multiple bae-master.apk xx.apk
复制代码

这样测试回归流程则可以加上 aab,但是让 qa 同学每次使用脚本安装总也是个麻烦的事情,所以能否更彻底点呢?答案当然是可以的,既然可以通过 install-multiple 安装 apks 文件,那么 CI 流程上每次 aab 构建的时候,输出 aab 和 apks 2个产物,然后通过一个安装 apks 文件的 APP 进行安装。

我们可以通过 android.content.pm.PackageInstaller 这个 Android API 实现这个功能

代码如下:

val installer = InstallApp.application().packageManager.packageInstaller
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = installer.createSession(params)

val installSession = installer.openSession(sessionId)
apks.forEach {
    installSession.openWrite(it.hashCode().toString(), 0, -1)
        .use { out->
            FileInputStream(it).use {fin->
            val buffer = ByteArray(16384)
            var len: Int
            while (fin.read(buffer).also { len = it } != -1) {
                out.write(buffer, 0, len)
            }
        }
        installSession.fsync(out)
        installSession.close()
    }
}

val intent = Intent(InstallApp.application(), RetActivity::class.java)
intent.action = PACKAGE_INSTALLED_ACTION
val pendingIntent = PendingIntent.getActivity(InstallApp.application(), 0, intent, FLAG_MUTABLE)
val statusReceiver = pendingIntent.intentSender
installSession.commit(statusReceiver)
复制代码

安装结果我们可以通过 Intent 里面的 android.content.pm.extra.STATUS 获取。

这里我们就可以不适用脚本命令行,直接使用安装工具安装aab文件,app 的回归发布流程就比较完善了:

Google Play 签名

Android 应用通过 Google Play 发布的时候,还需要开启 Google Play 应用签名功能,具体的操作和规则可以参考 Play 管理中心文档:

support.google.com/googleplay/…

按照官方图示,Google Play 会把开发者上传的密钥重新签名为新的密钥进行发布。

最终 Google Play 控制台里面会显示最终的密钥指纹和上传密钥指纹:

Google Play 之所以设计这套看起来有点复杂的秘钥管理,是为了保障 APP 的签名安全。当我们的上传秘钥出现被盗取或者丢失的情况下,也只需要申请重新替换上传秘钥即可。 但是我们的 APP 在发布的时候,我们不仅需要在 Google Play 进行发布,还需要发布自己的 APK 渠道包。在后台升级密钥的时候,会有如下几个选项

如果使用默认的 Google Play 生成新的密钥,我们只能导出一个后缀名为 der 的证书,这个证书里面只包括了公钥,所以即使同 keystore 工具导出 jks 文件,也不能正常打包。所以我们需要选择 “从Java密钥库上传新的应用签名密钥”

这里还需要注意一点,选择新的密钥规则默认选择 Android T 及以上版本升级,且此选项默认收起。我们需要选择下面的 “所有Android版本的所有新安装”,否则无法达到最终目的。

所有我们最终签名流程如下图所示:

我们拥有 2 个打包签名文件,分别为 release.jks 和 store.jks,通过 Google 的 pepk.jar 工具把 Google Play 的签名换位 store.jks。最终在发布的时候:

  • aab 文件使用 release.jks 构建,上传后会重签为 store.jks 发布
  • release 渠道包的apk文件使用 store.jks 构建,这样 apk 和商店下载的 aab 文件签名才一致,才能算是同一个 APP

Google Play 发布问题

在使用 Google Play 发布的时候,如果我们使用了 uses-feature 声明功能的时候,最终在发布的时候,可能会导致最终发布后显示支持设备类型数为 0,这样用户将无法下载甚至无法在 Google Play上看到该版本。

我们需要在声明的地方添加上 android:required="false"即可。为了避免底层库和上层的定义有矛盾导致 AndroidManifest 合并出错,我们可以通过 Gradle 脚本修改合并后的 AndroidManifest 文件,把 reuqired 的值全部改为 true:

android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        def processManifest = output.getProcessManifestProvider().get()
        processManifest.doLast { task ->
            def outputDir = task.multiApkManifestOutputDirectory
            File outputDirectory
            if (outputDir instanceof File) {
                outputDirectory = outputDir
            } else {
                outputDirectory = outputDir.get().asFile
            }
            File manifestOutFile = file("$outputDirectory/AndroidManifest.xml")

            if (manifestOutFile.exists() && manifestOutFile.canRead() && manifestOutFile.canWrite()) {
                def manifestPath = manifestOutFile
                def xml = new XmlParser().parse(manifestPath)
                def androidSpace = new Namespace('http://schemas.android.com/apk/res/android', 'android')
                xml."uses-feature".each {it->
                    println it.attributes().get(androidSpace.name)
                    if (it.attributes()[androidSpace.name] == "android.hardware.camera.front" ||
                            it.attributes()[androidSpace.name] == 'android.hardware.camera.front.autofocus') {
                        it.attributes()[androidSpace.required] = false
                    }
                }
                PrintWriter pw = new PrintWriter(manifestPath)
                def content = XmlUtil.serialize(xml)
                println content
                pw.write(content)
                pw.close()
            }
        }
    }
}
复制代码

应用多语言

多语言工作流

提到应用出海,还有一个绕不开的话题就是应用多语言问题。 我们通过设置 Locale 来设置语言。并且在语言切换的时候重建 Activity:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
	config.locale = target
	res.updateConfiguration(config, res.displayMetrics)
	config.setLocale(target)
	context.createConfigurationContext(config)
} else {
	config.locale = target
	res.updateConfiguration(config, res.displayMetrics)
}
复制代码

具体多语言我们会从内部的多语言平台拉取打包后的xml文件,放到对应的文件夹下。应用在 Locale 修改后会自动选择对应语言的文件。例如英文目录为 /res/values-en ,印尼语为 /res/values-in。流程如下图:

随着出海APP增多及运营国家支持语种增多,上述简单的多语言导入流程也逐渐的不够使用,包括:

  • 语言较多,并且定义在代码内,每次新增语言配置都需要各个使用的地方(例如注册选择语言,设置切换语言等)修改代码。配置化程度比较低。一旦漏改,就会存在bug。
  • 从多语言平台下载文案并放入res文件夹里面的时候,需要有一个 values 文件夹作为默认语言文案,在开发阶段,我们从交互稿上看到并且录入的基本为中文,但是发布后的默认文案应该为英文。如果全程手动操作非常繁琐。

我们使用 Gradle 插件来解决这2个问题。

  • 每个应用支持的多语言类型通过配置文件定义,Gradle 插件根据配置文件内容生成语言信息的常量代码。
  • 在编译期添加一个自动拉取多语言的 task,注册在 pre${variant}Build task 之后。当 variant 属于 debug 的时候,res/values 里面放的为中文的xml文件。当 variant 属于 release 的时候,res/values 里面放的为英文的xml文件。

整个 language plugin 的工作如下:

其中,自动拉取插件在替换文案之前,还可以做一次预检查操作。防止因为翻译错误等原因导致编译报错。例如

  • 文案里面检查 1 转为 1 a %1 s 的时候,是否有字符缺失或者增加了空字符导致 String.format 出错
  • 文案里面存在 & 符号,需要修改为 &

多语言解耦

在 app 的日常维护中,时常会有多语言文案需要替换。在上述工作流中,非客户端开发在需要替换文案的时候,需要频繁的提问客户端开发需要替换的具体 key。这样无疑增加了需要沟通成本。我们还可以通过一些技术手段来减少这部分的耦合。 常见的文案的替换场景大概分为两类

  • 测试、走查阶段发现某些语种存在翻译缺失
  • 开新区增加新翻译的时候,某些语种的文案长度不合理需要精简 这两种场景,非开发角色不经过沟通并不知道具体的多语言 key 是什么。 针对上述两种情况,我们的多语言插件设计了两部分功能。

缺失文案检查及 mock 文案生成 多语言插件在文案拉取的时候,对平台生成的多语言 xml 文件进行分别检查。当某语种中某个文案不存在的时候,会生成一个模拟的多语言文案写入到xml文件。模拟文案则会带上这条文案的 key。

例如 key 为 common_hello 的文案在印尼语有缺失,那么运行时切换到印尼语时使用的文案就是 mock 的文案 "客户端mock common_hello(id)",这样 qa 或者策划看到就知道这里缺失了一条文案翻译。

运行时查询多语言key

Cuando el lado comercial de la aplicación desarrolla nuevas áreas, también podemos separar la redacción de consultas de la tecnología tanto como sea posible. Proporcionamos una herramienta de ventana flotante cuando se ejecuta la depuración. Cuando se abre la herramienta, puede seleccionar el TextView de la página actual. Si el contenido del TextView se carga a través de la identificación de la cadena, entonces esta clave se mostrará en la pantalla. . El efecto específico es el siguiente:

De esta forma, podemos ahorrar gran parte de la comunicación de consulta de claves multilingües en el proceso de apertura de una zona, y aumentar la eficiencia de la apertura de zona.

Perspectiva y Resumen

Aquí hay una introducción a algunas prácticas de Android APP en el extranjero, que cubren el diseño del marco técnico, el proceso de lanzamiento, varios idiomas, etc. Y para la mayoría de las regiones en el extranjero, la distribución de modelos de Android es caótica, hay más modelos de gama baja y el entorno de red es peor que en China. En términos de velocidad de inicio, administración de memoria, optimización de red, etc., todavía hay muchas áreas que mejorar en nuestra aplicación en el extranjero, y esperamos compartir y comunicarnos con usted.

Este artículo fue publicado por el equipo de tecnología de NetEase Cloud Music. Se prohíbe cualquier forma de reimpresión sin autorización. Reclutamos varios puestos técnicos durante todo el año. Si va a cambiar de trabajo y le gusta la música en la nube, ¡únase a nosotros en grp.music-fe(at)corp.netease.com!

Supongo que te gusta

Origin juejin.im/post/7195374985077063739
Recomendado
Clasificación