Android——模块化加载

Posted 化作孤岛的瓜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android——模块化加载相关的知识,希望对你有一定的参考价值。

其实google发布app bundle已经是18年的事情了,只是一直在业务上接触不到。最近刚好打算用到爱奇艺的Qigsaw框架来做国内的模块化加载,所以打算一起学习一下。

说到模块化加载。其实本质就是为了缩小apk大小,增加下载量,增加应用功能的自由装载机制。

本文会先从官网的android app bundle文档与实例开始分析,先快速入手,写一个最简单的模块化调用demo。

1.分析官网的demo:

演示了如何使用 PlayCore API 请求和下载功能模块:

https://github.com/android/app-bundle-samples/tree/main/DynamicFeatures

演示了从安装的功能模块安全访问代码的三种不同方法:

https://github.com/googlesamples/android-dynamic-code-loading

2.首先新建一个工程,名字叫PlayFeatureDemo

导入依赖:api "com.google.android.play:core:1.8.3"

3.file->new module->Dynamic Feature ->输入module名字,game->next->finish

创建一个GameSampleActivity

4.创建SplitInstallManager实例,配置监听器,发起跳转。

代码:

package com.ng.playfeaturedemo;

import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.Nullable;

import com.google.android.play.core.splitinstall.SplitInstallManager;
import com.google.android.play.core.splitinstall.SplitInstallManagerFactory;
import com.google.android.play.core.splitinstall.SplitInstallRequest;
import com.google.android.play.core.splitinstall.SplitInstallSessionState;
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener;
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus;
import com.google.android.play.core.tasks.OnFailureListener;
import com.google.android.play.core.tasks.OnSuccessListener;

public class MainActivity extends BaseSplitActivity implements SplitInstallStateUpdatedListener {
    private TextView mProgressBar;
    private SplitInstallManager manager;
    private String PACKAGE_NAME = "com.ng.game";
    private String KOTLIN_SAMPLE_CLASSNAME =  "com.ng.game.GameSampleActivity";
    private int CONFIRMATION_REQUEST_CODE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mProgressBar = findViewById(R.id.progressBar);
        manager = SplitInstallManagerFactory.create(this);

        findViewById(R.id.btn_test).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                loadAndLaunchModule(getString(R.string.module_feature_game));
            }
        });
    }

    private void loadAndLaunchModule(String string) {
        if (manager.getInstalledModules().contains(string)) {
            Toast.makeText(this, "已安装", Toast.LENGTH_SHORT).show();
            launchActivity(KOTLIN_SAMPLE_CLASSNAME);
            return;
        }
        SplitInstallRequest request = SplitInstallRequest.newBuilder().addModule(string).build();
        manager.startInstall(request).addOnSuccessListener(new OnSuccessListener<Integer>() {
            @Override
            public void onSuccess(Integer result) {
                toastAndLog("成功: " + result);
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(Exception e) {
                toastAndLog("失败: " + e.getMessage());
            }
        });
    }

    private void launchActivity(String className) {
        Intent intent = new Intent().setClassName(BuildConfig.APPLICATION_ID, className);
        startActivity(intent);
    }

    @Override
    protected void onResume() {
        manager.registerListener(this);
        super.onResume();
    }

    @Override
    protected void onPause() {
        manager.unregisterListener(this);
        super.onPause();
    }

    @Override
    public void onStateUpdate(SplitInstallSessionState state) {
        //是否多模块安装
        boolean multiInstall = state.moduleNames().size() > 1;
        switch (state.status()) {
            case SplitInstallSessionStatus.DOWNLOADING:
                displayLoadingState(state, "下载中 ");
            case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION:
                try {
                    manager.startConfirmationDialogForResult(state, this, CONFIRMATION_REQUEST_CODE);
                } catch (IntentSender.SendIntentException e) {
                    e.printStackTrace();
                }
                break;
            case SplitInstallSessionStatus.INSTALLED:
                launchActivity(KOTLIN_SAMPLE_CLASSNAME);
                break;
            case SplitInstallSessionStatus.INSTALLING:
                displayLoadingState(state, "安装进度");
                break;

            case SplitInstallSessionStatus.FAILED:
                toastAndLog(getString(R.string.error_for_module, state.errorCode(),
                        state.moduleNames()));
                break;

        }
    }

    private void displayLoadingState(SplitInstallSessionState state, String message) {
        int max = (int) state.totalBytesToDownload();
        int progress = (int) state.bytesDownloaded();
        mProgressBar.setText(message + " " + progress + " / " + max);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable  Intent data) {
        if (requestCode == CONFIRMATION_REQUEST_CODE) {
            // Handle the user's decision. For example, if the user selects "Cancel",
            // you may want to disable certain functionality that depends on the module.
            if (resultCode == Activity.RESULT_CANCELED) {
                toastAndLog("用户拒绝");
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    void toastAndLog(String text) {
        Toast.makeText(this, text, Toast.LENGTH_LONG).show();
        Log.d("MainActivity", text);
    }

    @Override
    public void onPointerCaptureChanged(boolean hasCapture) {
    }
}

本地测试:

api文档:https://developer.android.com/guide/app-bundle/test/testing-fakesplitinstallmanager

生成aab之后:

​​

 拆包:

https://blog.csdn.net/qq_36168049/article/details/101002012

拆包命令:

bundletool build-apks --local-testing --bundle=/Users/xiaoguagua/AndroidProjects/OpenSourceProjects/PlayFeatureDemo/app/release/app-release.aab --output=/Users/xiaoguagua/AndroidProjects/OpenSourceProjects/PlayFeatureDemo/app/release/my_app.apks

 

得到了一个my_app.apks,可以解压

 

 

安装到手机:

bundletool install-apks --apks=/Users/xiaoguagua/AndroidProjects/OpenSourceProjects/PlayFeatureDemo/app/release/my_app.apks

运行,点击按钮:

 

 

可以看到开始下载组件

然后自动跳转到写的组件界面

就大功告成了。接下来会开始分析 实现原理以及Qigsaw的实现步骤。

 

以上是关于Android——模块化加载的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向类加载器 ClassLoader ( 类加载器源码简介 | BaseDexClassLoader | DexClassLoader | PathClassLoader )(代码片段

如何在android中将json数据加载到片段中

详解Android WebView加载html片段

在android中动态创建选项卡并使用传入的参数加载片段

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段