Android 開発における JNI の包括的な分析


序文

JNI (Java Native Interface、Java ローカル インターフェイス) は、Java プラットフォームで定義された標準ローカル プログラミング インターフェイスのセットです。JNI を使用すると、Java コードをネイティブ コード (特定のハードウェアやオペレーティング システムに依存する C/C++ によって実装されたコードなど) と相互運用できます。つまり、Java コードはネイティブ コードを呼び出すことができ、ネイティブ コードは Java コードも呼び出すことができます。JNIを介してネイティブコードを呼び出すことで、Java言語では実現できない機能を実現できます。Android プラットフォームでは、Dalvik 仮想マシンは JNI によって定義されたインターフェースを実装します。

概要: JNI は、Java とローカルのネイティブ コードの間の架け橋です


1. Android システムにおける JNI の位置付け

Android は階層化されたアーキテクチャを採用しています。上位のアプリケーション レイヤーとアプリケーション フレームワーク レイヤーは主に Java 言語で開発され、下位のレイヤーは Linux カーネルを実行し、さまざまなコア ライブラリとサードパーティ ライブラリをカーネルの上に統合してすべてのリソースを提供します。必要なサービスは、C および C++ 言語で開発されています。これら 2 つの部分をつなぐリンクが JNI です。

ここに画像の説明を挿入
図からわかるように、JNI はローカル コード ライブラリを直接呼び出すことができ、Dalvik 仮想マシンを介してアプリケーション レイヤーおよびアプリケーション フレームワーク レイヤーとのやり取りを実現できます。Android JNI 部分のコードは、主に Android アーキテクチャの上位 2 つの層に配置されます。
□アプリケーション層: NDK を使用して開発され、主に標準の JNI プログラミング モデルを使用して実装されます。
□アプリケーション フレームワーク層: Android は一連の JNI プログラミング モデルを定義し、関数登録方式を使用して、標準の JNI プログラミング モデルの不足を補います。
Android アプリケーション フレームワーク レイヤーの JNI 部分はモジュールに従って編成され、さまざまなモジュールがさまざまな共有ライブラリにコンパイルされて、上位レイヤーにさまざまなサービスが提供されます。
NDK与JNI的区别:NDK是为便于开发基于JNI的应用而提供的一套开发和编译工具集;而JNI则是一套编程接口,可以运用在应用层,也可以运用在应用框架层,以实现Java代码与本地代码的互操作。

JNI プログラミング モデルの構造は非常に明確で、次の 3 つの手順に要約できます。

  1. Java レイヤーはネイティブ メソッドを宣言します。
  2. JNI 層は、Java 層によって宣言されたネイティブ メソッドを実装し、JNI 層は、基礎となるライブラリを呼び出したり、Java 層メソッドをコールバックしたりできます。この部分は、システムがロードする動的ライブラリ (SO ファイル) にコンパイルされます。
  3. JNI レイヤー コードのコンパイル後に生成された共有ライブラリを読み込みます。

二、JNI フレームワーク層のインスタンス分析

Androidアプリの開発では、アプリフレームワーク層のandroid.util.Log.javaが提供するJavaインターフェースを呼び出してログシステムを利用するのが一般的です。たとえば、次のコードを記述してログを出力します。

Log.d(TAG,"debug 1og");

この Java インターフェイスは、実際には JNI を介してシステム ランタイム ライブラリ (つまり、ローカル ライブラリ) を呼び出し、最後にカーネル ドライバーのロガーを呼び出して、ログをカーネル空間に書き込みます。Android では、ログ システムは非常に一般的であり、その JNI 構造は非常に単純であるため、今日はそれを例として使用して、JNI の特定の呼び出しプロセスを分析します。

1. ログ システム Java レイヤー分析

まず、ログ システムの Java 層の部分を見てください。Log.java ファイルを開くと、ここでは isLoggable と println_native の 2 つのネイティブ メソッドのみが定義されていることがわかります。コードは次のとおりです (例)。

