Android JNI初体验

开始接触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源码的\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;
}

总结:这两天收获还是很大的,尽管中间也遇到了诸多的问题,网上能找到的答案也不尽然,很感谢那些给出了办法解决了我问题的人,下面附上我这两天发现的几篇我觉得很好的文章:

Android源码

安卓动态调试七种武器之孔雀翎 – Ida Pro

安卓动态调试七种武器之长生剑 - Smali Instrumentation

No Symbol table is loaded

eclipse单步调试JNI

ndk配置自动编译

深入理解JNI

时间: 2024-10-10 05:46:14

Android JNI初体验的相关文章

使用Kotlin开发Android应用初体验

使用Kotlin开发Android应用初体验 昨晚,最近一届的谷歌IO大会正式将Kotlin确定为了官方开发语言,作为一名Android开发鸟,怎么能不及时尝尝鲜呢? Kotlin的简要介绍 在开发之前,很多同学一定有很多疑问,Kotlin到底有啥好处,怎么和现有的项目共存呢?Java那么些特性Kotlin都有吗?嗯,让我们一一来看. 以下内容摘自:Kotlin的官方网站:https://www.kotlincn.net/docs/reference/android-overview.html

Android Studio初体验之启动AVD模拟器异常:cannot set up guest memory &#39;pc.ram&#39;

启动AVD模拟器异常:Cannot set up guest memory 'pc.ram' 错误信息: HAX is working and emulator runs in fast virt mode Cannot set up guest memory 'pc.ram': Invalid argument Error accepting connect 分析 各种查资料,没有发现网上有同样问题的,在一篇相关文章中找到类似的解决方法. 从语意看,应该是hax安装后没有启动.(不懂hax是什

Android Studio 初体验

Google在I/O2013大会上公布了Android新的开发工具Android Studio,趁周末时间做了一下尝试.有须要的能够 在http://developer.android.com/sdk/installing/studio.html下载,当前版本号是V0.1.官方解释:Android Studio is a new Android development environment based on IntelliJ IDEA. Similar to Eclipse with the

Android wear 初体验

最近一直在研究android wear SDK,总体感受来说就是和现有的android 其他的开发SDK还是有很多新的东西.例如手机终端与手表端的通信机制,手表端的UI规范.但是从开发本身来讲,还是存在很大的困难. 首先就是环境的搭建,大家都知道国内现在上不去google developer官网,不过在同事的帮助下,我fan 墙了.具体fan墙方法可以自行百度,或者购买VPN. Android developer 官网首页中部就有一个androidwear 部分的入口 进去之后发现就是和之前的a

Android Studio 初体验,从Eclipse到AS

这些天一直在忙着面试方面的准备以及不断更新完善我的Memory软件,不知不觉过去了好久. 由于我想对软件加入一些material design的新元素,因此去网上找对应的示例源码,突然发现除了各种教程上还在使用eclipse,这个世界几乎所有新的东东都被AndroidStudio包揽了,瞬间感觉不学AS简直就是不可逾越的交流鸿沟.因此网上买了个VPN翻墙,用了几天时间,终于搞明白了AS的基本用法.在此分享一下自己的学习心得. 自己是从Eclipse转到AS上的,所以在学习AS当中, 必然下意识的

Android开发初体验

第一个Androd应用-GeoQuiz,它能给出一道道地理问题,用户点击TRUE或FALSE按钮来回答屏幕上的问题,GeoQuiz及时做出反馈,首先我们来看看它完成后的样子 1.首先我们创建一个Android项目 接下来SDK的选择最低版本是4.4我选择的的是7.0,然后继续Next 选择Empty Activity,继续Next 命名activity的子类为Quizactivity,点击finsh.这样就完成了项目的创建 2.接下来就是源码,在activity_quiz.xml添加组件 <?x

Android开发初体验--第一个activity--&gt;GeoQuiz

1.首先创建项目GeoQuiz,过程就不细说了...... 2.activity_quiz.xml 1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4

Android Studio初体验之揭秘AS的项目的目录结构

(因为是初步,难免有错误出现,如果哪里记录的有问题,欢迎各位指正,共同成长) 我们默认你的AS是已经安装并好的,关于AS的安装,网上一大把,请各位自行寻找资源 下面记录一些配置以及AS的项目的目录结构 配置: 1.config:用户的配置文件,保存用户的个人设置(你所设置的所有的熟悉,注意备份,就不会一次次去设置)   2.restart:不用关心   3.system:运行的生成的缓存环境(会越来越大) ①考虑配置在磁盘空间大的磁盘中 ②可以删除,但是第一次使用的时候会初始化一些数据 项目结构

第 1 章 Android 应用初体验

本章将通过设计首个应用(名为GeoQuiz)介绍编写Android应用需掌握的一些基概念和设计方法. 移动系统的的应用也叫App,本章的App能测试用户的地理知识.图1-1显示了用户点击"错"按钮的结果. 图1-1 正确答案应该是台湾,而不是海南岛 1.1 Android应用基础 GeoQuiz应用由一个activity(活动)和一个布局(layout)组成. 其中,Activity中文可以翻译为活动或者活动界面. 从用户角度来看, Activity就是包含用户界面的一个大组件,主要用