主頁 > 移動端開發 > android jni (jni_onload方式)

android jni (jni_onload方式)

2020-12-28 10:54:47 移動端開發

一、簡述
JNI(Java Native Interface)Java本地介面,是為方便java呼叫C或者C++等本地代碼所封裝的一層介面,由于Java的跨平臺性導致本地互動能力不好,一些和作業系統相關的特性Java無法完成,于是Java提供了JNI專門用于和本地代碼互動,

NDK(Native Development Kit)本地開發工具鏈,是android提供的一個工具合集,幫助開發者快速開發C(或C++)的動態庫,并能自動將.so和java應用一起打包成apk,NDK集成了交叉編譯器(交叉編譯器需要UNIX或LINUX系統環境),并提供了相應的mk檔案隔離CPU、平臺、ABI等差異,開發人員只需要簡單修改mk檔案(指出“哪些檔案需要編譯”、“編譯特性要求”等),就可以創建出.so,

ABI(Application binary interface)應用程式二進制介面,不同的CPU 與指令集的每種組合都有定義的 ABI (應用程式二進制介面),一段程式只有遵循這個介面規范才能在該 CPU 上運行,所以同樣的程式代碼為了兼容多個不同的CPU,需要為不同的 ABI 構建不同的庫檔案,當然對于CPU來說,不同的架構并不意味著一定互不兼容,

armeabi設備只兼容armeabi;
armeabi-v7a設備兼容armeabi-v7a、armeabi;
arm64-v8a設備兼容arm64-v8a、armeabi-v7a、armeabi;
X86設備兼容X86、armeabi;
X86_64設備兼容X86_64、X86、armeabi;
mips64設備兼容mips64、mips;
mips只兼容mips;

Android的應用層的類都是以Java寫的,這些Java類編譯為Dex型式的位元組碼之后,必須依靠Dalvik虛擬機來運行,在Android中Dalvik虛擬機扮演很重要的角色.而Android中間件是由C/C++寫的,這些C/C++寫的組件并不是在Dalvik虛擬機上運行的,
一旦使用 JNI,JAVA 程式就喪失了 JAVA 平臺的兩個優點:
1、 程式不再跨平臺,要想跨平臺,必須在不同的系統環境下重新編譯本地語言部分,
2、 程式不再是絕對安全的,本地代碼的不當使用可能導致整個程式崩潰, 一個通用規則是,你應該讓本地方法集中在少數幾個類當中,這樣就降低了 JAVA 和 C 之間的耦合性,

二、java 與c/c++的互動
在java代碼中,可以通過loadLibrary要求VM裝載so檔案,java代碼一般如下形式:

public class jnitest {
    static {
        System.loadLibrary("jnitest");
    }
}

注:在代碼運行時將會在/system/lib/目錄下查找libjnitest.so檔案,將載入VM中進行呼叫c庫代碼,

實作本地函式注冊方法有兩種:
1、靜態方法
通過javah 生成.h 檔案,接著實作.cpp檔案的方式,再通過ndk-build方式生成so庫,最后在java代碼上宣告物件進行呼叫類中的方法,通過javah 生成的方法名稱 格式為

JNIEXPORT <回傳型別> JNICALL Java_<包名>_<類名>_<方法名>(JNIEnv *, jobject,<引數>); 
包名中的(.)用下劃線(_)代替

2、動態方法
JNI_OnLoad方式 當Android的VM執行到C組件(*so)里的System.loadLibrary()函式時,會產生一個Load事件,接著會去執行C組件里的JNI_OnLoad()函式,
JNI_OnLoad 函式中會執行兩個操作:
1)告訴java VM此C組件使用哪一個JNI版本,
2)JNI_OnLoad()來獲取JNIEnv,JNIEnv代表java環境,通過JNIEnv指標就可以對java端的代碼進行操作,

整個的流程:

1、首先進行重新 JNI_OnLoad函式,在函式中進行獲取vm的JNIEnv屬性資訊
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
	return -1;
}
2、其次 通過FindClas 找到對應的本地類
clazz = env->FindClass(className);
3、再接下來通過RegisterNatives 來注冊類中的方法
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
	return JNI_FALSE;
}

三、常用的jni開發知識
1、資料型別轉換
1.1、UTF-8 strings的轉換方法

// UTF-8 String (encoded to 1-3 byte, backward compatible with 7-bit ASCII)
// Can be mapped to null-terminated char-array C-string
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
 // Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.

