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==) return ;
- 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 -;
- }
- if (bufID < || bufID >= LOG_ID_MAX) {
- jclass npeClazz;
- npeClazz = env->FindClass("java/lang/NullPointerException");
- assert(npeClazz != NULL);
- env->ThrowNew(npeClazz, "bad bufID");
- return -;
- }
- 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 -;
- }
- 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 -;
- }
- int result = ;
- if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < ) {
- LOGE("RegisterNatives failed for '%s'\n", className);
- result = -;
- }
- (*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 = ; 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)<)
- {
- return JNI_FALSE;
- }
- return JNI_TRUE;
- }
- static int registerNatives(JNIEnv *env)
- {
- if (!registerNativeMethods(env,"com/example/jniexception/MainActivity",gmethods,sizeof(gmethods)/sizeof(gmethods[])))
- {
- return JNI_FALSE;
- }
- return JNI_TRUE;
- }
- jint JNI_OnLoad(JavaVM* vm, void* reserved)
- {
- jint result = -;
- 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;
- }
总结:这两天收获还是很大的,尽管中间也遇到了诸多的问题,网上能找到的答案也不尽然,很感谢那些给出了办法解决了我问题的人,下面附上我这两天发现的几篇我觉得很好的文章:
安卓动态调试七种武器之长生剑 - Smali Instrumentation
Android JNI初体验的更多相关文章
- Android RecyclerView初体验
很早之前就听说过RecyclerView这个组件了,但一直很忙没时间学习.趁着周末,就花了一天时间来学习RecyclerView. 准备工作 在Android Studio里新建一个Android项目 ...
- Android wear 初体验
近期一直在研究android wear SDK,整体感受来说就是和现有的android 其它的开发SDK还是有非常多新的东西.比如手机终端与手表端的通信机制,手表端的UI规范.可是从开发本身来讲,还是 ...
- Android程序初体验
第一个程序的实现的最终功能是: 点击"正确"或者"错误"会得到一个是否正确的提示. 直接上效果图. 此次涉及代码编写的文件有4个: package co ...
- Android开发初体验
本文通过开发一个应用来学习Android基本概念及构成应用的UI组件. 开发的应用名叫GeoQuiz,它能给出一道道地理知识问题.用户点击true或false按钮回答问题,应用即时做出反馈 第一步请先 ...
- .net程序员的android studio 初体验 (环境设置2022年10月)
很久以前用DevExtreme写的一个Hybird APP要添加蓝牙打印功能,但是用来打包APP的phonegap被adobe关闭了,所以,只能自己用cordova去打包安卓APP,不得已,研究了 ...
- Android Studio 初体验
Google在I/O */
- Android开发学习之路--百度地图之初体验
手机都有gps和网络,通过gps或者网络可以定位到自己,然后通过百度,腾讯啊之类的地图可以显示我们的地理位置.这里学习下百度地图的使用.首先就是要申请开发者了,这个详细就不多讲了.http://dev ...
- Xamarin.iOS开发初体验
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKwAAAA+CAIAAAA5/WfHAAAJrklEQVR4nO2c/VdTRxrH+wfdU84pW0
- Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验
Mac OS 虚拟机安装方法 最近把自己的电脑升级了一下SSD固态硬盘,总算是有容量安装Mac 虚拟机了!经过心碎的安装探索,尝试了国内外的各种安装方法,最后在youtube上找到了一个好方法. 简单 ...
随机推荐
- 【Win10】开发中的新特性及原有的变更(二)
声明:本文内容适用于 Visual Studio 2015 RC 及 Windows 10 10069 SDK 环境下,若以后有任何变更,请以新的特性为准. 十一.x:Bind 中使用强制转换 这点是 ...
- Python学习-30.Python中的元组(tuple)
元组使用()定义,元组一旦定义就无法修改. 元组的索引方式同列表,也是使用[]. 元组也可以进行切片操作,使用方式同列表一样. 可以说,一个没法修改的列表就是元组. 在没有修改操作的情况下,应尽可能使 ...
- AbpZero的Swagger汉化之旅
做汉化主要是为了出一份前后端都能看得懂的在线文档,废话不多说,我们开始准备, 我们要在启动项目的Startup.cs中重定向一下swagger的读取方式 1.在这个类下面,新增一个方法: public ...
- WebAPI的AuthorizeAttribute扩展类中获取POST提交的数据
在WEBAPI中,AuthorizeAttribute类重写时,如何获取post数据是个难题,网上找资料也不好使,只能自己研究,通过研究发现,WEBAPI给了我们获取POST数据的可能,下面介绍一下: ...
- [C#学习笔记]lock锁的解释与用法
写在前面 前几时在写业务代码的时候,看到有用到lock这个方法的,而我竟然并不知道是做什么用的,所以查找了许多博客文章,弄懂了百分之七八十,在此做下笔记. 感谢博客 http://www.cnblog ...
- PHP/ThinkPHP5 框架集成微博登录入库流程示意
PHP/ThinkPHP5 框架集成微博登录入库流程示意 第三方登陆这个东东,目前主要是 微信.微博.qq.淘宝.支付宝 等几个.他们都是基于oath2协议的.原理差不多.这里记录的是我测试的新郎微博 ...
- .Net常用正则判断方法
/// <summary> /// 判断string类型否为数字 /// </summary> /// <param name="strNumber" ...
- MongoDB复制集成员及状态转换
此文已由作者温正湖授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 复制集(Replica Set)是MongoDB核心组件,相比早期版本采用的主从(Master-Slave) ...
- for循环、for in整理
for循环 作用:按照一定的规律,重复去做某件事情,此时我们就需要使用循环来处理了 例子1:倒着输出每一项 <script type="text/javascript"> ...
- 用layui遇到过的问题
1.报错“layui.form is not a function”问题 把代码中这一串修改一下:form = layui.form(); 括号去掉就行: form = layui.form; 如果你 ...