package android.util; 
public final class Log ( 
......

public static int d(String tag,String msg)(
//使用Native方法打印日志.LOG_ID_MAIN表示日志ID,有4种:main、radio.events、system 
	return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
//声明Native方法isLoggable
public static native boolean isLoggable(String tag, int level); 

......

//声明Native方法printin_native
/**@hide*/ 
public static native int printin_native(int bufID,int priority, String tag, String msg); 
)

Java レイヤーが行う必要があることは、それと同じくらい単純です。メソッドを実装せずにネイティブとして宣言するだけでよく、コンパイル エラーなしで直接呼び出すことができます。

2. ログシステムの JNI 層

JNI 層は、Java 層のメソッドを実装する上で最も重要な部分です。Log クラスの場合、対応する JNI ファイルは android_util_Log.cpp です。コードは次のとおりです (例)。

#include "jni.h” //符合JNI规范的头文件,必须包含进来#include "JNIHelp.h"
//Android为更好地支持JNI提供的头文件 
#include "utils/misc.h"
#include "android_runtime/AndroidRuntime.h" 

/*这里便是Java层声明的isLoggable方法的实现代码。
 *JNI方法增加了JNIEnv和jobject两个参数,其余参数和返回值只是将Java参数映射成JNI
 *的数据类型,然后通过调用本地库和JNIEnv提供的JNI函数处理数据,最后返回给Java层*/

static jboolean isLoggable(const char* tag, jint level) {
    
    
    return __android_log_is_loggable(level, tag, ANDROID_LOG_INFO);
}

static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
    
    
    if (tag == NULL) {
    
    
        return false;
    }
	//这里调用了JNI函数
    const char* chars = env->GetStringUTFChars(tag, NULL);
    if (!chars) {
    
    
        return false;
    }

    jboolean result = false;
    if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
    
    
        char buf2[200];
        snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %zu characters\n",
                chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));

        jniThrowException(env, "java/lang/IllegalArgumentException", buf2);
    } else {
    
    
    	//这里调用了本地库函数
        result = isLoggable(chars, level);
    }

    env->ReleaseStringUTFChars(tag, chars);
    return result;
}
//以下是Java层声明的printin_Native方法的实现代码
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
        jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
    
    
    const char* tag = NULL;
    const char* msg = NULL;

    if (msgObj == NULL) {
    
    
        jniThrowNullPointerException(env, "println needs a message");
        return -1;
    }

    if (bufID < 0 || bufID >= LOG_ID_MAX) {
    
    
        jniThrowNullPointerException(env, "bad bufID");
        return -1;
    }

    if (tagObj != NULL)
        tag = env->GetStringUTFChars(tagObj, NULL);//这里调用了JNI函数
    msg = env->GetStringUTFChars(msgObj, NULL);

    int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);

    if (tag != NULL)
        env->ReleaseStringUTFChars(tagObj, tag);//这里调用了JNI函数释放资源
    env->ReleaseStringUTFChars(msgObj, msg);

    return res;
}

ここからわかるように、JNI層の実装方法は、Java層が宣言したメソッドと一定のルールに従ってマッピングするだけで、ネイティブライブラリ関数が提供するJNI関数やJNIEnvを使って応答することができます。 Java 層の呼び出し。

3. LogシステムのJNIメソッド登録

JNI 層は、Java 層によって宣言された Native メソッドを実装しています。しかし、これら 2 つの方法はどのように関連しているのでしょうか。または android_util_Log.cpp のソース コード:

