第一次写博客。主要原因是上次格系统的时候,one note记录的笔记忘备份了,结果积累的技术资料全没了。这次发网上,以后有需要的时候耗能回头再看看。
毕业之后一直做j2ee开发,后来接触点android,认识了NDK这东西,感觉不错,所以一直想了解下。因为本人C语言水平太差,所以就从helloworld开始吧。
1、开发环境
Windows 7 X64
Eclipse 4.2 juno(带adt插件,也可自己安装。)
Jdk 1.7
Androidsdk 4.3
Ndk 9
以上是我的环境。sdk版本4.0以上就可以,ndk7以上就可以(包括7)。7以下的ndk需要安装cygwin环境来编译C文件。至于cdt插件,我的eclipse里面是有的,感觉感觉没用到。
2、调试环境
android自带模拟器或真机,千万不要用genymotion,后面讲为什么。
3、配置eclipse的androidsdk、ndk环境变量
Window->Preferences->Android
Window->Preferences->Android->NDK
4、配置eclipse自动编译c/c++文件(这步可以放在工程创建之后再完成)
Project->Properties->Builders->New->Program
注意:Working Directory里面填写的是之后要开发的android工程,也可以在工程创建后,通过Browse Workspace选项来选
注意: 勾选Specifyworking set of relevant resources后,会提示让选择SpecifyResources。这里需要在工程创建后,点击这个按钮选择目标工程就可以了。
配置完这个builder之后,当手动clean工程的时候就可以自动编译c/c++文件了,不用再使用cmd或cygwin来生成.so文件了。
5、创建android工程
创建完工程后,在根目录下创建jni目录
这时,就出现分歧了:
a、先创建jni文件,之后再写java类调用
b、先创建java调用类,之后再使用javah工程生成对应的jni文件接口。
个人更倾向于b方案,感觉比较符合java面向接口的思想。
方案a没啥好说的,直接创建文件,写c文件就得了。下面讲讲方案b。
6、在要使用到jni的java类中,定义native函数
packagecom.neusoft.jni.helloworld;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.widget.TextView;
/**
* 测试jni调用
* @author 薛毅
*/
public classHelloworldActivity extends Activity {
//显示jni返回结果的ui组件
privateTextView tv ;
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_helloworld);
tv= (TextView)findViewById(R.id.hello_world);
//调用jni函数,并在页面上显示
tv.setText(stringFromJNI());
}
//定义JNI函数
publicnative String stringFromJNI();
//加载jni库
static{
//这名跟库名去掉lib一样
System.loadLibrary("hellojni");
}
}
7、生成c接口文件
如果之前没有配置过java的环境变量,那么这里需要配置下
打开windows控制台(cmd),切换到工程的bin/classes文件夹下,键入命令:
javahcom.neusoft.jni.helloworld.HelloworldActivity
javah后面的文件名就是要调用so文件的java类名。
这里需要注意下:如果报如下的错误
我就遇到这个问题了。估计这个问题是因为classpath不对造成的。那么可以把当前目录切换到工程的src目录下,再执行上面的命令,就可以在src目录下生成对应的.h文件,如下图:
这里分析下,生成的文件名为对应的类的全路径名,把“.”换成了“_”
里面内容如下:
/* DO NOT EDIT THISFILE - it is machine generated */
#include<jni.h>
/* Header for classcom_neusoft_jni_helloworld_HelloworldActivity */
#ifndef_Included_com_neusoft_jni_helloworld_HelloworldActivity
#define_Included_com_neusoft_jni_helloworld_HelloworldActivity
#ifdef __cplusplus
extern "C"{
#endif
/*
* Class: com_neusoft_jni_helloworld_HelloworldActivity
* Method: stringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstringJNICALL Java_com_neusoft_jni_helloworld_HelloworldActivity_stringFromJNI
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
文件中定义了一个Java_com_neusoft_jni_helloworld_HelloworldActivity_stringFromJNI函数,命名分三部分:Java是固定的;com_neusoft_jni_helloworld_HelloworldActivity是调用类的全路径名;stringFromJNI是类中定义的native方法名。
8、创建jni文件实现
把生成的这个文件拷贝到工程的jni目录下,同时创建个同名的com_neusoft_jni_helloworld_HelloworldActivity.c文件,实现如下:
#include<com_neusoft_jni_helloworld_HelloworldActivity.h>
#include<stdlib.h>
#include<stdio.h>
#ifdef __cplusplus
extern "C"
{
#endif
/*
* Class: com_neusoft_jni_helloworld_HelloworldActivity
* Method: stringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstringJNICALL Java_com_neusoft_jni_helloworld_HelloworldActivity_stringFromJNI
(JNIEnv *env, jclass arg)
{
jstring str = (*env)->NewStringUTF(env,"HelloWorld from JNI 123!");
return str;
}
#ifdef __cplusplus
}
#endif
在jni目录下再创建Android.mk文件,内容如下:
LOCAL_PATH := $(callmy-dir)
include$(CLEAR_VARS)
##在java中loadLibrary的名称
LOCAL_MODULE :=hellojni
##编译的c文件的名称
LOCAL_SRC_FILES :=com_neusoft_jni_helloworld_HelloworldActivity.c
include$(BUILD_SHARED_LIBRARY)
完工后,就可以实现jni自动编译了。
Q&A:
1、以上步骤都完成后,我使用genymotion调试,死活给我报:
java.lang.UnsatisfiedLinkError: Couldn't load XXX: findLibrary returned null
这个问题网上给的答案全是System.loadLibrary()的名字和Android.mk中的库名对不上,load的时候不要加lib前缀和so扩展名。我被网上的答案折磨了半天,怎么看我的名称也是对的。后来我改用真机调试,居然好使了。我估计因为c预言的编译是依托于平台的,我们编译的so文件都是针对arm架构的,而genymotion是直接用X86架构的芯片,所以不兼容,而不是真的找不到库。之后我又使用adt自带的模拟器实验了一下,果然也好使。
2、闪退。报
12-17 16:29:05.525:E/dalvikvm(1832): VM aborting
12-17 16:29:05.525:A/libc(1832): Fatal signal 11 (SIGSEGV) at 0xdeadd00d (code=1), thread 1832(.jni.helloworld)
因为字符集的事,不能编译带中文的
想要在c文件中带有中文,则需要.c的文件使用UTF-8的格式。