Как начать действие в модуле динамических функций?

При вводе для запуска Activity в модуле динамических функций через конфигурацию запуска Android Studio я получаю следующее предупреждение: 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. Как запустить действие в модуле динамических функций?


Боковое примечание: при попытке установить развернутый функциональный модуль, похоже, не устанавливается:

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 из пакета приложений ", функциональный модуль будет установлен.


person Martin Zeitler    schedule 29.10.2019    source источник
comment
Когда мне пришлось участвовать в одном из GDG Events и их Bundles функции (которая, я думаю, как-то связана с тем же вопросом, потому что вам нужно как можно больше разделить все по разным пакетам). Они сказали, что прямо сейчас вы можете вызвать Activity из другого пакета с отражением и у вас есть несколько обходных путей для этого (на самом деле такое же отражение, но более приятным способом).   -  person GensaGames    schedule 29.10.2019
comment
@GensaGames Я знаю, как запускать их во время выполнения, но это не помогает при (автоматическом) тестировании. С момента добавления функционального модуля есть два имени пакета, что является основной проблемой - и оба имеют одно и то же имя пакета, что тоже кажется неправильным. При попытке добавить их в отладку AndroidManifest.xml также не разрешается пространство имен.   -  person Martin Zeitler    schedule 29.10.2019
comment
Вопрос выше: How to run the activities from dynamic features modules?   -  person GensaGames    schedule 29.10.2019
comment
но он помечен как run-configuration (поскольку подход времени выполнения задокументирован). Одним из возможных обходных путей может быть добавление действия отладки с фильтром намерения для имени класса, который затем запускает эти действия модуля функции во время выполнения через отражение, но запуск их с ActitivtiyRule для автоматического тестирования все еще кажется сложным. Также возможно создание Intent для рабочей конфигурации вручную.   -  person Martin Zeitler    schedule 29.10.2019
comment
Спасибо. Если я правильно понимаю, для целей тестирования мы можем использовать bundletool, который в основном просто устанавливает все модули один за другим. В зависимости от комплектации запустить. Вот документация по этому инструменту developer.android.com/studio/command-line/bundletool < / а>   -  person GensaGames    schedule 29.10.2019
comment
@GensaGames для целей ручного тестирования можно создавать конфигурации запуска, в которых есть функциональный модуль независимо от того, развернут он или нет. См. Мой ответ о том, как запустить такой функциональный модуль Activity с Intent из конфигурации выполнения.   -  person Martin Zeitler    schedule 30.10.2019


Ответы (1)


Поскольку нельзя ссылаться на действия функционального модуля в AndroidManifest.xml базового модуля, я написал SplitInstallActivity, который находится в debug исходном наборе базового модуля, где он также доступен для тестов. Его можно вызвать с конфигурацией запуска, которая передает флаги запуска:

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

Он либо устанавливает функциональный модуль до moduleName, и / или запускает Activity до className.

По крайней мере, это работает при развертывании «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);
    }

    ...
}

Запуск определенного Activity можно автоматизировать с помощью ActivityTestRule<?>:

@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"}

Для тестирования также есть:

person Martin Zeitler    schedule 30.10.2019