/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] = {
    
    
    /* name, signature, funcPtr */
    {
    
     "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    {
    
     "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};

ここでは、JNINativeMethod 型のデータを格納する配列 gMethods が定義されています。JNINativeMethod の定義は、jni.h ファイルにあります。

typedef struct {
    
    
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

JNINativeMethod は、宣言された関数と実装された関数の間の 1 対 1 の対応を保存する構造体型であることがわかります。
gMethods の最初の文を見てください。

( "isLoggable", "(Ljava/lang/String;I)2", (void*) android_util_Log_isLoggable } 
  • Java 層によって宣言されたネイティブ関数は、isLoggable という名前です。
  • Java 層によって宣言されたネイティブ関数の署名は (Ljava/lang/String;I)Z です。
  • JNI レイヤーの実装メソッドへのポインタは (void*)android_util_Log_isLoggable です。

これまで、Java 層のメソッドと JNI 層のメソッドの対応関係を示してきました。しかし、仮想マシンにこの対応をどのように伝えるのでしょうか? android_util_Log.cpp のソース コードの分析を続けます。次のセクションに移動します。

int register_android_util_Log(JNIEnv* env)
{
    
    
    jclass clazz = FindClassOrDie(env, "android/util/Log");

    levels.verbose = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "VERBOSE", "I"));
    levels.debug = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "DEBUG", "I"));
    levels.info = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "INFO", "I"));
    levels.warn = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "WARN", "I"));
    levels.error = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ERROR", "I"));
    levels.assert = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ASSERT", "I"));

    return RegisterMethodsOrDie(env, "android/util/Log", gMethods, NELEM(gMethods));
}

この関数は最後に AndroidRuntime::registerNativeMethods を呼び出し、gMethods 配列、Java レイヤー クラス名、および JNIEnv 型のポインターを registerNativeMethods に渡します。次に、AndroidRuntime.cpp を開き、registerNativeMethods 関数を見つけます。

/*
 * Register native methods using JNI.
 */
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    
    
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

次に、JNIHelp.cpp を開きます

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    
    
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);

    ALOGV("Registering %s's %d native methods...", className, numMethods);

    scoped_local_ref<jclass> c(env, findClass(env, className));
    if (c.get() == NULL) {
    
    
        char* msg;
        asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className);
        e->FatalError(msg);
    }

    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
    
    
        char* msg;
        asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
        e->FatalError(msg);
    }

    return 0;
}

ここでは、JNIEnv の RegisterNatives メソッドが最後に呼び出され、gMethods に格納されている関連情報が仮想マシンに渡され、RegisterNatives が jni.h で見つかります。

jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,jint);

その機能は、jclass で指定されたクラスにローカル メソッドを登録することで、Java 層と jni 層の対応関係を微妙に取得し、Java と C/C++ 間の相互運用性を実現することです。

プロセス全体が基本的に明確になったので、register_android_util_Log 登録メソッドが呼び出される場所を見てみましょう。
Android システムが起動すると、init プロセスが Zygote を起動すると、仮想マシンが作成され、システムの jni メソッドが登録されることがわかります. 主なコードは次のとおりです。

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    
    
    ... ...
    
    if (startVm(&mJavaVM, &env, zygote) != 0) {
    
    
        return;
    }
    onVmCreated(env);

    /*
     * Register android functions.
     */
    if (startReg(env) < 0) {
    
    
        ALOGE("Unable to register all android natives\n");
        return;
    }

    ... ...

startReg を呼び出すもの:

/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
    
    
    /*
     * This hook causes all future threads created in this process to be
     * attached to the JavaVM.  (This needs to go away in favor of JNI
     * Attach calls.)
     */
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);

    ALOGV("--- registering native functions ---\n");

    /*
     * Every "register" function calls one or more things that return
     * a local reference (e.g. FindClass).  Because we haven't really
     * started the VM yet, they're all getting stored in the base frame
     * and never released.  Use Push/Pop to manage the storage.
     */
    env->PushLocalFrame(200);

    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
    
    
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);

    //createJavaThread("fubar", quickTest, (void*) "hello");

    return 0;
}

次に、関数に gRegJNI パラメーターがある register_jni_procs を呼び出し、それが定義されている場所を調べます。

