如何在动态功能模块中启动一个Activity?

3

当我通过Android Studio的运行配置尝试启动动态特性模块中的Activity时,我会收到以下警告:The activity 'SomeActivity' is not declared in AndroidManifest.xml。 (因为它在动态特性模块的AndroidManifest.xml中声明)。供参考,这是使用的库:

// https://developer.android.com/guide/app-bundle/playcore
api "com.google.android.play:core:1.6.4"

运行配置显示并部署了两个模块,但是它只认识基本模块AndroidManifest.xml中的活动。如何在动态特性模块中启动一个Activity?
附注:尝试安装已部署的特性模块时,似乎无法安装。
I/PlayCore: SplitInstallListenerRegistry : registerListener
I/PlayCore: SplitInstallInfoProvider : No metadata found in AndroidManifest.
I/PlayCore: SplitInstallService : startInstall([feature_module],[])
I/PlayCore: SplitInstallService : Initiate binding to the service.
I/PlayCore: SplitInstallService : ServiceConnectionImpl.onServiceConnected(ComponentInfo{com.android.vending/com.google.android.finsky.splitinstallservice.SplitInstallService})
I/PlayCore: SplitInstallService : linkToDeath
I/PlayCore: SplitInstallService : onError(-5)
I/PlayCore: SplitInstallService : Unbind from service.

其中-5代表SplitInstallErrorCode.API_NOT_AVAILABLE(可能是因为它是一个调试版本);尽管如此,getInstalledModules()应该能够找到已部署的功能模块...但它没有。 SplitInstallInfoProvider : No metadata found in AndroidManifest似乎是这个问题的原因 - 当部署“默认APK”而不是“来自应用程序包的APK”时,功能模块将被安装。


当我参加GDG活动之一并了解到他们的“Bundles”功能时(我认为这与同一个问题有些关联,因为您需要尽可能将所有内容拆分到不同的包中)。他们说,现在您可以通过反射从另一个包中调用Activity,并有几种解决方法(实际上是相同的反射,但以更好的方式实现)。 - GensaGames
@GensaGames 我知道如何在运行时启动它们,但这对于(自动化)测试没有帮助。由于添加了功能模块,存在2个包名称,这是根本问题 - 两者都具有相同的包名称似乎也不正确。当尝试将它们添加到调试的 AndroidManifest.xml 中时,名称空间也无法解析。 - Martin Zeitler
以上问题是“如何从动态功能模块中运行活动?” - GensaGames
但它被标记为“运行配置”(因为运行时方法已记录)。一个可能的解决方法是,添加一个带有类名意图过滤器的调试活动,然后通过反射在运行时启动这些功能模块活动-但是使用ActivityRule进行自动化测试仍然似乎很复杂。手动构建运行配置的Intent也是一种选择。 - Martin Zeitler
谢谢。如果我理解正确,为了测试目的,我们可以使用 bundletool,它基本上只是逐个安装所有模块。根据配置运行。这里有关于该工具的文档 https://developer.android.com/studio/command-line/bundletool - GensaGames
为了进行手动测试,可以创建运行配置,其中包含功能模块是否已部署。请参阅我的答案,了解如何从运行配置中使用“Intent”启动此功能模块“Activity”。@GensaGames - Martin Zeitler
1个回答

2

由于无法在基础模块的 AndroidManifest.xml 中引用特性模块活动,因此我编写了一个 SplitInstallActivity,它位于基础模块的 debug 源集中,并可供测试使用。 可以通过运行配置来调用它,并传递启动标志:

-e "moduleName" "feature_module" -e "className" "com.acme.feature.SomeActivity"

它通过moduleName安装功能模块和/或通过className启动Activity

在部署“默认APK”而非“应用程序包中的APK”时,这至少可以正常工作。

ArgumentKeys.java

public class ArgumentKeys {

    /** {@link SplitInstallActivity} dynamic features, the module name */
    public static final String ARGUMENT_FEATURE_MODULE_MODULE_NAME = "moduleName";

    /** {@link SplitInstallActivity} dynamic features, the activity class name to launch */
    public static final String ARGUMENT_FEATURE_MODULE_CLASS_NAME = "className";
}

SplitInstallActivity.java

/**
 * Split-Install {@link AppCompatActivity}.
 * @author Martin Zeitler
**/
public class SplitInstallActivity extends AppCompatActivity implements SplitInstallStateUpdatedListener {

    private static final String LOG_TAG = SplitInstallActivity.class.getSimpleName();

    private SplitInstallRequest request;
    private SplitInstallManager sim;

    private String moduleName;
    private String className;

    public SplitInstallActivity() {}

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        /* instance the {@link SplitInstallManager}: */
        this.sim = SplitInstallManagerFactory.create(this.getApplicationContext());

