JNI多執行緒與全域性引用

鋸齒流沙發表於2017-12-13

之前的JNI學習文章《JNI異常處理和快取策略》中有介紹過全域性變數,在本文中將派上用用場,直接使用。

本次實戰主要是在C層開闢子執行緒,然後通過訪問java類,獲取得到UUID,並且列印出來。

具體步驟:

1、建立一個NDK專案,編寫native方法 NDKTest.java

public class NDKTest {

	public native static String getStrFromJNI();//測試

	public native void pthread();

	public native void init();

	public native void destroy();

	static {
		System.loadLibrary("myndk");
	}

}
複製程式碼

init也就是初始化,主要是獲取class,通過class獲取jmethodID等操作。

pthread:建立執行緒,訪問類的方法。

destroy:釋放資源

2、編寫方法獲取UUID

public class UUIDUtils {
	public static String get(){
		return UUID.randomUUID().toString();
	}
}
複製程式碼

3、通過javah獲得標頭檔案com_example_ndkfile_NDKTest.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndkfile_NDKTest */

#ifndef _Included_com_example_ndkfile_NDKTest
#define _Included_com_example_ndkfile_NDKTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_ndkfile_NDKTest
 * Method:    getStrFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_ndkfile_NDKTest_getStrFromJNI
  (JNIEnv *, jclass);

//初始化
JNIEXPORT void JNICALL Java_com_example_ndkfile_NDKTest_init
  (JNIEnv *, jobject);

//銷燬
JNIEXPORT void JNICALL Java_com_example_ndkfile_NDKTest_destroy
  (JNIEnv *, jobject);
//建立執行緒
JNIEXPORT void JNICALL Java_com_example_ndkfile_NDKTest_pthread
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

複製程式碼

4、建立.c檔案

5、專案右鍵--->Android Tools------->add native support

6、配置Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := myndk
LOCAL_SRC_FILES := myndk.c
LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)
複製程式碼

7、在myndk.c實現com_example_ndkfile_NDKTest.h函式。

我們知道每個執行緒都有獨立的JNIEnv,那麼如何獲取JNIEnv? 首先JavaVM 代表的是Java虛擬機器,所有的工作都是從JavaVM開始,可以通過JavaVM獲取到每個執行緒關聯的JNIEnv。 那麼又如何如何獲取JavaVM?

1)、在JNI_OnLoad函式中獲取,每次執行jni系統就會首先自動呼叫JNI_OnLoad函式。

2)、(*env)->GetJavaVM(env,&javaVM);

這裡我們通過JNI_OnLoad獲取:

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
	LOGI("%s","JNI_OnLoad");
	javaVM = vm;
	return JNI_VERSION_1_4;
}
複製程式碼

myndk.c:

#include "com_example_ndkfile_NDKTest.h"
#include <jni.h>
#include <stdio.h>
#include <pthread.h>
#include <android/log.h>
#include <unistd.h>

#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"test",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"test",FORMAT,##__VA_ARGS__);

JavaVM *javaVM;
jobject uuidutils_class_global;
jmethodID uuidutils_get_mid;

//動態庫載入時會執行
//相容Android SDK 2.2之後,2.2沒有這個函式
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
	LOGI("%s","JNI_OnLoad");
	javaVM = vm;
	return JNI_VERSION_1_4;
}

JNIEXPORT jstring JNICALL Java_com_example_ndkfile_NDKTest_getStrFromJNI
  (JNIEnv *env, jclass jcls){
	return (*env)->NewStringUTF(env,"hello formjni");
}


void* th_fun(void* arg){
	int i;
	for (i = 0; i < 5; i++) {
		JNIEnv* env;
		//關聯引數
		//JavaVMAttachArgs args = {JNI_VERSION_1_4, "my_thread", NULL};
		//(*javaVM)->AttachCurrentThread(javaVM,&env,&args);
		(*javaVM)->AttachCurrentThread(javaVM,&env,NULL);
		jobject uuid_jstr = (*env)->CallStaticObjectMethod(env,uuidutils_class_global,uuidutils_get_mid);
		const char* uuid_cstr = (*env)->GetStringUTFChars(env,uuid_jstr,NULL);
		LOGI("uuid:%s",uuid_cstr);
		//退出執行緒
		if(i == 4){
			goto end;
		}
		sleep(1);
	}
end:
	//取消關聯
	(*javaVM)->DetachCurrentThread(javaVM);
	pthread_exit((void*)0);

}

//初始化
JNIEXPORT void JNICALL Java_com_example_ndkfile_NDKTest_init
(JNIEnv *env, jobject jobj){
	//獲取class必須要在主執行緒中
	jclass uuidutils_class_tmp = (*env)->FindClass(env,"com/example/ndkfile/UUIDUtils");
	//建立全域性引用
	uuidutils_class_global = (*env)->NewGlobalRef(env,uuidutils_class_tmp);
	//獲取jmethodId也可以在子執行緒中
	uuidutils_get_mid = (*env)->GetStaticMethodID(env,uuidutils_class_global,"get","()Ljava/lang/String;");
}

//建立執行緒
JNIEXPORT void JNICALL Java_com_example_ndkfile_NDKTest_pthread
(JNIEnv *env, jobject jobj){
	//(*env)->GetJavaVM(env,&javaVM);
	//建立多執行緒
	pthread_t tid;
	pthread_create(&tid, NULL,th_fun,(void*)"NO1");
}


//銷燬
JNIEXPORT void JNICALL Java_com_example_ndkfile_NDKTest_destroy
(JNIEnv *env, jobject jobj){
	//釋放全域性引用
	(*env)->DeleteGlobalRef(env,uuidutils_class_global);

}

複製程式碼

1)、在初始化的時候還通過了(*env)->NewGlobalRef建立全域性引用,在銷燬的時候需要通過(*env)->DeleteGlobalRef釋放全域性引用。

2)、通過以下語句建立多執行緒,而th_fun方法就是執行在子執行緒中。

pthread_t tid; pthread_create(&tid, NULL,th_fun,(void*)"NO1");

呼叫

MainActivity:

public class MainActivity extends Activity {

	private TextView mTextView;
	private NDKTest ndktest;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mTextView = (TextView)this.findViewById(R.id.mytext);
		mTextView.setText(NDKTest.getStrFromJNI());
		
		ndktest = new NDKTest();
		ndktest.init();
		
		mTextView.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				ndktest.pthread();
			}
		});
		
	}
	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		ndktest.destroy();
	}
}
複製程式碼

注意: 需要在onCreate中呼叫init,在onDestroy呼叫destroy

執行結果:

JNI多執行緒

從結果中我們看到了,確實通過JNI多執行緒訪問java類獲取得到了UUID。

相關文章