static const RegJNIRec gRegJNI[] = {
    
    
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    ... ...
    REG_JNI(register_android_util_Log),
    ... ...

案の定、これには register_android_util_Log 関数が含まれているため、システムの起動時に Log システムの jni が登録されます。
JNIEnv是一个指针,指向一个线程相关的结构,该结构维护了一个数组,该数组每个元素指向一个JNI函数,通过JNIEnv操作JNI函数,访问虚拟机,进而操作java对象

上記は伝説的な JNI 動的登録方法で、主に Android のフレームワーク層での JNI 登録に使用されます。

3. Java で JNI 実装メソッドを呼び出す

1. Javaデータ型とJNIデータ型の変換

  1. 基本的な型変換関係
    Java で jni のネイティブ メソッドを呼び出すことによって渡されるパラメーターは Java 型であり、これらのパラメーターは、図に示すように、JNI レイヤーによって認識される前に、Dalvik 仮想マシンによって JNI 型に変換される必要があります。
    Java タイプ JNI タイプ 語長
    ブール値 jboolean 8
    バイト jバイト 8
    チャー jchar 16
    短い jshort 16
    整数 ジント 32
    長さ 長さ 64
    浮く jfloat 32
    ダブル jdouble 64
    空所 空所 -

使いやすくするために、意図的に次のように定義されています。

# define JNI_FALSE 0
# define JNI_TRUE 1
typedef jint jsize;//jsize整数类型表示大小
  1. 参照型の変換関係
    JNI の参照型は、9 つ​​の配列型と、jobject、jclass、jstring、jthrowable の 4 型が定義されており、それらの継承関係は次のとおりです
    JNI型の継承関係
    Java タイプ JNI タイプ
    java.lang.Class jクラス
    java.lang.String jstring
    java.lang.Throwable jthrowable
    Object[]、boolean[]、byte[]、char[]、short[]、int[]、long[]、float[]、double[] jobjectArray、jbooleanArray、jbyteArray、jcharArray、jshortArray、jintArray、jlong​​Array、jfloatArray、jdoubleArray
    java.lang.Object ジョブジェクト

2. JNI メソッドの命名規則

Log システムでは、JNI の実装方法と Java の宣言方法が異なります。たとえば、Java 層によって宣言されたネイティブ メソッド名は isLoggable ですが、対応する NI 実装メソッドのメソッド名は android_util_Log_isLoggable です。データ型の対応関係に加えて、メソッド名の対応関係もあることがわかります。

JNI インターフェース ポインターは、JNI 実装メソッドの最初のパラメーターであり、その型は JNIEnv です。2 番目のパラメーターは、ネイティブ メソッドが静的か非静的かによって異なります。非静的ネイティブ メソッドの 2 番目のパラメータは Java オブジェクトへの参照ですが、静的ネイティブ メソッドの 2 番目のパラメータはその Java クラスへの参照です。残りのパラメーターは、Java メソッドのパラメーターに対応します。
JNI 仕様では、JNI 実装メソッドの命名規則が規定されています。メソッド名は、次の部分で構成されます。

  • javaプレフィックス
  • 完全修飾クラス名
  • アンダースコア (_) セパレーター
  • 最初のパラメーター JNIEnv* env を追加します
  • 2 番目のパラメータ jobject を追加します
  • 他のパラメータはタイプ別にマップされます
  • 戻り値は型ごとにマッピングされます

引き続きログ システムを例に説明します。Java の一部のメソッドは、次のように宣言されています。

public static native boolean isLoggable(String tag,int level);

JNI のいくつかのメソッドは、次のように実装されています。

static jboolean android_util_Log_isLoggable(JNIEnv*env,jobject clazz,jstring tag,jint level).....)

Log システムの NI 実装メソッドを見ると、Android が JNI のメソッド命名規則に厳密に従っていないことがわかります。
Android在框架层采用函数注册的方式,建立Java层声明方法与JNI层实现方法之间的对应关系,可以不遵守上述命名规则。

ここでの命名規則に厳密に従って登録する、つまり静的登録. 通常、NDK を開発する場合は、この方法を使用する方が便利です.

3. JNI メソッド署名規則

データ型間の対応関係により、JNI は Java 型を正しく識別して変換できます。では、JNI はどのようにして Java メソッドを識別するのでしょうか? Java はメソッドのオーバーロードをサポートしており、メソッドは関数名だけでは一意に決定できません。そのため、JNI は一連の署名規則を提供し、文字列を使用してメソッドを一意に決定します。そのルールは次のとおりです。

