标签:
开发环境:
加载库名,然后系统自动到库目录下找.so动态库
目录/库文件名
loadLibrary
? ? ?
du -mh tags
androidL/art/
vi -t Runtim_nativeLoad
? ? ?
javah -jni Hello 生成头文件
JNINativeMethod methods[]就相当于在库中的函数集合
gcc -shared -fPIC native.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -o liblednative.so
? ? ?
? ? ?
java Hello 执行Java可执行程序
export LD_LIBRARY_PATH=.
java Hello
java中的编码格式和C里面是不一样的,因为Java中是UTF-8能支持所有编码?而C中是Unicode编码,如何找到编码转换函数呢?搜JNI编程文档嘛,可以搜RegisterNative嘛,可以直接搜,或者想想在哪个步骤中会涉及这个东西。convert string 如果实在不知道搜什么,就往下翻吧。
? ? ?
如何把单个Java源文件放到eclipse里面结构里面
我们在eclipse里面做一个app,也就是在MainActivity.java里,你当然是造一个按钮啦
点击一个按钮就调用一个函数嘛ioctl
android studio和eclipse功能几乎一样啊,为什么以为差别很大呢?
? ? ?
private Button button = null;
button = (Button)findViewById(R.id....);
ledon = !ledon;
button.setText("led on");
? ? ?
可以在文件管理器里添加一个类包目录Le,然后把lednative.java拷贝进去,然后在eclipse里面刷新,项目会自动添加文件夹
package com.example.Led;
public class lednative {
static {
system.loadLibrary("lednative");
}
public native int ledopen();
publc
}
NDK
因为一切皆对象嘛,所以你总是在创建类,然后new对象,然后用对象调用方法来实现功能,而方法又调用其他代码,比如JNI代码啦
把native.c编译生成动态库啊
arm-linux-gnueabi-gcc -shared -fPIC .../include -nostdlib 这里是非标库路径 -o liblednative.so
find -name libc.so
? ? ?
file liblednative.so 查看交叉编译器是否生成了arm格式的库,拷贝到windowsde fastboot目录,上传(打开一键烧写工具)到/system/lib目录,adb push liblednative.so /system/lib/,如果没有权限则mount -o remount /dev/block/by-name/system /system也就是arm平台目录
? ? ?
最后连接平板继续调试,看LogCat是否打印出错误信息
好吧,又出错啦:
因为虽然加载了我们的本地动态库,但是本地动态库又要调用C库libc.so.6,这什么鬼东西?
那就到Android源码里面找啊 find -name libc.so.6
首先明白lib.so是非标准库,然后搜GCC 使用非标准库 -nostdlib 不使用标准库,使用非标嘛
? ? ?
【学习方法】从成功范例里面学习,对于编译命令选项这些,一定要理解每个选项参数的意义,你可以把参数删掉,然后对照学习。便可以理解了。只管写代码啊,要动手。
? ? ?
--------------------首先我们在activity中创建一个button--修改button属性:
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------接着我们在主程序中引用这个button,注意定义在类里面,而不是OnCreate函数里面哦:
--------------------接着我们初始化这个button对象,管理我们创建的那个button:
接着调用button的按键按下监听事件接口方法,在方法里面调用按下处理函数(匿名类函数(注意格式:是在小括号里面实现类的函数体的)哦):
--------------------接着填充按键处理方法(技巧:new后面空一格,然后再按Alt + /可以自动填充):
这里的ledon是一个状态指示灯,其实就指示了led硬件的状态啦!
注意要添加一个ledon的数据域field
接下来我么简单调试一下这个demo:
--------------------接下啦放大招了,我们来通过JNI来调用C来访问硬件试试看,这里的C被封装成了C动态库 来直接和内核打交道了:
这里和C打交道的中间层(下层只认识上层,而不认识上上层,也就是你理解的时候只要每次在两个层之间就可以了)就是JNI,而JNI我们封装成一个类,在这个类里面我们加载C库,然后声明C库里面可以调用的方法
然后在主程序里面声明这个JNI类就好啦!
--------------------我们需要平板里面有动态库,然后应用程序APP去调用它,好的首先要创建一个动态库libled.so,这个库说白了就是C程序,只不过打包成了一个库的格式而已
? ? ?
问题层出不穷,主要出在依赖上而且Android系统有他自己实现,有时接口名字是很像的(其实本质就是接口名啦,然后上层又封装了而已而已):
? ? ?
--------------------我们先在eclipse里面写一个,JNI的调用类LedJNICall.java吧
我们的MainActivity.java是在package com.example.hello;包(文件夹)下面,我们再创建(如果跨包创建对象需要import包到时候)一个包com.example.led,然后把LedJNICall.java放到里面,不太清楚把LedJNICall.java也放到com.example.hello包里面可不可以?
在src上右键新建Class即可:
? ? ?
--------------------接着在这个里面我们加载C语言写的动态库,加载后,我们再调用这个动态库里面的函数,这些函数都是用C语言写的,所以可以做一些Java语言无法做的事情:
--------------------好的,写完了LedJNICall.java,看一下有没有警告之类,然后Run As Android Application,好像没有反应啊:主活动MainActivity.java还没有调用LedJNICall.java这个类的方法呢!下面调用一下:
note:JNI接口类这里其实是个方法类,我们要它的目的是为了调用里面的方法,所以这里new类的对象的时候可以就放在主类的函数里面,哪里开始调用方法我们就在哪里开始new对象:
--------------------这里的led操作其实就是相当于一个led驱动啊,所以访问led的时候,第一步是打开led,而不是操作led:
--------------------打开设备后,就是根据具体的应用需求来操作led了,通用框架接口就是ledioctl,测试方法:因为我们这里是测试用的,所以在ledioctl里面用的是打印提示操作而非真的硬件操作:
note:很显然么这里的ledioctl()就相当于一个驱动,而驱动是可以统治多个设备的,所以我们自然要选择一个设备,选择方法就是传输一个ID,然后控制动作也是通过参数传递的。
--------------------好吧,调试后出错,因为我们还没有加载C动态库,所以现在创建C动态库,我们在Ubuntu主机的Android目录下测试:
--------------------我们先编写一个Java的JNI文件调用C库:
我们编译一下它javac LedJNICall.java得到Java的可执行程序文件:LedJNICall.class
运行一下java LedJNICall,运行出现异常:
--------------------提示没有在java.library.path路径下找到lednative库文件:当然找不到了,因为我们还没有创建这个库,所以JNI无法完成调用,所以接下来就是写库文件lednative.c(note:下面这个程序中有错误,会在下面逐步修改):
接着很自然地我们要编译这个lednative.c,但是得到错误:
看看这个提示,说没有找到头文件jni.h,stdio.h能被编译器找到,是因为stdio.h所在的头目录/usr/include/已经被gcc编译器包含了,所以没有报错,现在jni.h所在的目录没有被配置,所以现在配置一下编译:
gcc -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include lednative.c
但是依然提示出错,因为库源文件是不包含main函数的,所以gcc链接的时候会报错:
解决方法是编译成库就好了,编译方法是添加必要的选项然后让输出文件为动态链接库格式.so:
gcc -shared -fPIC lednative.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -o lednative.so
好的,现在我们生成了lednative.so文件,接下来想到是,运行Java程序LedJNICall.class试试看java LedJNICall:
好吧,又出错了,正如编译要添加头文件的目录,运行程序当然也要添加动态库的目录,道理是一样的 export LD_LIBRARY_PATH=.
设置了链接库路径,结果还是错了,此时有两种情况,一是路径错了,而是路径里面的库错了。我当时没想到是什么情况,然后谷歌了一下,突然想起库文件名是有一定规范的:
gcc -shared -fPIC lednative.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -o liblednative.so
好的,正确的库文件名生成成功:
然后运行一下看看效果:
又出错了,看错误提示,应该是本地方法出问题了,检查了好几次,终于发现问题:
好的,把上面的错误改正一下在编译然后运行:
还是出错,经过检查总算对了:
好的,终于运行成功了:
? ? ?
--------------------至此,JNI调用生成的C动态库在Ubuntu主机上测试成功,现在该一直到eclipse上去了,让我们的APP应用程序通过JNI调用C动态库里面的函数ledon和ledioctl,怎么办呢?
我们应该把这个liblednative.so放到我们的平板上的Java库目录里面去,但是平板上的Java库目录是哪个?
Java中有一个函数可以获得java.library.path变量值:String javaLibraryPath = System.getProperty("java.library.path");
上面的代码没有测试出来,先直接说答案吧,是/system/lib/ 和 /vendor/lib/两个目录
在你的代码里使用Log.i, Log.e, Log.w, Log.v, Log.d这几个函数可以输入log到Logcat
i 普通信息
e 错误信息
w 警告作息
v 详细信息
d 调试信息
分几个函数主要是为了给log信息分类。另外这些函数的第一个参数是一个字符型tag标志,也是用于给log分类的,第二个参数是你要输出的日志内容。
连接adb,先把动态库文件拷贝到fastboot目录下面去,然后把动态库推送到平板的目录里面去,我们来推一下:
好的推送成功,推送步骤如截图所示,其中有一步显示 /system/lib/ 是只读目录,然后我们 adb remount 了一下就可以拥有读写权限了。我不太知道原理,不过 remount 的意思是把设备重新挂载到 /system 目录上去,可以这样理解,操作系统有一个目录 /system 目录,这个目录是只读的权限,但是访问这个目录其实访问的是挂载在它上面的设备,如果挂载的时候以读写的方式挂载,那么就可以访问这个设备而忽略 /system 目录本身的权限属性。
上面这是一种临时生效的方法,也就是系统重启后又不可以了。还有一种永久生效的方法,原理其实是一样的:
暂时忘了,就是修改那个存储设备挂载文件,然后重新编译系统再烧写到平板上。
--------------------好了,现在平板上已经有了动态库了,再运行一下APP看看:
错误信息说动态库是64位的,而我们ARM要32位的,所以重新编译生成一下动态库:
gcc -shared -fPIC lednative.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -m32 -o liblednative.so
编译之前,手残了,把lednative.c给删除了,没办法只能重新输入一遍,看了上git还是有必要的。
好的,把重新编译后的32位动态库推送到平板上,还是出错:
错误信息说动态库的目标机器出错,所以估计是架构错了,因为我们是在x86机器上编译的,现在转移到ARM上,所以要用交叉编译工具才行啊,-m32去掉:
arm-linux-gnueabi-gcc -shared -fPIC lednative.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -o liblednative.so
提示交叉编译工具链没有找到,那么这个工具链肯定是存在的,因为编译uboot和内核是也是用的这个编译器,所以怎么找呢?所以想到了,在lichee目录下全局搜索: grep "arm-linux-gnueabi-" * ./* -nR
功夫不负有心人,找到了这个命令所在的目录,所以接下来只要把这个目录添加到环境变量里面就可以了,所以添加到 /etc/profile 中,这个对当前用户有效,然后 source /etc/profile 一下:
~/fspad-733/lichee/brandy/gcc-linaro/bin/
但是这样写出现问题了:
环境变量丢失了:
只能重新配置一下,这个/etc/profile好像是配置的当前终端,而不是当前用户啊:
export PATH=/home/linux/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
linux@ubuntu:~/fspad-733/lichee/brandy/gcc-linaro/bin:~/fspad-733/lichee/brandy/gcc-linaro/bin/
新开一个终端,输入:
export PATH=$PATH:~/fspad-733/lichee/brandy/gcc-linaro/bin/
重新编译动态库文件:
arm-linux-gnueabi-gcc -shared -fPIC lednative.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -o liblednative.so
又提示错误了,这个是什么意思?无法找到 crti.o 和 crtbegins.o,这两个好像是标准C库提供的链接文件,所以猜想,是因为调用了非安卓有的函数,这里是ARM编译器,所以可能会调用不同的C库,先把printf注释掉吧:
还是不行,那会是什么原因呢?原因是(不是我想出来的,是老师讲的):交叉编译器无法使用标准C库(C库的源代码如何查看?标准C库libc.so.6非标准C库,下载标准C库源码文件sudo apt-get source libc6-dev),既然无法使用标准C库,那么就让GCC不使用标准C库来编译吧:-nostdlib
OK!编译成功,再次下载到板子上去测试:
好吧,又出错了:
错误信息显示没有"puts",可是动态库里面我没有使用"puts"啊,只使用了"printf",看来printf被优化成了"puts",我是这样理解的。为什么找不到,因为没有啊,安卓系统里的非标C库没有这个系统调用,所以不能用这种方式打印,先把那个printf注释掉再说吧:
又出错了,由于错误信息最上面的是错误最新发生的地方,幸好我们有出错检查代码,所以检测到了一个错误返回JNI_ERR,但是我们库源文件里面有两个地方都是返回的同一个JNI_ERR,根本无法定位啊,所以改下源代码吧,把返回值改一下:
好的,编译-加载动态库-eclipse调试,终于可以看到哪里错了:
可见是调用FindClass函数出错了,常见的出错原因:
所以我们把类改下:
编译-加载动态库-eclipse调试,又错了:
很好,还给出了错误提示,也就是类名写错了,/是linux系统下的目录分隔符,和url的类似:
再次编译-加载动态库-eclipse调试,又错了:
这个错误我是真懵逼了,不过幸好有老师的标准编译代码做参考,这里的编译选项缺少了非标准库目录:
查找方法是 grep "libc.so" * ./* -nR,错了,我们要搜的是文件而不是文件内容,所以是用 find -name libc.so
我们选择ndk目录下的这些基于特定架构的非标准C库文件,因为我们的平板上就包含了这些文件,所以编译的时候也要根据这些非标准库来实现编译,我们选择平台版本最高的那个版本:
? ? ?
arm-linux-gnueabi-gcc -shared -fPIC -nostdlib ~/fspad-733/androidL/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/lib/libc.so lednative.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -o liblednative.so
但是编译后,还是同样的错误:这怎么解决?
好吧,仔细看下出错信息,发现有222,说明找类错误了,怎么会找类又错了?盯着eclipse看了半天,发现类名错了,应该是:
好了,这次测试程序终于成功,没有出错信息输出了。
? ??
标签:
原文地址:http://www.cnblogs.com/dcscodelife/p/5813036.html