NDK 开发中的几个重要知识点

NDK 开发中的几个重要知识点_第1张图片

该文章首发于微信公众号“字节流动”

本博客 NDK 开发系列文章:

  • NDK 编译的三种方式
  • NDK 开发中引入第三方静态库和动态库
  • NDK 开发中 Native 与 Java 交互
  • NDK POSIX 多线程编程
  • NDK Android OpenSL ES 音频采集与播放
  • NDK FFmpeg 编译
  • NDK FFmpeg 音视频解码
  • NDK 直播流媒体服务器搭建
  • NDK 直播推流与引流
  • NDK 开发中快速定位 Crash 问题
  • NDK 开发中 Native 方法的静态注册与动态注册
  • NDK 开发中的几个重要知识点

在 Native 层访问 Java 对象的一般步骤

在 Native 层中通过 JNI 可以自由地访问 Java 对象,访问 Java 对象一般分为 3 步。

    1. 通过 GetObjectClassFindClass 获取到 class 对象的引用;
    1. 通过 GetXXMethodIDGetXXFieldID 方法分别获取到 Java 对象方法和属性的访问 ID ;
    1. 通过 CallXXMethodGetXXFieldSetXXField 方法分别实现对 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 对象的引用。

在 Native 层中 Java 对象的引用类型

在 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 和 JavaVM 类型的作用域

JNIEnv 这个结构体比较特殊,主要提供 JNI 调用环境,JNIEnv 类型的变量是线程相关,即一个线程会对应一个 JNIEnv 变量,也就是说 JNIEnv 类型的指针不能在不同的线程中共用。

若要在一个子线程里访问 Java 对象,就需要获得对应 JNIEnv 类型变量的指针,通过 AttachCurrentThreadDetachCurrentThread 获取和释放。

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;
}

联系与交流

微信公众号
NDK 开发中的几个重要知识点_第2张图片
个人微信
NDK 开发中的几个重要知识点_第3张图片

你可能感兴趣的:(NDK,Android,NDK,开发)