20.Eclipse下Ndk開發(pthread開啟執行緒呼叫Java方法)

黑夜路口發表於2018-03-12

本專案最終的目的是在pthread執行緒中,呼叫Java一個工具類得到多個uuid,然後呼叫類中另一個方法彈出toast,實現在c中獲取安卓上下文物件Context

編譯native方法,生成標頭檔案的一系列過程不再贅述,直接上程式碼,都在註釋中

PosixUtils:

package com.example.ndk_pthread;

public class PosixUtils {
    
    static{
        System.loadLibrary("ndk_pthread");
    }
    /**
     * pthread開啟子執行緒前的一些初始化操作,比如獲取本類的jclass物件,生成需要的
     * 全域性引用等等,在子執行緒中無法獲取到類的jclass物件,就是這行程式碼,獲取class必須要在主執行緒中
     * jclass uuidutils_class_tmp = (*env)->FindClass(env,"com/example/ndk_pthread/UUIDUtils");
     */
    public native void init();
    /**
     * 執行一些善後的操作,比如init方法中生成的全域性引用的銷燬等等
     */
    public native void destroy();
    /**
     * 子執行緒操作
     */
    public native void pthread(); 
}

UUIDUtils:

package com.example.ndk_pthread;

import java.util.UUID;

import android.content.Context;
import android.widget.Toast;

public class UUIDUtils {

    public static String get(){
        return UUID.randomUUID().toString();
    }
    
    public static void showToast(Context context){
        Toast.makeText(context, "c端彈出了toast", 0).show();
    }
    
}

編譯生成的標頭檔案com_example_ndk_pthread_UUIDUtils.h:

package com.example.ndk_pthread;

import java.util.UUID;

import android.content.Context;
import android.widget.Toast;

public class UUIDUtils {

    public static String get(){
        return UUID.randomUUID().toString();
    }
    
    public static void showToast(Context context){
        Toast.makeText(context, "c端彈出了toast", 0).show();
    }
    
}

MainActivity:

package com.example.ndk_pthread;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

public class MainActivity extends Activity {

    private PosixUtils p;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        p = new PosixUtils();
        p.init();
    }

    public void start(View btn){
        p.pthread();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //頁面銷燬時同時銷燬c端的一些東西
        p.destroy();
    }

}

ndk_pthread.c

#include "com_example_ndk_pthread_PosixUtils.h"
#include <stdio.h>
#include <pthread.h>
#include <android/log.h>
#include <unistd.h>
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"renzhenming",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"renzhenming",FORMAT,##__VA_ARGS__);

JavaVM *javaVM;
jobject uuidutils_jcls;
jmethodID get_mid;
jmethodID toastId;
jobject jcontext;
//動態庫載入時會被呼叫執行,不需要我們手動呼叫
//相容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;
}

/**
 * 目標:呼叫UUIDUtils類中的方法得到一個uuid
 */
