JNI 创建 Android Activity:两种方案解析
2025-01-02 19:51:17
JNI 中创建 Android Activity 的探索
在 Android 应用开发中,经常会遇到这样的需求:在 C++ 层通过 JNI 直接与 JVM 交互,进而操作 Android 相关的 API。其中一个典型问题就是:是否能从 JNI 中创建并启动一个 Android Activity? 核心挑战在于如何桥接原始 JVM 环境与 Android 框架的上下文,本文将探讨可行方案,以及可能遇到的问题,并提供相应的解决方案。
问题核心:上下文的缺失
问题关键在于直接创建的 JVM 并没有 Android 应用上下文环境。 Android SDK 中的类,如 android.content.Intent
和 android.content.Context
,它们依赖于特定的 Android 系统服务和生命周期管理机制。简单的说,单独的JVM并不知道 Android 上下文。试图在这样一个隔离的环境中直接实例化 Context 对象并使用 startActivity() 会导致失败,因为 Context
是抽象类,必须通过系统赋予具体实现。
解决方案一:反射调用 Android 系统服务
这种方法通过反射来访问 Android 系统提供的服务,而不是直接实例化 Android 对象。我们可以利用 ActivityManager 来启动一个新的 Activity,这需要深入了解 ActivityManager 的工作机制以及相关 API 的签名。
步骤:
- 找到 ActivityManagerService : 首先,你需要获得 ActivityManagerService 的实例。Android 系统利用反射机制暴露此服务,具体方法为先获取 ActivityManager 类,再从 ActivityManager 中获取 IActivityManager 接口实例。
- 构建 Intent 对象: 像平时在 Android 开发中使用 Activity 一样,构建 intent。
- 利用
startActivity
启动 Activity: IActivityManager 提供了startActivity()
方法。我们需要通过反射调用这个方法,传入必要的参数,包括 Intent 对象,flags 以及一个 call 对象 (一般可设为空)。
代码示例 (Java 部分,JNI 调用):
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import java.lang.reflect.Method;
import android.os.IInterface;
import java.lang.reflect.InvocationTargetException;
import android.app.ActivityManager;
public class JniHelper {
public static void startMainActivity() {
try {
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
Method getDefault = activityManagerNativeClass.getMethod("getDefault");
IInterface activityManager = (IInterface)getDefault.invoke(null);
Method startActivity = activityManager.getClass().getMethod(
"startActivity",
android.os.IBinder.class, // IApplicationThread
java.lang.String.class, // calling package
android.content.Intent.class,
java.lang.String.class, //resolvedType
android.os.IBinder.class, //ResultTo
java.lang.String.class, // resultWho
int.class, // requestCode
int.class, // flag
android.app.ProfilerInfo.class,
android.os.Bundle.class // options
);
Intent intent = new Intent();
intent.setComponent(new ComponentName("your.package.name", "your.package.name.MainActivity")); //设置ComponentName MainActivity路径
Bundle bundle = new Bundle();
startActivity.invoke(activityManager,
null, "your.package.name", intent, null,null , null,0, 0,null, bundle); // invoke startActivity()
}
catch(ClassNotFoundException | NoSuchMethodException | IllegalAccessException| InvocationTargetException e ){
e.printStackTrace();
}
}
}
命令行指令(编译 JniHelper.java)
假设已正确配置 android.jar
的路径:
javac JniHelper.java
操作步骤:
- 在 Java 层编写
JniHelper
类,并在其中实现startMainActivity()
方法,代码如上所示。将包名 (your.package.name
) 和 MainActivity 完全限定名(your.package.name.MainActivity
) 替换为自己的值。 - 编译 Java 类,并将 class 文件路径添加到 JNI 代码的 JVM classpath 中。
- 通过 JNI 调用
JniHelper.startMainActivity()
方法。 - 确保 APP 拥有权限
android.permission.START_ACTIVITIES_FROM_BACKGROUND
, 否则会失败。
优点: 这种方法不直接依赖实例化 Context。
缺点:
- 反射方法涉及隐藏API,可能有兼容性问题,随着Android系统升级,需要注意兼容性问题,建议充分测试。
- 代码逻辑较复杂,需要熟悉Android系统的实现细节。
解决方案二:通过 Android ContextWrapper 进行桥接
此方案并非完全从头构建Activity,而是借助 Android 环境现有的 Activity 或 Service Context。 我们可以通过在Android activity里进行JNI加载和调用。
这是一种将 JNI 层调用请求传递给已运行的 Android 实例的方法,这样就不会受到直接构造抽象类的限制。
步骤:
-
在现有 Activity 中建立 JNI 调用: 将 JNI 方法封装在 Android Activity 中调用, Activity 本身具备合适的
Context
, 可以传递这个上下文到 JNI 层。 -
在 JNI 层存储 context : 通过JNI层把 context 转化为 java对象句柄传递到 C++ 层进行存储。
-
使用
context.startActivity()
: 在 JNI 层可以使用 Context 对象执行context.startActivity()
,注意通过Jobect
调用方法
代码示例(Activity.java):
public class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 调用 C++ 方法
System.loadLibrary("native-lib"); //加载 .so 库
nativeStart(this); // 传递 Activity Context 到 JNI 层
}
//native 方法声明
public native void nativeStart(Context ctx);
}
代码示例(native-lib.cpp):
#include <jni.h>
#include <string>
//保存Java context 句柄的全局变量
static jobject g_activity_context = nullptr;
static JavaVM* jvm = nullptr; // 全局的jvm
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
jvm = vm;
JNIEnv* env;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
return JNI_VERSION_1_6;
}
extern "C" JNIEXPORT void JNICALL
Java_your_package_name_MainActivity_nativeStart(
JNIEnv* env,
jobject activity_object, // 接收 MainActivity 的 Activity 上下文 context
jobject j_activity_context ) //Java activity context obj
{
g_activity_context = env->NewGlobalRef(j_activity_context);
//后续可以用该全局变量 context调用 start activity方法
env->DeleteLocalRef(j_activity_context);
// 创建intent 和 ComponentName
jclass intent_class = env->FindClass("android/content/Intent");
jmethodID intent_constructor = env->GetMethodID(intent_class, "<init>", "()V");
jobject intent = env->NewObject(intent_class, intent_constructor);
jclass component_name_class = env->FindClass("android/content/ComponentName");
jmethodID component_constructor = env->GetMethodID(component_name_class, "<init>",
"(Ljava/lang/String;Ljava/lang/String;)V");
jstring package_name = env->NewStringUTF("your.package.name");
jstring mainactivity_name = env->NewStringUTF("your.package.name.MainActivity");
jobject component = env->NewObject(component_name_class, component_constructor, package_name,mainactivity_name);
jmethodID intent_setComponent = env->GetMethodID(intent_class, "setComponent","(Landroid/content/ComponentName;)Landroid/content/Intent;");
env->CallObjectMethod(intent, intent_setComponent, component );
jclass context_class = env->GetObjectClass(g_activity_context);
jmethodID start_activity_method = env->GetMethodID(context_class,"startActivity",
"(Landroid/content/Intent;)V");
env->CallVoidMethod(g_activity_context, start_activity_method, intent);
// 这里你可以根据自己的需求启动 Activity
if(g_activity_context){
env->DeleteGlobalRef(g_activity_context);
}
if(package_name){
env->DeleteLocalRef(package_name);
}
if(mainactivity_name){
env->DeleteLocalRef(mainactivity_name);
}
}
命令行指令(CMakeLists.txt示例):
cmake_minimum_required(VERSION 3.4.1)
project(native-lib)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
add_library(native-lib
SHARED
src/native-lib.cpp )
target_include_directories(native-lib
PUBLIC
# your Android NDK include path ,example below.
${ANDROID_NDK}/sysroot/usr/include
)
find_library(
log-lib
log
)
target_link_libraries( # Specifies the target library.
native-lib
${log-lib})
操作步骤:
- 在
MainActivity
中加载 native-lib 并调用 JNI 方法nativeStart(Context)
。传递 activity context 。 - 在 JNI 中将 context 设置为全局变量,方便在合适的地方进行使用
- C++ 代码中实现业务逻辑并利用保存的 context 启动新的 Activity。
优点:
- 利用 Android 系统的环境,更容易使用相关 Android API。
- 无需处理抽象
Context
实例化的问题。
缺点:
- 需要至少有一个活动的Android Activity才能启动新的 Activity。
安全建议
无论是反射还是 ContextWrapper
方案,在JNI 调用过程中需要特别关注:
- 参数校验: 确保从 JNI 层接收的数据合法,防止数据异常导致应用崩溃。
- 线程安全: JNI 调用在多线程环境下,尤其需要考虑线程安全,比如防止
context
被并发访问。 - 资源释放: 在 C++ 中操作完 context 和 Intent 对象记得及时释放内存,避免内存泄漏。
选择哪种方案,取决于具体场景和你的偏好。如果只是简单地想在特定 Activity 触发的情况下启动一个新 Activity ,那么 Context 传递会是更好的选择; 如果你需要在更底层,没有 Context 的情况下操作, 可以采用反射机制,当然其复杂度也更高。每种方案都需要谨慎的实现和测试。