Android JNI初體驗

歡迎轉載,轉載請註明出處:http://www.cnblogs.com/lanrenxinxin/p/4696991.htmlhtml

開始接觸Android JNI層面的內容,推薦一本不錯的入門級的書《Android的設計與實現:卷一》,這兩天看了一下關於Java層和Native層函數映射的章節,加深對JNI的理解。java

 先是寫了一個很是簡單的計算器,關鍵的運算放在Native層實現,而後把運算的結果返回到Java層,寫這個的時候仍是本身手動建jni文件夾,javah的命令行,寫makefile文件,用ndk-build命令行來編譯,後來發現要調試C代碼了,才發現高版本的ndk環境已經全都集成好了,編譯,運行,調試甚至和VS差很少方便,只是本身沒配好而已。android

下面是很是簡單的計算器源碼,只是用來熟悉JNI的基本語法,其中我本身碰到過的一個問題,就是LoadLibrary()調用以後,程序直接崩潰,最開始覺得是模擬器是x86的模式,而編譯的so文件是arm的模式,可是將模擬器改爲arm以後仍是崩潰,最後無奈在本身手機上測試也是如此,一打開就直接崩潰,在網上能找到的各類方法都試了,最後發現是so命名的問題具體能夠參考這篇博客Android Eclipse JNI 調用 .so文件加載問題 安全

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
   <LinearLayout 
       android:layout_width="fill_parent"
       android:layout_height="wrap_content">
       <TextView 
           android:id = "@+id/tvResult"
           android:layout_width="fill_parent"
           android:layout_height="wrap_content"
           android:height="40dp"/>
   </LinearLayout>
   
   <LinearLayout
       android:layout_width="fill_parent"
       android:layout_height="wrap_content">
       <Button
              android:id="@+id/btnBackSpace"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:width="150dp"
            android:text = "@string/strbtnbackspace"  />
       <Button 
           android:id="@+id/btnCE"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:width="150dp"
           android:text="@string/strbtnCE"/>
   </LinearLayout>
   
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
       <Button
           android:id="@+id/btn7"
           android:layout_width = "wrap_content" 
           android:layout_height="wrap_content"
           android:width="75dp"
           android:text="@string/strbtn7"/>
       <Button 
           android:id="@+id/btn8"
           android:layout_width = "wrap_content"
           android:layout_height="wrap_content"
           android:width = "75dp"
           android:text="@string/strbtn8"/>
       <Button 
           android:id="@+id/btn9"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:width = "75dp"
           android:text="@string/strbtn9"/>
     <Button 
           android:id="@+id/btnADD"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:width = "75dp"
           android:text="@string/strbtnADD"/>
     
    </LinearLayout>
    
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height = "wrap_content">
       <Button 
           android:id="@+id/btn4"
           android:layout_width="wrap_content"
           android:layout_height = "wrap_content"
           android:width="75dp"
           android:text="@string/strbtn4"/>
       <Button 
           android:id="@+id/btn5"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:width="75dp"
           android:text="@string/strbtn5"/>
       <Button 
           android:id="@+id/btn6"
           android:layout_width = "wrap_content"
           android:layout_height="wrap_content"
           android:width="75dp"
           android:text="@string/strbtn6"/>
       <Button 
           android:id="@+id/btnSUB"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:width = "75dp"
           android:text="@string/strbtnSUB"/>
     
    </LinearLayout>
    
    <LinearLayout 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
       
        <Button 
            android:id="@+id/btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:width="75dp"
            android:text="@string/strbtn1"/>
         <Button 
            android:id="@+id/btn2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:width="75dp"
            android:text="@string/strbtn2"/>
          <Button 
            android:id="@+id/btn3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:width="75dp"
            android:text="@string/strbtn3"/>
          <Button 
           android:id="@+id/btnMUL"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:width = "75dp"
           android:text="@string/strbtnMUL"/>           
    </LinearLayout>

    <LinearLayout 
        android:layout_width = "fill_parent"
        android:layout_height="wrap_content">
       <Button 
           android:id="@+id/btn0"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:width = "75dp"
           android:text="@string/strbtn0"/>       
       <Button 
           android:id="@+id/btnC"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:width = "75dp"
           android:text="@string/strbtnC"/>       
        <Button 
           android:id="@+id/btnRESULT"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:width = "75dp"
           android:text="@string/strbtnRESULT"/>
         <Button 
           android:id="@+id/btnDIV"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:width = "75dp"
           android:text="@string/strbtnDIV"/>         
    </LinearLayout>