void thread_fun(void* arg){
    //得到這個類UUIDUtils,需要用到JNIEnv,但是這是一個子執行緒,每個執行緒都有獨立的JNIEnv,所以我們需要獲取到
    //這個執行緒的JNIEnv,通過JavaVM關聯當前執行緒,獲取當前執行緒的JNIEnv,(*javaVM)->AttachCurrentThread(javaVM,&env,NULL);
    //那麼就需要先獲取到JavaVM
    //如何獲取JavaVM?
    //1.在JNI_OnLoad函式中獲取
    //2.(*env)->GetJavaVM(env,&javaVM);

    //的到JavaVM後,會得到env並賦值給變數
    JNIEnv* env = NULL;
    (*javaVM)->AttachCurrentThread(javaVM,&env,NULL);

    char* no = (char*)arg;
    int i;
    for (i = 0; i < 5; ++i) {
        LOGI("thread %s, i:%d",no,i);
        jobject uuid = (*env)->CallStaticObjectMethod(env,uuidutils_jcls,get_mid);
        char* uuid_char = (*env)->GetStringUTFChars(env,uuid,NULL);
        LOGI("%s",uuid_char);
        if(i == 4){
            goto end;
        }
        (*env)->ReleaseStringUTFChars(env,uuid,uuid_char);
        sleep(1);
    }
end:
    //如果直接將這兩行寫在迴圈之後是有問題的,當i=4的時候,執行緒直接退出,導致沒有執行DetachCurrentThread,報異常
    (*javaVM)->DetachCurrentThread(javaVM);
    pthread_exit((void*)0);
}
/**
 * 當在JNI呼叫Android自帶的類時,經常需要傳入Context引數,那怎麼在JNI層獲取Context呢?
 * 我們知道Application和Activity是Context的子類,由於每個Activity對應的Context是不一樣的,
 * 所以一般情況下我們使用Application的Context,它在整個程式中只有一個例項。所以現在問題就變成了
 * 怎麼在JNI中獲取Application呢?
 * Android APP在啟動時會建立一個Activity Thread作為主執行緒,只要程式存活,這個執行緒就一直存在,
 * 所以我們可以考慮從Activity Thread中獲取Application,檢視Activity Thread的原始碼發現,
 * 它提供了一個方法可以獲取Application,如下:
 *
 * public Application getApplication() {
 *    return mInitialApplication;
 * }
 *
 * 也就是說我們只需要獲取到Activity Thread的物件即可,Activity Thread提供了一個靜態方法用於獲取其例項,如下:
 *
 * public static ActivityThread currentActivityThread() {
 *    return sCurrentActivityThread;
 * }
 *
 * 至此獲取Context的步驟已經很清晰了
 */
JNIEXPORT void JNICALL Java_com_example_ndk_1pthread_PosixUtils_init
(JNIEnv *env, jobject jobj){
    /**
     * 列印uuid
     */
    //獲取class必須要在主執行緒中
    jclass uuidutils_class_tmp = (*env)->FindClass(env,"com/example/ndk_pthread/UUIDUtils");
    //建立全域性引用
    uuidutils_jcls = (*env)->NewGlobalRef(env,uuidutils_class_tmp);
    //獲取jmethodId也可以在子執行緒中
    get_mid = (*env)->GetStaticMethodID(env,uuidutils_jcls,"get","()Ljava/lang/String;");
    /**
     * show toast
     */
    //獲取jmethodId也可以在子執行緒中
    toastId = (*env)->GetStaticMethodID(env,uuidutils_jcls,"showToast","(Landroid/content/Context;)V");

    //獲取Activity Thread的例項物件
    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);
    //獲取Application,也就是全域性的Context
    jmethodID getApplication = (*env)->GetMethodID(env,activityThread, "getApplication", "()Landroid/app/Application;");
    jobject context = (*env)->CallObjectMethod(env,at, getApplication);

    jcontext = (*env)->NewGlobalRef(env,context);
}


JNIEXPORT void JNICALL Java_com_example_ndk_1pthread_PosixUtils_destroy
(JNIEnv *env, jobject jobj){
    //釋放全域性引用
    (*env)->DeleteGlobalRef(env,uuidutils_jcls);
}

JNIEXPORT void JNICALL Java_com_example_ndk_1pthread_PosixUtils_pthread
  (JNIEnv *env, jobject jobj){
    LOGI("%s","begin");
    //獲取JavaVM的第一種方式,本專案中我們採用在JNI_OnLoad中獲取的方式
    //(*env)->GetJavaVM(env,&javaVM);
    pthread_t tid;
    pthread_create(&tid,NULL,thread_fun,(void*)"NO1");

    //呼叫Java函式show toast
    (*env)->CallStaticVoidMethod(env,uuidutils_jcls,toastId,jcontext);
}

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := ndk_pthread
LOCAL_SRC_FILES := ndk_pthread.c
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)


相關文章