        /* obtain the feature module & class name from arguments */
        if(this.getIntent() != null) {
            Bundle extras = this.getIntent().getExtras();
            if(extras != null) {
                this.moduleName = extras.getString(ArgumentKeys.ARGUMENT_FEATURE_MODULE_MODULE_NAME);
                this.className = extras.getString(ArgumentKeys.ARGUMENT_FEATURE_MODULE_CLASS_NAME);
                if(this.moduleName != null && this.className != null) {
                    this.startFeatureActivity(this.moduleName, this.className);
                } else {
                    Log.e(LOG_TAG, "module and class are required.");
                }
            }
        }
    }

    /** it listens for the split-install session state */
    @Override
    public void onStateUpdate(SplitInstallSessionState state) {
        if(state.errorCode() == SplitInstallErrorCode.NO_ERROR && state.status() == SplitInstallSessionStatus.INSTALLED) {
            Log.d(LOG_TAG, "dynamic feature " + this.moduleName + " had been installed.");
            this.startFeatureActivity(this.moduleName, this.className);
        } else {
            // this.OnSplitInstallStatus(state);
        }
    }

    /** it checks if the dynamic feature module is installed and then either installs it - or starts the desired activity */
    private void startFeatureActivity(@NonNull String moduleName, @NonNull String className) {
        if (this.sim.getInstalledModules().contains(moduleName)) {
            Log.d(LOG_TAG, "dynamic feature module " + moduleName + " already installed.");
            Intent intent = this.getIntent();
            intent.setClassName(BuildConfig.APPLICATION_ID, className);
            this.startActivity(intent);
            this.finish();
        } else {
            Log.d(LOG_TAG, "dynamic feature module " + moduleName + " is not installed.");
            this.installFeatureModule(moduleName);
        }
    }

    /** it installs a dynamic feature module on demand */
    private void installFeatureModule(@NonNull String moduleName) {
        Log.d(LOG_TAG, "dynamic feature module " + moduleName + " will be installed.");
        this.request = SplitInstallRequest.newBuilder().addModule(moduleName).build();
        this.sim.registerListener(this);
        this.sim.startInstall(this.request);
    }

    ...
}

使用ActivityTestRule<?>可以自动化启动特定的Activity
@Rule
public ActivityTestRule<SplitInstallActivity> mRule = new ActivityTestRule<SplitInstallActivity>(SplitInstallActivity.class) {

    @Override
    protected Intent getActivityIntent() {
        Intent intent = new Intent();
        Bundle extras = new Bundle();
        extras.putString(ArgumentKeys.ARGUMENT_FEATURE_MODULE_MODULE_NAME, "feature_module");
        extras.putString(ArgumentKeys.ARGUMENT_FEATURE_MODULE_CLASS_NAME, "com.acme.feature.SomeActivity");
        intent.putExtras(extras);
        return intent;
    }
};

为了进行测试,需要在特性模块的build.gradle中提供以下依赖项:

androidTestDebugImplementation "com.google.android.gms:play-services-basement:17.1.1"
androidTestDebugImplementation "com.google.android.play:core:1.6.4"

否则,它将无法链接测试应用程序的资源,并出现以下错误:
> Task :feature_module:processDebugAndroidTestResources FAILED
AGPBI: {"kind":"error","text":"Android resource linking failed","sources":[{"file":"/home/user/.gradle/caches/transforms-2/files-2.1/7435b27a13269cffdd35a7dd69f0b9d2/core-1.6.4/AndroidManifest.xml","position":{"startLine":8,"startColumn":4,"endColumn":277}}],"original":"/home/user/.gradle/caches/transforms-2/files-2.1/7435b27a13269cffdd35a7dd69f0b9d2/core-1.6.4/AndroidManifest.xml:9:5-278: AAPT: error: resource style/Theme.PlayCore.Transparent (aka com.acme.feature.test:style/Theme.PlayCore.Transparent) not found.","tool":"AAPT"}
AGPBI: {"kind":"error","text":"Android resource linking failed","sources":[{"file":"/home/user/.gradle/caches/transforms-2/files-2.1/c1b8b45e2f49fbe83ea45d80000bd6e9/jetified-play-services-basement-17.0.0/AndroidManifest.xml","position":{"startLine":22,"startColumn":8,"endLine":24,"endColumn":68}}],"original":"/home/user/.gradle/caches/transforms-2/files-2.1/c1b8b45e2f49fbe83ea45d80000bd6e9/jetified-play-services-basement-17.0.0/AndroidManifest.xml:23:9-25:69: AAPT: error: resource integer/google_play_services_version (aka com.acme.feature.test:integer/google_play_services_version) not found.","tool":"AAPT"}

针对测试,还有以下选项:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接