/*************************************************************************** * Android_ndk_jni_hello-jni_hacking * 声明: * 1. 本文用的是android-2.2_froyo的源代码; * 2. 本文仅仅是对Android自带的ndk中的samples中的jni_hello示例的解读; * 3. 本文更多的是想通过这个自带的简单示例来了解jni的框架结构,所以没 * 有提供除此之外的更多的内容解读. :) * * 2015-4-19 周日 晴 深圳 南山 西丽平山村 曾剑锋 **************************************************************************/ \\\\\\\\\\\\* 目录 *///////////////////////// | 一. 参考文章: | | 二. 分析源码来源及文件结构: | | 三. 解析流程: | | 四. 解析src/com/example/hellojni/HelloJni.java文件: | | 五. 解析jni/Android.mk文件: | | 六. 解析jni/hello-jni.c文件: | \\\\\\\\\\\\\\///////////////////////////// 一. 参考文章: 主要参考ndk自带的文档(ndk根目录下的docs文件夹): . |-- ANDROID-MK.TXT |-- APPLICATION-MK.TXT |-- CHANGES.TXT |-- CPU-ARCH-ABIS.TXT |-- CPU-ARM-NEON.TXT |-- CPU-FEATURES.TXT |-- DEVELOPMENT.TXT |-- HOWTO.TXT |-- INSTALL.TXT |-- LICENSES.TXT |-- NDK-BUILD.TXT |-- NDK-GDB.TXT |-- OVERVIEW.TXT |-- STABLE-APIS.TXT `-- SYSTEM-ISSUES.TXT 二. 分析源码来源及文件结构: 1. 本文是用NDK自带的samples中的hello-jni来分析jni的编写流程; 2. NDK提供的hello-jni示例文件结构: . |-- AndroidManifest.xml |-- default.properties |-- jni | |-- Android.mk ---> jni makefile | |-- Android_ndk_jni_hello-jni_hacking.c | `-- hello-jni.c ---> jni C文件 |-- libs | `-- armeabi |-- res | `-- values | `-- strings.xml `-- src `-- com `-- example `-- hellojni `-- HelloJni.java ---> java源文件 3. 从文件结构上分析可知,这是一个Android工程项目; 三. 解析流程: 1. 本文采用java程序-->jni接口-->C程序的解析流程,这也比较符合常规需求分析; 2. 解析文件流程: 1. src/com/example/hellojni/HelloJni.java 2. jni/Android.mk 3. jni/hello-jni.c 四. 解析src/com/example/hellojni/HelloJni.java文件: 1. cat src/com/example/hellojni/HelloJni.java ...... package com.example.hellojni; //javah会利用包名生成jni C函数名 import android.app.Activity; import android.widget.TextView; import android.os.Bundle; public class HelloJni extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* Create a TextView and set its content. * the text is retrieved by calling a native * function. */ TextView tv = new TextView(this); tv.setText( stringFromJNI() ); setContentView(tv); } /* A native method that is implemented by the * 'hello-jni' native library, which is packaged * with this application. */ /** * 这个本地方法在hello-jni库中已经实现了, * 并且hello-jni库,已经被打包进这个应用里. */ public native String stringFromJNI(); //本地方法声明方式,多加个native /* This is another native method declaration that is *not* * implemented by 'hello-jni'. This is simply to show that * you can declare as many native methods in your Java code * as you want, their implementation is searched in the * currently loaded native libraries only the first time * you call them. * * Trying to call this function will result in a * java.lang.UnsatisfiedLinkError exception ! */ /** * 这是另一个本地方法,但是没有在hello-jni库中实现, * 这仅仅是为了告诉你,你可以声明很多本地方法在你的Java * 代码中,只有当你调用这个函数的时候,才会去查找这个本地 * 是否实现了. * * 如果你去调用这个没有实现的本地方法,你将会得到: * java.lang.UnsatisfiedLinkError异常. */ public native String unimplementedStringFromJNI(); /* this is used to load the 'hello-jni' library on application * startup. The library has already been unpacked into * /data/data/com.example.HelloJni/lib/libhello-jni.so at * installation time by the package manager. */ /** * 在应用程序启动的时候加载hello-jni库.这个库在你的安装这个app的 * 时候已经解压放到了/data/data/com.example.HelloJni/lib/libhello-jni.so */ static { System.loadLibrary("hello-jni"); //本地方法所在的库文件名 } } 2. 获取jni头文件的方法: 1. 我们一般会觉得,拿着工程中的.class文件就可以直接获取jni头文件: javah HelloJni, 经过测试,结果是不行的. 2. 将HelloJni.class要放在com/example/hellojni文件夹下,在android的工程bin目录下 是这么放置的,个人猜测是为了得到包名. 3. 在com目录下的同一级目录下执行: javah com.example.hellojni.HelloJni,这样就可以 获得文件: com_example_hellojni_HelloJni.h,也就是我们想要的头文件.
4. 在src目录也是可以直接用javah来获取jni头文件的,尤其是遇到javah 找不到类Android.app.Activity的时候可以一试.
. 五. 解析jni/Android.mk文件: 1. cat jni/Android.mk ...... /** * Android.mk文件,必须在开始就定义LOCAL_PATH变量,指定源文件的目录, * my-dir这个宏函数,是由系统提供的,会返回当前文件夹的绝对路径 */ LOCAL_PATH := $(call my-dir) /** * CLEAR_VARS是由系统提供的变量,会清空绝大多数的的LOCAL_XXX变量, * 但是LOCAL_PATH是一个例外 */ include $(CLEAR_VARS) /** * 定义模块名,生成的共享库,会自动加上lib前缀,和.so后缀 */ LOCAL_MODULE := hello-jni /** * 指定生成模块的所需要的C/C++源文件,不需要列出头文件 */ LOCAL_SRC_FILES := hello-jni.c /** * BUILD_SHARED_LIBRARY是由系统提供的变量,这个是用创建出一个动态 * 共享库的方式,还有一种生成静态库的变量是:BUILD_STATIC_LIBRARY */ include $(BUILD_SHARED_LIBRARY) 六. 解析jni/hello-jni.c文件: 1. cat jni/hello-jni.c ...... #include <string.h> #include <jni.h> /* This is a trivial JNI example where we use a native method * to return a new VM String. See the corresponding Java source * file located at: * * apps/samples/hello-jni/project/src/com/example/HelloJni/HelloJni.java */ /** * 在这个简单的JNI示例中,我们是用了一个本地方法返回一个新的VM字符串 */ jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { return (*env)->NewStringUTF(env, "Hello from JNI !"); } 2. JNI命名规则: 1. 前缀: Java 2. 类的包名: 上例是---com.example.hellojni 3. 类名: 上例是---HelloJni 4. 方法名: 上例是---stringFromJNI 5. 第一个参数: JNIEnv* env 6. 第二个参数: jobject thiz 7. 实际的Java参数: 上例没有 8. 返回值的参数: 上例没有 3. 如果第一次写jni函数,尤其是当我们很多时候工作中是复制别人的函数内容的时候, 很可能会忘记了给自动生成的jni头文件函数声明的参数起名字,您可能回到如下 错误(这是本人生成另一个共享库犯的低级错误 :) ),错误原因也指出来了: APLEX@APLEX-PC /cygdrive/e/android_app/myserial $ $NDK/ndk-build Android NDK: WARNING: APP_PLATFORM android-18 is larger than android:minSdkVersion 8 in ./AndroidManifest.xml [armeabi] Compile thumb : serialPort <= SerialPort.c jni/SerialPort.c: In function 'Java_com_android_aplex_SerialPort_open': jni/SerialPort.c:77:3: error: parameter name omitted ---> 省略了参数名称 (JNIEnv *, jclass, jstring, jint, jint) ^ jni/SerialPort.c:77:3: error: parameter name omitted jni/SerialPort.c:77:3: error: parameter name omitted jni/SerialPort.c:77:3: error: parameter name omitted jni/SerialPort.c:77:3: error: parameter name omitted jni/SerialPort.c: In function 'Java_com_android_aplex_SerialPort_close': jni/SerialPort.c:155:3: error: parameter name omitted ---> 省略了参数名称 (JNIEnv *, jobject) ^ jni/SerialPort.c:155:3: error: parameter name omitted ---> 省略了参数名称 /cygdrive/d/ndk/android-ndk-r10d/build/core/build-binary.mk:455: recipe for target 'obj/local/armeabi/objs/serialPort/SerialPort.o' failed make: *** [obj/local/armeabi/objs/serialPort/SerialPort.o] Error 1