最近几年,Android动态化,插件化相关技术在国内市场弄的风声水起,可以说Android程序员不去了解一下相关技术都会被人鄙视,但是相关技术却遭到苹果和Google双重封杀,毕竟人家希望平台的生态完全掌握在自己的手中。但国人的努力并非没有得到认可,Google就为此开发了App Bundle,其实就是对相关技术的认可。使用App Bundle,apk必须上传到google play才支持,幸运的是,国内华为的HMS为了兼容android平台,也支持相关技术,所以实例中会同时说到两个产家的方案。

App Bundle是什么东西?介绍性的东西,无论是官网还是简书上都有不少介绍,这里就不累述,可参考:
Android App Bundle
AndroidAppBundle

这里主要侧重于实例。

什么是Base APK
你用Android Studio新建任何一个工程(Application),都可以是Base APK。

  1. 新建base apk与Dynamic feature
    新建一个Empty Activity工程作为Base apk,然后app右键,通过New -> New Module,新建一个Dynamic Feature Module:

    在Dynamic Feature Module同样新建一个Empty Activity。

那么如何使Base APk与Dynamic Feature Module关联起来呢?其实这部分工作Android Studio默认就做了:

在Base apk中,它会添加:

dynamicFeatures = [":dynamictest"]

而在Dynamic部分,会添加:

implementation project(':app')
  1. 动态管理
    1)依赖包,前者为Google的依赖包,后者为华为的依赖包,
api "com.google.android.play:core:1.7.3"api 'com.huawei.hms:dynamicability:1.0.11.302'

要使得华为的依赖包可以下载,还需要在Project中添加仓库maven {url ‘http://developer.huawei.com/repo’}

allprojects {    repositories {        google()        jcenter()        maven {url 'http://developer.huawei.com/repo'}    }}

2)华为的动态管理实现
华为的APP Bundle需要在base apk和dynamic feature分别初始化,base apk端在application初始化:

public class DynamicApplication extends Application {    @Override    protected void attachBaseContext(Context base) {        super.attachBaseContext(base);        FeatureCompat.install(base);    }}

而dynamic feature处在Activity中初始化:

package net.wen.dynamic.test;public class MainActivity extends AppCompatActivity {    @Override    protected void attachBaseContext(Context newBase) {        super.attachBaseContext(newBase);        FeatureCompat.install(newBase);    }}

除了初始化,其它部分的代码都在base apk中。