void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
   // Informs the VM that the native code no longer needs access to utf.
   
jstring NewStringUTF(JNIEnv *env, const char *bytes);
   // Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.
   
jsize GetStringUTFLength(JNIEnv *env, jstring string);
   // Returns the length in bytes of the modified UTF-8 representation of a string.
   
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);
   // Translates len number of Unicode characters beginning at offset start into modified UTF-8 encoding 
   // and place the result in the given buffer buf.

注:
1)JNI 中定義了 jstring 型別代替Java中的String 型別,String 型別的傳遞相比基本型別要復雜, 因為在Java中String 是個物件, 而在c中, string 是個 char型別陣列,所以在傳遞String型別的時候, 在String(被jstring替換) 型別和 (char*)型別之間做轉化,
2)第三個引數isCopy 如果設為JNI_TRUE , 則回傳結果是原始Java String 的拷貝, 如果設為JNI_FALSE, 則直接回傳 Java String 的地址, 然而在這種情況下, 無法對string內容進行修改,JNI在運行時會試圖回傳指標, 如果可以的話,否則會回傳一個拷貝, 通常情況下, 我們并不關心底層string 的內容呢, 所以通常都設為 NULL,
1.2、unicode String型別

 // Unicode Strings (16-bit character)
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
   // Returns a pointer to the array of Unicode characters
   
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
   // Informs the VM that the native code no longer needs access to chars.
   
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);
   // Constructs a new java.lang.String object from an array of Unicode characters.
   
jsize GetStringLength(JNIEnv *env, jstring string);
   // Returns the length (the count of Unicode characters) of a Java string.
   
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);
   // Copies len number of Unicode characters beginning at offset start to the given buffer buf

注:NewStringUTF 為根據傳入的UTF-8字串創建一個Java String物件(即是要回傳java層的),而GetStringUTFChars 是根據java層的資料來獲得jni層(c底層)的資料,

jstring  NewString (JNIEnv *env, const jchar *unicodeChars,   jsize len);         
    功能:利用 Unicode 字符陣列構造新的 java.lang.String 物件,               
    引數:   env:JNI 介面指標,            
            unicodeChars:指向 Unicode 字串的指標,         
            len:Unicode 字串的長度,             
    回傳值: Java 字串物件,如果無法構造該字串,則為NULL,             
    拋出: OutOfMemoryError:如果系統記憶體不足,                  

 jsize  GetStringLength (JNIEnv *env, jstring string);            
    功能:回傳 Java 字串的長度(Unicode 字符數),                 
    引數:  env:JNI 介面指標,            
           string:Java 字串物件,          
    回傳值: Java 字串的長度,            

  const  jchar *  GetStringChars (JNIEnv*env, jstring string,  jboolean *isCopy);           
   功能:回傳指向字串的 Unicode 字符陣列的指標,該指標在呼叫 ReleaseStringchars() 前一直有效,如果 isCopy 非空,則在復制完成后將 *isCopy 設為 JNI_TRUE,如果沒有復制,則設為JNI_FALSE,  
   引數:   env:JNI 介面指標,            
           string:Java 字串物件,          
           isCopy:指向布林值的指標,               
   回傳值:   指向 Unicode 字串的指標,如果操作失敗,則回傳NULL,               
   
 void  ReleaseStringChars (JNIEnv *env, jstring string,  const jchar *chars);                  
   功能:通知虛擬機平臺相關代碼無需再訪問 chars,引數chars 是一個指標,可通過 GetStringChars() 從 string 獲得,    
   引數: env:JNI 介面指標,            
         string:Java 字串物件,          
         chars:指向 Unicode 字串的指標,               
              
 jstring  NewStringUTF (JNIEnv *env, const char *bytes);            
   功能:利用 UTF-8 字符陣列構造新 java.lang.String 物件,               
   引數: env:JNI 介面指標,如果無法構造該字串,則為 NULL,         
              bytes:指向 UTF-8 字串的指標,          
   回傳值:Java 字串物件,如果無法構造該字串,則為NULL,             
   拋出:  OutOfMemoryError:如果系統記憶體不足,                  

 jsize  GetStringUTFLength (JNIEnv *env, jstring string);              
   功能:以位元組為單位回傳字串的 UTF-8 長度,                
   引數:   env:JNI 介面指標,            
           string:Java 字串物件,          
   回傳值:  回傳字串的 UTF-8
   
 const char* GetStringUTFChars (JNIEnv*env, jstring string, jboolean *isCopy);           
   功能:回傳指向字串的 UTF-8 字符陣列的指標,該陣列在被ReleaseStringUTFChars() 釋放前將一直有效.如果 isCopy 不是 NULL,*isCopy 在復制完成后即被設為 JNI_TRUE,如果未復制,則設為 JNI_FALSE,             
   引數:  env:JNI 介面指標,            
           string:Java 字串物件,          
           isCopy:指向布林值的指標,               
   回傳值:  指向 UTF-8 字串的指標,如果操作失敗,則為 NULL,             

  void  ReleaseStringUTFChars (JNIEnv *env, jstring string,  const char *utf);               
   功能:通知虛擬機平臺相關代碼無需再訪問 utf,utf 引數是一個指標,可利用 GetStringUTFChars() 獲得,                  
   引數:   env:JNI 介面指標,            
           string:Java 字串物件,          
           utf:指向 UTF-8 字串的指標,   

