APP通过JNI调用直接访问LED灯DEMO


本章将实现一个简单的app通过JNI本地调用访问开发板LED灯的demo,用来理解App是如何来访问我们的开发板硬件的。

开发环境:Android stdio 3.5.3
开发板:tiny4412开发板
软件版本:Android5.0.2 + kernel3.0.86

一、实现一个简单的LED操作APP

下面将在AS ide中实现一个简单的APP操作界面,总共使用了四个checkbox和一个button,四个checkbox分别用来单独控制四个LED的亮灭,button按钮同时控制四个led的亮灭。下面就是实际的layout界面图,比较简单,不过多介绍。
在这里插入图片描述
如下所示在四个checkbox中均设定了click函数为onCheckBoxClick,在这个函数中根据具体的checkbox调用native的ledctrl方法来打开或者关闭对应的led灯。

 activity_main.xml
        <CheckBox
            android:onClick="onCheckBoxClick"
            android:id="@+id/checkBox"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/led1" />

        <CheckBox
            android:onClick="onCheckBoxClick"
            android:id="@+id/checkBox2"
            android:layout_width="461dp"
            android:layout_height="wrap_content"
            android:text="@string/led2" />
 MainActivity.java
  public void onCheckBoxClick(View view) {
        boolean checked = ((CheckBox) view).isChecked();
        switch(view.getId()) {
            case R.id.checkBox:
                if (checked) {
                    // Put some meat on the sandwich
                    **HardControl.ledCtrl(1, 1);**
                    Toast.makeText(MainActivity.this, "LED1 on", Toast.LENGTH_SHORT).show();
                }
                else {
                    // Remove the meat
                    **HardControl.ledCtrl(1, 0);**
                    Toast.makeText(MainActivity.this, "LED1 off", Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.checkBox2:
                if (checked) {
                    // Put some meat on the sandwich
                    HardControl.ledCtrl(2, 1);
                    Toast.makeText(MainActivity.this, "LED2 on", Toast.LENGTH_SHORT).show();
                }
                else {
                    // Remove the meat
                    HardControl.ledCtrl(2, 0);
                    Toast.makeText(MainActivity.this, "LED2 off", Toast.LENGTH_SHORT).show();
                }
                break;
               .......

在button的setOnClickListener函数中同样是调用 HardControl.ledCtrl方法来控制四个led灯了。

   protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LED1 = (CheckBox) findViewById(R.id.checkBox);
        LED2 = (CheckBox) findViewById(R.id.checkBox2);
        LED3 = (CheckBox) findViewById(R.id.checkBox3);
        LED4 = (CheckBox) findViewById(R.id.checkBox4);
        Butt_ctrl = (Button) findViewById(R.id.button);

        **HardControl hardControl = new HardControl();
        HardControl.ledOpen();**
       Butt_ctrl.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ledall_status) {
                    HardControl.ledCtrl(1, 1);
                    HardControl.ledCtrl(2, 1);
                    HardControl.ledCtrl(3, 1);
                    HardControl.ledCtrl(0, 1);

                    Butt_ctrl.setText("All On");
                    LED1.setChecked(true);
                    LED2.setChecked(true);
                    LED3.setChecked(true);
                    LED4.setChecked(true);
                    Toast.makeText(MainActivity.this, "打开所有LED", Toast.LENGTH_SHORT).show();
                }
                else {
                    HardControl.ledCtrl(1, 0);
                    HardControl.ledCtrl(2, 0);
                    HardControl.ledCtrl(3, 0);
                    HardControl.ledCtrl(0, 0);
                    Butt_ctrl.setText("All Off");
                    LED1.setChecked(false);
                    LED2.setChecked(false);
                    LED3.setChecked(false);
                    LED4.setChecked(false);
                    Toast.makeText(MainActivity.this, "关闭所有LED", Toast.LENGTH_SHORT).show();

                }
                ledall_status = !ledall_status;
            }
        });

从上述代码可以看到内容很少,点亮关闭led灯的关键之处就是在onCreate方法中调用了 HardControl.ledCtrl这个方法了,下面我们具体来看看这个方法是如何实现的。

