欢迎转载,转载请注明出处: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;
}

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

Android4.4源码

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

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

No Symbol table is loaded

eclipse单步调试JNI

ndk配置自动编译

深入理解JNI

Android JNI初体验的更多相关文章

  1. Android RecyclerView初体验

    很早之前就听说过RecyclerView这个组件了,但一直很忙没时间学习.趁着周末,就花了一天时间来学习RecyclerView. 准备工作 在Android Studio里新建一个Android项目 ...

  2. Android wear 初体验

    近期一直在研究android wear SDK,整体感受来说就是和现有的android 其它的开发SDK还是有非常多新的东西.比如手机终端与手表端的通信机制,手表端的UI规范.可是从开发本身来讲,还是 ...

  3. Android程序初体验

    第一个程序的实现的最终功能是: 点击"正确"或者"错误"会得到一个是否正确的提示. 直接上效果图.     此次涉及代码编写的文件有4个: package co ...

  4. Android开发初体验

    本文通过开发一个应用来学习Android基本概念及构成应用的UI组件. 开发的应用名叫GeoQuiz,它能给出一道道地理知识问题.用户点击true或false按钮回答问题,应用即时做出反馈 第一步请先 ...

  5. .net程序员的android studio 初体验 (环境设置2022年10月)

      很久以前用DevExtreme写的一个Hybird APP要添加蓝牙打印功能,但是用来打包APP的phonegap被adobe关闭了,所以,只能自己用cordova去打包安卓APP,不得已,研究了 ...

  6. Android Studio 初体验

    Google在I/O */

  7. Android开发学习之路--百度地图之初体验

    手机都有gps和网络,通过gps或者网络可以定位到自己,然后通过百度,腾讯啊之类的地图可以显示我们的地理位置.这里学习下百度地图的使用.首先就是要申请开发者了,这个详细就不多讲了.http://dev ...

  8. Xamarin.iOS开发初体验

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKwAAAA+CAIAAAA5/WfHAAAJrklEQVR4nO2c/VdTRxrH+wfdU84pW0

  9. Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验

    Mac OS 虚拟机安装方法 最近把自己的电脑升级了一下SSD固态硬盘,总算是有容量安装Mac 虚拟机了!经过心碎的安装探索,尝试了国内外的各种安装方法,最后在youtube上找到了一个好方法. 简单 ...

随机推荐

  1. java细节问题

    保留两位小数:1.234, 1.23, 1.2, 1.0, 1. package test; import java.math.BigDecimal; import java.text.Decimal ...

  2. SQL反模式-1

    SQL反模式讲了很多数据库设计中遇到的难题.跟我最近的工作很相关.因此特意拜读了下.本文主要讲解"多值列"和"幼稚的树" 多值列要分成映射表,比如(A,B),其 ...

  3. 设计模式之代理模式(Proxy Pattern)_远程代理解析

    一.什么是代理模式? 顾名思义,代理就是第三方,比如明星的经纪人,明星的事务都交给经纪人来处理,明星只要告诉经纪人去做什么,经纪人自然会想办法去做,做完之后再把结果告诉明星就好了 本来是调用者与被调用 ...

  4. ASP.NET下使用Combres对JS、CSS合并和压缩

    记录一下,如何简单快捷压缩js和css,通过合并来减少请求次数. 用到的网址: http://www.nuget.org/packages/combres/ https://github.com/bu ...

  5. python web开发——c2 flask框架和flask_script

    重定向/error 通过flask中的redirect方法和自定义的newpath函数.redirect_demo函数实现重定向: #coding:utf-8 from flask import Fl ...

  6. Android四种数据存储方式

    一.SharedPreference数据存储篇 1.作用范围 (1).它是一种轻型的数据存储方式 (2).本质是基于XML文件存储key-value键值对数据 (3).通常用来存储一些简单的配置方式 ...

  7. django 模型对象的 update() get_or_create() 的使用

    update() 如果一个查询集是一个列表对象, 需要更新该列表对象里所有的单个数据集的数据,可以使用update()方法,而不须遍历整个查询集对象一个个逐一进行修改 obj_list = UserI ...

  8. zoj2893 Evolution(矩阵快速幂)

    题意:就是说物种进化,有N种物种,编号是0——N-1,M次进化后,问你编号为N-1的物种有多少数量:其中要注意的就是i物种进化到j物种的概率是p:(那么剩下的不要忘了):所以单位矩阵初始化对角线的值为 ...

  9. nginx代理websocket协议

    以下是代码段.location /wsapp/ {     proxy_pass http://wsbackend;     proxy_http_version 1.1;     proxy_set ...

  10. day 51 cooike 与 session

    前情提要: cooike 和session 一:cooike 一.会话跟踪技术   1.什么是会话跟踪技术  我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可 ...