Мне нужно иметь два отдельных действия в приложении для 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 ниже.
Создайте еще один класс и расширьте 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 для каждой нативной библиотеки.
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);
В частности, есть ли способ получить Intent только из указанной категории в моем приложении, например?
@CharlesLohr, почему бы вам не создать этот класс в java-файле? в вашем проекте нет других классов Java? и вы хотите продолжить проект без класса Java? Если да, то дайте мне знать, чтобы я мог вам помочь
Да, цель состоит в том, чтобы сделать это без файла класса Java. Теперь я могу из NativeActivity перечислить все остальные действия с помощью PackageManger. (Со временем опубликую решение), но если бы был более чистый способ, я бы ЛЮБИЛ ЭТО ЗНАТЬ!
Хотя я могу получить два отдельных ActivityInfo, я не могу создать намерение для запуска каждого из них соответствующим образом. :(. Да, мне еще нужно найти способ сделать это без дополнительной Java.
Я много думал об этом случае, наконец, я думаю, что в лучшем случае нужен хотя бы один класс Java. это может быть использование ComponentFactory и пользовательского ClassLoader или класса Application для управления тегом <meta-data>. Я написал для вас кусок кода и опубликовал его в этой сути Я написал комментарий поверх класса, который поможет вам его использовать.
вставьте код gist в свой проект и после этого просто используйте intent.putExtra(NativeActivity.META_DATA_LIB_NAME, "your-lib-name")
У меня есть еще один файл Java, работающий в моем дереве компиляции, но он требует, чтобы я отдельно создавал файл Java вручную :(. Я делаю то, что вы описали выше. Я очень ценю вашу работу по расследованию этого и хотел бы дать вам больше, чем бесплатные точки доступа в Интернет! (Которые я буду свободно распространять) Мое решение может быть каким-то странным сочетанием того, что у вас есть здесь.
добро пожаловать :). Если ваша проблема не решена или у вас есть дополнительные ограничения в использовании файлов Java, запишите их. Возможно, я могу помочь тебе
Я не уверен, что это возможно, так как в NativeActivities нет JVM. Легко вызвать Java-код, но невозможно (насколько мне известно) создать новый класс.