因为要实现通过jni和串口通信,考虑用ndk方式,最近把自己折腾疯了,换一个电脑又折腾了一次,每次都是迷迷糊糊,百度百度,赶紧趁有印象记下来,下次不用到处找了,来个基础的总结帖。
==可能参考性不是很大了,因为别人用eclipse最后才用这个,其实最开始是用as,等下次再as上面再调试一次再来写不一样的地方。
我用的是最新的r10。打开Eclipse,点Window->Preferences->Android->NDK,设置NDK路径。
question: 为何我的打开不出现NDK选项呢?
answer: 如果用的是android打包好的adt bundle,就会出现这个问题。目前官方就不再提供bundle的版本下载,先下载eclipse,然后在eclipse上安装adt,只要是全选,这个时候是不存在没有NDK选项这个问题的。但是很多以前下载的bundle版本的,是没有NDK这个选项的,这个时候就需要我们手动去安装,步骤如下:
1. Help-->Install New Software... --> Work with 输入 https://dl-ssl.google.com/android/eclipse/。
2.在打开的窗口出现的列表中会出现Developer tools,将其全选。
3.点击Next。若有提示就点击OK,一路下去。最后提示你重启Eclipse(ADT)。
重启后发现 Window->References->Android 里面有NDK设置选项了。
在工程上右键点击Android Tools->Add Native Support…,然后给我们的.so文件取个名字,例如:tread,注意,不要再加lib,它前面是有lib三个字母。
这时候工程就会多一个jni的文件夹,jni下有Android.mk和tread.cpp文件。Android.mk是NDK工程的Makefile,tread.cpp就是NDK的源文件。
!!!注意:在Android.mk文件中,修改过程容易遇到Android.mk: * missing separator. Stop.错误,原因在于:$符号前面必须加一个空格或者是=后面的数据和=号之间也要用一个空格隔开。
写上需要的类名,例如:private static native String helloWorld();
再加上下面这段,之后进行库的加载以用来调用里面方法。
static {
System.loadLibrary(库名(注意,不加lib));
}
==JNI接口的命名规范:
Java_ + 调用该方法的包名(包名的点用代替) + + 调用该接口的类名 + _ + 方法名,对于实例方法,有两个参数是必要的,一个JNI的环境指针JNIEnv *,另一个是调用该方法的Java实例jobject,所以,如果自己直接写函数名很麻烦,对照还容易出错,so,自动生成头文件最方便。
参考链接:http://blog.csdn.net/sunnyfans/article/details/16916451。
(1) 一般用法是转到android项目bin/classes目录下,然后执行:
javah -classpath ./ -d ../../jni -jni com.example.hellojni.HelloJni
个人习惯直接用:
在项目根目录下,直接执行:
javah -classpath ./bin/classes -d ./jni -jni com.example.hellojni.HelloJni这种风格。
执行后会在项目目录下生产jni文件夹,里面存放着自动生产的.h文件com_example_hellojni_HelloJni
(2)如果不想用命令可以用eclipse自动生成,选项实在太多,我试过,但是很快就忘记了,参照楼下这个链接吧
http://www.oschina.net/question/1402563_133543?fromerr=UQ70Bga3
二者区别:一个在项目马上就看到头文件,一个需要从classes目录下拷贝到jni目录下。
拓展分析:
(1)javah
javah [选项] <类>
-help 输出此帮助消息并退出 -classpath <路径> 用于装入类的路径 -bootclasspath <路径> 用于装入引导类的路径 -d <目录> 输出目录 -o <文件> 输出文件(只能使用 -d 或 -o 中的一个) -jni 生成 JNI样式的头文件(默认) -version 输出版本信息 -verbose 启用详细输出 -force 始终写入输出文件 使用全限定名称指定 <类>
(2)jni的JNIEnv指针和jobject指针
Java本地接口(Java Native Interface (JNI))允许运行在Java虚拟机(Java Virtual Machine (JVM))上的代码调用本地程序和类库,或者被它们调用,这些程序和类库可以是其它语言编写的,比如C、C++或者汇编语言。
在JNI中,本地函数是通过一个独立的.c或.cpp文件来实现的(C++为JNI提供的界面会更简洁一些)。当JVM调用该函数时,它传递了一个JNIEnv指针、一个jobject指针和通过Java方法定义的Java参数,JNI函数的形式如下:
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobjectobj)
{
//Method native implemenation
}
重要:::env指针是一个包含了JVM接口的结构,它包含了与JVM进行交互以及与Java对象协同工作所必需的函数,JNI函数可以在本地数组和Java数组类型之间、本地字符串和Java字符串类型之间进行转换,其功能还包括对象的实例化、抛出异常等(如:java用的是unicode编码,而c用的是ascii编码,实现转换后再传递)。基本上您可以使用JNIEnv来实现所有Java能做到的事情,虽然要简单很多。
本地方法将JNI界面指针当作一个参数,如果在同一个Java线程中,出现对该本地方法的多重调用,JVM则保证传递相同的界面指针到本地方法。不过,一个本地方法可以被不同的Java线程调用,因而也可能会收到不同的JNI界面指针。
jobject指向在此java代码实例化的java对象LocalFunction的一个句柄,相当于this指针。(如果是static则传进来的是jclass)
本地方法是通过System.loadLibrary方法加载的,在以下的例子中,类的初始化方法加载了一个指定平台的本地类库,该类库定义了本地方法:
packagepkg;
class Cls {
native double f(inti, String s);
static {
System.loadLibrary(pkg_Cls"); } }
(1)配置一个Builder
(a)Project->Properties->Builders->New,新建一个Builder。
(b)在弹出的【Choose configuration type】对话框,选择【Program】,点击【OK】:
(c)在弹出的【Edit Configuration】对话框中,配置选项卡【Main】。
在“Name“中输入新builders的名称(这个名字可以任意取)。
在“Location”中输入nkd-build.cmd的路径(建议放在根目录下面,路径不能有空格和中文)。根据各自的ndk路径设置,也可以点击“Browser File System…”来选取这个路径。
在“Working Diretcoty”中输入jni项目位置(也可以点击“Browse Workspace”来选取目录)。
(d)继续在这个【Edit Configuration】对话框中,配置选项卡【Refresh】。
勾选“Refresh resources upon completion”,
勾选“The entire workspace”,
勾选“Recuresively include sub-folders”。
(e)继续在【Edit Configuration】对话框中,配置选项卡【Build options】。
勾选“After a “Clean””,(勾选这个操作后,如果你想编译ndk的时候,只需要clean一下项目 就开始交叉编译)
勾选“During manual builds”,
勾选“During auto builds”,
勾选“Specify working set of relevant resources”。
点击“Specify Resources…”勾选工程中新建的“jni“目录,点击”finish“。 点击“OK“,完成配置。
扩展:ndk-build也可以在命令行中进行,参考:http://blog.csdn.net/smfwuxiao/article/details/8523087
==》这两个在ndk里面并不提供支持,如果是用jni和系统服务,把库在系统中进行编译加载才有支持,ndk中不行。
输出log可以用#include
__android_log_print(ANDROID_LOG_ERROR,"hello","-----------set");
可能原因有多种:
a.在函数调用之前没有调用System.loadLibrary() 。测试,如果你没有自定义JNI_OnLoad 函数,考虑加上,输出日志来判断是否库被成功加载。
b.如果你是用C++来处理,记得函数外面包裹extern “C”。
解决:项目右键->属性->c/c++常规->Code Analysis,选择”Use project settings” 中的方法无法被解析(Method cannot be resolved)取消选择,应用->确定,然后刷新、清理、刷新、build项目。
原因:在jni方法中
jint *buf = buffer ? env->GetIntArrayElements(buffer, NULL) : NULL;
却没有进行释放。
解决:env->ReleaseIntArrayElements(buffer,buf,0);
原因:关掉了服务和应用,thread并没有很好的释放,还在进行写操作,并且无法控制。
解决:
handlerThread = new HandlerThread("threadone");
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
mHandler.post(mRunnable);
private Runnable mRunnable = new Runnable() {
public void run() {
if(!isStop){
//这里进行jni
mHandler.post(mRunnable);
}
}
};
//最后在onDestory中进行退出操作
isStop = true;
mHandler.removeCallbacks(mRunnable);
handlerThread.quit();
handlerThread = null;
==》停止thread可以调用stop(容易出问题),或者像上面那个,在run()中设置停止位。求助:如果有更好的方式希望告诉我一声啊。handlerthread继承自thread,但是我后面直接用thread设置停止位,第一次thread还是会出现2个,不知道啥情况。。。。。。。。
场景:导入一个jni ndk项目,出现头文件错误
参考链接:http://www.bubuko.com/infodetail-666021.html
解决方法:
一、方法一
1、关掉eclipse
2、打开项目的目录,找到.project,文本方式打开,这里有至少2个需要被删除。
移除以下内容。
<buildCommand> node with name org.eclipse.cdt.managedbuilder.core.genmakebuilder and all its children, 以及 <buildCommand> node with name org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder and its children.
最后移除下面四行
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.core.ccnature</nature> <nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature> <nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
3、删除 .cproject文件。
4、打开eclipse,选中项目,android tools 选择Add Native Support…搞定。
二、方法二:
1.Paths and Symbols中路径不正确,考虑修改Android.MK文件内容,比如增加空行等(注意Android文件中不需要有LOCAL_C_INCLUDES字段 ,系统会根据LOCAL_SRC_FILES自动添加需要的头文件,否则头文件将出现二义性),重新编译一次项目,Android.MK文件中LOCAL_SRC_FILES所依赖的头文件将会在Project Properties -> C/C++ General -> Paths and Symbols中被自动添加;
2.string.h、jni.h等C头文件“Unresolved inclusion”,考虑: NDK Project -> New -> Folder -> Advanced -> Link to alternate location(Linked Folder),添加:D:\ADT\android-ndk-r9d\platforms\android-18\arch-arm\usr\include;
3.考虑:properties—>C/C++ General,关闭Code Analysis功能,解决大部分错误;
参考:1、关于thread文章:http://www.cnblogs.com/dolphin0520/p/3920357.html
2、Handler HandlerThread AsyncQueryHandler 三者的关系:
http://www.cnblogs.com/moonvan/archive/2011/04/22/2024979.html
(1)、Java基本类型的传递
java中的基本类型包括boolean,byte,char,short,int,long,float,double这样几种,如果你用这几种类型做native方法的参数,当你通过javah-jni生成.h文件的时候,只要看一下生成的.h文件,就会一清二楚,这些类型分别对应的类型是jboolean,jbyte,jchar,jshort,jint,jlong,jfloat,jdouble。这几种类型几乎都可以当成对应的C++类型来用
(2)、String参数的传递
Java的String和C++的string是不能对等起来的,jstring是JNI中对应于String的类型,但是和基本类型不同的是,jstring不能直接当作C++的string用。
constchar*str;
str=env->GetStringUTFChars(prompt,false);
env->ReleaseStringUTFChars(prompt,str);
在上面的例子中,作为参数的prompt不能直接被C++程序使用,先做了如下转换
str=env->GetStringUTFChars(prompt,false);
将jstring类型变成一个char*类型。
返回的时候,要生成一个jstring类型的对象,也必须通过如下命令,
jstringrtstr=env->NewStringUTF(tmpstr);
(3)、数组类型的传递
jint*carr=env->GetIntArrayElements(arr,false);
env->ReleaseIntArrayElements(arr,carr,0);释放
GetIntArrayElements和ReleaseIntArrayElements函数就是JNI提供用于处理int数组的函数。如果试图用arr的方式去访问jintArray类型,毫无疑问会出错。JNI还提供了另一对函数GetIntArrayRegion和ReleaseIntArrayRegion访问int数组
(4)二维数组和String数组
在JNI中,二维数组和String数组都被视为object数组,因为数组和String被视为object。
参考链接:http://blog.sina.com.cn/s/blog_706e79d60101ceh2.html
参考链接:
http://blog.csdn.net/tianruxishui/article/details/37592903 通过jni操作串口,例子写的很不错。
http://www.2cto.com/kf/201404/292918.html
http://blog.csdn.net/sunnyfans/article/details/16916451
http://android.tgbus.com/Android/androidnews/201206/438987.shtml
http://blog.sina.com.cn/s/blog_706e79d60101ceh2.html