1.3、陣列型別
在JNI 中定義了 8種基本型別的陣列對應Java 的8種基本型別陣列,jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray , 和一種物件陣列jobjectArray對應Java中的物件陣列,
陣列傳遞是處理JNI 陣列和Native陣列之間的轉換,例如:

jintArray <-> jint[], jdoubleArray <-> jdouble[]
jintArray(JNI) --> (Native)jint[] : jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)

jint[] --> jintArray : 呼叫 jintArray NewIntArray(JNIEnv *env, jsize len)分配記憶體,
然后呼叫 SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf) 將jin[] 拷貝到 jintArray
// ArrayType: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray
// PrimitiveType: int, byte, short, long, float, double, char, boolean
// NativeType: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean
NativeType * Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, NativeType *buffer);
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, const NativeType *buffer);
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

GET|ReleaseArrayElements() 用于根據jxxxArray創建 jxxx[]
GET|SetArrayRegion() 可以用于拷貝一個jxxxArray(或者其中一部分)到一個 預分配(pre-allocated)存盤的 jxxx[]
NewArray() 用于為jxxxArray分配記憶體, 然后呼叫 SetArrayRegion() 方法 將jxxx[] 設值,
Get|ReleasePrimitiveArrayCritical() 則是在get 和 release周期之間, 不允許阻塞呼叫(blocking calls),
1.4、訪問物件的屬性和方法

jclass GetObjectClass(JNIEnv *env, jobject obj);
// Returns the class of an object.
   
jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
// Returns the field ID for an instance variable of a class.
 
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
// Get/Set the value of an instance variable of an object
// <type> includes each of the eight primitive types plus Object.

通過以上方法,我們就可以實作在Native代碼中訪問Java物件的成員變數了, 具體實作為:
首先 通過GetObjectClass() 方法獲取該物件的類的參考;
其次 通過GetFieldID() 方法從類參考中(Step1得到該參考)獲取FieldID; 呼叫該方法需要傳入成員變數的名稱和對應field的描述(descriptor)(或者簽名(signature)),

   jmethodID GetMethodID(JNIEnv *env, jclass clazz,    const char *name, const char *sig);                
    功能:回傳類或介面實體(非靜態)方法的方法 ID,方法可在某個 clazz 的超類中定義,也可從 clazz 繼承,該方法由其名稱和簽名決定, GetMethodID() 可使未初始化的類初始化,要獲得建構式的方法 ID,應將 <init> 作為方法名,同時將 void (V) 作為回傳型別,          
    引數:  env:JNI 介面指標,            
            clazz:Java 類物件,            
            name:方法名,          
            sig:方法的簽名,           
    回傳值: 方法 ID,如果找不到指定的方法,則為 NULL,             
    拋出:   NoSuchMethodError:如果找不到指定方法,            
            ExceptionInInitializerError:如果由于例外而導致類初始化程式失敗,          
            OutOfMemoryError:如果系統記憶體不足,                  
  Call<type>Method 例程  、Call<type>MethodA 例程  、Call<type>MethodV 例程 
  NativeType Call<type>Method (JNIEnv*en v,  jobject obj , jmethodID methodID, ...);     //引數附加在函式后面,              
  NativeType Call<type>MethodA (JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args);  //引數以指標形式附加  
  NativeType Call<type>MethodV (JNIEnv *env, jobject obj,jmethodID methodID, va_list args); //引數以"鏈表"形式附加

注:
說明:這三個操作的方法用于從本地方法呼叫Java 實體方法,它們的差別僅在于向其所呼叫的方法傳遞引數時所用的機制,
這三個操作將根據所指定的方法 ID 呼叫 Java 物件的實體(非靜態)方法,引數 methodID 必須通過呼叫 GetMethodID() 來獲得,當這些函式用于呼叫私有方法和建構式時,方法 ID 必須從obj 的真實類派生而來,而不應從其某個超類派生,當然,附加引數可以為空 ,
引數: env:JNI 介面指標,
obj:Java 物件,
methodID:方法 ID,
回傳值: 回傳呼叫 Java 方法的結果,
拋出: 執行 Java 方法時拋出的例外,

用戶應將CallMethod 中的 type 替換為所呼叫方法的Java 型別(或使用表中的實際方法名),同時將 NativeType 替換為該方法相應的本地型別,

Java層回傳值		方法族		本地回傳型別NativeType
回傳值為voidCallVoidMethod( )A / V	()
回傳值為參考型別:	CallObjectMethod( )		jobect
回傳值為boolean :  CallBooleanMethod ( )	jboolean
回傳值為byteCallByteMethod( )		jbyte
回傳值為charCallCharMethod( )       	jchar
回傳值為short       CallShortMethod( )       jshort       
回傳值為intCallIntMethod( )         jint  
回傳值為longCallLongMethod()         jlong         
回傳值為floatCallFloatMethod()        jfloat        
回傳值為doubleCallDoubleMethod()       jdouble    
呼叫靜態方法:也存在如下方法:
      jfieldID   GetStaticMethodID (JNIEnv *env,jclass clazz, const char *name, const char *sig);     
       NativeType  Call<type>Method (JNIEnv*env,jclass classzz , jfieldID fieldID);           
   它們與于實體方法的唯一區別在于第二個引數jclass classzz代表的是類參考,而不是類實體,

1.5、注冊本地方法

 jint  RegisterNatives (JNIEnv *env, jclass clazz, const JNINativeMethod *methods,    jint  nMethods);               
   功能:向 clazz 引數指定的類注冊本地方法,methods 引數將指定 JNINativeMethod 結構的陣列,其中包含本地方法的名稱、
    簽名和函式指標,nMethods 引數將指定陣列中的本地方法數,JNINativeMethod 結構定義如下所示:            
         typedef struct {                       
               char *name;          
               char *signature;               
              void *fnPtr;               
         } JNINativeMethod;                  
     函式指標通常必須有下列簽名:        
     ReturnType (*fnPtr)(JNIEnv *env, jobject objectOrClass, ...);                              
   引數: env:JNI 介面指標,            
             clazz:Java 類物件,            
             methods:類中本地方法和具體實作方法的映射指標,               
             nMethods:類中的本地方法數,                 
   回傳值:  成功時回傳 "0";失敗時回傳負數,         
   拋出:  NoSuchMethodError:如果找不到指定的方法或方法不是本地方法,            

 jint  UnregisterNatives (JNIEnv *env, jclass clazz);              
  功能: 取消注冊類的本地方法,類將回傳到鏈接或注冊了本地方法函式前的狀態,      該函式不應在常規平臺相關代碼中使用,相反,它可以為某些程式提供一種重新加載和重新鏈接本地庫的途徑,             
  引數:  env:JNI 介面指標,            
          clazz:Java 類物件,            
  回傳值: 成功時回傳“0”;失敗時回傳負數,    

