JNI實用封裝


Android的官方文檔詳細的描述了JNI的使用,建議先閱讀官方文檔。

簽名(signature)

我們在調用這類Get<Static>MethodIDGet<Static><TYPE>FieldCall<Static><TYPE>Method接口的時候都需要填入signature,signature用於表示描述Java類型對應C/C++類型。基本類型使用單字符表示,結構體使用L + 包名 + 結構名 + ;表示,因爲JNI需要知道結構體的完整包名才能找到對應的類型。

類型 signature
boolean Z
byte B
char C
short S
int I
long J
float F
double D
object L
String Ljava/lang/String;
Context Landroid/content/Context;

引用

JNI的引用分爲三種Local References、Global References和Weak Global References,他們的關係可用簡單理解爲C++的堆、棧,引用。

  • Local References
    大部分JNI方法返回的引用類型都是local引用(例如FindClass),local引用僅僅在此native方法中有效,當native方法返回時此local引用會被自動釋放掉,也可以調用DeleteLocalRef手動釋放。local引用的個數是有限制的,所以建議當不使用的時候就手動釋放一下。
  • Global References
    Global引用在整個生命週期中都是有效的(直到手動釋放它),同樣它的引用個數也是有限的,所以在不需要的時候需要手動釋放一下。使用NewGlobalRef創建一個Global引用,使用DeleteGlobalRef刪除一個Global引用。
  • Weak Global References
    Weak Global如其名,所以在使用的時候需要判斷一下這個應用對象是否還可用IsSameObject(o, nullptr),同樣它的引用個數也是有限的,所以在不需要的時候需要手動釋放一下。使用NewWeakGlobalRef創建一個Weak Global引用,使用DeleteWeakGlobalRef刪除一個Weak Global引用。

JNIEnv

JNIEnv是線程相關的數據結構,每一個Java線程存儲一個對應的JNIEnv對象(pthread_setspecific),JNIEnv是Java和C/C++交互的橋樑。Native方法中的JNIEnv參數是從調用線程中獲取的,所以不同線程調用這個JNIEnv是不同的。

  • C/C++線程如何綁定JNIEnv
    C/C++創建的線程是不含JNIEnv,如果想要讓這個線程跟Java層交互,我們需要讓C/C++線程綁定一個JNIEnv。以下是WebRTC內部的一個實現,其實Android源碼裏面的實現也是一樣的。我簡單說明一下,當一個線程綁定了一個JNIEnv,我們可以通過GetEnv獲取對應的JNIEnv,當一個線程沒有綁定JNIEnv,我們可以通過AttachCurrentThread爲當前線程綁定一個JNIEnv。那麼當線程退出的時候我們如何釋放這個JNIEnv呢?通過pthread_setspecific把這個JNIEnv存到線程中,當線程退出的時候通過pthread_getspecific取出,然後釋放掉它就好了。
// Return a |JNIEnv*| usable on this thread or NULL if this thread is detached.

JNIEnv* GetEnv()

{

  void* env = nullptr;

  jint status = g_jvm->GetEnv(&env, JNI_VERSION_1_6);

  RTC_CHECK(((env != nullptr) && (status == JNI_OK)) ||

            ((env == nullptr) && (status == JNI_EDETACHED)))

      << "Unexpected GetEnv return: " << status << ":" << env;

  return reinterpret_cast<JNIEnv*>(env);

}



// Return thread ID as a string.

static std::string GetThreadId()

{

    char buf[21];  // Big enough to hold a kuint64max plus terminating NULL.

    RTC_CHECK_LT(snprintf(buf, sizeof(buf), "%ld", static_cast<long>(syscall(__NR_gettid))), sizeof(buf))

        << "Thread id is bigger than uint64??";

    return std::string(buf);

}



// Return the current thread's name.

static std::string GetThreadName() {

    char name[17] = {0};

    if (prctl(PR_GET_NAME, name) != 0)

        return std::string("<noname>");

    return std::string(name);

}



