欢迎转载,转载请注明出处: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. jquery ui widgets-datepicker

    jquery ui的用法就不在此讲述,直接进入jquery ui的窗体小部件(widgets)——datepicker. 相信很多像我这样子的菜鸟少年,如果同一个页面上有两个input文本输入框是用来 ...

  2. 微软研发流程(ALM)管理培训会议(比亚迪汽车)

    主要讨论和演示整体研发流程,包括需求管理.项目计划.开发管理.生成和发布.测试管理等. Figure 1 - 客户现场培训 Figure 2 - 客户现场培训 Figure 3 - 客户现场培训

  3. C#开发微信小程序

    个人见解,欢迎交流,不喜勿喷.   微信小程序相比于微信公众号的开发,区别在于微信小程序只请求第三方的数据,整个界面的交互(view)还是在微信小程序上实现,前后端完全分离,说白了,微信小程序开发与具 ...

  4. ASP.NET在请求中检测到包含潜在危险的数据,因为它可能包括 HTML标记或脚本

    背景:程序迁移到新的服务器上,在程序进行修改操作时,提示包含危险数据.然而在旧服务器上却没有问题,我猜想的可能是,新服务器IIS安装的ASP.NET版本框架高于以前的IIS上的版本框架,导致web.c ...

  5. CentOS ASP.NET Core Runtime Jexus跨平台布署

    .net core 开源和跨平台,能布署到当前主流的Windows,Linux,macOS 系统上.本篇我们将在 Linux 系统上使用 ASP.NET Core Runtime 和 Jexus 布署 ...

  6. 为什么不能用Abort退出线程

    在使用线程时,如果线程还未结束直接退出线程很有可能会导致数据丢失. class threadAbort { static void Main(string[] args) { WriteMessage ...

  7. The rapid development platform upgrade, leave the time to yourself, the work is lost to the soft platform

    Bring me back to your home. Please leave your work behind! Soft agile development framework V7.0 new ...

  8. AJPFX的监管与执照

      AJPFX受到英国金融行为监管局(FCA)授权和监管. 英国FCA是目前世界上金融服务最完善.最健全的监管机构,英国FCA对所有在其境内注册的金融服务机构进行严格的监管. 英国金融行为监管局(FC ...

  9. 《Python黑帽子:黑客与渗透测试编程之道》 扩展Burp代理

    下载jython,在Burpsuite的扩展中配置jython路径: Burp模糊测试: #!/usr/bin/python #coding=utf-8 # 导入三个类,其中IBurpExtender ...

  10. django orm 以列表作为筛选条件进行查询

    在Django的orm中进行查询操作时,可以通过传入列表,列表内的元素为索引值,作为一个筛选条件来进行行查询 from .models import UserInfo user_obj = UserI ...