(参数1类型签名参数2类型签名…参数n类型签名)返回值类型签名 //中间没有空格

型シグネチャの規則は次のとおりです。

Java タイプ タイプ署名 Java タイプ タイプ署名
ブール値 Z 長さ J
バイト B 浮く
チャー ダブル D
短い S 親切 L 完全修飾クラス名。
整数 配列 [要素型シグネチャ

gMethods のコードを思い出してください。

static JNINativeMethod gMethods[] = {
    
    
    /* name, signature, funcPtr */
    {
    
     "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    {
    
     "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};

isLoggable には 2 つのパラメーターがあり、1 つは String 型、もう 1 つは int 型で、戻り値は boolean であることがわかります。

4. JNI による Java オブジェクトの操作

前述のように、JNI は Java と Native の間のブリッジであり、相互運用が可能ですが、Java 層は Native メソッドを呼び出すということですが、Native 層はどのように Java 層を呼び出したり、操作したりするのでしょうか? 次に、JNI メソッド関数の第 2 パラメーター jobject に注意する必要があります。

1. Java オブジェクトにアクセスする

ジョブジェクトを操作するとは, このオブジェクトにアクセスして, その変数やメソッドを操作することです. JNIが提供するクラスやオブジェクトの操作関数は数多くありますが, よく使われるのはFindClassとGetObjectClassの2つで, CとC++では関数プロトタイプが異なります.
C++ の関数プロトタイプは次のとおりです。

jclass FindClass(const char* name); //查找类信息
jclass GetObjectClass(jobject obj); //返回对象的类

C の関数プロトタイプは次のとおりです。

jclass (*Findclass)(JNIEnv,const char);
jclass (*GetObjectClass)(JNIEnv*, jobject);

Log システムが Java オブジェクトでどのように動作するかを見ることができます。android_util_Log.cpp を開き、register_android_util_Log 関数を見つけます。

int register_android_util_Log(JNIEnv* env){
    
    
	jclass clazz = env->FindClass("android/util/Log");
	... ...
}

検索するクラスの完全修飾クラス名を FindClass に渡すだけで (パスを "/" で区切ります)、メソッドは jclass オブジェクトを返すため、このクラスのメソッドと変数を操作できます。

2. メンバー変数とメソッドの操作

上記で取得したクラスの情報クラズ。これにより、その変数とメソッドを操作できます

levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));

ここで、DEBUG はアクセスする Java ドメインの名前で、I は Java ドメインの型シグネチャです。GetStaticFieldID の関数プロトタイプは次のとおりです。

jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)

この関数は、Java メンバー変数を表す jfieldID を返します。最後に、jfieldID を GetStaticIntField メソッドに渡して、Java 層のメンバー変数 DEBUG の値である 3 を取得します。Log.java を開いて、変数 DEBUG の値が 3 であるかどうかを確認できます。
フィールドとメソッドを操作するために JNI が提供する関数を次の表に示します。

オブジェクト変数にアクセスする インスタンスメソッドを呼び出す 静的変数にアクセスする 静的メソッドを呼び出す
GetFieldID GetMethodID GetStaticFieldID GetStaticMethodID
Get<Type>フィールド Call<Type>メソッド GetStatic<Type>フィールド CallStatic<Type>メソッド
Set<Type>フィールド CallNonvirtual<Type> メソッド SetStatic<Type>フィールド -

3. グローバル参照、弱いグローバル参照、ローカル参照

Java オブジェクトのライフ サイクルは仮想マシンによって管理されます. 仮想マシンは内部のオブジェクトの参照カウントを保持します. オブジェクトの参照カウントが 0 の場合, オブジェクトはガベージ コレクターによって回収され, メモリが消費されます.リリースされました。Java オブジェクトでネイティブ メソッドを使用すると、オブジェクトのライフ サイクルにどのような影響があるでしょうか。この質問に答える前に、ログ システムの例を見てみましょう。コードは以下のように表示されます:

//static jobject clazz_ref1 = NULL;
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz,jstring tag,jint level){
    
    
	//clazz_ref1 = clazz;
	//static jobject clazz_ref2 = NULL;
	//clazz_ref2 = clazz;
	if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY _KEY_MAX)(
		//异常处理代码
		... ...
	}else{
    
    
		result = isLoggable(chars, level);
	}
	
}

上記のコメントアウトされたコードは、2 つの方法で Java オブジェクトを参照しています.仮想マシンは、この記述形式でこのオブジェクトに参照カウントを正しく追加できますか? ? ? 答えはノーです。仮想マシンはそのカウントを正しく増やすことができず、clazz オブジェクトがリサイクルされる可能性があり、その後 clazz_ref1 または clazz_ref2 がワイルド ポインターを参照する可能性があります。

幸いなことに、JNIEnv は解決策を提供してくれました。ローカル参照、グローバル参照、弱いグローバル参照です。

  1. ローカル参照: 参照カウントを増やすことができます。アクションの範囲はこのスレッドであり、ライフ サイクルはネイティブ コールです。ローカル参照には、ほとんどの JNI 関数、ネイティブ メソッドの戻り値、およびパラメーターによって作成された参照が含まれます。A local reference is only valid in the thread that created its Native method, and is only valid in one call of the Native method. メソッドが戻った後、仮想マシンによって再利用されます (C のローカル変数とは異なります。返却後すぐにリサイクル)。
  2. グローバル参照: 参照カウントを増やすことができます。アクションの範囲は、マルチスレッド、複数のネイティブ メソッド、ライフ サイクルから明示的なリリースまでです。グローバル参照は、JNI 関数 NewGlobalRef によって作成され、DeleteGlobalRef によって解放されます。プログラマーが明示的に解放しない場合、ガベージ コレクションは行われません。
  3. 弱いグローバル参照: 参照カウントを増やすことはできません。アクションの範囲は、マルチスレッド、複数のネイティブ メソッド、ライフ サイクルから明示的なリリースまでです。ただし、対応する Java オブジェクトのライフ サイクルは依然として仮想マシンに依存します。つまり、弱いグローバル参照が解放されていなくても、それが参照する Java オブジェクトは解放されている可能性があります。弱いグローバル参照は、JNI 関数 NewWeakGlobalRef によって作成され、DeleteWeakGlobalRef によって解放されます。弱いグローバル参照の利点は、オブジェクトのリサイクルを妨げずにオブジェクトを保存できることです。
    IsSameObject函数用来判断弱引用对应的对象是否已经被回收,方法是用弱全局引用和NULL进行比较,如果返回JNI_TRUE,则说明弱全局引用指向的对象已经被回收。
    したがって、上記のコードは次のように変更できます。
//static joject g_clazz_ref = NULL;
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz,jstring tag,jint level){
    
    
	//g_class_ref = env->NewGlobalRef(clazz);
	if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY _KEY_MAX)(
		//异常处理代码
		... ...
	}else{
    
    
		result = isLoggable(chars, level);
	}
}
//在不使用该类的时候显式删除
env->DeleteGlobalRef(g_clazz_ref)

五、JNI例外処理

JNI には、例外をチェックする 2 つの方法が用意されています。

  1. 最後の JNI 関数呼び出しの戻り値が NULL かどうかを確認します
  2. JNI 関数 ExceptionOccurred() を呼び出して、例外が発生したかどうかを判断します。

例外を処理するには、次の 2 つの方法もあります。

  1. ネイティブ メソッドはすぐに戻ることを選択でき、例外はネイティブ メソッドを呼び出す Java コードでスローされるため、Java レイヤーはそれをキャッチする必要があります。
  2. Native メソッドは ExceptionClear() を呼び出して例外をクリアし、独自の例外処理コードを実行します。

例外をチェックして処理するために JNI が提供する関数を次の表に示します。

JNI例外処理関数 機能説明
投げる 既存の例外をスローします
スローニュー 新しい例外をスローする
例外が発生しました 例外が発生したかどうかを判断し、例外への参照を取得する
例外チェック 例外が発生するかどうかを判断する
ExceptionDescribe 例外スタック情報
例外クリア 未処理の例外をクリアする
致命的な誤り 重大なエラー、終了