</LinearLayout>
calc xml
public class MainActivity extends Activity implements OnClickListener{
    
    static{

            System.loadLibrary("CalcJni");    
    }
    enum  OP
    {
        NON,
        ADD,
        SUB,
        MUL,
        DIV
    }
    private TextView tvResult = null;
    private Button btn0 =null;
    private Button btn1 =null;
    private Button btn2 =null;
    private Button btn3 =null;
    private Button btn4 =null;
    private Button btn5 =null;
    private Button btn6 =null;
    private Button btn7 =null;
    private Button btn8 =null;
    private Button btn9 =null;
    private Button btnAdd =null;
    private Button btnSub =null;
    private Button btnMul =null;
    private Button btnDiv =null;
    private Button btnEqu =null;
    private Button btnBackspace=null;
    private Button btnCE=null;
    private Button btnC=null;
    private OP        operator = OP.NON;
    
    private int  num1;
    private int  num2;
    private int  result;
    
    private native int Add(int num1,int num2);
    private native int Sub(int num1,int num2);
    private native int Mul(int num1,int num2);
    private native int Div(int num1,int num2);
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn0 = (Button)findViewById(R.id.btn0);
        btn1 = (Button)findViewById(R.id.btn1);
        btn2 = (Button)findViewById(R.id.btn2);
        btn3 = (Button)findViewById(R.id.btn3);
        btn4 = (Button)findViewById(R.id.btn4);
        btn5 = (Button)findViewById(R.id.btn5);
        btn6 = (Button)findViewById(R.id.btn6);
        btn7 = (Button)findViewById(R.id.btn7);
        btn8 = (Button)findViewById(R.id.btn8);
        btn9 = (Button)findViewById(R.id.btn9);
        btnAdd = (Button)findViewById(R.id.btnADD);
        btnSub = (Button)findViewById(R.id.btnSUB);
        btnMul = (Button)findViewById(R.id.btnMUL);
        btnDiv = (Button)findViewById(R.id.btnDIV);
        tvResult = (TextView)findViewById(R.id.tvResult);
        tvResult.setTextSize(30);
        tvResult.setGravity(Gravity.RIGHT);
        btnBackspace=(Button)findViewById(R.id.btnBackSpace);
        btnCE=(Button)findViewById(R.id.btnCE);
        btnC=(Button)findViewById(R.id.btnC);
        btnEqu = (Button)findViewById(R.id.btnRESULT);
        
        
        btnBackspace.setOnClickListener(this);
        btnCE.setOnClickListener(this);
        btn0.setOnClickListener(this);
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
        btn3.setOnClickListener(this);
        btn4.setOnClickListener(this);
        btn5.setOnClickListener(this);
        btn6.setOnClickListener(this);
        btn7.setOnClickListener(this);
        btn8.setOnClickListener(this);
        btn9.setOnClickListener(this);

        
        btnAdd.setOnClickListener(this);
        btnSub.setOnClickListener(this);
        btnMul.setOnClickListener(this);
        btnDiv.setOnClickListener(this);
        btnEqu.setOnClickListener(this);
    }

    
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch (v.getId()) {
        case R.id.btnBackSpace:
            String mystr = tvResult.getText().toString();
            try {
                tvResult.setText(mystr.substring(0, mystr.length()-1));
            } catch (Exception e) {
                // TODO: handle exception
                tvResult.setText("");
            }
            break;
        case R.id.btnCE:
            tvResult.setText(null);
            break;
    //btn 0 -- 9
        case R.id.btn0:
            String myString0 = tvResult.getText().toString();
            myString0 += "0";
            tvResult.setText(myString0);
            break;
        case R.id.btn1:
            String myString1 = tvResult.getText().toString();
            myString1 += "1";
            tvResult.setText(myString1);
            break;
        case R.id.btn2:
            String myString2 = tvResult.getText().toString();
            myString2 += "2";
            tvResult.setText(myString2);
            break;
        case R.id.btn3:
            String myString3 = tvResult.getText().toString();
            myString3 += "3";
            tvResult.setText(myString3);
            break;
        case R.id.btn4:
            String myString4 = tvResult.getText().toString();
            myString4 += "4";
            tvResult.setText(myString4);
            break;
        case R.id.btn5:
            String myString5 = tvResult.getText().toString();
            myString5 += "5";
            tvResult.setText(myString5);
            break;
        case R.id.btn6:
            String myString6 = tvResult.getText().toString();
            myString6 += "6";
            tvResult.setText(myString6);
            break;
        case R.id.btn7:
            String myString7 = tvResult.getText().toString();
            myString7 += "7";
            tvResult.setText(myString7);
            break;
        case R.id.btn8:
            String myString8 = tvResult.getText().toString();
            myString8 += "8";
            tvResult.setText(myString8);
            break;
        case R.id.btn9:
            String myString9 = tvResult.getText().toString();
            myString9 += "9";
            tvResult.setText(myString9);
            break;

            
       //+-*/
        case R.id.btnADD:
            String myAddString = tvResult.getText().toString();
            if (myAddString.equals(null)) {
                return;
            }
            num1 = Integer.valueOf(myAddString);
            tvResult.setText(null);
            operator = OP.ADD;
            
            break;
        case R.id.btnSUB:
            String mySubString = tvResult.getText().toString();
            if (mySubString.equals(null)) {
                return;
            }
            num1 = Integer.valueOf(mySubString);
            tvResult.setText(null);
            operator = OP.SUB;
            break;
        case R.id.btnMUL:
            String myMulString = tvResult.getText().toString();
            if (myMulString.equals(null)) {
                return;
            }
            num1 = Integer.valueOf(myMulString);
            tvResult.setText(null);
            operator = OP.MUL;
            break;
        case R.id.btnDIV:
            String myDivString = tvResult.getText().toString();
            if (myDivString.equals(null)) {
                return;
            }
            num1 = Integer.valueOf(myDivString);
            tvResult.setText(null);
            operator = OP.DIV;
            break;    
            
                    
        case R.id.btnRESULT:
            String myResultString = tvResult.getText().toString();
            if(myResultString.equals(null)){
                return;
            }
            num2 = Integer.valueOf(myResultString);
            switch (operator) {
            case ADD:
                result = Add(num1, num2);
                break;
            case SUB:
                result = Sub(num1, num2);
                break;
            case MUL:
                result = Mul(num1, num2);
                break;
            case DIV:
                result = Div(num1, num2);
                break;
                
            default:
                break;
            }            
            tvResult.setText(Integer.toString(result));
            break;
        default:
            break;
        }
        
        
    }
}
calc java
JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Add
  (JNIEnv * env, jobject obj, jint num1, jint num2)
{

    return (jint)(num1+num2);

}


JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Sub
  (JNIEnv * env, jobject obj    , jint num1, jint num2)
{
    return (jint)(num1-num2);
}


JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Mul
  (JNIEnv * env, jobject obj, jint num1, jint  num2)
{
    return (jint)(num1*num2);
}


JNIEXPORT jint JNICALL Java_com_example_calcjni_MainActivity_Div
  (JNIEnv * env, jobject obj, jint num1, jint num2)
{
    if(num2==0) return 0;
    return (jint)(num1/num2);
}
calc native

 

咱們常常會寫以下的代碼輸出日誌:app

Log.d(TAG,」Debug Log」);eclipse

咱們就以Log系統爲例來學習JNI。ide

咱們先看一下Log類的內容,在android源碼的\frameworks\base\core\java\android\Log.java文件中函數

/**
     * Send a {@link #DEBUG} log message.
     * @param tag Used to identify the source of a log message.  It usually identifies
     *        the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static int d(String tag, String msg) {
        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}


    /** @hide */ public static final int LOG_ID_MAIN = 0;
    /** @hide */ public static final int LOG_ID_RADIO = 1;
    /** @hide */ public static final int LOG_ID_EVENTS = 2;
    /** @hide */ public static final int LOG_ID_SYSTEM = 3;

    /** @hide */ public static native int println_native(int bufID,
            int priority, String tag, String msg);

能夠看到全部的Log的方法都調用了native 的println_native方法,在android源碼中的\frameworks\base\core\jni\android_until_Log.cpp文件中實現:工具

