Android 已有项目插件化改造导引

这篇文章,简单总结了公司安卓组已有项目做插件化改造的必要性、目标和步骤,供公司内安卓组小伙伴参考之用。

标题党的目录

    • 前言
    • 背景
    • 目标
    • 步骤
      • 1、build.gradle 改造
      • 2、Manifest.xml 改造
      • 3、启动页改造
      • 4、启动页 UI 及跳出逻辑改造
      • 5、token失效处理方式改造
      • 6、个人中心页/设置页改造
      • 7、主页面改造
      • 8、多语言切换改造
      • 9、打包
    • 总结

前言

首先要对本篇标题做个澄清,虽然叫做 “插件化改造” ,可实际上使用并不是大家所熟知的 Android 插件化技术:既不是滴滴公司的 VirtualAPK,也不是 DroidPlugin 等。

那是什么呢?

就是将所谓的 “插件APP” 的桌面图标隐藏,通过 “宿主APP” 对其进行安装、卸载、版本更新、统一登录等管理。

贻笑大方了。

至于为什么要叫做 “插件化改造”,一是我这里没有合适的称呼,二是在公司内业务层面上已经叫习惯了,这样叫他们更容易理解。(实际上私下我曾叫过 ghost 和影子应用,结果让同事们一脸懵,向领导层汇报起来也比较 lowB。)

那为什么放着牛X闪闪的 VirtualAPK 和 DroidPlugin 的方案不用,却选用内行看起来这么蠢逼的方案呢?感兴趣的小伙伴,可以看看我的这篇文章:Android 项目插件化改造记录——关于 DroidPlugin 和 VirtualAPK——还没写

背景

技术服务业务,需求引导开发。

我们公司目前为集团服务的应用有 6 款,这 6 款应用各司其职,但它们的用户严重重合。

解释一下:

  1. 围绕着集团的核心业务,这 6 款应用的核心功能各不相同;
  2. 各应用的用户,是相对稳定的集团体系内的用户,根据其层级和权限不同,其需要使用到的应用,少则一两款,多则达到 6 款;
  3. 加之服务区域不同以及其业务上的差异,同一款应用可能会分离出拥有着不同 applicationId 的多个 APP,这样 6 款应用甚至会分离出二十多个 APP。
    (组件化改造,我们几个月前就已经在做了。完全完成组件化改造的应用,可以很好的改善 “同一应用,由于服务区域不同及业务差异,需要拆分为多个 APP” 的情况)

当这么多的 APP 安装到用户的手机上,用户需要来回切换才能完成日常工作、上报数据、查看报表时,用户会很痛苦。

所以,我们需要一个管理软件(下文称宿主 APP),对这么多APP(下文称插件 APP)进行统一管理,像加载宿主 APP 的一个模块一样加载插件 APP,但又要保持插件 APP 的相对独立,不能对其业务和代码有太强的侵入性。

最终,我们选择了本文所述的解决方案,让我们来看看做插件化改造的具体目标是什么。

目标

  1. 由 Server 中台打通各应用的账户系统,宿主 APP 一次登录,加载插件 APP 处理各自独立的业务;

  2. 将各插件 APP 共有业务抽离出来,作为宿主 APP 的一个模块,直接对接 Server 中台;

  3. 宿主 APP 对插件 APP 的安装、卸载、更新等进行管理;

  4. 插件 APP 的 Project,要既能打包为独立 APP,又能打包为插件 APP,要尽量支持动态化;

    宿主 APP 模式的完善和推广,一定是一步步进行的,将在很长一段时间内,既要支持一部分用户使用多个独立 APP,又要支持另一部分用户使用宿主 APP。

  5. 各 APP 原有的账户相关操作,如账号登录、指纹登录、退出登录、修改密码等,在作为插件 APP 存在时要全部交给宿主APP处理,不可以有相关功能和 UI 的开放;

  6. 插件 APP 在桌面没有启动图标(当然在手机设置的“应用管理”中是可见的);

  7. 宿主 APP 与插件 APP 间支持进程间通信等。

