Android的NDK开发~Hellow world!
1、到Google官网下载adt-bundle----开发Android App的工具打包下载,下载后解压即可,免去开发环境的配置。
然后下载NDK,建议下载最新版本的,(ps:之前下载过r8a的,有bug,导致编译很慢,r8b就没有),配置ADT中的NDK Path。
~~这样子就完成了NDK开发的全部准备了。
2、新建Android项目,step by step,这里就不罗嗦了...
3、ADT v20之后的版本,提供了较为友善的NDK开发支持(ps:所以一定要升级ADT到最新版本哦),Android Tools-->Add Native Support :
然后是所如so的名称,也就是编译后,库的名称,默认即可,之后可以在mk文件上改的:
完成后项目的结构会变成:
我们重点关注jni文件夹的内容,文件里放置的是我们的源码(.h, .c, .cpp)和mk文件(即make文件,告诉编译器应该如何进行编译,详细请自行Google),默认自动生成的cpp基本是空白的,mk文件如下:
上面mk文件定义了编译的路径(LOCAL_PATH),include的路径(BUILD_SHARED_LIBRARY),编译后库的名称(LOCAL_MODULE),编译源文件的路径(LOCAL_SRC_FILES)
4、下面来一个例子:
Scgps_Client.h
#include <string.h> #include <jni.h> #include <android/log.h> #ifndef LOG_TAG #define LOG_TAG "android-ndk" #endif #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
头文件除了引用到C的头文件之外,还引用了android方面的头文件log.h,用以实现打印log。
Scgps_Client.c
#include <Scgps_Client.h> JNIEXPORT jstring JNICALL Java_com_scgps_client_MainActivity_sayHellow(JNIEnv* env, jobject thiz) { const char * ret = "Hellow Form Ndk"; LOGI("stationAndLines --> %s", ret); return (*env)->NewStringUTF(env, ret); }
.c文件里面就是实现Java的回调方法了,主要就是返回字符串“Hellow From Ndk”。
MainActivity.java
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toast.makeText(getApplicationContext(), sayHellow(), Toast.LENGTH_LONG).show(); } public native String sayHellow(); static { System.loadLibrary("Scgps_Client"); } }
接着,我们一步步地解析:
1、Java方面只管调用,倒是很简单,使用关键字native,声明本地方法,然后在静态代码块中加载本地库。
注意,loadLibrary的参数是库的名称,也就是mk文件中LOCAL_MODULE定义的值。
ps:之前也做过windows下Jni方面的开发,编译出来是dll,然后Java加载的参数就是dll的名称;
但是ndk编辑的so文件名称却是libScgps_Client,这里有待研究...
2、本地代码方面,回调方法的定义 JNIEXPORT jstring JNICALL Java_com_scgps_client_MainActivity_sayHellow(JNIEnv* env, jobject thiz); ,稍稍有点复杂,我尽量详述:
首先是方法名,Java_com_scgps_client_MainActivity_sayHellow,这里与Java层是关系密切的,根据JNI的编程规范,调用的方法必须以Java开头,然后详细描述Java层native方法所在的详细信息,并以“_”分隔,区分大小写!
因此,sayHellow是Java声明的本地方法,MainActivity是类名,com_scgps_client是包名。
假如C定义的方法名称与Java声明的名称对不上,运行则会报错,抛出下面的信息:
友情提示:
使用JDK的javah生成JNI的头文件!
先编译Java工程,之后项目目录下会有bin文件夹,bin存放编译好的class文件;
使用console,cd到bin目录下:
javah -classpath . -jni com.scgps.client.MainActivity
然后是参数:JNIEnv* env 和 jobject thiz
开讲之前,先简单说明一下,Jni中与Java基本数据类型的映射关系:
定义都在jni.h的头文件中,各看官可自行查阅,基本上Java中的各种数据类型,jni中都有定义,也提供了相应的转换方法,供C与Java之间传递。
OK,现在让我们继续分析 Java_com_scgps_client_MainActivity_sayHellow(JNIEnv* env, jobject thiz);
JNIEnv* env : 指向 JNI 函数表的指针变量。
jobject thiz : 该参数的意义取决于该方法是静态还是实例方法(static or an instance method)。
- 当本地方法作为一个实例方法时,第二个参数相当于对象本身,即 this;
- 当本地方法作为一个静态方法时,指向所在类;
在上面的例子中,Java_com_scgps_client_MainActivity_sayHellow是一个实例方法,因为thiz指向Java对象本身!
最后是返回值,jstring
很明显,这是返回Java字符串类型的值,通过 (*env)->NewStringUTF(env, ret); ,调用JNI函数表中的NewStringUTF方法,转换ret指向的字符串,使其能被Java所识别。
最后的最后,当然就是编译、运行了:
先看看现在的mk文件:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := Scgps_Client LOCAL_SRC_FILES := \ Scgps_Client.c \ LOCAL_LDLIBS := -llog -landroid LOCAL_STATIC_LIBRARIES := android_native_app_glue include $(BUILD_SHARED_LIBRARY) $(call import-module,android/native_app_glue)
因为引用到Android的库,所以也要在这里声明一下,见LOCAL_LDLIBS和LOCAL_STATIC_LIBRARIES的值。
总结几个开发中的技巧:
由于CDT(eclipse开发c/c++的插件)开发时,依赖编译的结果,所以提示总是会滞后。如果提示源码中有错,或找不到include依赖时,不妨先查看一下mk文件是否已经描述清楚了,然后再rebuild一下。
当整个编译的结构有大的改动时,也不要忘了clear project,在build~~。