JNIEnv* AttachCurrentThreadIfNeeded()

{

    JNIEnv* jni = GetEnv();

    if (jni)

        return jni;

    RTC_CHECK(!pthread_getspecific(g_jni_ptr)) << "TLS has a JNIEnv* but not attached?";



    std::string name(GetThreadName() + " - " + GetThreadId());

    JavaVMAttachArgs args;

    args.version = JNI_VERSION_1_6;

    args.name = &name[0];

    args.group = nullptr;

    // Deal with difference in signatures between Oracle's jni.h and Android's.

#ifdef _JAVASOFT_JNI_H_  // Oracle's jni.h violates the JNI spec!

    void* env = nullptr;

#else

    JNIEnv* env = nullptr;

#endif

    RTC_CHECK(!g_jvm->AttachCurrentThread(&env, &args)) << "Failed to attach thread";

    RTC_CHECK(env) << "AttachCurrentThread handed back NULL!";

    jni = reinterpret_cast<JNIEnv*>(env);

    RTC_CHECK(!pthread_setspecific(g_jni_ptr, jni)) << "pthread_setspecific";

    return jni;

}

檢查異常

理論上來說調用大部分JNI接口都需要在調用之後判斷一下是否有錯誤,如果不檢查是否存在錯誤,那麼會在下一個調用的時候直接奔潰,不利於問題定位。一般來說這類接口都需要在調用之後判斷一下:Get<Static>MethodIDGet<Static><TYPE>FieldCall<Static><TYPE>MethodNewGlobalRefDeleteGlobalRefNewStringUTF等。

if (env->ExceptionCheck()) {
   env->ExceptionDescribe();
   env->ExceptionClear();
}

獲取枚舉字段

class內的其他class或者enum都使用$拼接

  • 通過字段名獲取
jclass c = env->FindClass("android/graphics/Bitmap$Config");
jfieldID id = env->GetStaticFieldID(c,"ARGB_8888", "Landroid/graphics/Bitmap$Config;");
jobject o = env->GetStaticObjectField(c, id);
  • 通過索引獲取
jclass c = env->FindClass("android/graphics/Bitmap$Config");
jmethodID m = env->GetStaticMethodID(c, "values", "()Landroid/graphics/Bitmap$Config;");
jobjectArray arr = static_cast<jobjectArray>(env->CallStaticObjectMethod(c, m));
jobject o = env->GetObjectArrayElement(arr, index);

獲取枚舉值名(字符串)

jclass c = env->FindClass("android/graphics/Bitmap$Config");
jmethodID m = env->GetStaticMethodID(c, "name", "()Ljava/lang/String;");
jstring name = reinterpret_cast<jstring>(env->CallObjectMethod(j_enum, m));

調用構造函數

構造函數的方法名是<init>

jclass c = env->FindClass(env, "org/stone/WebView");
jmethodID m = jni->GetMethodID(c, "<init>", "(JLjava/lang/String;Landroid/content/Context;II)V");
jobject o = env->NewObject(c, m , reinterpret_cast<intptr_t>(this), env->NewStringUTF("ThreadName", j_content, width, height));

調用靜態方法

jclass c = env->FindClass(env, "org/stone/Utility");
jmethodID m = jni->GetStaticMethodID(c, "DrawBitmap", "(JLandroid/graphics/Bitmap;Landroid/os/Handler;Landroid/view/View;)V");
env->CallStaticVoidMethod(c, m, reinterpret_cast<intptr_t>(this), j_bitmap, j_handler, j_view);

調用對象方法

jmethodID m = jni->GetMethodID(c, "goForward","()V");;
env->CallVoidMethod(o, m);

Java String轉換爲std::string

