Переключение между двумя NativeActivities в Android

Мне нужно иметь два отдельных действия в приложении для Android. Насколько я понимаю, это делается с помощью нескольких тегов <activity> в теге <application>. Многие вещи в Интернете указывают на то, что вы используете что-то вроде этого кода:

 Intent intent = new Intent(this, mynativeactivity.class);
 startActivity(intent);

Однако android:name (и, соответственно, класс) обоих нативных действий — android.app.NativeActivity. Они просто в отдельных .so файлах. Даже если я использую JNI.

Есть ли способ переключения действий на основе имени строки, псевдонима или метки конкретной активности Android?

Мой AndroidManifest.xml выглядит примерно так:

    <application android:debuggable = "true" android:hasCode = "false" android:label = "cnfgtest" tools:replace = "android:icon,android:theme,android:allowBackup,label" android:icon = "@mipmap/icon">
        <activity android:configChanges = "keyboardHidden|orientation" android:label = "app_i_want_to_launch" android:name = "android.app.NativeActivity">
            <meta-data android:name = "android.app.lib_name" android:value = "app_i_want_to_launch"/>
            <intent-filter>
                <category android:name = "android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:configChanges = "keyboardHidden|orientation" android:label = "app_that_currently_launches" android:name = "android.app.NativeActivity">
            <meta-data android:name = "android.app.lib_name" android:value = "app_that_currently_launches"/>
            <intent-filter>
                <action android:name = "android.intent.action.MAIN"/>
                <category android:name = "android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

Обновлено: В этой среде сборки действительно нет возможности создать какой-либо дополнительный код Java. Все решения должны быть только с NativeActivity.

EDIT2: чтобы уточнить мое окончательно выполненное решение. В итоге я скомпилировал отдельный файл Java вне своего дерева и включил class.dex в свой проект, а система сборки продолжила свою работу, как и ожидалось. Это позволило мне создать отдельный класс, который тоже был NativeActivity, но с другим именем. т.е.

# javac -source 1.7 -target 1.7 -d bin/org/yourorg/cnfgtest/ -classpath ${ANDROID_HOME}/platforms/android-30/android.jar -sourcepath src src/org/yourorg/cnfgtest/MyOtherNativeActivity.java

# ~/android-sdk/build-tools/30.0.2/dx --output=makecapk/ --dex ./bin #makecapk is the root of my project.

Затем используйте каждый класс для определения нового собственного действия.

Кроме того, я смог использовать части ответов @dalmif ниже.

0
0
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Создайте еще один класс и расширьте NativeActivity следующим образом.

public class MyOtherNativeActivity extends NativeActivity {}

теперь вы можете вставить его в AndroidManifest.xml.

<activity
    android:name = "android.app.NativeActivity"
    android:configChanges = "keyboardHidden|orientation"
    android:label = "app_that_currently_launches">
    <meta-data
        android:name = "android.app.lib_name"
        android:value = "first_so_name" />
    <intent-filter>
        <action android:name = "android.intent.action.MAIN" />
        <category android:name = "android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<!--SECOND NATIVE ACTIVITY-->
<activity
    android:name = ".MyOtherNativeActivity"
    android:configChanges = "keyboardHidden|orientation"
    android:label = "app_i_want_to_launch">
    <meta-data
        android:name = "android.app.lib_name"
        android:value = "second_so_name" />
</activity>

наконец просто startActivity с этим новым классом.

startActivity(new Intent(this, MyOtherNativeActivity.class))

Обновлять

В общем, у вас есть два варианта наличия NativeActivity для каждой нативной библиотеки.

  1. Создайте класс Java для каждой собственной библиотеки (как объяснялось ранее).
  2. Используйте одно объявление NativeActivity с модифицируемым тегом <meta-data>. (В случаях, когда вы не хотите создавать файлы Java)

первый вариант описан выше, а вот про второй можно использовать этот класс Application

import android.app.Activity;
import android.app.Application;
import android.app.NativeActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * NativeActivity is an special activity for implementing in native libraries but
 * when you want to make more than one NativeActivity you have to create java classes
 * which extend NativeActivity and declare them in AndroidManifest.xml
 * and it's not possible in some situations but now using this Application class
 * you just put your library name which you want to load the activity from in
 * the intent like this
 *      <p><code>Intent intent = new Intent(this, NativeActivity.class); <br>
 *      intent.putExtra(NativeActivity.META_DATA_LIB_NAME, "my-native-library-name"); <br>
 *      startActivity(intent);</code></p>
 * Don't forget to 
 * 1. Add this class in the application tag of AndroidManifest.xml
 * 2. Add `android.app.NativeActivity` as an activity in AndroidManifest.xml
 */

public class NativeActivityLoaderApplication extends Application implements InvocationHandler, Application.ActivityLifecycleCallbacks {

