Android硬件访问服务-JNI

本文介绍了Android中通过JNI接口直接访问硬件的方法,讨论了其局限性,并提供了一个简单的LED控制程序示例,展示了如何在Java中声明native方法,在C++中实现,并通过交叉编译生成SO库文件,最后将其集成到Android应用中进行硬件控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值