package com.example.hardlibrary;

public class HardControl {
    public static native int ledCtrl(int which, int status);
    public static native int ledOpen();
    //public static native int ledClose();

    static {
        try {
            System.loadLibrary("hardcontrol");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

从上图可以看到HardControl 方法也非常简单,只有两三个native方法,然后就去通过loadLibrary 调用我们的libhardcontrol.so动态库。这里需要注意的是我们自己的动态库如何添加到AS工程里面,下图是AS的工程层级图,我们的动态库是放在app\src\main\libs\armeabi目录下,然后还要设置build.gradle文件,添加如下sourceSets 和ndk内容工程才能找到我们的库文件。

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"
    defaultConfig {
        applicationId "com.example.tiny_demo"
        minSdkVersion 15
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        **sourceSets {
            main {
                jniLibs.srcDirs = ['app/libs']
                jniLibs.srcDirs = ['src/main/libs']
            }
        }
        ndk {
            // 设置支持的SO库架构
            abiFilters 'armeabi'
        }**
    }

至此,app端的内容已经介绍完毕了,app通过checkbox和button响应点击事件来通过native方法调用实现功能。

二、通过jni本地调用操作led灯

上面用到的libhardcontrol.so就这这一小节的重点了,先看源码,对着源码分析介绍

#include <jni.h>  /* /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <android/log.h>  /* liblog */

//__android_log_print(ANDROID_LOG_DEBUG, "JNIDemo", "native add ...");
static jint fd;
jint ledOpen(JNIEnv *env, jobject cls)
{
	fd = open("/dev/leds", O_RDWR);
	__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledOpen :");
	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)
{
	int ret = ioctl(fd, status, which);
	__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledCtrl : %d, %d", which, status);
	return ret;
//	return 0;
}

static const JNINativeMethod methods[] = {
	{"ledOpen", "()I", (void *)ledOpen},
	//{"ledClose", "()V", (void *)ledClose},
	{"ledCtrl", "(II)I", (void *)ledCtrl},
};

/* System.loadLibrary */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
	JNIEnv *env;
	jclass cls;
	__android_log_print(ANDROID_LOG_DEBUG, "TNT", "JNI_OnLoad start ...");

	if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
		return JNI_ERR; /* JNI version not supported */
	}
	cls = (*env)->FindClass(env, "com/example/hardlibrary/HardControl");
	if (cls == NULL) {
		return JNI_ERR;
	}
	/* 2. map java hello <-->c c_hello */
	if ((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0)
		return JNI_ERR;
	__android_log_print(ANDROID_LOG_DEBUG, "TNT", "JNI_OnLoad  end ...");
	return JNI_VERSION_1_4;
}

app侧的 System.loadLibrary(“hardcontrol”);方法会调用到我们的JNI_OnLoad函数,先通过GetEnv获得JAVA的环境,然后通过FindClass来找到对应的我们java实现的HardControl类,然后通过RegisterNatives注册和HardControl方法中同名的函数,这样app侧调用native方法我们c语言中对应的同名函数就会得到调用,就可以通过Linux标准的系统调用接口open、ioctl来操作我们具体的硬件了。下面是通过交叉编译工具链来编译动态库的指令

arm-linux-gcc -fPIC -shared hardcontrol.c -o libhardcontrol.so -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/  -nostdlib -I /home/tangtao/work/tiny_4412/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/include /home/tangtao/work/tiny_4412/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/lib/liblog.so

小结:从上述流程可以看到APP通过JNI调用我们实现的c函数可以很轻松的完成对硬件的访问,但是这仅仅是个demo,实际上Android系统中对硬件的访问并不是这么简单,因为直接通过jni调用硬件这种方法存在很大的局限性,如果我们多个app都会访问到同一个硬件就会造成硬件响应混乱的局面,无法统一管理,这是不可接受的,后续在介绍Android系统是如何完成app端到对硬件的访问的,可以预想上述的步骤肯定是必不可少的,只是Android系统更加规范的设计了整个流程,下回分解,好好学习,天天向上!

发布了27 篇原创文章 · 获赞 18 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_24622489/article/details/105188384