/*
 * In class android.util.Log:
 *  public static native int println_native(int buffer, int priority, String tag, String msg)
 */
/*
*JNI方法增長了JNIEnv和jobject兩參數,其他的參數和返回值只是將Java層參數映**射成JNI的數據類型,而後經過調用本地庫和JNIEnv提供的JNI函數處理數據,最後返給java層
*/
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) {   //異常處理
        jclass npeClazz;

        npeClazz = env->FindClass("java/lang/NullPointerException");
        assert(npeClazz != NULL);
        //拋出異常
        env->ThrowNew(npeClazz, "println needs a message");
        return -1;
    }

    if (bufID < 0 || bufID >= LOG_ID_MAX) {
        jclass npeClazz;

        npeClazz = env->FindClass("java/lang/NullPointerException");
        assert(npeClazz != NULL);

        env->ThrowNew(npeClazz, "bad bufID");
        return -1;
    }

    if (tagObj != NULL)
        tag = env->GetStringUTFChars(tagObj, NULL);
    msg = env->GetStringUTFChars(msgObj, NULL);
        //向內核寫入日誌
    int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);

    if (tag != NULL)
        env->ReleaseStringUTFChars(tagObj, tag);
    env->ReleaseStringUTFChars(msgObj, msg);

    return res;
}

至此,JNI層已經實現了在java層聲明的Native層方法,可是這兩個又是如何聯繫到一塊兒的呢?咱們再看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 },
};

在\dalvik\libnativehelper\include\nativehelper\Jni.h文件中有JNINativeMethod 的定義:

typedef struct {
    const char* name;        //java層聲明的native函數的函數名
    const char* signature;   //Java函數的簽名
    void*       fnPtr;       //函數指針,指向JNI層的實現方法
} JNINativeMethod;

咱們能夠看到printIn_native的對應關係:

{"println_native","(IILjava/lang/String;Ljava/lang/String;)I",(void*)android_util_Log_println_native }

Java層聲明的函數名是print_native

Java層聲明的native函數的簽名爲(IILjava/lang/String;Ljava/lang/String;)I

JNI方法實現方法的指針爲(void*)android_util_Log_println_native

咱們知道了java層和JNI層的映射關係,可是如何把這種關係告訴Dalvik虛擬機呢?,咱們繼續看android_util_Log.cpp的源碼

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

    if (clazz == NULL) {
        LOGE("Can't find android/util/Log");
        return -1;
    }
    
    levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
    levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
    levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
    levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
    levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
    levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
                
    return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
}

}; // namespace android

這個函數的最後調用了AndroidRuntime::registerNativeMethods函數

能夠在\frameworks\base\core\jni\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);
}

他的內部實現只是調用了jniRegisterNativeMethods ()。

在\dalvik\libnativehelper\JNIHelp.c中jniRegisterNativeMethods函數的實現

/*
 * Register native JNI-callable methods.
 *
 * "className" looks like "java/lang/String".
 */
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    LOGV("Registering %s natives\n", className);
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s'\n", className);
        return -1;
    }

    int result = 0;
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s'\n", className);
        result = -1;
    }

    (*env)->DeleteLocalRef(env, clazz);
    return result;
}

這裏是調用了JNIEnv的RegisterNatives函數,能夠閱讀函數的註釋,註冊一個類的Native方法。已經告訴了虛擬機java層和native層的映射關係。

/*
 * Register one or more native functions in one class.
 *
 * This can be called multiple times on the same method, allowing the
 * caller to redefine the method implementation at will.
 */
static jint RegisterNatives(JNIEnv* env, jclass jclazz,
    const JNINativeMethod* methods, jint nMethods)
{
    JNI_ENTER();

    ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz);
    jint retval = JNI_OK;
    int i;

    if (gDvm.verboseJni) {
        LOGI("[Registering JNI native methods for class %s]\n",
            clazz->descriptor);
    }

    for (i = 0; i < nMethods; i++) {
        if (!dvmRegisterJNIMethod(clazz, methods[i].name,
                methods[i].signature, methods[i].fnPtr))
        {
            retval = JNI_ERR;
        }
    }

    JNI_EXIT();
    return retval;
}

