找回密码
 会员注册
查看: 32|回复: 0

AndroidAPP出海实践

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64801
发表于 2024-9-21 15:54:28 | 显示全部楼层 |阅读模式
当前国内各个公司APP出海创收已经是互联网行业的常见操作。笔者最近约2年的时间里,都在进行云音乐旗下首个出海应用Android客户端的开发。本文对海外APP一些开发经验做一些分享。初次出海的时候,我们总结了需要适配海外环境的方方面面,包括客户端内的很多通用模块需要支持海外环境。这里包括我们的目的是,尽量保持原有的技术框架去开发新的APP,不要因为运营环境变了,技术架构也大改。确认一些三方服务对于海外环境的支持程度,例如云信、声网SDK一些常见APP功能的海外版本封装,例如登录,文件上传,推送,分享底层库功能自查,支持上架政策和一些资源配置。AndroidAPP的发布渠道和发布格式。海外Android应用以GooglePlay上架发布为主,这里我们需要额外支持aab(androidappbundle)格式进行发布。海外应用设计基础库海外实现层基础模块我们遵循接口实现分离的设计原则,以文件上传底层库为例,我们会有3个最终打成aar的module:uploader_interface提供文件上传相关的各种接口uploader_module uploader_interfacemodule各个接口的具体实现,例如文件通过中台的CDN接口上传。uploader_module_oversea同样是uploader_interfacemodule里面各个接口的具体实现,实现逻辑从直接CDN接口上传改为先上传至亚马逊云,然后把亚马逊云的上传信息同步给CDN。得益于上面的设计原则,基础模块我们只需要提供对应的海外实现即可。业务代码内调用的仍然是接口module的API,这样做一来一些依赖底层的业务代码可以直接复用,二来开发同学也不需要再去熟悉另一套底层库API。底层库合规检查海外APP在GooglePlay作为主要分发渠道的情况下,隐私政策可能和国内略有不同。而一些底层库可能包括了一些不合规的代码,这部分需要进行排查,一般来说,遵循下面2个原则就不容易出现问题:底层库代码里面没有违规的API调用,例如和热修复这种动态代码下发的。GooglePlay不允许相关功能底层库的依赖里不要包含海外环境用不到的功能。例如一些之前全公司APP都通用的三方服务的SDK被集成在了某个底层库,虽然海外没有使用相关功能,但是这些SDK非常有可能因为包括了动态下发so而被检查出来。GooglePlay隐私政策可以参考https://support.google.com/googleplay/android-developer/answer/9888170?hl=zh-Hans&ref_topic=9877467[1]底层库资源另一方面,对于比较简单的底层逻辑,我们一般情况也不会对其做接口与实现拆分,但是底层有可能会使用一些通用的资源,例如文案、图标等。如果我们把这些值作为变量设置进去,一方面底层库的改动比较大,另一方面初始化时候的设置也非常的繁琐。这里我们可以利用Android自身的资源合并策略。如上图,底层库里面定义的key1字符串,我们在上层定义同名的字符串key2,最终在打包的时候,资源合并会保留key2。所以也需要我们在设计底层库的时候避免直接使用字符串硬编码,以免不能灵活支持海外应用。aab文件与PlayStore分发appbundle格式使用appbundle格式当下在GooglePlay进行分发是唯一选择。我们使用./gradlew :app:bundleRelease构建我们的appbundle文件上传至GooglePlay后台进行发布。但是由于aab文件并不能直接安装在设备上,所以在日常的测试、回归阶段,我们仍然是安装apk文件来进行,流程如下图:从理论上来说,apk测试回归没有什么问题,aab也就没什么问题。但是在日常实践,我们可能会有一些GradlePlugin的task在hook一些编译任务的时候,忽略了aab的情况,从而导致一些运行时的错误。针对这种情况,在正式的aab文件发布前,我们还是有必要对其做一个快速的走查。Google官方也提供了方法让我们安装aab文件到设备上,使用bundletool工具根据aab文件生成apks文件,然后使用adbinstall-multiple 命令安装:java -jar bundletool.jar build-apks --bundle=${FILE_NAME} --output=${target_apks}unzip target_apkscd splitsadb install-multiple bae-master.apk xx.apk这样测试回归流程则可以加上aab,但是让qa同学每次使用脚本安装总也是个麻烦的事情,所以能否更彻底点呢?答案当然是可以的,既然可以通过install-multiple安装apks文件,那么CI流程上每次aab构建的时候,输出aab和apks2个产物,然后通过一个安装apks文件的APP进行安装。我们可以通过android.content.pm.PackageInstaller这个AndroidAPI实现这个功能代码如下:val installer = InstallApp.application().packageManager.packageInstallerval params = ackageInstaller.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 = ACKAGE_INSTALLED_ACTIONval pendingIntent = endingIntent.getActivity(InstallApp.application(), 0, intent, FLAG_MUTABLE)val statusReceiver = pendingIntent.intentSenderinstallSession.commit(statusReceiver)安装结果我们可以通过Intent里面的android.content.pm.extra.STATUS获取。这里我们就可以不适用脚本命令行,直接使用安装工具安装aab文件,app的回归发布流程就比较完善了:GooglePlay签名Android应用通过GooglePlay发布的时候,还需要开启GooglePlay应用签名功能,具体的操作和规则可以参考Play管理中心文档:https://support.google.com/googleplay/android-developer/answer/9842756[2]。按照官方图示,GooglePlay会把开发者上传的密钥重新签名为新的密钥进行发布。最终GooglePlay控制台里面会显示最终的密钥指纹和上传密钥指纹:GooglePlay之所以设计这套看起来有点复杂的秘钥管理,是为了保障APP的签名安全。当我们的上传秘钥出现被盗取或者丢失的情况下,也只需要申请重新替换上传秘钥即可。但是我们的APP在发布的时候,我们不仅需要在GooglePlay进行发布,还需要发布自己的APK渠道包。在后台升级密钥的时候,会有如下几个选项如果使用默认的GooglePlay生成新的密钥,我们只能导出一个后缀名为der的证书,这个证书里面只包括了公钥,所以即使同keystore工具导出jks文件,也不能正常打包。所以我们需要选择“从Java密钥库上传新的应用签名密钥”这里还需要注意一点,选择新的密钥规则默认选择AndroidT及以上版本升级,且此选项默认收起。我们需要选择下面的“所有Android版本的所有新安装”,否则无法达到最终目的。所有我们最终签名流程如下图所示:我们拥有2个打包签名文件,分别为release.jks和store.jks,通过Google的pepk.jar工具把GooglePlay的签名换位store.jks。最终在发布的时候:aab文件使用release.jks构建,上传后会重签为store.jks发布release渠道包的apk文件使用store.jks构建,这样apk和商店下载的aab文件签名才一致,才能算是同一个APPGooglePlay发布问题在使用GooglePlay发布的时候,如果我们使用了uses-feature声明功能的时候,最终在发布的时候,可能会导致最终发布后显示支持设备类型数为0,这样用户将无法下载甚至无法在GooglePlay上看到该版本。我们需要在声明的地方添加上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                    }                }                rintWriter pw = new rintWriter(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}Buildtask之后。当variant属于debug的时候,res/values里面放的为中文的xml文件。当variant属于release的时候,res/values里面放的为英文的xml文件。整个languageplugin的工作如下:其中,自动拉取插件在替换文案之前,还可以做一次预检查操作。防止因为翻译错误等原因导致编译报错。例如文案里面检查转为s的时候,是否有字符缺失或者增加了空字符导致String.format出错文案里面存在&符号,需要修改为&多语言解耦在app的日常维护中,时常会有多语言文案需要替换。在上述工作流中,非客户端开发在需要替换文案的时候,需要频繁的提问客户端开发需要替换的具体key。这样无疑增加了需要沟通成本。我们还可以通过一些技术手段来减少这部分的耦合。常见的文案的替换场景大概分为两类测试、走查阶段发现某些语种存在翻译缺失开新区增加新翻译的时候,某些语种的文案长度不合理需要精简这两种场景,非开发角色不经过沟通并不知道具体的多语言key是什么。针对上述两种情况,我们的多语言插件设计了两部分功能。缺失文案检查及mock文案生成多语言插件在文案拉取的时候,对平台生成的多语言xml文件进行分别检查。当某语种中某个文案不存在的时候,会生成一个模拟的多语言文案写入到xml文件。模拟文案则会带上这条文案的key。例如key为common_hello的文案在印尼语有缺失,那么运行时切换到印尼语时使用的文案就是mock的文案"客户端mockcommon_hello(id)",这样qa或者策划看到就知道这里缺失了一条文案翻译。运行时查询多语言key当app业务方开发新区的时候,我们也可以把查询文案这件事尽可能的和技术剥离开。我们在debug运行时提供了一个悬浮窗工具,当工具开启的时候,可以选择当前页面的TextView,如果这个TextView得内容是通过stringid加载的,那么就会把这个key显示在屏幕上。具体效果如下图:这样我们能节省开区过程中很大一部分查询多语言key的沟通,增加开区效率。展望与总结这里介绍了一些AndroidAPP出海的实践,涵盖了技术框架设计,发布流程,多语言等内容。并且对于大部分海外地区来说,Android机型分布比较混乱,低端机型较多,且网络环境较国内比较差。在启动速度,内存管理、网络优化等方面,我们出海的APP还有很多需要建设的地方,希望能和大家进行分享交流。本文发布自网易云音乐技术团队,文章未经授权禁止任何形式的转载。我们常年招收各类技术岗位,如果你准备换工作,又恰好喜欢云音乐,那就加入我们grp.music-fe(at)corp.netease.com!参考资料[1]https://support.google.com/googleplay/android-developer/answer/9888170?hl=zh-Hans&ref_topic=9877467:https://support.google.com/googleplay/android-developer/answer/9888170?hl=zh-Hans&ref_topic=9877467[2]https://support.google.com/googleplay/android-developer/answer/9842756:https://support.google.com/googleplay/android-developer/answer/9842756?hl=zh-Hans
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-28 23:16 , Processed in 0.457245 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表