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

android 专栏收录该内容
22 篇文章 0 订阅

乱叨叨

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

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

Eclipse

配置

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目录下。
(3)使用ant工具自动生成头文件

windows-show view-ant。(基于xml)
1、在工程中创建一个xml文件,右键,open with - other -ant edit
2、alt+/创建工程模板。
这里写图片描述
3、修改模板,创建target,点击ant中按钮,找到对应的xml文件,
这里写图片描述
4、多添加了一个需要创建头文件的类
这里写图片描述
这里写图片描述

拓展分析:
(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、编译多个源文件

比如在jni下有多个c++文件,并且在创建的jni库所在文件里面有进行调用,此时直接编译会出现错误,不存在其他cpp文件。
解决方式:
在Android.mk下面,添加其他需要编译的。
这里写图片描述
这样看可能不是很好看,下划线后面不能有空格,只能直接回车,这样等于让回车键无效。
这里写图片描述

7、面向多种CPU架构编译

1、一般的ndk不能用gen….虚拟机,因为是x86平台,自带虚拟机是arm。
解决方式:
在jni目录下增加一个xml文件Application.mk。
不写内容:arm
写上:APP_ABI := x86,此时就可以用gen….不能用自带虚拟机。
2、在这两种情况下,libs下都只生成对应的一种,要么x86要么armeabi。
如何让它在两种cpu上都可以运行?
APP_ABI := x86 armeabi
这样就可以同时生成两个库文件,同时运行在2个平台上。

8、遇到问题

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

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

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

Android.mk中加入log库

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := tread
LOCAL_SRC_FILES := tread.cpp

LOCAL_SHARED_LIBRARIES += libutils libcutils
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog


include $(BUILD_SHARED_LIBRARY)

(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);释放

-----------------------------
jint *buf = buffer ? env->GetIntArrayElements(buffer, NULL) : NULL;
  /*buf[0] = 1;
  buf[2] = 1;*/
  env->SetIntArrayRegion(buffer, 3, 5, buf);
  env->ReleaseIntArrayElements(buffer,buf,0);

做了实验,下面第二种代码是针对怎么讲java传递下来的数组在c中进行操作之后能返回改变的值。
env->SetIntArrayRegion(buffer, 3, 5, buf);函数作用:用buf的前五位替换buffer[3]开始的五个数。
最后的释放内存并没有把buffer内容清理了,我觉着可能只是清理c中的缓存,不是很懂,有会的求指教。
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
env->ReleaseIntArrayElements(arr,carr,0);释放,

二、参考

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

Java为何不能访问底层?怎样才能访问?

这里写图片描述

java源代码通过java编译器后变成字节码,然后装载到java平台运行期环境(java虚拟机),在不同的平台下游不同的java虚拟机,window下有window的,linux下有linux下的java虚拟机,java虚拟机屏蔽了与底层直接的细节,做到java运行与平台无关,所以java是不能访问底层的。
什么是NDK(android native develop kits ):android 本地开发工具集 ,可以把c/c++ ->编译成一个 linux下可以执行的二进制文件 java代码里面就可以通过jni 调用执行二进制的文件.
什么是JNI :java本地开发接口,JNI是一个协议这个协议用来沟通java代码和外部的本地代码(c/c++).通过这个协议,java代码就可以调用外部的c/c++,代码外部的c/c++代码也可以调用java代码。

配置

1、配置ndk。

下载和eclipse一样,通用。新建一个project。打开project structure(快捷键CTRL + ALT + SHIFT + S)设置ndk路径。如下图:
这里写图片描述

自动会在local.properties 文件中设置ndk的路径:
这里写图片描述

2、命令生成头文件

在java代码中,用native来标识一个方法,告诉程序这是一个本地方法。

public class MainActivity extends Activity {

    static {        System.loadLibrary("MyJni");//导入生成的链接库文件
             }
    public native String getStringFromNative();//本地方法
    public native String getString_From_c();
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        System.out.println(getStringFromNative());
    }
    public void onClick(View view){
        System.out.println(getString_From_c());
        Toast.makeText(this, getStringFromNative(), Toast.LENGTH_LONG).show();
    }
}

这一步可以先不加这行代码:System.loadLibrary(“MyJni”);//导入生成的链接库文件 ,因为这里还没有建立C文件,不能生成库文件。

make project

然后make project一下,目的就是编译成对应的class文件。然后根据生成的class文件,利用javah生成对应的 .h头文件。如果没有编译就执行javah命令会提示找不到这个类文件的。
这里写图片描述

build之后会在build/intermediates/classes/debug/下面生成对应的class文件。

这里写图片描述