其做用是向clazz參數指定的類註冊本地方法,這樣,虛擬機就能獲得Java層和JNI層之間的對應關係,就能夠實現java和native層代碼的交互了。咱們注意到在Log系統的實例中,JNI層實現方法和註冊方法中都使用了JNIEnv這個指針,經過它調用JNI函數,訪問Dalvik虛擬機,進而操做Java對象。

咱們能夠在\Dalvik\libnativehelper\include\nativehelper\jni.h中找到JNIEnv的定義:

struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)   //定義了C++
typedef _JNIEnv JNIEnv;   //C++中的JNIEnv的類型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

這裏只是用關鍵字typedef關鍵字作了類型定義,那麼_JNIEnv和JNINativeInterface的定義

/*
 * C++ object wrapper.
 *
 * This is usually overlaid on a C struct whose first element is a
 * JNINativeInterface*.  We rely somewhat on compiler behavior.
 */
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); }

    jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
        jsize bufLen)
    { return functions->DefineClass(this, name, loader, buf, bufLen); }

    jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }

    jmethodID FromReflectedMethod(jobject method)
    { return functions->FromReflectedMethod(this, method); }

  ………..

_JNIEnv只是對const struct JNINativeInterface*類型的封裝,並間接調用const struct JNINativeInterface*上定義的方法

/*
 * Table of interface function pointers.
 */
struct JNINativeInterface {
……
  jclass      (*FindClass)(JNIEnv*, const char*);
  jboolean    (*IsSameObject)(JNIEnv*, jobject, jobject);
……
};

這裏才真正涉及JNI函數的調用,也只是一個接口,具體的實現要參考Dalvik虛擬機。

可是咱們能夠得出以下結論:

C++中:JNIEnv就是struct _JNIEnv。JNIEnv *env 等價於 struct _JNIEnv *env ,在調用JNI函數的時候,只須要env->FindClass(JNIEnv*,const char ),就會間接調用JNINativeInterface結構體裏面定義的函數指針,而無需首先對env解引用。

C中:JNIEnv就是const struct JNINativeInterface *。JNIEnv *env 等價於const struct JNINativeInterface ** env,所以要獲得JNINativeInterface結構體裏面的函數指針就必須先對env解引用獲得(*env),獲得const struct JNINativeInterface *,纔是真正指向JNINativeInterface結構體的指針,而後再經過它調用具體的JNI函數,所以須要這樣調用:

(*env)->FindClass(JNIEnv*,const char*)。

接下來了解關於Jni和java層數據類型的關係,Jni.h文件中關於基本數據類型的定義

/*
 * Primitive types that match up with Java equivalents.
 */
#ifdef HAVE_INTTYPES_H
# include <inttypes.h>      /* C99 */
typedef uint8_t         jboolean;       /* unsigned 8 bits */
typedef int8_t          jbyte;          /* signed 8 bits */
typedef uint16_t        jchar;          /* unsigned 16 bits */
typedef int16_t         jshort;         /* signed 16 bits */
typedef int32_t         jint;           /* signed 32 bits */
typedef int64_t         jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
#else
typedef unsigned char   jboolean;       /* unsigned 8 bits */
typedef signed char     jbyte;          /* signed 8 bits */
typedef unsigned short  jchar;          /* unsigned 16 bits */
typedef short           jshort;         /* signed 16 bits */
typedef int             jint;           /* signed 32 bits */
typedef long long       jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
#endif

關於一些返回狀態值的定義:

#define JNI_FALSE   0
#define JNI_TRUE    1
#define JNI_OK          (0)     /* no error */
#define JNI_ERR         (-1)    /* generic error */
#define JNI_EDETACHED   (-2)    /* thread detached from the VM*/
#define JNI_EVERSION    (-3)    /* JNI version error */

#define JNI_COMMIT      1       /* copy content, do not free buffer */
#define JNI_ABORT       2       /* free buffer w/o copying back */

JNI引用類型採用了與Java類型類似的繼承關係,樹根是Jobject

下面是Jni.h中關於引用類型的定義,在C++中全都繼承自class jobjct{};而C中都是void*的指針。

