zoukankan      html  css  js  c++  java
  • Android JNI初体验

    欢迎转载,转载请注明出处:http://www.cnblogs.com/lanrenxinxin/p/4696991.html

    开始接触Android JNI层面的内容,推荐一本不错的入门级的书《Android的设计与实现:卷一》,这两天看了一下关于Java层和Native层函数映射的章节,加深对JNI的理解。

     先是写了一个非常简单的计算器,关键的运算放在Native层实现,然后把运算的结果返回到Java层,写这个的时候还是自己手动建jni文件夹,javah的命令行,写makefile文件,用ndk-build命令行来编译,后来发现要调试C代码了,才发现高版本的ndk环境已经全都集成好了,编译,运行,调试甚至和VS差不多方便,只是自己没配好而已。

    下面是非常简单的计算器源码,只是用来熟悉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

    我们经常会写如下的代码输出日志:

    Log.d(TAG,”Debug Log”);

    我们就以Log系统为例来学习JNI。

    我们先看一下Log类的内容,在android源码的frameworksasecorejavaandroidLog.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源码中的frameworksasecorejniandroid_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 },
    };

    在dalviklibnativehelperinclude ativehelperJni.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函数

    可以在frameworksasecorejniAndroidRuntime.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 ()。

    在dalviklibnativehelperJNIHelp.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
    ", className);
        clazz = (*env)->FindClass(env, className);
        if (clazz == NULL) {
            LOGE("Native registration unable to find class '%s'
    ", className);
            return -1;
        }
    
        int result = 0;
        if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
            LOGE("RegisterNatives failed for '%s'
    ", 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]
    ",
                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对象。

    我们可以在Dalviklibnativehelperinclude ativehelperjni.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

  • 相关阅读:
    struts2在result中使用el表达式碰到的问题
    JSP学习笔记—— jsp中include文件指令乱码的三种解决方案
    SSH整合,applicationContext.xml中配置hibernate映射文件问题
    struts上传文件失败 ContentType not allowed错误解决方法【转】
    mysql5 乱码问题解决方案
    java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory的解决
    JQuery使用on绑定动态生成元素时碰到的问题
    Oracle异常处理
    C#窗口拦截键盘事件
    Oracle中动态SQL详解
  • 原文地址:https://www.cnblogs.com/lanrenxinxin/p/4696991.html
Copyright © 2011-2022 走看看