该文章首发于微信公众号“字节流动”
本博客 NDK 开发系列文章:
在 Native 层中通过 JNI 可以自由地访问 Java 对象,访问 Java 对象一般分为 3 步。
GetObjectClass
或 FindClass
获取到 class 对象的引用;GetXXMethodID
、GetXXFieldID
方法分别获取到 Java 对象方法和属性的访问 ID ;CallXXMethod
、GetXXField
、SetXXField
方法分别实现对 Java 对象方法的调用和属性的访问。一个简单的例子。
package com.byteflow.framework;
public interface NDKCallback {
void onProcessorCallback(int processorUId, int errorCode, Object objResult);
}
//获取到 class 对象的引用
jclass callbackClass = env->GetObjectClass(callbackObj);
//获取到 Java 对象方法的访问 ID
mOnProcessorCallbackMID = env->GetMethodID(callbackClass, "onProcessorCallback", "(IILjava/lang/Object;)V");
//调用 Java 对象的方法
env->CallVoidMethod(callbackObj, mOnProcessorCallbackMID, processorUID, errCode, objResult);
利用 JNI 访问 Java 对象时,需要注意值传递和引用传递的区分,如 jint、jchar、jdouble 等基本类型是值传递,而 jstring、jobject 等属于引用传递。
另外,注意区分 FindClass 和 GetObjectClass 两个 JNI 方法的使用。FindClass 只需要完整的类名便可获得 class 对象的引用,而 GetObjectClass 通过 JNI 传入的一个 Java 对象的引用来获取 class 对象的引用。
在 JVM 中 Java 对象的引用类型分为 “强、软、弱、虚” ,我们常用的一般是弱引用,而在 Native 层中 Java 对象的引用类型一般分为 3 种,即 LocalReference 、GlobalReference 、WeakGlobalReference 。
LocalReference 称为本地引用或局部引用,JNI 传入的 jobject 以及利用 JNI 函数创建的临时 jobject 对象一般是本地引用,其特点是一旦 JNI 调用完成,jobject 对象就会被回收掉,但可能不会被立即回收,需要注意其生命周期,强制立即回收调用 env->DeleteLocalRef(obj);
。
GlobalReference 称为全局引用,其特点是如果不主动释放,在进程生命周期里其对应的对象一直不会被回收。由此可见,全局引用若使用不当容易造成内存泄漏,全局引用的使用和释放应成对出现:
//创建
env->NewGlobalRef(g_obj);
...
//释放
env->DeleteGlobalRef(g_obj);
其使用场景是,在 JNI 调用完成后,想要继续使用 jobject 对象,需要将其设置为 GlobalReference 。
WeakGlobalReference 称为弱全局引用,其特点是在程序运行期间,该引用对应的对象随时可能会被 GC 回收(如内存不足时),需要谨慎使用。
JNIEnv 这个结构体比较特殊,主要提供 JNI 调用环境,JNIEnv 类型的变量是线程相关,即一个线程会对应一个 JNIEnv 变量,也就是说 JNIEnv 类型的指针不能在不同的线程中共用。
若要在一个子线程里访问 Java 对象,就需要获得对应 JNIEnv 类型变量的指针,通过 AttachCurrentThread
、DetachCurrentThread
获取和释放。
JavaVM 类型的变量是进程相关,即一个 Java 虚拟机对应一个 JavaVM 类型的变量,通过 env->GetJavaVM(&g_jvm);
可获取当前进程 JavaVM 的指针。
一个简单的例子。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public void callbackFromJNI(int errCode, Object result) {
Log.d(TAG, "callbackFromJNI() called with: errCode = [" + errCode + "], result = [" + result + "]");
}
}
//全局变量
JavaVM *g_jvm = NULL;
jobject g_obj = NULL;
void *posix_run(void* arg)
{
JNIEnv *env;
jclass cls;
jmethodID mid;
//Attach 当前线程获取 JNIEnv
if(g_jvm->AttachCurrentThread(&env, NULL) != JNI_OK)
{
return NULL;
}
//获取 class 对象
cls = env->GetObjectClass(g_obj);
//获取 MethodID
mid = env->GetMethodID(cls, "callbackFromJNI", "(ILjava/lang/Object;)V");
//调用对象方法
env->CallVoidMethod(cls, mid , 0, NULL);
//Detach 当前线程
g_jvm->DetachCurrentThread();
return NULL;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_byteflow_ndk_MainActivity_stringFromJNI(JNIEnv* env, jobject obj) {
//获取 JavaVM
env->GetJavaVM(&g_jvm);
//创建对象的全局引用
g_obj = env->NewGlobalRef(obj);
pthread_t tid;
//创建子线程
pthread_create(&tid, NULL, &posix_run, NULL);
//阻塞主线程等待子线程结束
pthread_join(tid, NULL);
//释放对象的全局引用
env->DeleteGlobalRef(g_obj);
return NULL;
}