步骤

怎样对原有 APP 的 Project 进行 插件化改造 ,是这部分的重点。

同时,为各插件 APP 提供了 SDK。SDK 将在与 Server 中台进行业务交互、与宿主 APP 进行通信、抽离共有功能模块及 UI 等方面为各插件 APP 提供支持,尽可能地降低插件 APP Project 的改造成本。

所以,这一部分的内容,是强业务相关的,非公司内的小伙伴,不用拘泥于细节。

1、build.gradle 改造

Appliaction module 的 build.gradle 中新增渠道配置如下所示。

这样做的目的是便于分别打独立包和插件包,同时也便于在代码中根据当前运行的是独立 APP 或者插件 APP 执行不同的代码逻辑。

android {
……
    /*多渠道:独立包或者插件包*/
    productFlavors {
    	// 独立包
        independent {
            manifestPlaceholders = [FLAVOR: "independent"]
        }
        // 插件包
        ghost {
            manifestPlaceholders = [FLAVOR: "ghost"]
        }
    }
    flavorDimensions 'flavor'
}

2、Manifest.xml 改造

标签中新增 如下所示。

这之后,就可以在代码中获取 name 是 FLAVOR的 value,根据 value 判断当前运行的 APP 是独立 APP 或者 插件 APP,以便执行不同的代码逻辑。

	<meta-data
		android:name="FLAVOR"
		android:value="${FLAVOR}" />

3、启动页改造

在启动页 LauncherActivity中新增 如下所示。

这样做,就限定了 LauncherActivity启动方式,将不允许通过桌面图标启动(实际桌面上就没有图标了),宿主 APP 可通过 host + scheme匹配的方式启动插件 APP。

	<intent-filter>
		……
      	<data
          	android:host="LauncherActivity"
          	android:scheme="com.company.usercenter.ui"
          	tools:ignore="AppLinkUrlError" />
 	</intent-filter>

注意:不可以为插件包设置独立的启动页,如LauncherGhostActivity,否则当用户手机上安装的是独立APP时,宿主APP无法找到其启动页。

这里有一点要说明:

插件 APP 为什么还要用启动页 LauncherActivity,而不是直接到插件 APP 的主页面 MainActivity

原因有两方面:

  1. 插件 APP 在系统看来,依旧是完整的应用,当其启动时,会出现大家所熟知的 “黑白屏问题” 。

    “黑白屏问题” 的解决办法大家也熟知,就是给第一个启动的 Activity 设置 theme

    <!-- 启动页theme -->
    <style name="StartAppTheme" parent="AppTheme">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowBackground">@drawable/bg_window</item>
    </style>

上述代码中的资源文件 bg_window,就是黑白屏的替换图片。要解决黑白屏问题, bg_window不可缺少,而其样式一般与启动页的样式一致或接近。

  1. 业务决定。

    当各 APP 以独立 APP 启动时,访问它自己的 login接口,会拿到一个 LoginResultBean对象,其中包含着账户相关的信息,是后续业务所必须的基础数据。

    当各 APP 以插件 APP 启动时,需要从宿主 APP 拿到其中台 centralizationToken,再使用 centralizationToken作为参数访问另一个接口 authLogin拿到同样的 LoginResultBean对象,这是必需的。

    所以插件 APP 中访问 authLogin的代码,要么放在启动页,要么放在主页面,其成功访问是其他所有接口的前置条件。

    bg_window又是不可缺少的。

    我们就干脆单独拎出来了启动页,在其中访问接口 authLogin,成功则跳转插件 APP 主页面,失败则回到宿主 APP。这样也不需要对主页面的初始化代码进行任何调整。

