Android硬件访问服务-JNI

来源:互联网 发布:泰国移动4g网络制式 编辑:程序博客网 时间:2024/05/17 09:21

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++).

Android驱动开发中访问硬件有以下两种方式:

  1. 使用Java直接通过JNI访问C库,C库里面实现硬件上的Open,Read/Write,ioctl等linux驱动函数,但是这种方法有缺点,比如说一个LCD设备驱动有很多应用程序(电话、QQ、微信等)都需要访问,难道每个都要来open、read、write(/dev/fb)吗?显示是不行的,直接操作JNI访问硬件驱动只适合功能简单,代码量较小的访问,因此常用的是下面的第二种方式

  2. 使用”硬件访问服务”,并不是直接访问硬件,而是通过一个硬件访问服务来间接访问,应用程序将请求发送给硬件访问服务,由硬件访问服务通过JNI来访问我们的驱动程序,至此可以说明android=linux+封装(JNI),所以android的重点在于这个服务,对于不同的驱动需要构建不同的硬件访问服务,这种方式后面再详细描述。


下面用一个Android Studio的APP工程来简单的说明一下第一种方式的过程,APP功能非常简单,就是一个LED控制程序,直接通过JNI访问即可。

  1. 首先在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>
  1. 代码里面涉及到的控件都是一些类对象,例如文字描述使用“TextView”,选中后使用快捷键“Ctrl+H”可以看到类的继承关系,在这里它是 “View”的子类或者继承于“View”,属于View的直接子类,像Button、ChenckBox这些是属于View的间接子类。关于控件的属性设置在代码里面都非常的显现,所以配置起来非常方便。

  2. 在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 JNICALLJNI_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界面如下:
这里写图片描述

整个过程如下图所示:
这里写图片描述