《App Bundle》:实例展示Android动态化技术(appfound)(theappbuilder)

访客 123 0
在过去几年里,国内市场掀起了一股Android动态化和插件化相关技术的热潮。这些技术对于Android程序员来说已经成为必备的知识,否则可能会被人嘲笑。然而,这些相关技术却遭到了苹果和Google的双重封杀,因为他们希望完全控制平台生态系统。但是国内开发者的努力并没有被完全忽视,Google推出了App Bundle作为对这些技术的认可。使用App Bundle需要将apk上传到Google Play才能支持,在此幸运的是,华为HMS也兼容了这些相关技术,并提供相应解决方案。因此,在本文中我们将同时介绍两个厂家的方案。

App Bundle是一种什么东西呢?关于它的介绍性内容可以在官网和简书上找到很多,这里就不再重复了。如果想了解更多,可以参考以下资源:Android App Bundle和AndroidAppBundle。

这里主要侧重于实例。

Base APK是指在Android Studio中新建的任何一个工程(Application),都可以作为基础APK。

  1. 新建base apk与Dynamic feature
    新建一个Empty Activity工程作为Base apk,然后app右键,通过New -> New Module,新建一个Dynamic Feature Module:
    《App Bundle》:实例展示Android动态化技术(appfound)(theappbuilder)-第1张图片-谷歌商店上架
    在Dynamic Feature Module同样新建一个Empty Activity。

那么如何将Base APK与Dynamic Feature Module关联起来呢?其实这一步骤在Android Studio中已经默认完成了。

在基本apk中,它将包含以下内容:

在Dynamic部分,将添加:dynamicFeatures = [:dynamictest] 
修改后的文案如下:

1. 修改前:
```implementation project(':app')```

2. 修改后:
动态管理依赖包,其中前者为Google的依赖包,后者为华为的依赖包。 
修改后的文本如下:

```bash
allprojects {
    repositories {
        google()
        jcenter()
        maven 
```
 
```java
public class DynamicApplication extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
    }
}
```

改写为:

```java
/**
 * This is the DynamicApplication class that extends the Application class.
 */
public class DynamicApplication extends Application {
    
    /**
     * Override method to attach the base context.
     *
     * @param base The context to be attached.
     */
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
    }
}
``` 
package net.wen.dynamic.test;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
    }
} 

华为的动态管理类叫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() {
                @Override
                public void onSuccess(Integer integer) {
                    Log.d(TAG, load feature onSuccess.session id: + integer);
                }
            })
            .addOnListener(new OnFeatureFailureListener() {
                @Override
                public void onFailure(Exception e) {
                    Log.e(TAG, load feature onFailure:  + e.getMessage());
                }
            });
} 

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 in);
        }
    }
}; 
private void requestDynamicInstall() {
    SplitInstallRequest request = SplitInstallRequest.newBuilder()
        .addModule(DYNAMIC_MODULE)
        .build();
    splitInstallManager.startInstall(request)
        .addOnSuccessListener(id -> sessionId = id)
        .addOnFailureListener(e -> {
            // handle failure
        });
} 

3 本地测试 为了在本地测试App Bundle的功能,您需要下载bundletool工具。您可以在google_bundletool下载地址找到该工具。如果您觉得下载速度较慢,也可以选择下载我上传的资源:bundletool-all-0.15.0.jar。

1) 首先,我们使用Android Studio进行编译。与编译APK的过程相似,不同之处在于这次我们选择构建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生成后,以压缩文件的方式打开,即可看到:
《App Bundle》:实例展示Android动态化技术(appfound)(theappbuilder)-第2张图片-谷歌商店上架
默认情况下,Android studio 会自动根据 CPU 架构、屏幕分辨率、语言这三个维度将app 分拆;如果希望自由控制分拆维度,可以在app/build.gradle 文件中android {} 增加控制开个

bundle {
    language {
        enableSplit = false
    }
    density {
        enableSplit = true
    }
} 
D:\Android\android-sdk\platform-tools>adb install-multiple D:\apks\base-master.apk D:\apks\base-xxhdpi.apk D:\apks\base-zh.apk

然后运行,可以看到:
《App Bundle》:实例展示Android动态化技术(appfound)(theappbuilder)-第3张图片-谷歌商店上架
由于我们的动态模块需要从华为商城或者google play下载,而我们apks并没有走上传这一步,所以后续的结果是看不到的。

当然,我们也可以将整个(包括动态部分)打包成一个完整的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动态代码加载

标签: 谷歌商店上架 动态 技术

发表评论 (已有0条评论)

还木有评论哦,快来抢沙发吧~