Android应用程序访问硬件驱动(JNI方式)
Java应用程序通过JNI方式访问C库,C库向上给Java程序提供接口,向下调用了硬件驱动,这里以LED的控制为例。
一、LED驱动
leds_4412.c是tiny4412开发板一个简单的LED驱动
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
static int led_gpios[] = {
EXYNOS4212_GPM4(0),
EXYNOS4212_GPM4(1),
EXYNOS4212_GPM4(2),
EXYNOS4212_GPM4(3),
};
static int led_open(struct inode *inode, struct file *file)
{
/* 配置GPIO为输出引脚 */
int i;
for (i = 0; i < 4; i++)
s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
return 0;
}
/* app : ioctl(fd, cmd, arg) */
static long led_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
/* 根据传入的参数设置GPIO */
/* cmd : 0-off, 1-on */
/* arg : 0-3, which led */
if ((cmd != 0) && (cmd != 1))
return -EINVAL;
if (arg > 4)
return -EINVAL;
gpio_set_value(led_gpios[arg], !cmd);
return 0;
}
static struct file_operations leds_ops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = led_open,
.unlocked_ioctl = led_ioctl,
};
static int major;
static struct class *cls;
int leds_init(void)
{
major = register_chrdev(0, "leds", &leds_ops);
/* 为了让系统udev,mdev给我们创建设备节点 */
/* 创建类, 在类下创建设备 : /sys */
cls = class_create(THIS_MODULE, "leds");
device_create(cls, NULL, MKDEV(major, 0), NULL, "leds"); /* /dev/leds */
return 0;
}
void leds_exit(void)
{
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "leds");
}
module_init(leds_init);
module_exit(leds_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xy");
将驱动程序放入内核编译:
a. 放入Linux内核 drivers/char中
b. 修改 drivers/char/Makefile, 添加:
obj-y += leds_4412.o
为避免干扰,将自带的 leds 驱动屏蔽:
config TINY4412_LEDS
tristate "LED Support for FriendlyARM Tiny4412 GPIO LEDs"
depends on MACH_TINY4412
default n
c. 重新编译内核: make zImage
d. 重新烧录zImage,系统起来后会看到/dev/leds设备。
二、JNI文件(C库)
hardcontrl.c用于实现后面的HardContrl.java里面的native方法对应的C函数,C函数分别对应了驱动里面各个接口。
JNI_OnLoad在java程序执行System.loadLibrary的时候被调用,作用:
1. 通过GetEnv获取运行环境
2. 通过FindClass(env, “com/thisway/hardlibrary/HardControl”)找到HardControl这个Java类。
3. 通过RegisterNatives注册下面三个native方法
/* Java方法名,JNI字段描述符,C语言函数名 */
static const JNINativeMethod methods[] = {
{"ledOpen", "()I", (void *)ledOpen},
{"ledClose", "()V", (void *)ledClose},
{"ledCtrl", "(II)I", (void *)ledCtrl},
};
编译命令:
arm-linux-gcc -fPIC -shared hardcontrol.c -o libhardcontrol.so
//编译共享动态库so文件
-I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include/
//指定jni.h文件路径;
-nostdlib /work/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/lib/libc.so
// 编译时不会自动使用标准的libc.so.6
-I /work/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/include
//指定log.h文件路径
/work/android-5.0.2/prebuilts/ndk/9/platforms/android-19/arch-arm/usr/lib/liblog.so
//__android_log_print依赖的库
hardcontrol.c完整代码:
#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 ...");
#if 0
typedef struct {
char *name; /* Java里调用的函数名 */
char *signature; /* JNI字段描述符, 用来表示Java里调用的函数的参数和返回值类型 */
void *fnPtr; /* C语言实现的本地函数 */
} JNINativeMethod;
#endif
static jint fd;
jint ledOpen(JNIEnv *env, jobject cls)
{
fd = open("/dev/leds", 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)
{
int ret = ioctl(fd, status, which);
__android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledCtrl : %d, %d, %d", which, status, ret);
return ret;
}
/* Java方法名,JNI字段描述符,C语言函数名 */
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;
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
cls = (*env)->FindClass(env, "com/thisway/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;
return JNI_VERSION_1_4;
}
三、HardControl文件(java类)
用于声明native方法,让其他应用程序的代码调用。
package com.thisway.hardlibrary;
public class HardControl {
public static native int ledCtrl(int which, int status);
public static native int ledOpen();
public static native void ledClose();
static {
try {
System.loadLibrary("hardcontrol");
} catch (Exception e) {
e.printStackTrace();
}
}
}
当应用程序使用到该HardControl 类的时候,会先通过System.loadLibrary 加载hardcontrol.so库文件;
hardcontrol.c编译生成的hardcontrol.so必须放在应用程序的app/libs/armeabi目录下;
Android studio应用程序的build.gradle还要添加:
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
四、应用程序调用HardControl类方法
- 通过 import com.thisway.hardlibrary.*; 导入HardControl类;
- 在MainActivity的onCreate方法中open LED设备:HardControl.ledOpen();
- 在逻辑判断中操作LED:HardControl.ledCtrl(which, status);
package com.thisway.app_0001_leddemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.view.View;
import android.widget.CheckBox;
import android.widget.Toast;
import com.thisway.hardlibrary.*;
public class MainActivity extends AppCompatActivity {
private boolean ledon = false;
private Button button = null;
private CheckBox checkBoxLed1 = null;
private CheckBox checkBoxLed2 = null;
private CheckBox checkBoxLed3 = null;
private CheckBox checkBoxLed4 = null;
class MyButtonListener implements View.OnClickListener {
@Override
public void onClick(View v) {
ledon = !ledon;
if (ledon) {
button.setText("ALL OFF");
checkBoxLed1.setChecked(true);
checkBoxLed2.setChecked(true);
checkBoxLed3.setChecked(true);
checkBoxLed4.setChecked(true);
for (int i = 0; i < 4; i++)
HardControl.ledCtrl(i, 1);
}
else {
button.setText("ALL ON");
checkBoxLed1.setChecked(false);
checkBoxLed2.setChecked(false);
checkBoxLed3.setChecked(false);
checkBoxLed4.setChecked(false);
for (int i = 0; i < 4; i++)
HardControl.ledCtrl(i, 0);
}
}
}
public void onCheckboxClicked(View view) {
// Is the view now checked?
boolean checked = ((CheckBox) view).isChecked();
// Check which checkbox was clicked
switch(view.getId()) {
case R.id.LED1:
if (checked) {
// Put some meat on the sandwich
Toast.makeText(getApplicationContext(), "LED1 on", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(0, 1);
}
else {
// Remove the meat
Toast.makeText(getApplicationContext(), "LED1 off", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(0, 0);
}
break;
case R.id.LED2:
if (checked) {
// Put some meat on the sandwich
Toast.makeText(getApplicationContext(), "LED2 on", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(1, 1);
}
else {
// Remove the meat
Toast.makeText(getApplicationContext(), "LED2 off", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(1, 0);
}
break;
case R.id.LED3:
if (checked) {
// Put some meat on the sandwich
Toast.makeText(getApplicationContext(), "LED3 on", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(2, 1);
}
else {
// Remove the meat
Toast.makeText(getApplicationContext(), "LED3 off", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(2, 0);
}
break;
case R.id.LED4:
if (checked) {
// Put some meat on the sandwich
Toast.makeText(getApplicationContext(), "LED4 on", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(3, 1);
}
else {
// Remove the meat
Toast.makeText(getApplicationContext(), "LED4 off", Toast.LENGTH_SHORT).show();
HardControl.ledCtrl(3, 0);
}
break;
// TODO: Veggie sandwich
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.BUTTON);
HardControl.ledOpen();
checkBoxLed1 = (CheckBox) findViewById(R.id.LED1);
checkBoxLed2 = (CheckBox) findViewById(R.id.LED2);
checkBoxLed3 = (CheckBox) findViewById(R.id.LED3);
checkBoxLed4 = (CheckBox) findViewById(R.id.LED4);
button.setOnClickListener(new MyButtonListener());
/*
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
ledon = !ledon;
if (ledon)
button.setText("ALL OFF");
else
button.setText("ALL ON");
}
});
*/
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}