    private Object base;
    private String currentLibName;

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(this);
    }

    @Override
    protected void attachBaseContext(Context base) {
        hookPackageManager(base);
        super.attachBaseContext(base);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // if currentLibName is equal to null then run in normal way, otherwise change the lib_name
        if (currentLibName != null  && "getActivityInfo".equals(method.getName())) {
            ComponentName componentName = (ComponentName) args[0];
            if (componentName.getClassName().equals(NativeActivity.class.getName())) {
                ActivityInfo info = (ActivityInfo) method.invoke(base, args);
                if (info != null) {
                    if (info.metaData == null) info.metaData = new Bundle();
                    info.metaData.putString(NativeActivity.META_DATA_LIB_NAME, currentLibName);
                }
                return info;
            }
        }
        return method.invoke(base, args);
    }

    private void hookPackageManager(Context context) {
        try {
            // get ActivityThread and currentActivityThread Method
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod =
                    activityThreadClass.getDeclaredMethod("currentActivityThread");
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);

            // get PackageManager instance in the current activityThread
            Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true);
            Object sPackageManager = sPackageManagerField.get(currentActivityThread);

            // get Class of IPackageManager
            Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
            this.base = sPackageManager;

            Object proxy = Proxy.newProxyInstance(
                    iPackageManagerInterface.getClassLoader(),  new Class<?>[]{ iPackageManagerInterface }, this);

            // set sPackageManager inside ActivityThread object to our hook
            sPackageManagerField.set(currentActivityThread, proxy);

            // set packageManager (mPM) to our hook
            PackageManager pm = context.getPackageManager();
            Field pmField = pm.getClass().getDeclaredField("mPM");
            pmField.setAccessible(true);
            pmField.set(pm, proxy);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
        if (activity instanceof NativeActivity) {
            currentLibName = activity.getIntent().getStringExtra(NativeActivity.META_DATA_LIB_NAME);
        }
        ActivityLifecycleCallbacks.super.onActivityPreCreated(activity, savedInstanceState);
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}

    @Override
    public void onActivityStarted(Activity activity) {}

    @Override
    public void onActivityResumed(Activity activity) {}

    @Override
    public void onActivityPaused(Activity activity) {}

    @Override
    public void onActivityStopped(Activity activity) {}

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}

    @Override
    public void onActivityDestroyed(Activity activity) {}
}

и объявите этот класс приложения в своем манифесте

<application
    android:name = ".NativeActivityLoaderApplication"
    ....

теперь вы можете просто использовать этот код, чтобы установить имя библиотеки (<meta-data android:name = "android.app.lib_name" ...>) в своем коде вместо AndroidManifest.xml

Intent intent = new Intent(this, NativeActivity.class);
intent.putExtra(NativeActivity.META_DATA_LIB_NAME, "native-activity");
startActivity(intent);

Я не уверен, что это возможно, так как в NativeActivities нет JVM. Легко вызвать Java-код, но невозможно (насколько мне известно) создать новый класс.

Charles Lohr 25.07.2023 00:34

В частности, есть ли способ получить Intent только из указанной категории в моем приложении, например?

Charles Lohr 25.07.2023 00:53

@CharlesLohr, почему бы вам не создать этот класс в java-файле? в вашем проекте нет других классов Java? и вы хотите продолжить проект без класса Java? Если да, то дайте мне знать, чтобы я мог вам помочь

dalmif 25.07.2023 02:14

Да, цель состоит в том, чтобы сделать это без файла класса Java. Теперь я могу из NativeActivity перечислить все остальные действия с помощью PackageManger. (Со временем опубликую решение), но если бы был более чистый способ, я бы ЛЮБИЛ ЭТО ЗНАТЬ!

Charles Lohr 25.07.2023 02:53

Хотя я могу получить два отдельных ActivityInfo, я не могу создать намерение для запуска каждого из них соответствующим образом. :(. Да, мне еще нужно найти способ сделать это без дополнительной Java.

Charles Lohr 25.07.2023 03:53

Я много думал об этом случае, наконец, я думаю, что в лучшем случае нужен хотя бы один класс Java. это может быть использование ComponentFactory и пользовательского ClassLoader или класса Application для управления тегом <meta-data>. Я написал для вас кусок кода и опубликовал его в этой сути Я написал комментарий поверх класса, который поможет вам его использовать.

dalmif 25.07.2023 06:33

вставьте код gist в свой проект и после этого просто используйте intent.putExtra(NativeActivity.META_DATA_LIB_NAME, "your-lib-name")

dalmif 25.07.2023 06:35

У меня есть еще один файл Java, работающий в моем дереве компиляции, но он требует, чтобы я отдельно создавал файл Java вручную :(. Я делаю то, что вы описали выше. Я очень ценю вашу работу по расследованию этого и хотел бы дать вам больше, чем бесплатные точки доступа в Интернет! (Которые я буду свободно распространять) Мое решение может быть каким-то странным сочетанием того, что у вас есть здесь.

Charles Lohr 25.07.2023 19:06

добро пожаловать :). Если ваша проблема не решена или у вас есть дополнительные ограничения в использовании файлов Java, запишите их. Возможно, я могу помочь тебе

dalmif 25.07.2023 21:44

Другие вопросы по теме