std::string JavaToStdString(JNIEnv* jni, const jstring& j_string)
{
  const jclass string_class = GetObjectClass(jni, j_string);
  const jmethodID get_bytes = GetMethodID(jni, string_class, "getBytes", "(Ljava/lang/String;)[B");
  const jstring charset_name = jni->NewStringUTF("ISO-8859-1");
  const jbyteArray j_byte_array = (jbyteArray)jni->CallObjectMethod(j_string, get_bytes, charset_name);
  const size_t len = jni->GetArrayLength(j_byte_array);
  std::vector<char> buf(len);
  jni->GetByteArrayRegion(j_byte_array, 0, len, reinterpret_cast<jbyte*>(&buf[0]));
  return std::string(buf.begin(), buf.end());
}

std::map轉換爲Java HashMap

jobject StdMapStringToJava(std::map<std::string, std::string> additional_http_headers)
{
    jclass c = jni->FindClass("java/util/HashMap");
    jobject o = jni->NewObject(c, jni->GetMethodID(c, "<init>", "()V"));
    jmethodID m = jni->GetMethodID(c, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
    for (const auto& it : additional_http_headers) {
        jni->CallObjectMethod(o, m, jni->NewStringUTF(it.first.c_str()), jni->NewStringUTF(it.second.c_str())); 
    }
    return o;
}

Java Map轉換爲std::map

std::map<std::string, std::string> JavaToStdMapStrings(JNIEnv* jni, jobject j_map)
{
    jclass map_class = jni->FindClass("java/util/Map");
    jclass set_class = jni->FindClass("java/util/Set");
    jclass iterator_class = jni->FindClass("java/util/Iterator");
    jclass entry_class = jni->FindClass("java/util/Map$Entry");
    jmethodID entry_set_method = jni->GetMethodID(map_class, "entrySet", "()Ljava/util/Set;");
    jmethodID iterator_method = jni->GetMethodID(set_class, "iterator", "()Ljava/util/Iterator;");
    jmethodID has_next_method = jni->GetMethodID(iterator_class, "hasNext", "()Z");
    jmethodID next_method = jni->GetMethodID(iterator_class, "next", "()Ljava/lang/Object;");
    jmethodID get_key_method = jni->GetMethodID(entry_class, "getKey", "()Ljava/lang/Object;");
    jmethodID get_value_method = jni->GetMethodID(entry_class, "getValue", "()Ljava/lang/Object;");

    jobject j_entry_set = jni->CallObjectMethod(j_map, entry_set_method);
    jobject j_iterator = jni->CallObjectMethod(j_entry_set, iterator_method);

    std::map<std::string, std::string> result;
    while (jni->CallBooleanMethod(j_iterator, has_next_method)) {
        jobject j_entry = jni->CallObjectMethod(j_iterator, next_method);
        jstring j_key = static_cast<jstring>(jni->CallObjectMethod(j_entry, get_key_method));
        jstring j_value = static_cast<jstring>(jni->CallObjectMethod(j_entry, get_value_method));
        result[JavaToStdString(jni, j_key)] = JavaToStdString(jni, j_value);
    }

    return result;
}

Native方法定義

native方法的定義規則是:Java_包名_方法名(JNIEnv*, jclass/jobject, ...),包名使用下劃線_劃分,靜態方法第二個參數是jclass類型,非靜態方法第二個參數jobject類型。Native方法的參數類型要和Java層保持一致,否則會出現野指針或者值不對的情況(根本原因是因爲參數的傳遞是通過棧傳遞的,比如Java傳遞的類型佔用8個字節,Native接收的類型佔用4個字節,那麼JVM會從棧上取4個字節強制轉換爲此類型,結果就是從這個參數及之後的參數內容都不對了)。

  • 靜態
extern "C" JNIEXPORT void Java_org_stone_Utility_DrawBitmap(JNIEnv* jni, jclass clz, int64_t func_ptr, jobject j_bitmap, int32_t x, int32_t y, int32_t width, int32_t height);
  • 非靜態
extern "C" JNIEXPORT void Java_org_stone_Utility_DrawBitmap(JNIEnv* jni, jobject obj, int64_t func_ptr, jobject j_bitmap, int32_t x, int32_t y, int32_t width, int32_t height);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章