Android驱动之JNI编写
要想用java去调用C函数接口那么就需要使用JNI(Java Native Interface,Java 本地调用)去访问本地的动态链接库。
关于NDK的安装,现在有linux环境下的版本,也有windows环境下的版本,这个可自行百度,这里不多说
在eclipse中配置NDK:打开我们的eclipse->window->preference->android->ndk设置ndk路径->ok。
1、使用cygwin编译生成.so文件:右键单击项目->Android Tools->Add Native Support...->.so文件命名例如test->ok。
完成之后eclipse会生成一个jni文件夹,里面会生成一个test.cpp的文件。
但是我一般不用这个文件,直接delete,新建三个文件(.h .c .mk)。
.h和.c文件是用来编写kernel访问接口,可以直接调用驱动程序里的函数,这里需要注意命名规则:Java_包名_类名_接口名,此类表示调用.h中的方法的类
.mk文件是用来编译生成.so文件的,这里需要注意LOCAL_MODULE := HelloJni,表示生成HelloJni.so库,
在java中调用该库的入口是System.loadLibrary("HelloJni");
2、使用脚本编译的配置方法
在NDKr7开始,google的windos版NDK提供了一个ndk-build.cmd的脚本,这样就可以直接利用这个脚本编译,而不需要cygwin了。 1、选择你的android工程,右击->Properties->Builders->new,新添加一个编译器,点击之后出现添加界面,选择Program,点击ok。 2、出现了添加界面,我们先给编译器设置名称,如XXX_builder。设置Location为<NDK安装目录>\ndk-build.cmd 设置Working Directory为${workspace_loc:/项目名称} 3、切换到Refersh选项卡,给Refersh resources upon completion打上勾,选择“the entire resource”选项 4、切换到Build Options选项卡,勾选上最后三项。再点击Specify Resource按钮,选择你的android工程 5、在编译工具列表,我们最好将我们新建的编译器置顶。选中点击Up按钮置顶ok.
样例如下:
1、com_pngcui_HelloJni.h //命名规则:Java_包名_类名_接口名
1 /* DO NOT EDIT THIS FILE - it is machine generated */ 2 #include <jni.h> 3 /* Header for class com_neojet_scanner_key */ 4 5 #ifndef _Include_com_pngcui_helloJni 6 #define _Inlcude_com_pngcui_helloJni 7 8 #ifdef _cplusplus 9 extern "C"{ 10 #endif 11 12 /*Java_packagename_classname_methodname*/ 13 /*open()*/ 14 JNIEXPORT jint JNICALL Java_com_pngcui_helloJni_HelloJni_Open 15 (JNIEnv *,jobject); 16 17 /*close()*/ 18 JNIEXPORT jint JNICALL Java_com_pngcui_helloJni_HelloJni_Close 19 (JNIEnv *,jobject); 20 21 /*ioctl()*/ 22 JNIEXPORT jint JNICALL Java_com_pngcui_helloJni_HelloJni_Ioctl 23 (JNIEnv *,jobject,jint num,jint en); 24 25 #ifdef _cplusplus 26 } 27 #endif 28 #endif
2、com_pngcui_HelloJni.c //实现.h方法
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <fcntl.h> 4 #include <errno.h> 5 #include <unistd.h> 6 #include <sys/types.h> 7 #include <sys/stat.h> 8 #include <string.h> 9 #include <stdint.h> 10 #include <termios.h> 11 #include <android/log.h> 12 #include <sys/ioctl.h> 13 14 #include "com_pngcui_helloJni.h" 15 16 #undef TCSAFLUSH 17 #define TCSAFLUSH TCSETSF 18 #ifndef _TERMIOS_H_ 19 #define _TERMIOS_H_ 20 #endif 21 22 23 int fd = 0; 24 25 26 /*open()*/ 27 JNIEXPORT jint JNICALL Java_com_pngcui_helloJni_HelloJni_open 28 (JNIEnv *env , jobject obj){ 29 /*只允许打开一次设备!*/ 30 if(fd<=0) 31 fd = open("/dev/jni",O_RDWR|O_NDELAY|O_NOCTTY); 32 if(fd<=0) 33 __android_log_print(ANDROID_LOG_INFO,"serial","open /dev/jni Error.."); 34 else 35 __android_log_print(ANDROID_LOG_INFO,"serial","open /dev/jni Sucess fd = %d",fd); 36 } 37 38 JNIEXPORT jint JNICALL Java_com_pngcui_helloJni_HelloJni_close 39 (JNIEnv *env,jobject obj){ 40 41 if(fd > 0) 42 close(fd); 43 } 44 45 JNIEXPORT jint JNICALL Java_com_pngcui_helloJni_HelloJni_ioctl 46 (JNIEnv *env,jobject obj,jint num , jint en){ 47 48 ioctl(fd,en,num); 49 }
3、Android.mk
1 LOCAL_PATH := $(call my-dir) 2 include $(CLEAR_VARS) 3 LOCAL_MODULE := HelloJni 4 LOCAL_SRC_FILES := com_pngcui_helloJni.c 5 LOCAL_LDLIBS += -llog 6 LOCAL_LDLIBS +=-lm 7 include $(BUILD_SHARED_LIBRARY)
在android.mk文件中我们可以知道最后会生成一个libHelloJni.so本地库文件。
LOCAL_PATH - 编译时的目录
$(call
目录,目录….)
目录引入操作符
如该目录下有个文件夹名称
src,则可以这样写
$(call
src),那么就会得到
src
目录的完整路径
include
$(CLEAR_VARS) -清除之前的一些系统变量
LOCAL_MODULE
-
编译生成的目标对象
LOCAL_SRC_FILES
-
编译的源文件
LOCAL_C_INCLUDES
-
需要包含的头文件目录
LOCAL_SHARED_LIBRARIES
-
链接时需要的外部库
LOCAL_PRELINK_MODULE
-
是否需要prelink处理
include$(BUILD_SHARED_LIBRARY)
-
指明要编译成动态库
把以上三个文件放入jni文件夹中,最后编写一个java类,也就是命名规则的那个java类名
Jni.java
1 package com.pngcui.helloJni; 2 3 public class Jni { 4 5 public native int Open(); 6 public native int Close(); 7 public native int Ioctl(int num, int en); 8 }
最后在调用Jni.java的类中需要声明本地库的路径
1 static { 2 System.loadLibrary("HelloJni"); 3 }
附上完整的MainActivity.java代码
1 package com.pngcui.helloJni; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.view.View; 6 import android.view.View.OnClickListener; 7 import android.widget.Button; 8 9 public class MainActivity extends Activity { 10 11 12 Jni jni = new Jni(); 13 14 private Button y1; 15 private Button y2; 16 private Button n1; 17 private Button n2; 18 private Button start; 19 20 int i; 21 22 @Override 23 protected void onCreate(Bundle savedInstanceState) { 24 super.onCreate(savedInstanceState); 25 setContentView(R.layout.activity_main); 26 27 y1 = (Button)findViewById(R.id.y1); 28 y2 = (Button)findViewById(R.id.y2); 29 n1 = (Button)findViewById(R.id.n1); 30 n2 = (Button)findViewById(R.id.n2); 31 start = (Button)findViewById(R.id.start); 32 33 jni.Open(); 34 35 y1.setOnClickListener(new manager()); 36 n1.setOnClickListener(new manager()); 37 y2.setOnClickListener(new manager()); 38 n2.setOnClickListener(new manager()); 39 start.setOnClickListener(new manager()); 40 41 42 } 43 44 class manager implements OnClickListener{ 45 46 @Override 47 public void onClick(View v) { 48 49 //gpio_set_value(led_gpio[i],cmd),且二极管为低电平有效 50 51 switch(v.getId()){ 52 53 case R.id.y1: 54 jni.Ioctl(0, 0); 55 break; 56 case R.id.n1: 57 jni.Ioctl(0, 1); 58 break; 59 60 case R.id.y2: 61 jni.Ioctl(1, 0); 62 break; 63 case R.id.n2: 64 jni.Ioctl(1, 1); 65 break; 66 67 case R.id.start: 68 try { 69 start(); 70 } catch (InterruptedException e) { 71 // TODO Auto-generated catch block 72 e.printStackTrace(); 73 } 74 break; 75 76 } 77 78 } 79 80 private void start() throws InterruptedException { 81 // TODO Auto-generated method stub 82 83 i = 10; 84 while(i>0){ 85 i--; 86 jni.Ioctl(1, 0); 87 jni.Ioctl(0, 1); 88 89 Thread.sleep(200); 90 91 jni.Ioctl(1, 1); 92 jni.Ioctl(0,0); 93 94 Thread.sleep(200); 95 } 96 jni.Ioctl(1, 0); 97 } 98 99 100 101 } 102 103 static { 104 System.loadLibrary("HelloJni"); 105 } 106 }
最后还有一个问题,那就是权限问题!也就是说到现在我们还没有给我们的驱动程序生成的设备节点设置权限,也就是可读可写权限,如果不设置这个权限,那么我们的android应用时无法打开设备节点的!!!在调用open函数时会直接返回-1!!而在exynos4412平台上的android设置权限的文件在device/samsung/smdk4x12/conf/init.smdk4x12.rc中,而不是网上说的init.rc和ueventd.rc文件中,但是!!本人在里面设置了chmod 777 /dev/jni 依旧不能正常open,百思不得其解,有哪位大神知道怎么去自动设置权限,还麻烦教导我一下,不甚感激!
附上蠢蠢的解决办法:在串口终端使用命令chmod 777 /dev/jni然后运行app,可暂时性解决此问题,但是开发板重启以后就需要重修设置权限!!
---------------------------------2016.4.12 update------------------------------------------------------
权限问题的解决方案:为什么在脚本中写了chmod命令还是不能open设备文件,因为没有烧写ramdisk.img文件啊啊啊啊!!!只烧写了system.img,因为rc脚本会编译到ramdisk镜像中。
参考链接:http://blog.csdn.net/loongembedded/article/details/39778409
最后附上查看设备权限命令:ls -al /dev/xxx
r: 对应数值4
w: 对应数值2
x:对应数值1
-:对应数值0
eg。-rwxrwxrwx 对应0777 即所有用户都可读可写可执行
---------------------2016.4.20补充驱动函数返回数据到java中----------------------
如果需要读取底层硬件状态等信息,并返回到app上,则需要使用jni里的方法去调用驱动函数的ioctl,而不能直接在java中写data = jni.Ioctl(a,b);
1 JNIEXPORT jint JNICALL Java_com_pngcui_fm_Jni_GetData 2 (JNIEnv *env,jobject obj,jint cmd ){ 3 4 jint data = 0; 5 6 data = ioctl(fd,cmd,0); 7 8 return data; 9 }
使用如上方法才能正常返回!
作者:pngcui
博客园:http://www.cnblogs.com/pngcui/
github:https://github.com/pngcui
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明。