注意异常出现后,Native相关代码必须先检查清除异常,然后才能进行其他的JNI函数调用。当有异常未被清除时,只有以下JNI异常处理函数可被安全地调用:ExceptionOccurred()、ExceptionDescribe()、ExceptionClear()
次に、ソース コードの分析に戻りましょう。

static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
    
    
    if (tag == NULL) {
    
    
        return false;
    }

    const char* chars = env->GetStringUTFChars(tag, NULL);
    if (!chars) {
    
    
        return false;
    }

    jboolean result = false;
    if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
    
    
        char buf2[200];
        snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %zu characters\n",
                chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));

        jniThrowException(env, "java/lang/IllegalArgumentException", buf2);
    } else {
    
    
        result = isLoggable(chars, level);
    }

    env->ReleaseStringUTFChars(tag, chars);
    return result;
}

jniThrowException 関数定義を見つけます。

extern "C" int jniThrowException(C_JNIEnv* c_env, const char* className, const char* msg) {
    
    
    JNIEnv* env = reinterpret_cast<JNIEnv*>(c_env);
    jclass exceptionClass = env->FindClass(className);

    if (exceptionClass == NULL) {
    
    
        ALOGD("Unable to find exception class %s", className);
        /* ClassNotFoundException now pending */
        return -1;
    }

    if (env->ThrowNew(exceptionClass, msg) != JNI_OK) {
    
    
        ALOGD("Failed throwing '%s' '%s'", className, msg);
        /* an exception, most likely OOM, will now be pending */
        return -1;
    }

    env->DeleteLocalRef(exceptionClass);
    return 0;
}

ThrowNew を呼び出すと、それを呼び出した Java 層に新しい例外がスローされることがわかります。

6. 2 つの登録方法の比較

1.静的登録

長所: JNI 仕様に準拠し、便利でシンプルです。
短所: JNI 実装メソッドの面倒な命名規則に従う必要があります。共有ライブラリのコードをロードすると、アプリケーション層が頻繁に呼び出すと、効率が大幅に低下します。仮想マシンが共有ライブラリで JNI 実装メソッドを検索して配置する効率も影響を受けます。

2.動的登録

利点: 高効率、命名規則に従う必要がない、変更や移植が容易。
短所: 実装手順は静的登録以上のものであり、少し面倒です。

NDK 開発で動的登録を使用するには? 上記のコード:

static JNINativeMethod gmethods[] = {
    
    
    {
    
    "show", "()Ljava/lang/String;, (void*)Java_com_hision_jni_AppJniActivity_show}
};

static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods, int numMethods){
    
    
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
    
    
        return JNI_FALSE;
    }
    //调用JNIEnv提供的注册函数向虚拟机注册
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0){
    
    
        return JNI_FALSE;
    }
    return JNI_TRUE;

}

static int registerNatives(JNIEnv* env){
    
    
    if (!registerNativeMethods(env, "com/hision/jni/AppJniActivity", methods, sizeof(methods) / sizeof(methods[0]))) {
    
    
        return  JNI_FALSE;
    }
    return JNI_TRUE;
}

/*虚拟机执行System.loadLibrary("app_jni")后,进入libapp_jni.so后
*会首先执行这个方法,所以我们在这里做注册的动作*/

jint JNI_OnLoad (JavavM* vm, void* reserved){
    
    
    jint result = -1;
    JNIEnv* env = NULL;
    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4)){
    
    
        goto fail;
    }
    //最终调用(*env)->RegisterNatives,这跟Log系统是一样的
    if (registerNatives(env) != JNI_TRUE) {
    
    
        goto fail;
    }
    result = JNI_VERSION_1_4;

fail:
    return result;
}


要約する

この記事では、Log システムの JNI インスタンスを中心に、JNI テクノロジの主要な側面について説明します. この記事を注意深く読むと、JNI について十分に理解できるようになります。フレームワーク層のコード。記事に誤りがありましたら、訂正してください!

おすすめ

転載: blog.csdn.net/h397318057/article/details/130055402