1.6、類操作

  jclass DefineClass (JNIEnv *env, jobject loader,   const jbyte *buf , jsize bufLen);                 
     功能:從原始類資料的緩沖區中加載類,             
     引數: env        JNI 介面指標,            
            loader    分派給所定義的類的類加載器,          
            buf        包含 .class 檔案資料的緩沖區,               
            bufLen  緩沖區長度,          
     回傳值:回傳 Java 類物件,如果出錯則回傳NULL,            
     拋出: ClassFormatError      如果類資料指定的類無效,                 
            ClassCircularityError  如果類或介面是自身的超類或超介面,               
            OutOfMemoryError  如果系統記憶體不足,                  
   jclass FindClass (JNIEnv *env, const char *name);                
     功能: 該函式用于加載本地定義的類,它將搜索由CLASSPATH 環境變數為具有指定名稱的類所指定的目錄和 zip檔案,            
     引數:env    JNI 介面指標,            
          name  類全名(即包名后跟類名,之間由"/"分隔).如果該名稱以“[(陣列簽名字符)打頭,則回傳一個陣列類,         
     回傳值:回傳類物件全名,如果找不到該類,則回傳 NULL,               
     拋出:   ClassFormatError          如果類資料指定的類無效,                 
              ClassCircularityError      如果類或介面是自身的超類或超介面,                
              NoClassDefFoundError  如果找不到所請求的類或介面的定義,            
              OutOfMemoryError       如果系統記憶體不足,                  
  jclass GetObjectClass (JNIEnv *env, jobject obj); 
     功能:通過物件獲取這個類,該函式比較簡單,唯一注意的是物件不能為NULL,否則獲取的class肯定回傳也為NULL,     
     引數:  env   JNI 介面指標,            
             obj   Java 類物件實體,         
  jclass GetSuperclass (JNIEnv *env, jclass clazz);          
     功能:獲取父類或者說超類 , 如果 clazz 代表類class而非類 object,則該函式回傳由 clazz 所指定的類的超類, 如果 clazz 
           指定類 object 或代表某個介面,則該函式回傳NULL,           
     引數:  env   JNI 介面指標,            
            clazz  Java 類物件,            
     回傳值:    由 clazz 所代表的類的超類或 NULL,               
  jboolean IsAssignableFrom (JNIEnv *env, jclass clazz1,  jclass clazz2);           
    功能:確定 clazz1 的物件是否可安全地強制轉換為clazz2,            
    引數:  env  JNI 介面指標,            
            clazz1 第一個類引數,               
            clazz2 第二個類引數,               
    回傳值:  下列某個情況為真時回傳 JNI_TRUE:               
              1、 第一及第二個類引數參考同一個 Java 類,              
              2、 第一個類是第二個類的子類,             
              3、 第二個類是第一個類的某個介面, 

2、方法簽名
“()” 中的字符表示引數,后面的則代表回傳值,例如"()V" 就表示void Func();
“(II)V” 表示 void Func(int, int);
1)常用型別簽名
具體的每一個字符的對應關系如下:

V      void         void
Z      jboolean     boolean
I      jint         int
J      jlong        long
D      jdouble      double
F      jfloat       float
B      jbyte        byte
C      jchar        char
S      jshort       short

2)陣列簽名
陣列則以"["開始,用兩個字符表示:

[I     jintArray      	int[]
[F     jfloatArray    	float[]
[B     jbyteArray    	byte[]
[C     jcharArray    	char[]
[S     jshortArray   	short[]
[D     jdoubleArray 	double[]
[J     jlongArray     	long[]
[Z     jbooleanArray 	boolean[]
String[] [Ljava/lang/String;
Object[] [Ljava/lang/Object;
int[][] [[I

3)類簽名
采用L+包名+型別+;的形式,只需要將其中的.替換為/即可, 例如java.lang.String,它的簽名為Ljava/lang/String;,末尾的;也是一部分

Ljava/lang/String; String jstring
Ljava/net/Socket;  Socket jobject
jthrowable --> java.lang.Throwable

注:
物件的簽名就是物件所屬的類簽名,
4)方法簽名
采用L+包名+型別+;的形式,只需要將其中的.替換為/即可, 例如java.lang.String,它的簽名為Ljava/lang/String;,末尾的;也是一部分

boolean fun(int a, double b, int[] c);		-->		(ID[I)Z
void fun(int a, String s, int[] c);			-->		(ILjava/lang/String;[I)V
int fun();									-->		()I
int fun(float f); 							-->		(F)I

注:
可以使用javap 生成方法簽名
3、JNIEnv 介紹
1)JNIEnv概念
JNIEnv是一個執行緒相關的結構體, 該結構體代表了 Java 在本執行緒的運行環境
2)JNIEnv與JavaVM
JavaVM : JavaVM 是 Java虛擬機在 JNI 層的代表, JNI 全域只有一個;
JNIEnv : JavaVM 在執行緒中的代表, 每個執行緒都有一個, JNI 中可能有很多個 JNIEnv;
3)作用
呼叫 Java 函式 : JNIEnv 代表 Java 運行環境, 可以使用 JNIEnv 呼叫 Java 中的代碼;
操作 Java 物件 : Java 物件傳入 JNI 層就是 Jobject 物件, 需要使用 JNIEnv 來操作這個 Java 物件;
4)JNIEnv 在c和c++的區別

c風格:
(*env)->FindClass(env,"com/example/jnisample/Prompt");
c++風格:
env->FindClass("com/example/jnisample/Prompt");

JNIEnv 在jni.h中定義

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

struct JNINativeInterface {
   ......很多方法
    jclass      (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
};


struct _JNIEnv {
    /* do not rename this; it does not seem to be entirely opaque */
    const struct JNINativeInterface* functions;

#if defined(__cplusplus)

    jint GetVersion()
    { return functions->GetVersion(this); }
.....其他方法
}

可以看到JNINativeInterface 其實定義了很多方法,都是對Java的資料進行操作,而_JNIEnv則封裝了一個JNINativeInterface的指標,并且宣告與JNINativeInterface中一模一樣的方法,并且都是通過JNINativeInterface的指標來調方法,其實就是對JNINativeInterface做了一層封裝,
JNIEnv,指代了Java本地介面環境,是一個JNI介面指標,指向了本地方法的一個函式表, jobject與jclass通常作為JNI函式的第二個引數,當所宣告Native方法是靜態方法時,對應引數jclass,因為靜態方法不依賴物件實體,而依賴于類,所以引數中傳遞的是一個jclass型別,相反,如果宣告的Native方法時非靜態方法時,那么對應引數是jobject,

四、使用
1、新建javahello類
javahello.java

package com.testjnionload;

public class javahello {
    public static native String getJniHello();
    static {
        System.loadLibrary("testttJni");
    }
}

2、實作jni層程式 hello.cpp

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>

JNIEXPORT jstring JNICALL native_getJniHello(JNIEnv *env, jclass clazz)
{
 return env->NewStringUTF("hello!!! this is jni layer");
}
#define JNIREG_CLASS "com/testjnionload/javahello"//指定要注冊的類

static JNINativeMethod gMethods[] = {
	{"getJniHello", "()Ljava/lang/String;", (void*)native_getJniHello },//系結
};

static int registerNativeMethods(JNIEnv* env, const char* className,
        JNINativeMethod* gMethods, int numMethods)
{
	jclass clazz;
	clazz = env->FindClass(className);
	if (clazz == NULL) {
		return JNI_FALSE;
	}
	if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
		return JNI_FALSE;
	}

	return JNI_TRUE;
}

static int registerNatives(JNIEnv* env)
{
	if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,
                                 sizeof(gMethods) / sizeof(gMethods[0])))
		return JNI_FALSE;

	return JNI_TRUE;
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
	JNIEnv* env = NULL;
	jint result = -1;

	if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
		return -1;
	}
	assert(env != NULL);
	if (!registerNatives(env)) {//注冊
		return -1;
	}
	return JNI_VERSION_1_4;
}

3、撰寫Android.mk檔案

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := testttJni
LOCAL_SRC_FILES := hello.cpp
include $(BUILD_SHARED_LIBRARY)

4、配置
4.1 app build.gradle

 ndk{
            abiFilters "armeabi-v7a", "x86", "armeabi"
            //abiFilters 'armeabi-v7a' //, 'armeabi','x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
        }

        sourceSets.main {
            jniLibs.srcDir 'src/main/libs'
            // jni.srcDirs = [] //disable automatic ndk-build call
        }

5、在jni目錄下使用ndk-build編譯 生成so庫
6、在MainActivity.java中呼叫

package com.testjnionload;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {
    javahello jh;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        jh = new javahello();
        Log.i("MainActivity-------",jh.getJniHello()+"");
    }
}

注:
1、FindClass呼叫時傳入的java層類的路徑并不需要相對jni中cpp檔案的路徑來配置 ,是直接在java檔案夾路徑下 即上面的com/testjnionload/javahello (類對應的包名),否則會出現 java.lang.UnsatisfiedLinkError: JNI_ERR returned from JNI_OnLoad in "/data/app/com.testjni_onload-NZTsWCAr2eyG1BYwpngVQA==/lib/arm/libtestJni.so"錯誤
在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/241367.html

標籤:其他

上一篇:「 UITableView 」串列 Cell 高度自適應,UITableViewCell 高度自適應

下一篇:Android開發 ListView(垂直滾動串列項視圖)的簡單使用

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more