JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++).
Android驱动开发中访问硬件有以下两种方式:
使用Java直接通过JNI访问C库,C库里面实现硬件上的Open,Read/Write,ioctl等linux驱动函数,但是这种方法有缺点,比如说一个LCD设备驱动有很多应用程序(电话、QQ、微信等)都需要访问,难道每个都要来open、read、write(/dev/fb)吗?显示是不行的,直接操作JNI访问硬件驱动只适合功能简单,代码量较小的访问,因此常用的是下面的第二种方式
使用”硬件访问服务”,并不是直接访问硬件,而是通过一个硬件访问服务来间接访问,应用程序将请求发送给硬件访问服务,由硬件访问服务通过JNI来访问我们的驱动程序,至此可以说明android=linux+封装(JNI),所以android的重点在于这个服务,对于不同的驱动需要构建不同的硬件访问服务,这种方式后面再详细描述。
下面用一个Android Studio的APP工程来简单的说明一下第一种方式的过程,APP功能非常简单,就是一个LED控制程序,直接通过JNI访问即可。
- 首先在Android Studio上建立一个Activity(可视化界面)工程,layout也非常简单,直接使用android studio提供的控件即可。每放置一个控件就会自动产生一段对应的控件源码,我们只需要修改这段源码即可,这段源码在工程的res目录下面的layout->activity_main.xml文件里面,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.lzf.app_0001_leddemo.MainActivity"
android:orientation="vertical"
android:weightSum="1">
/*****************将上面的layout改为LinearLayout(并改为在垂直方向摆放)*****************/
/* 文字属性设置:
* --宽度:取决于内容
* --高度:取决于内容
* --内容
* --显示:水平居中
*/
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is the first project!"
android:layout_gravity="center_horizontal"/>
/* 按键属性设置:
* --ID :实现方法可以通过此ID来找到按键
* --宽度:填充整个窗口
* --高度:取决于内容
* --内容
* --双击"Button"按"Shift+F1"可查看Button操作手册
*/
<Button
android:id="@+id/BUTTON"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ALL ON" />
/* 复选框设置:
* --ID :实现方法可以通过此ID来找到checkbox
* --宽度:填充整个窗口
* --高度:取决于内容
* --内容
* --定义一个onClick方法,选中时调用此方法,2个复选框对应同一个方法
*/
<CheckBox
android:id="@+id/LED1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="LED1"
android:onClick="onCheckClicked"/>
<CheckBox
android:id="@+id/LED2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="LED2"
android:onClick="onCheckClicked"/>
</LinearLayout>
代码里面涉及到的控件都是一些类对象,例如文字描述使用“TextView”,选中后使用快捷键“Ctrl+H”可以看到类的继承关系,在这里它是 “View”的子类或者继承于“View”,属于View的直接子类,像Button、ChenckBox这些是属于View的间接子类。关于控件的属性设置在代码里面都非常的显现,所以配置起来非常方便。
在Android里面访问C库:JNI
led_Ctrl(int which, int status);
led_Open();
led_Close();
以上三个定义在一个HardControl.java并声明为native方法,java代码如下:
/* 声明此文件在这个目录下面 */
package com.lzf.hardlibrary;
public class HardControl{
/* 定义LED控制的三个native方法 */
/* 加了static可以省略类名直接在主方法调用,不加则必须先实例化后用实例调用 */
public static native int ledCtrl(int which, int status);
public static native int ledOpen();
public static native void ledClose();
/* 静态块:加了static只会执行一次,不加static则每调用一次都执行 */
/* 可以使用Crtl+Alt+T生成捕获异常的代码 */
static {
try {
System.loadLibrary("hardcontrol");/* 加载库,库名为hardcontrol */
} catch (Exception e) {
e.printStackTrace();
}
}
}
接下来写一个Hardcontrol.c文件来实现上面的native方法(即JNI文件),代码如下:
#include <stdio.h>
#include <jni.h> /* /usr/lib/jvm/jdk1.6.0_43/include/ */
#include <stdlib.h>
#include <android/log.h> /* liblog */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
//添加上面的头文件后可以使用下面这个函数来打印
//__android_log_print(ANDROID_LOG_DEBUG, "JNIDemo", "native add ...");
static jint fd;
jint ledOpen(JNIEnv *env, jobject cls)
{
fd = open("/dev/leds_nano", O_RDWR);//可读可写的方式打开
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledOpen : %d", fd);
if(fd >= 0)
return 0;
else
return -1;
}
void ledClose(JNIEnv *env, jobject cls)
{
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledClose...");
close(fd);
}
jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
{
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "fd_sta : %d", fd);
int ret = ioctl(fd, status, which);//根据传入的参数控制驱动
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledCtrl : %d, %d, %d", which, status, ret);
return ret;
}
/* 定义JNI字段描述符 */
static const JNINativeMethod methods[] = {//定义一个映射数组
{"ledOpen", "()I", (void *)ledOpen},//对应c语言的ledOpen,这个函数没有参数,返回值是int类型
{"ledClose", "()V", (void *)ledClose},//对应c语言的ledClose,这个函数没有参数,返回值是void类型
{"ledCtrl", "(II)I", (void *)ledCtrl},//对应c语言的ledCtrl,这个函数有两个int参数,返回值是int类型
};
/* System.loadLibrary */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
/* 获得一个运行环境,JNI版本号为1.4 */
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "com/lzf/hardlibrary/HardControl");//查找路径下对应的类
if (cls == NULL) {
return JNI_ERR;
}
/* 注册native方法 */
if((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0)//把methods数组注册到这个环境里面的这个cls类里
{
return JNI_ERR;
}
return JNI_VERSION_1_4;//返回版本号
}
接下来是将这个JNI文件放到linux系统下使用交叉编译工具来编译,我这里使用的编译选项如下,需要根据自己出现的报错添加了几个文件路径:
arm-linux-gcc -fPIC -shared hardcontrol.c -o libhardcontrol.so
-I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/
-nostdlib /work/nanoPi_T3/android/prebuilts/ndk/9/platforms/android-21/arch-arm/usr/lib/libc.so
-I /work/nanoPi_T3/android/prebuilts/ndk/9/platforms/android-21/arch-arm/usr/include
/work/nanoPi_T3/android/prebuilts/ndk/9/platforms/android-21/arch-arm/usr/lib/liblog.so解析如下:
/*
* 第一个-I是指定jni.h目录
* -nostdlib:表明不使用标准的libc库(因为libc6.so找不到),改为后面那个路径下的libc库
* 第二个-I是指定log.h目录
* 指定使用到liblog.so的路径
*/
编译之后,把编译出来的JNI文件(hardcontrol.c)编译到APP里面去,在工程的app/libs下建立armeabi子目录,放入so文件(编译生成so库文件),放入后修改下工程目录下的build.gradle(Module:app)加上以下代码表示我们的so文件是放在libs这个目录下面的
sourceSets{
main{
jniLibs.srcDirs = ['libs']
}
}
最后在控件的监听函数里面实现LED灯的控制即可,代码如下:
package com.lzf.app_0001_leddemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Toast;
import com.lzf.hardlibrary.*;/* 导入native的方法 */
public class MainActivity extends AppCompatActivity {
/* 用于表示LED状态 */
private boolean ledon = false;
/* 记得import android.widget.Button */
/* 定义button对象 */
private Button button = null;
/* 定义2个checkbox对象,对应2个led */
private CheckBox checkBoxLed1 = null;
private CheckBox checkBoxLed2 = null;
/* 实现一个按键监听器的类,继承于View.OnClickListener */
class MyButtonListener implements View.OnClickListener{
/* 在类里面按快捷键"Ctrl+I"会自动补全这个onClick方法 */
@Override
public void onClick(View v) {
HardControl hardControl = new HardControl();/* 由于静态块的关系一旦new就会调用到动态链接库 */
ledon = !ledon; /* 按下变换一次 */
if(ledon) {
button.setText("ALL OFF");
checkBoxLed1.setChecked(true);
checkBoxLed2.setChecked(true);
/* 调用JNI实现的HardControl类来点亮LED */
HardControl.ledCtrl(0, 1);
HardControl.ledCtrl(1, 1);
}
else {
button.setText("ALL ON");
checkBoxLed1.setChecked(false);
checkBoxLed2.setChecked(false);
/* 熄灭 */
HardControl.ledCtrl(0, 0);
HardControl.ledCtrl(1, 0);
}
}
}
/* *********** 实现onClick方法,参考CheckBox的操作手册 *********** */
public void onCheckClicked(View view){
boolean checked = ((CheckBox) view).isChecked();
/* 根据选中的ID来判断选中的是哪一个复选框 */
switch (view.getId()){
case R.id.LED1:
if(checked) {
/* 使用Toast提醒文字 */
Toast.makeText(getApplicationContext(),"LED1 on", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(0, 1);
}
else{
Toast.makeText(getApplicationContext(),"LED1 off", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(0, 0);
}
break;
case R.id.LED2:
if(checked) {
Toast.makeText(getApplicationContext(),"LED2 on", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(1, 1);
}
else{
Toast.makeText(getApplicationContext(),"LED2 off", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(1, 0);
}
break;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*
* 打开led设备
* 快捷键:Crtl+B 调到定义处
*/
HardControl.ledOpen();
/*
* 找到按键并返回一个类
* 快捷键:Ctrl+Shift+空格 可以自动补齐强制转换的类型"(Button)"
* Alt+F7 可以找到BUTTON的引用地方,定义了一个BUTTON变量
* Alt+Enter 可以找到相应的包import进来
*/
button = (Button)findViewById(R.id.BUTTON);
/* 设置按键监听器,当按键按下时MyButtonListener类里面的方法会被调用 */
button.setOnClickListener(new MyButtonListener());
/* 根据ID找到控件获得实例化对象,跟上面的button类似 */
checkBoxLed1 = (CheckBox)findViewById(R.id.LED1);
checkBoxLed2 = (CheckBox)findViewById(R.id.LED2);
}
}
APP界面如下:
整个过程如下图所示: