attachBaseContext中调用loadLibrary时如何在JNI_OnLoad获取Context
问题描述
- 需求:需要在
JNI_OnLoad执行阶段获取Context对象,受场景限制,必须在attachBaseContext()方法内而非onCreate()方法中调用System.loadLibrary()加载native库 - 异常表现:参考公开实现编写代码后,在
attachBaseContext()中加载库时,JNI层获取到的context返回值为NULL;将System.loadLibrary()调用逻辑移动到onCreate()中时,同一段代码可正常执行
相关实现代码如下:
// CustomApplication.java public class CustomApplication extends Application { @Override protected void attachBaseContext(Context context) { super.attachBaseContext(context); System.loadLibrary("hook_main"); } // @Override // public void onCreate() { // super.onCreate(); // System.loadLibrary("hook_main"); // } }
// hook_main.c JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { // ... jclass activityThread = (*env)->FindClass(env, "android/app/ActivityThread"); jmethodID currentActivityThread = (*env)->GetStaticMethodID(env, activityThread, "currentActivityThread", "()Landroid/app/ActivityThread;"); jobject at = (*env)->CallStaticObjectMethod(env, activityThread, currentActivityThread); jmethodID getApplication = (*env)->GetMethodID(env, activityThread, "getApplication", "()Landroid/app/Application;"); // at和getApplication均不为NULL jobject context = (*env)->CallObjectMethod(env, at, getApplication); // 获取到的context为NULL return JNI_VERSION; }
根因分析
出现该问题的核心原因是Android Application生命周期执行顺序:
- 调用
super.attachBaseContext(context)时,仅完成了当前Application实例的base Context挂载,此时ActivityThread类中mApplication字段还未完成赋值,因此调用getApplication()方法必然返回NULL - 当生命周期走到
onCreate()时,ActivityThread已经完成了Application实例的绑定赋值,此时再加载库调用getApplication()就能拿到正常的实例对象
可行解决方案
方案1:主动传参(推荐,无兼容问题)
这是稳定性最高的实现方式,完全不依赖系统隐藏API,不受Android版本升级影响:
- 加载完native库后,主动定义一个native初始化方法,将
attachBaseContext拿到的Context参数直接传入JNI层 - JNI层收到Context后,创建全局引用保存即可使用
代码示例:
Java层修改:
public class CustomApplication extends Application { @Override protected void attachBaseContext(Context context) { super.attachBaseContext(context); System.loadLibrary("hook_main"); // 主动传入Context到native层 nativeHookInit(context); } /** * native初始化方法,传入Context */ private native void nativeHookInit(Context context); }
Native层对应实现:
// 全局保存Context引用 jobject g_global_context = NULL; JNIEXPORT void JNICALL Java_对应包名_CustomApplication_nativeHookInit(JNIEnv *env, jobject thiz, jobject context) { // 创建全局引用,避免局部引用随方法结束被回收 g_global_context = (*env)->NewGlobalRef(env, context); // 后续初始化逻辑直接使用g_global_context即可 }
方案2:反射获取已初始化的Context(不推荐,有兼容风险)
如果必须在JNI_OnLoad中直接获取、不修改Java层代码,可以反射ActivityThread内部的LoadedApk对象,从LoadedApk中拿到已经创建的ContextImpl实例。但该方式依赖系统隐藏API,Android 9及以上版本受隐藏API访问限制,不同厂商系统可能修改内部字段名,稳定性差,非必要不使用。
内容的提问来源于stack exchange,提问作者Zhou Hongbo
相关产品推荐
相关产品推荐