4、启动页 UI 及跳出逻辑改造

  • 独立 APP 逻辑
  1. 应用的一些初始化;
  2. 会有倒计时和 “立即启动” 按钮的 UI,在倒计时结束或者用户点击 “立即启动” 后进行页面跳转;
  3. 根据登录状态、指纹登录配置情况,跳往登录页、指纹验证登录页或者主页面。
  • 插件 APP 逻辑
  1. 应用的一些初始化;
  2. 倒计时和 “立即启动” 的 UI 和功能不开放;
  3. 如上文第 3 步所述,从宿主 APP 拿到其中台 centralizationToken,再使用 centralizationToken作为参数访问接口 authLogin拿到 LoginResultBean的数据,此接口访问成功可跳转页面,访问失败可提示用户后回到宿主 APP;
  4. 页面跳转,只能跳往主页面,不可跳往登录页或指纹验证登录页,且其 UI 和功能不开放。

5、token失效处理方式改造

  • 独立 APP 逻辑
    (1) 清除账户配置、缓存等(集中在SharePreference中);
    (2) 跳转登录页重新登录。

  • 插件 APP 和 宿主 APP 逻辑

    这里分为两种情况:

  1. 插件 APP 自己的 token 失效

    1.1 清除账户配置、缓存等(集中在SharePreference中);

    1.2 提示用户后,杀掉插件 APP 进程,回到宿主 APP。

    那么,为什么这里是杀掉插件 APP 进程回到宿主 APP,而不是跳往 启动页重新访问接口 authLogin

    原因是,登录统一后,插件 APP token 是与宿主 APP token 强相关的,插件 APP token 失效,难以确定是自己的业务逻辑造成的还是宿主 token 失效造成的,所以干脆回到宿主 APP。如果是插件 APP 自己的业务逻辑造成自己的 token 失效,用户重新启动一次插件 APP 即可;如果是宿主 APP token 失效造成,宿主 APP 自然会跳往宿主 APP 的登录页。

  2. 宿主 APP token 失效

       跳往宿主 APP 的登录页。

6、个人中心页/设置页改造

插件 APP 中,账户相关的 UI 和 功能不开放,包括修改密码、退出登录、指纹验证设置等。

7、主页面改造

目前主要是指在主页面对键盘返回键点击事件的处理。

  • 独立 APP 逻辑

    一般会支持 “双击返回键退出应用” ,还会有诸如 “再点一次退出应用” 的 toast 提示。

  • 插件 APP 逻辑

    去除 “再点一次退出应用” 的 toast 提示,用户点一次返回键直接 finish 主页面,之后就回到了 宿主 APP。

    注意,这里尽量不要遍历 finish 掉 Activity 栈中的所有 Activity,因为宿主 APP 的 Activity 与 插件 APP 的 Activity 大概率在同一栈中,可能会连同宿主 APP 的所有 Activity 一并 finish 掉,就回到手机桌面了。

8、多语言切换改造

在插件 APP 中要禁掉设置中 “多语言切换” 的功能,通过宿主 APP 提供 “多语言切换” 的功能,插件 APP 采用的语言随着宿主 APP 语言的改变而改变。

9、打包

由于我们在 build.gradle 中配置了 flavor ,程序运行时可以判断是独立包或者插件包了,上述各步骤中涉及到的代码,在我们打独立包和插件包间不需要做什么调整。

目前无法实现动态化的只有上述步骤第 3 条涉及到的 标签中的 host属性和 scheme属性,打独立包要将 标签注释掉,打插件包再放开。

总结

如大家所见,这篇文章所探究的内容,在技术上没什么深度,我认为值得关注的在于业务上的思考和实践。

如果大家有遇到跟我们类似的业务需求,且在使用 VirtualAPK、DroidPlugin 这样主流的解决方案遇到了技术上难以克服的困难的话,我们尝试的这种非主流、略显蠢逼的解决方案,不失为需求迫切时的救命稻草了。

你可能感兴趣的:(架构设计)