eclipse android jni 和 ndk以及调用过程--tread

  • 乱叨叨
  • 配置
    • 1下载所需要的ndk版本
    • 2新建一个Android工程
    • 3编写java类对CC 进行调用
    • 4生成h头文件
    • 5根据头文件编写自己的cc文件并编译
    • 6遇到问题
      • 1include utilsLogh和include JNIHelph等缺失
      • 2Android NDK C JNI no implementation found for native
      • 3Eclipse Ndk开发中的Method NewStringUTF could not be resolved问题
      • 4调用jni运行一段时间出现ReferenceTable overflow max1024
      • 5在java中用了thread循环不停的调用jni中的write函数但是退出程序再进入出现多个thread进行写操作
      • 6Unresolved inclusion jnih
    • 7安卓开发之JNI编程详解
    • 8JNI内存释放问题
  • 二参考

乱叨叨

因为要实现通过jni和串口通信,考虑用ndk方式,最近把自己折腾疯了,换一个电脑又折腾了一次,每次都是迷迷糊糊,百度百度,赶紧趁有印象记下来,下次不用到处找了,来个基础的总结帖。

==可能参考性不是很大了,因为别人用eclipse最后才用这个,其实最开始是用as,等下次再as上面再调试一次再来写不一样的地方。

配置

1、下载所需要的ndk版本

我用的是最新的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设置选项了。

2、新建一个Android工程

在工程上右键点击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.错误,原因在于:$符号前面必须加一个空格或者是=后面的数据和=号之间也要用一个空格隔开。

3、编写java类对C++/C 进行调用

写上需要的类名,例如:private static native String helloWorld();
再加上下面这段,之后进行库的加载以用来调用里面方法。

static { 
  System.loadLibrary(库名(注意,不加lib)); 
  } 

4、生成.h头文件

==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");   }   }

5、根据头文件编写自己的c++/c文件并编译

(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

6、遇到问题

(1)include “utils/Log.h”和include “JNIHelp.h”等缺失

==》这两个在ndk里面并不提供支持,如果是用jni和系统服务,把库在系统中进行编译加载才有支持,ndk中不行。
输出log可以用#include

__android_log_print(ANDROID_LOG_ERROR,"hello","-----------set");

(2)Android NDK C++ JNI (no implementation found for native…)

可能原因有多种:
a.在函数调用之前没有调用System.loadLibrary() 。测试,如果你没有自定义JNI_OnLoad 函数,考虑加上,输出日志来判断是否库被成功加载。
b.如果你是用C++来处理,记得函数外面包裹extern “C”。

(3)Eclipse Ndk开发中的Method ‘NewStringUTF’ could not be resolved问题

解决:项目右键->属性->c/c++常规->Code Analysis,选择”Use project settings” 中的方法无法被解析(Method cannot be resolved)取消选择,应用->确定,然后刷新、清理、刷新、build项目。

(4)调用jni,运行一段时间,出现ReferenceTable overflow (max=1024)

原因:在jni方法中

jint *buf = buffer ? env->GetIntArrayElements(buffer, NULL) : NULL;

却没有进行释放。
解决:env->ReleaseIntArrayElements(buffer,buf,0);

(5)在java中用了thread循环不停的调用jni中的write函数,但是退出程序再进入,出现多个thread进行写操作。

原因:关掉了服务和应用,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个,不知道啥情况。。。。。。。。

(6)Unresolved inclusion jni.h

场景:导入一个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

7、安卓开发之JNI编程详解

(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。

8、JNI内存释放问题

参考链接: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

你可能感兴趣的:(android,jni)