华为的动态管理类叫FeatureInstallManager,在Activity中可以对其进行初始化和添加listener:

    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mFeatureInstallManager = FeatureInstallManagerFactory.create(this);        findViewById(R.id.click_test).setOnClickListener(v -> launchDynamic());    }    @Override    protected void onResume() {        super.onResume();        if(mFeatureInstallManager != null) {            mFeatureInstallManager.registerInstallListener(mStateUpdateListener);        }    }    @Override    protected void onPause() {        super.onPause();        if(mFeatureInstallManager != null) {            mFeatureInstallManager.unregisterInstallListener(mStateUpdateListener);        }    }    private InstallStateListener mStateUpdateListener = new InstallStateListener() {        @Override        public void onStateUpdate(InstallState state) {            Log.d(TAG, "install session state " + state);            if (state.status() == FeatureInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {                try {                    mFeatureInstallManager.triggerUserConfirm(state, MainActivity.this, 1);                } catch (IntentSender.SendIntentException e) {                    e.printStackTrace();                }                return;            }            if (state.status() == FeatureInstallSessionStatus.REQUIRES_PERSON_AGREEMENT) {                try {                    mFeatureInstallManager.triggerUserConfirm(state, MainActivity.this, 1);                } catch (IntentSender.SendIntentException e) {                    e.printStackTrace();                }                return;            }            if (state.status() == FeatureInstallSessionStatus.INSTALLED) {                Log.i(TAG, "installed success ,can use new feature");                makeToast("installed success , can test new feature ");                startDynamic();//use the dynamic feature                return;            }            if (state.status() == FeatureInstallSessionStatus.UNKNOWN) {                Log.e(TAG, "installed in unknown status");                makeToast("installed in unknown status ");                return;            }            if (state.status() == FeatureInstallSessionStatus.DOWNLOADING) {                long process = state.bytesDownloaded() * 100 / state.totalBytesToDownload();                Log.d(TAG, "downloading  percentage: " + process);                makeToast("downloading  percentage: " + process);                return;            }            if (state.status() == FeatureInstallSessionStatus.FAILED) {                Log.e(TAG, "installed failed, errorcode : " + state.errorCode());                makeToast("installed failed, errorcode : " + state.errorCode());            }        }    };

那么如何安装动态部分呢?通过FeatureInstallRequest新建一个任务,然后通过FeatureInstallManager来启动任务:

    private void requestDynamicInstall() {        FeatureInstallRequest request = FeatureInstallRequest.newBuilder()                // 添加dynamic feature 的名称                .addModule(DYNAMIC_MODULE)                .build();        mFeatureInstallManager.installFeature(request)                .addOnListener(new OnFeatureSuccessListener<Integer>() {                    @Override                    public void onSuccess(Integer integer) {                        Log.d(TAG, "load feature onSuccess.session id:" + integer);                    }                })                .addOnListener(new OnFeatureFailureListener<Integer>() {                    @Override                    public void onFailure(Exception exception) {                        if (exception instanceof FeatureInstallException) {                            int errorCode = ((FeatureInstallException) exception).getErrorCode();                            Log.d(TAG, "load feature onFailure.errorCode:" + errorCode);                        } else {                            exception.printStackTrace();                        }                    }                });    }

就可以就可以了。

3)Google的动态管理实现
其实华为的动态管理接口跟Google非常相似,估计是华为为了兼容Android,故意设计如此。
为了分开,google部分代码,写在一个ViewModel中。
开始也是初始化为添加listener:

    public BaseViewModel(@NonNull Application application) {        super(application);        splitInstallManager  = SplitInstallManagerFactory.create(application);        splitInstallManager.registerListener(listener);    }    @Override    protected void onCleared() {        super.onCleared();        splitInstallManager.unregisterListener(listener);    }    private SplitInstallStateUpdatedListener listener = state -> {        if(state.sessionId() == sessionId) {            if(state.status() == FAILED){                Log.d(TAG, "Module install failed with " + state.errorCode());                Toast.makeText(getApplication(), "Module install failed with " + state.errorCode(), Toast.LENGTH_SHORT).show();            } else if(state.status() == INSTALLED) {                Toast.makeText(getApplication(), "Storage module installed successfully", Toast.LENGTH_SHORT).show();                //saveCounter()            } else {                Log.d(TAG, "Status: " + state.status());            }        }    };

然后是新建任务与启动任务:

    private void requestDynamicInstall() {        SplitInstallRequest request = SplitInstallRequest.newBuilder().addModule(DYNAMIC_MODULE).build();        splitInstallManager.startInstall(request)                .addOnSuccessListener(id -> sessionId = id)                .addOnFailureListener(exception -> {                    Log.e(TAG, "Error installing module: ", exception);                    Toast.makeText(getApplication(), "Error requesting module install", Toast.LENGTH_SHORT).show();                });    }

google的也就这么多,华为HMS与google的接口是非常相似的。

3 本地测试
要在本地测试App Bundle的功能,需要下载bundletool工具,下载地址:google_bundletool
如嫌下载慢,可下载本人上传的资源:bundletool-all-0.15.0.jar

1)首先用Android Studio编译,跟编译APK一样,只不过这次我们选择build bundle(s),编译好后会在app\build\outputs\bundle\debug目录下生成app-debug.aab。
2)使用bundletool生成apk:
切换到app\build\outputs\bundle\debug目录下,命令行输入如下:

java -jar D:\Chrome\Download\bundletool.jar build-apks --bundle=app-debug.aab --output=aab.apks

aab.apks生成后,以压缩文件的方式打开,即可看到:

默认情况下,Android studio 会自动根据 CPU 架构、屏幕分辨率、语言这三个维度将app 分拆;如果希望自由控制分拆维度,可以在app/build.gradle 文件中android {} 增加控制开个

    bundle {        language {            enableSplit = false        }        density {            enableSplit = true        }        abi {            enableSplit = true        }    }

3)我们将其中的base-master.apk 、base-xxxhdpi.apk(对应分辨率)、base-zh.apk(对应语言),解压出来放到一个目录下,用adb安装:

D:\Android\android-sdk\platform-tools>adb install-multiple D:\apks\base-master.apk D:\apks\base-xxhdpi.apk D:\apks\base-zh.apk

然后运行,可以看到:

由于我们的动态模块需要从华为商城或者google play下载,而我们apks并没有走上传这一步,所以后续的结果是看不到的。

4)当然,我们也可以把整个(包括动态部分)打包成一个完整apk(android 5.0以下不支持bundle):

java -jar D:\Chrome\Download\bundletool.jar build-apks --bundle=app-debug.aab --output=aab_un.apks --mode=universal

然后安装:

adb install D:\apks\universal.apk

这样就跟普通apk没多大区别。

4 参考代码
文中代码:
DynamicApplication

google的样例
android-dynamic-code-loading

更多相关文章

  1. Android(安卓)Jpush的集成
  2. [置顶] Android系统下的动态Dex加载
  3. Android动态加载补充 加载SD卡中的SO库
  4. Android中使用kotlin实现多行文本的上下滚动播放
  5. Android(安卓)8.0刘海屏适配
  6. Android(安卓)MVP 实践 Dagger + activity/fragment + viewDeleg
  7. 安卓9.0设置WIFI静态IP地址。
  8. Android(安卓)Hook 机制之实战模拟
  9. Android视频解码及渲染

随机推荐

  1. 2015-10-30-02-Android Theme主题使用心
  2. Android 重力感应 翻转页面造成Activity
  3. android ListView向下展开 抽屉效果
  4. android 关于字符转化问题
  5. Android文档-开发者指南-第一部分:入门-中
  6. Android 基于UDP的Socket通信
  7. Android 的GUI 系统
  8. Android Makefile中是 如何识别 TARGET_P
  9. 说说在 Android(安卓)中如何发送自定义广
  10. Android贪吃蛇(不是SDK带的那个Sample哦)