#ifdef __cplusplus
/*
 * Reference types, in C++
 */
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};    
class _jobjectArray : public _jarray {};    //java層 object[]
class _jbooleanArray : public _jarray {};   //java層 boolean[]
class _jbyteArray : public _jarray {};      //byte[]
class _jcharArray : public _jarray {};      //char[]
class _jshortArray : public _jarray {};     //short[]
class _jintArray : public _jarray {};       //in[]
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};

typedef _jobject*       jobject;
typedef _jclass*        jclass;
typedef _jstring*       jstring;
typedef _jarray*        jarray;
typedef _jobjectArray*  jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray*    jbyteArray;
typedef _jcharArray*    jcharArray;
typedef _jshortArray*   jshortArray;
typedef _jintArray*     jintArray;
typedef _jlongArray*    jlongArray;
typedef _jfloatArray*   jfloatArray;
typedef _jdoubleArray*  jdoubleArray;
typedef _jthrowable*    jthrowable;
typedef _jobject*       jweak;


#else /* not __cplusplus */

/*
 * Reference types, in C.
 */
typedef void*           jobject;
typedef jobject         jclass;
typedef jobject         jstring;
typedef jobject         jarray;
typedef jarray          jobjectArray;
typedef jarray          jbooleanArray;
typedef jarray          jbyteArray;
typedef jarray          jcharArray;
typedef jarray          jshortArray;
typedef jarray          jintArray;
typedef jarray          jlongArray;
typedef jarray          jfloatArray;
typedef jarray          jdoubleArray;
typedef jobject         jthrowable;
typedef jobject         jweak;

#endif /* not __cplusplus */

 

JNI接口指針值JNI實現方法的第一個參數,其類型是JNIEnv。第二個參數因本地方法是靜態仍是非靜態而不一樣,非靜態本地方法的第二個參數是對Java對象的引用,而靜態本地方法的第二個參數是對其java類的引用,其他的參數都對應與java方法的參數。能夠藉助javah 工具來生成對應的native函數聲明。

而在Java層和native層都是支持函數重載,僅僅依靠函數名沒法肯定惟一的一個方法,因此JNI提供了一套簽名規則,用一串字符串來惟一肯定一個方法:

(參數1類型簽名 參數2類型簽名……參數n類型簽名)返回值類型

和smali語言中的規則同樣,就不加以贅述了,能夠參考非蟲的《Android軟件安全與逆向分析》中的相關章節或者這篇文章smali語法文檔,只簡單舉個例子。

仍是以咱們以前的println_native爲例:

Java層的聲明    public static native int println_native(int buffer, int priority, String tag, String msg) ;

對應的簽名就是 (IILjava/lang/String;Ljava/lang/String;)I

至此咱們實現的JNI層方法和java層聲明的方法創建的惟一的映射關係。

接下來咱們繼續學習在JNI層訪問java層對象,在JNI層操做jobject,就是要訪問這個對象並操做它的變量和方法,咱們經常使用的兩個JNI函數FindClass() 和 GetObjectClass():

C++中的函數原型:

jclass FindClass(const char* name);

class GetObjectClass(jobject obj);

C中的函數原型:

jclass (*FindClass)(JNIEnv*,const char* name);

class (*GetObjectClass)(JNIEnv*,jobject obj);

經過給FindClass傳入要查找類的全限定類名(以」/」分隔路徑),返回一個jclass的對象,這樣就能夠操做這個類的方法和變量了。

 下面是一個特別簡單的例子,點擊button之後,調用native層的getReply()方法,而後在native層getReply()方法的實現中反向調用java層的callBack()方法,輸入日誌。

public class MainActivity extends Activity {    
    static{
        System.loadLibrary("NewJni");
    }
    private String TAG = "CCDebug";
    private Button btnButton = null;    
    private native String  getReply();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);    
        btnButton = (Button)findViewById(R.id.btn1);    
        btnButton.setOnClickListener(new OnClickListener() {            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
            Log.d(TAG, getReply());                
            }
        });    
    }
    private void callBack() {
        Log.d(TAG, "call back form native !");        
        throw new NullPointerException();    
    }
}
java

 

#include <jni.h>
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jint JNICALL Java_com_example_newjni_MainActivity_getReply
  (JNIEnv * env, jobject obj);