!!!注意我工程里面的本地方法显示是红色,我一开始以为不能进行JNI开发,或者有错误,包括我后面的C文件和头文件里面也有很多地方是红色的,我开始定义这些方法的时候是红色的,但是有时又不是的,不知道是android studio对NDK支持不太好,还是怎么得,但是后面开发JNI程序没有任何影响的。所以就没有关了,如果那位大神知道还望告诉我一下,将非常感激。

打开命令窗口

这里写图片描述

进来后默认是指向当前的工程目录,接下来输入命令:cd app/src/main 回车,切换到main目录下:如图:
这里写图片描述

输入下面的命令自动生成头文件。

javah -d jni -classpath D:/swdeveloper/android_sdk/platforms/android-21/android.jar;../../build/intermediates/classes/debug com.ra.hellojni.MainActivity

这里写图片描述

(注意前后有英文的;号隔开的哈) 生成头文件(要先编译程序的,不然会报错)。可能有些人看到这里就茫然了,这么长怎么写出来的哟,这怎么记得到的,我告诉大家根本不用记,我给你说怎么得来的,你们就能很快写出来了,是不是真的哟,不信试一试的。
首先:javah是生成头文件需要的工具,这个很好记得把,相信学java是都用过的。-d jni 在工程下生成jni目录,到时会在这个目录下建JNI开始的C/C++源文件的。

-classpath E:/Android/sdk/platforms/android-22/android.jar 这个就是你SDK文件下android.jar所在的文件位置,找到后复制即可。在学java的时候讲了这个的,可以将E:/Android/sdk/platforms/android-22/android.jar配置到环境变量就可以不写出来,因为在生成头文件是需要这个jar包,因为我没有配置到环境变量,所以这里就显示写出来。否则报错如下。
这里写图片描述

或者用下面这个命令:
这里写图片描述

第二个命令我试了两次,如果java类不是activity的时候,我不加android.jar就可以成功,今天类是activity就报了图上所示的错误,加上就成功了。

==》得到所需要的头文件,将头文件拷贝到src/main目录下的jni目录下。

创建对应的c文件

这里写图片描述

这里写图片描述

从头文件那边拷贝过来函数名,加上函数体。
这里写图片描述

配置ndk及其生成so文件名称

修改build.gradle 文件

buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' ndk {
                moduleName "MyJni"             //生成的so名字
                abiFilters "armeabi", "armeabi-v7a", "x86"  //输出指定三种abi体系结构下的so库。
            }
        }

        debug {
            ndk {
                moduleName "MyJni"             //生成的so名字
                abiFilters "armeabi", "armeabi-v7a", "x86"  //输出指定三种abi体系结构下的so库。
            }
        }
    }

设置android.useDeprecatedNdk=true

如果你做到上面一步就直接编译会报错:“NDK integration is deprecated in the current plugin”
这里写图片描述

解决方式:打开工程目录下的gradle.properties文件,并在该文件中写入下面这行:
android.useDeprecatedNdk=true
这里写图片描述
不出意外的话,再次编译工程,NDK环境这块就OK了。

jni下面创建一个空的c文件

上面编译的时候依旧报错,

Error:Execution failed for task ':app:compileDebugNdk'.
> Error: NDK integration is deprecated in the current plugin.  Consider trying the new experimental plugin.  For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental.  Set "android.useDeprecatedNdk=true" in gradle.properties to continue using the current NDK integration.

解决方式:不知道为何,有知道的和我说一下,O(∩_∩)O谢谢

 在jni文件下建一个空的empty.c文件 编译运行即可
       如果还运行不了,在当前model的build.gradle下添加 

android{
   …………
    sourceSets.main {
        jni.srcDirs = []
    }
}    

在android studio中ndk的c/c++文件中输出log

和eclipse确实有点不同,Android.mk文件是根据build.gradle自动生成的,所以,在build.gradle下的ndk中加入 ldLibs.addAll([“android”, “log”]),即:

ndk {
             moduleName = 'hello-jni'
             toolchain = 'clang'
             ldLibs.addAll(["android", "log"])
             CFlags.addAll(['-Wall'])
         }
接着在需要使用log的文件加入头文件:
#include <android/log.h>
使用语句:__android_log_print(ANDROID_LOG_ERROR,"hello","------------------------------------open");输出结果,
或者定义之后调用
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "ProjectName", __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "ProjectName", __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO  , "ProjectName", __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN  , "ProjectName", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , "ProjectName", __VA_ARGS__)

void testlog(){
    LOGI("test");
}

类似这样更方便。

参考

1、http://www.th7.cn/Program/Android/201509/550864.shtml
2、http://blog.csdn.net/cuiran/article/details/50755767
3、http://blog.csdn.net/sun00743/article/details/49278169

  • 0
    点赞
  • 1
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值