JNIEXPORT jstring JNICALL Java_com_example_newjni_MainActivity_getReply
  (JNIEnv * env, jobject obj)
{

    jclass jcls = env->GetObjectClass(obj);
    jmethodID jmId = env->GetMethodID(jcls,"callBack","()V");
    env->CallVoidMethod(obj,jmId);
    if(env->ExceptionCheck())
    {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }

    return env->NewStringUTF("Hello From JNI!");
}
native

 

這是利用javah生成的函數聲明,嚴格遵照NDk的語法要求,固然,咱們本身也能夠像Log系統那樣,本身註冊函數的映射關係而沒必要遵照NDK語法,下面就是將getReply()函數手動註冊的例子,可是手動註冊我本身目前還存在幾個問題:

1. native的代碼始終不能下斷點到JNI_Onload()函數中

2. 第一次點擊Button,native層代碼沒有響應,必須是第二次點擊纔會響應

public class MainActivity extends Activity {
    private static  final String  TAG = "CCDebug"; 
    Button btnButton = null;    
    private native String getReply();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);        
        btnButton = (Button)findViewById(R.id.btn);
        btnButton.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                loadLibrary("jniException");
                Log.d(TAG, getReply());
            }
        });    
    }
    private void callBack() {
        Log.d(TAG, "call back form native !");        
        throw new NullPointerException();
    }
    /*
     * 若是在onClick()函數中直接調用System.loadLibrary(),在調試native代碼時會出現
     * No symbol table is loaded.  Use the "file" command.
     * 而不能調試native源代碼
     */
    public static  void loadLibrary(String libName)
    {
        System.loadLibrary(libName);
    }
}

 

 

#include <jni.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL MyFunc
    (JNIEnv *env, jobject obj);
static int registerNativeMethods(JNIEnv* env,const char *className,
    JNINativeMethod* gMethods,int numMethods);
static int registerNatives(JNIEnv *env);
#ifdef __cplusplus
}
#endif
#define LOGD(msg)  \
__android_log_write(ANDROID_LOG_ERROR,"CCDebug",msg);


JNIEXPORT jstring JNICALL MyFunc
    (JNIEnv *env, jobject obj)
{
    /*
     * 經過JNI函數GetObjectClass獲得傳入對象的類信息
     * 這裏傳入的對象就是調用Native方法的那個對象
     */
    jclass jcls = env->GetObjectClass(obj);
    //根據類信息獲得callback方法的jmethodID
    jmethodID jmId = env->GetMethodID(jcls,"callBack","()V");
    //調用callback方法
    env->CallVoidMethod(obj,jmId);
    /*
     * 若是檢查是否有異常發生
     * 若是有異常發生就處理,不然異常將會拋給java層的callback方法
     */
    if(env->ExceptionCheck())   //檢查異常
    {
        env->ExceptionDescribe();
        env->ExceptionClear();     //清除異常
    }

    return env->NewStringUTF("Show Message Form JNI!");
}

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

static int registerNativeMethods(JNIEnv* env,const char *className,
    JNINativeMethod* gMethods,int numMethods)
{
    jclass clazz;
    clazz = env->FindClass(className);
    if(clazz == NULL)
    {
        return JNI_FALSE;
    }
    //調用JNIEnv提供的註冊函數向虛擬機註冊
    if(env->RegisterNatives(clazz,gMethods,numMethods)<0)
    {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

static int registerNatives(JNIEnv *env)
{
    if (!registerNativeMethods(env,"com/example/jniexception/MainActivity",gmethods,sizeof(gmethods)/sizeof(gmethods[0])))
    {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{

    jint result = -1;
    JNIEnv* env = NULL;
    if (vm->GetEnv((void**)&env,JNI_VERSION_1_4))
    {
        return result;
    }
    if (registerNatives(env)!=JNI_TRUE)
    {
        return result;
    }
    result = JNI_VERSION_1_4;
    return result;
}

總結:這兩天收穫仍是很大的,儘管中間也遇到了諸多的問題,網上能找到的答案也不盡然,很感謝那些給出了辦法解決了我問題的人,下面附上我這兩天發現的幾篇我以爲很好的文章:

Android4.4源碼

安卓動態調試七種武器之孔雀翎 – Ida Pro

安卓動態調試七種武器之長生劍 - Smali Instrumentation

No Symbol table is loaded

eclipse單步調試JNI

ndk配置自動編譯

深刻理解JNI

相關文章
相關標籤/搜索