您的位置:首页 > 移动开发 > Android开发

Android驱动程序开发实例精讲-0_Android系统HAL驱动开发经典案例详解(基于Android4.0)

2015-04-21 10:20 597 查看
Android系统HAL驱动开发经典案例详解(基于Android4.0)

目的:通过学习一个LED点灯的简单功能,掌握Linux驱动程序与HAL硬件抽象层之间的调用方法,同时掌握JNI层的编写思想,学会使用Eclipse编写Android应用程序,深入体会Android HAL架构。本章内容主要参考文献:《Android深度探索(卷1) HAL与驱动开发》、《TQ210开发板Android_HAL_LED_V1.2.pdf》

目录:

一、架构分析

1.1 功能介绍

1.2 HAL架构

1.3 接口定义

二、驱动程序

2.1 驱动功能简介

2.2 驱动源码分析

2.3 利用可执行文件测试驱动

2.3.1 源文件

2.3.2 利用交叉编译器编译

2.3.3 使用原生C程序测试驱动

三、HAL硬件抽象层

3.1 HAL简介

3.2 HAL层源码与分析

3.3 HAL源码编译

四、Jni/Service服务层

4.1 直接使用JNI调用驱动程序

4.2 JNI/Service程序代码

五、APP应用程序层

5.1 activity_main.xml

5.2 LedServer.java

5.3 MainActivity.java

一、架构分析

1.1 功能介绍

Linux版本:3.0.8

Android版本:4.0

开发板:FriendlyARM smart210

功能介绍:应用程序界面如下图所示,为求方便,截图来自模拟器,实际开发板界面和下图是一样的。CheckBox复选框分别对应开发板上四个LED灯,选中复选框,按动led_hal_jni按键后,相应LED就会亮起来。功能很简单,代码中几乎不会涉及任何逻辑算法,主要是为了方便理清整个程序架构,体会Android系统通过HAL硬件抽象层与实际硬件之间交互的编程思想。



1.2 HAL架构

想要点亮一个灯当然简单,可以直接烧写ARM裸机程序,也可以在跑Linux系统的板子上通过写/dev/leds这样的设备文件来达到目的。Android系统为了解决一些调用接口和版权方面的问题提出了一个HAL架构。目前最新的HAL架构是下面这个样子的。



可以看出,Android应用程序直接调用的是JNI或者Service程序库文件(.so),程序库文件是通过一个ID来定位到相应的HAL的库文件(.so),最终和硬件打交道的是HAL模块。需要注意的是,HAL及其以上层均属于应用程序范畴,也就是都运行在了用户空间,只有Linux驱动程序运行在了Linux内核空间。

1.3 接口定义

从Android HAL架构来看,一套完整的点灯的程序应该包括四个层次。为了方便程序维护,层与层之间应该尽可能的降低耦合程度,每个层对其他层开放的仅仅是一个操作接口即可。每个层的源文件位置和其向其他层提供的关键接口表述如下,这里指的关键接口实际上就是“调用函数”。各个源文件的具体内容会在下文给出。如果你能亲自动手写完整套程序再回过头来看这部分接口定义,应该能体会得更深些。

1、底层驱动。

源文件位置:$(linux_source)/drivers/char/mini210_led.c

对应开发板上的驱动设备文件为:/dev/leds

关键接口定义:

//发送IO命令

ioctl(file_handler, cmd, arg);

//调用方法如下

./ioctl_test /dev/leds 1 2 //表示点亮第二盏灯

//发送写命令

write(fd,buf,strlen(buf));

//调用方法如下

./write_test /dev/s3c6410_leds "1111" // 点亮所有灯

这里的ioctl_test和write_test是两个可执行文件,它们分别是由ioctl_test.c和write_test.c源文件通过交叉编译器或Android原生代码编译出来的,这两个源文件是为了验证驱动程序专门写的,后文会贴出具体代码。

2、HAL层。

源文件位置:

//这个.c文件也可以放到其他目录

$(Android_source)/device/friendly-arm/mini210/libled/led_hal.c

//这个.h文件最好放到指定位置,以免包含头文件的时候发生错误

$(Android_source)/hardware/libhardware/include/hardware/led_hal.h

生成模块位置:Android_source/out/target/product/mini210/lib/hw/led_hal.default.so

关键接口定义:

int led_on(struct led_control_device_t *dev, int32_t LED_NUMBER) //表示LED_NUMBER灯亮

{

... ...

ioctl(fd,IOCTL_GPIO_ON,LED_NUMBER);

... ...

}

int led_off(struct led_control_device *dev, int32_t LED_NUMBER) //表示LED_NUMBER灯灭

{

... ...

ioctl(fd,IOCTL_GPIO_OFF,LED_NUMBER);

... ...

}

3、jni/service层

源文件位置:$(Android_source)/packages/apps/led/jni/LedHalService.cpp

生成模块位置:Android_source/out/target/product/mini210/lib/libled_hal_jni.so

关键接口定义:

jboolean Java_com_example_leds_MainActivity_ledSetOn (JNIEnv* env, jobject obj,jint number)

{

... ...

return sLedDevice->set_on(sLedDevice,number);

... ...

}

jboolean Java_com_example_leds_MainActivity_ledSetOff (JNIEnv* env, jobject obj,jint number)

{

... ...

return sLedDevice->set_off(sLedDevice,number);

... ...

}

类名定义:

//注意这里的kClassName决定了调用该JNI模块的应用程序的包名与类名。

int register_android_server_LedService(JNIEnv *env)

{

... ...

static const char* const kClassName = "com/example/leds/LedService";

... ...

}

二、驱动程序

2.1 驱动功能简介

mini210_led.c主要提供了mini210_leds_ioctl和mini210_leds_write两个接口。应用程序通过向/dev/leds发送I/O指令或写命令就可以实现控制LED灯的功能了。

2.2 驱动源码分析

<span style="font-family:KaiTi_GB2312;font-size:18px;">#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/miscdevice.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 <mach/gpio.h>

#include <mach/regs-gpio.h>

#include <plat/gpio-cfg.h>

#include <asm/uaccess.h>

#define DEVICE_NAME "leds"

static unsigned char mem[4];

static int led_gpios[] = {

S5PV210_GPJ2(0),

S5PV210_GPJ2(1),

S5PV210_GPJ2(2),

S5PV210_GPJ2(3),

};

#define LED_NUM ARRAY_SIZE(led_gpios)

//mini210_leds_ioctl函数实现了发送IO控制命令

static long mini210_leds_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

{

switch(cmd) {

case 0:

case 1:

if (arg > LED_NUM) {

return -EINVAL;

}

gpio_set_value(led_gpios[arg], !cmd);

//printk(DEVICE_NAME": %d %d\n", arg, cmd);

break;

default:

return -EINVAL;

}

return 0;

}

//mini210_leds_write函数实现了写文件命令

ssize_t mini210_leds_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos)

{

int i=0;

int tmp=count;

memset(mem,0,4);

if(count>4)

{

tmp=4;

}

if(copy_from_user(mem,buf,tmp))

{

return -EFAULT;

}

for(i=0;i<4;i++)

{

if(mem[i]=='1')

{

gpio_set_value(led_gpios[i], 0);

}

else if(mem[i]=='0')

{

gpio_set_value(led_gpios[i], 1);

}

}

return count;

}

static struct file_operations mini210_led_dev_fops = {

.owner = THIS_MODULE,

.unlocked_ioctl = mini210_leds_ioctl,

.write                    = mini210_leds_write,

};

static struct miscdevice mini210_led_dev = {

.minor = MISC_DYNAMIC_MINOR,

.name = DEVICE_NAME,

.fops = &mini210_led_dev_fops,

};

static int __init mini210_led_dev_init(void) {

int ret;

int i;

for (i = 0; i < LED_NUM; i++) {

ret = gpio_request(led_gpios[i], "LED");

if (ret) {

printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,

led_gpios[i], ret);

return ret;

}

s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);

gpio_set_value(led_gpios[i], 1);

}

ret = misc_register(&mini210_led_dev);

printk(DEVICE_NAME"\tinitialized\n");

return ret;

}

static void __exit mini210_led_dev_exit(void) {

int i;

for (i = 0; i < LED_NUM; i++) {

gpio_free(led_gpios[i]);

}

misc_deregister(&mini210_led_dev);

}

module_init(mini210_led_dev_init);

module_exit(mini210_led_dev_exit);

MODULE_LICENSE("GPL");</span>


2.3 利用可执行文件测试驱动

2.3.1 源文件

首先给出两个可执行文件的源文件

//ioctl_test.c

<span style="font-family:KaiTi_GB2312;font-size:18px;">#include <fcntl.h>

#include <stdio.h>

#include <stdlib.h>

#include <sys/ioctl.h>

/*

* ./ioctl_test /dev/leds 1 2  //表示将第二个灯点亮

*/

int main(int argc, char **argv)

{

int file_handler = 0;

int cmd = 0;

int arg = 0;

if(argc < 4)

{

printf("Usage: ioctl <dev_file> <cmd> <arg>\n");

return 0;

}

cmd = atoi(argv[2]);

arg = atoi(argv[3]);

printf("dev:%s\n", argv[1]);

printf("cmd:%d\n", cmd);

printf("arg:%d\n", arg);

file_handler = open(argv[1], 0);

ioctl(file_handler, cmd, arg);

close(file_handler);

return 0;

}</span>


//write_test.c

<span style="font-family:KaiTi_GB2312;font-size:18px;">#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <string.h>

/*

* ./write_test /dev/s3c6410_leds "1111" 表示四个灯全亮

*/

void main(int argc, char *argv[])

{

int fd;

char *buf;

buf=argv[2];

fd=open(argv[1],O_WRONLY);

if(fd<0)

{

printf("file_open failed!\n");

return;

}

write(fd,buf,strlen(buf));

close(fd);

}</span>


源文件的内容比较简单,不再深入讨论。Android系统有两种编译可执行程序的方法,下面分别介绍。

2.3.2 利用交叉编译器编译

这种方法比较容易实现。直接在命令行模式下编译即可。需要注意的是最好加上静态编译选项“-static”。

Pc:#arm-linux-gcc -static ioctl_test.c -o ioctl_test

Pc:#arm-linux-gcc -static write_test.c -o write_test

然后利用adb命令将可执行文件上传到开发板

Pc:# adb push ioctl_test write_test /data/local

进入开发板的命令行终端

Pc:# adb shell

然后就可以执行可执行文件了

Phone#: cd /data/local

Phone:# ./ioctl_test /dev/leds 1 2

Phone:#./write_test /dev/leds “1111”

2.3.3 使用原生C程序测试驱动

这种方式更加地道,其编译方式是通过Android源代码直接编译的。所使用的工具就是Android系统自带的编译器以及一些Android系统的头文件,所以编译之前一定确保自己的电脑上已经成功编译了一套Android系统。至于如何编译Android系统,网上有很多教程,难点就是编译的过程中会出现一些莫名其妙的错误,往往是少安装了某些库造成的,耐着性子上网查查一般就能解决。

接下来以ioctl_test为例讲解如何使用原生C程序测试驱动。首先一定要在Android系统源代码目录或子目录下为ioctl_test应用程序单独建立一个文件夹,作为其主目录。然后在ioctrl_test.c的主目录下新建一个Android.mk文件,内容如下:

<span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH:=$(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES:=ioctl_test.c

LOCAL_MODULE:=ioctl_test

LOCAL_MODULE_TAGS:=optional

include $(BUILD_EXECUTABLE)</span>


Android.mk其实就相当与Android系统中的Makefile文档,就是给出了一些编译选项,需要执行mm命令或mmm命令进行编译。关于Android.mk的书写规则,网上有很多,看看就知道了。

在使用mm命令之前,一定确保首先在Android系统源代码下执行:

Pc:# source ./build/envsetup.sh 执行后会出现类似下面的信息。

including device/friendly-arm/mini210/vendorsetup.sh

including device/moto/stingray/vendorsetup.sh

including device/moto/wingray/vendorsetup.sh

including device/samsung/crespo4g/vendorsetup.sh

including device/samsung/crespo/vendorsetup.sh

including device/samsung/maguro/vendorsetup.sh

including device/samsung/toro/vendorsetup.sh

including device/samsung/tuna/vendorsetup.sh

including device/ti/panda/vendorsetup.sh

including sdk/bash_completion/adb.bash

这个envsetup.sh就是包含自定义的vendorsetup.sh和初始化mm等命令用的(插一句题外话,如果你想从官方的Android源码定制适合自己开发板的Android系统的话,通常需要自己写一个新的vendorsetup.sh文件)。注意的是,每次启动一个新的Linux命令终端,好像都要重新运行source ./build/envsetup.sh,否则mm命令不能用。

然后执行lunch

Pc:# lunch

You're building on Linux

Lunch menu... pick a combo:

1. full-eng

2. full_x86-eng

3. vbox_x86-eng

4. full_mini210-userdebug

5. full_stingray-userdebug

6. full_wingray-userdebug

7. full_crespo4g-userdebug

8. full_crespo-userdebug

9. full_maguro-userdebug

10. full_toro-userdebug

11. full_tuna-userdebug

12. full_panda-eng

Which would you like? [full-eng] 4

在这里选择4。同样的,每次运行完source ./build/envsetup.sh之后也要重新运行lunch命令,否则系统仍会默认以full-eng的方式进行编译。当你编译ioctl_test.c这样的Android应用程序的时候就会出现类似下面的错误。

make: Entering directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'

make: *** No rule to make target `out/target/product/generic/obj/lib/crtbegin_dynamic.o', needed by `out/target/product/generic/obj/EXECUTABLES/ioctl_test_intermediates/LINKED/ioctl_test'. Stop.

make: Leaving directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'

一定要选择full_mini210-userdebug。然后会出现下面的信息。

============================================

PLATFORM_VERSION_CODENAME=REL

PLATFORM_VERSION=4.0.3

TARGET_PRODUCT=full_mini210

TARGET_BUILD_VARIANT=userdebug

TARGET_BUILD_TYPE=release

TARGET_BUILD_APPS=

TARGET_ARCH=arm

TARGET_ARCH_VARIANT=armv7-a-neon

HOST_ARCH=x86

HOST_OS=linux

HOST_BUILD_TYPE=release

BUILD_ID=IML74K

============================================

好了,最后然后进入到ioctl_test.c的主目录中执行:

Pc:#mm

============================================

PLATFORM_VERSION_CODENAME=REL

PLATFORM_VERSION=4.0.3

TARGET_PRODUCT=full_mini210

TARGET_BUILD_VARIANT=userdebug

TARGET_BUILD_TYPE=release

TARGET_BUILD_APPS=

TARGET_ARCH=arm

TARGET_ARCH_VARIANT=armv7-a-neon

HOST_ARCH=x86

HOST_OS=linux

HOST_BUILD_TYPE=release

BUILD_ID=IML74K

============================================

make: Entering directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'

target thumb C: ioctl_test <= /work/MySystem/3_Android_resource/android-4.0.3_r1/ioctl_test/ioctl_test.c

target Executable: ioctl_test (out/target/product/mini210/obj/EXECUTABLES/ioctl_test_intermediates/LINKED/ioctl_test)

target Symbolic: ioctl_test (out/target/product/mini210/symbols/system/bin/ioctl_test)

target Strip: ioctl_test (out/target/product/mini210/obj/EXECUTABLES/ioctl_test_intermediates/ioctl_test)

Install: out/target/product/mini210/system/bin/ioctl_test

make: Leaving directory `/home/zbl/Android/MySystem/3_Android_resource/android-4.0.3_r1'

从编译结果可以看出,编译出的可执行文件ioctl_test被放在了out/target/product/mini210/system/bin/文件下。按照2.3.2的方法将其上传到开发板后即可使用。

三、HAL硬件抽象层

3.1 HAL简介

Google为Android加入HAL主要有如下目的。

Ø 统一硬件的调用接口。由于HAL有标准的调用接口,可以利用HAL屏蔽Linux驱动复杂、不统一的接口。

Ø 解决了GPL版权问题。Android系统基于Apache Licence2.0协议。可以让那些不想开源的驱动作者屏蔽源代码。这是基于GPL开源协议的Linux系统所不允许的。所以Linux毫不客气的将Android系统开除族籍了。

Ø 访问用户空间资源。对于有些硬件,可能需要访问一些用户空间的资源,或在内核空间不方便完成的工作以及特殊需要。在这种情况下,可以利用位于用户空间的HAL代码来辅助Linux驱动完成一些工作。

在编写HAL层程序的时候要时刻注意HAL是运行在用户空间的,这有助于理解HAL层的设计思想。HAL(硬件抽象层)的诞生意味着Android系统被Linux大家族给剔除了。原因其实就是HAL层可以不开源。

3.2 HAL层源码与分析

撰写HAL程序关键之处在于3个重要的结构体。分别是描述HAL模块的hw_module_t结构体;描述HAL设备的hw_device_t结构体;描述模块入口函数的hw_module_methods_t结构体。

首先给出头文件led_hal.h

//led_hal.h

<span style="font-family:KaiTi_GB2312;font-size:18px;">#ifndef ANDROID_LED_INTERFACE_H

#define ANDROID_LED_INTERFACE_H

#include <stdint.h>

#include <sys/cdefs.h>

#include <sys/types.h>

#include <hardware/hardware.h>

#include <fcntl.h>

#include <errno.h>

#include <math.h>

#include <poll.h>

#include <unistd.h>

#include <dirent.h>

#include <sys/select.h>

#include <cutils/log.h>

__BEGIN_DECLS

/*下面这个ID重要的很,Jni/Service层主要通过它来寻找相应的HAL库文件(.so),所以HAL源文件的名字是可以随便改的,只要保证其中的ID值不变即可*/

#define LED_HARDWARE_MODULE_ID "led_hal"

/*HAL规定不能直接使用hw_module_t结构体,需要在hw_modult_t外再套一层结构体。hw_module_t结构体表示HAL模块的相关信息,成员变量可以随便起,但是hw_module_t结构体必须是led_module_t结构体的第1个成员变量的数据类型。这个东西是整个HAL的核心,后续的工作其实就是不断的完善这个module。*/

struct led_module_t

{

struct hw_module_t common;

};

#define IOCTL_GPIO_ON 1

#define IOCTL_GPIO_OFF 0

/*led_control_device_t是自定义的hw_device_t类型的结构体,但是hw_device_t必须是该结构体的第一个成员变量,另外还定义了两个控制LED的成员函数*/

struct led_control_device_t

{

struct hw_device_t common;

int (*set_on)(struct led_control_device_t* dev, int32_t LED_NUMBER);

int (*set_off)(struct led_control_device_t* dev, int32_t LED_NUMBER);

};

__END_DECLS

#endif         //ANDROID_LED_INTERFACE_H</span>


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Led_hal.c文件是HAL层的源文件,我在每个函数前都做了注解,其编号是按照代码书写顺序标注的。也就是说,HAL代码的编写顺序并不是自上而下一行一行写出来的,而是类似于驱动程序的形式,先从接口程序开始写,然后一层一层的把用到的函数补充出来。

//Led_hal.c

<span style="font-family:KaiTi_GB2312;font-size:18px;">#include <hardware/led_hal.h>

char const * const LED_DEVICE = "/dev/leds";

static int fd = -1;

/*4、打开设备文件,这里的open函数就是运行在了用户空间*/

static int open_led()

{

if((fd = open(LED_DEVICE,O_RDWR))==-1)

{

LOGV("LED stub: open %s failed\n",LED_DEVICE);

return -1;

}

else

{LOGV("LED stub: open %s successed\n",LED_DEVICE);}

return 1;

}

/*5、关闭设备,没什么说的*/

static int close_led(struct hw_device_t *dev)

{

LOGV("close_light is called");

if(fd!=-1)

{

close(fd);

if(dev)

free(dev);

}

return 0;

}

/*6、ioctl(fd,IOCTL_GPIO_ON,LED_NUMBER)运行在了用户空间*/

int led_on(struct led_control_device_t *dev, int32_t LED_NUMBER)

{

if(fd == -1)

return -1;

return ioctl(fd,IOCTL_GPIO_ON,LED_NUMBER);

}

/*7、ioctl(fd,IOCTL_GPIO_OFF,LED_NUMBER)运行在了用户空间*/

int led_off(struct led_control_device *dev, int32_t LED_NUMBER)

{

if(fd == -1)

return -1;

return ioctl(fd,IOCTL_GPIO_OFF,LED_NUMBER);

}

/*3、初始化设备文件*/

static int led_init(const struct hw_module_t* module, const char* name, struct hw_device_t** device)

{

struct led_control_device_t *dev;

/*为led_control_device结构体分配内存空间*/

dev = (struct led_control_device_t *)malloc(sizeof(*dev));

if(dev==NULL)

return 0;

memset(dev,0,sizeof(*dev));

dev->common.tag = HARDWARE_DEVICE_TAG;

dev->common.version = 0;

dev->common.module = (struct hw_module_t*)module;

//dev->common.close = (int (*)(struct hw_device_t *))close_led;

dev->common.close = close_led;

//设置打开LED的函数指针

dev->set_on = led_on;

//设置关闭LED的函数指针

dev->set_off = led_off;

*device = (struct hw_device_t *)&dev->common;

if(open_led() == -1)

{

free(dev);

dev = NULL;

return -1;

}

return 0;

}

/*2、hw_module_methods_t中定义了打开设备的open函数的指针。也就是在这个函数中进行了一些列的初始化工作*/

static struct hw_module_methods_t led_module_methods={

open: led_init

};

/*1、HAL_MODULE_INFO_SYM才是HAL真正的入口,这里面最重要的两个参数是id: LED_HARDWARE_MODULE_ID和methods: &led_module_methods*/

struct led_module_t HAL_MODULE_INFO_SYM={

common:

{

tag: HARDWARE_MODULE_TAG,

version_major: 1,

version_minor: 0,

id: LED_HARDWARE_MODULE_ID,

name: "sample led hal stub",

author: "xxx",

methods: &led_module_methods,

}

};</span>


3.3 HAL源码编译

//Android.mk

<span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH:= $(call my-dir)

# HAL module implemenation stored in

# hw/<COPYPIX_HARDWARE_MODULE_ID>.<ro.board.platform>.so

include $(CLEAR_VARS)

LOCAL_PRELINK_MODULE := false

LOCAL_SRC_FILES := led_hal.c

LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw

#LOCAL_SHARED_LIBRARIES := liblog

LOCAL_MODULE := led_hal.default

#LOCAL_MODULE := led_hal.$(TARGET_BOARD_PLATFORM)

LOCAL_MODULE_TAGS := optional

include $(BUILD_SHARED_LIBRARY)</span>


编译出来的led_hal.default.so模块保存在了$(Android_source)/out/target/produce/mini210/system/lib/hw中。将之上传到开发板中相应的/system/lib/hw文件夹中即可。

四、Jni/Service服务层

这一层的主要任务就是调用HAL层的程序库,并为上层应用程序层提供调用接口。

4.1 直接使用JNI调用驱动程序

在Java中是可以写C/C++代码的,虽然并不是100%的C/C++语言,但只需要改动一下数据类型的标志并注意一些新定义的用法即可。我们常把C/C++代码称之为原生代码,如果不是特别讲究叫法的话,在Java体系中还可以勉为其难的称之为JNI或NDK,其实JNI和NDK意义并不同,首先介绍一下JNI和NDK的区别。

JNI是java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。

NDK是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。它集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。它可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。

所以可以简单的理解为JNI是接口,NDK是工具。

在涉及到操作硬件的思路上,我们其实可以直接通过Java程序去调用驱动程序,Java程序提供了File.write(str)这样的写函数操作类,所以可以直接向/dev/leds设备驱动文件中写数据。不过Java并没有提供ioctl这样的发送IO控制命令的函数。如果就想发送IO控制命令该怎么办?可以通过JNI层来调用C/C++语言实现。

先给出完成后的操作界面。



然后直接贴出各个源文件。

//activity_main.xml

<span style="font-family:KaiTi_GB2312;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:orientation="vertical" >

<LinearLayout

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:orientation="horizontal" >

<CheckBox

android:id="@+id/checkbox_str_led1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED1" />

<CheckBox

android:id="@+id/checkbox_str_led2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED2" />

<CheckBox

android:id="@+id/checkbox_str_led3"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED3" />

<CheckBox

android:id="@+id/checkbox_str_led4"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED4" />

</LinearLayout>

<Button

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:onClick="onClick_write"

android:text="write_test" />

<LinearLayout

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:orientation="horizontal" >

<CheckBox

android:id="@+id/checkbox_cmd_led1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED1" />

<CheckBox

android:id="@+id/checkbox_cmd_led2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED2" />

<CheckBox

android:id="@+id/checkbox_cmd_led3"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED3" />

<CheckBox

android:id="@+id/checkbox_cmd_led4"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED4" />

</LinearLayout>

<Button

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:onClick="onClick_ioctl"

android:text="ioctl_test" />

<TextView

android:id="@+id/textView"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="TextView" />

</LinearLayout></span>


// jni/leds.c

<span style="font-family:KaiTi_GB2312;font-size:18px;">#include <string.h>

#include <stdio.h>

#include <jni.h>

#include <fcntl.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

#include <stdlib.h>

jstring Java_com_example_leds1_MainActivity_getText (JNIEnv* env, jobject obj)

{

return (*env)->NewStringUTF(env, "Test Android NDK!Test My NDK!");

}

char* jstring_to_pchar(JNIEnv* env, jstring str)

{

char* pstr = NULL;

jclass clsstring = (*env)->FindClass(env, "java/lang/String");

jstring strencode = (*env)->NewStringUTF(env, "utf-8");

jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",

"(Ljava/lang/String;)[B");

jbyteArray byteArray = (jbyteArray)(

(*env)->CallObjectMethod(env, str, mid, strencode));

jsize size = (*env)->GetArrayLength(env, byteArray);

jbyte* pbyte = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);

if (size > 0)

{

pstr = (char*) malloc(size);

memcpy(pstr, pbyte, size);

}

return pstr;

}

void Java_com_example_leds1_MainActivity_writeLeds(JNIEnv* env, jobject thiz, jstring str)

{

int fd;

fd = open("/dev/leds",O_RDWR);

char* pstr = jstring_to_pchar(env,str);

write(fd,pstr,strlen(pstr));

close(fd);

}

void Java_com_example_leds1_MainActivity_ioctlLeds(JNIEnv* env, jobject thiz, jint cmd, jint arg)

{

int fd;

fd = open("/dev/leds",O_RDWR);

ioctl(fd,cmd,arg);

close(fd);

}</span>


//jni/Android.mk

<span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := leds

LOCAL_SRC_FILES := leds.c

include $(BUILD_SHARED_LIBRARY)  </span>


//MainActivity.java

<span style="font-family:KaiTi_GB2312;font-size:18px;">package com.example.leds1;

import android.app.Activity;

import android.os.Bundle;

import android.view.Menu;

import android.view.View;

import android.view.MenuItem;

import android.widget.TextView;

import android.widget.CheckBox;

public class MainActivity extends Activity {

private CheckBox[] cbStrLeds = new CheckBox[4];

private CheckBox[] cbCmdLeds = new CheckBox[4];

TextView textView;

public native String getText();//声明native 方法

public native void writeLeds(String str);

public native void ioctlLeds(int cmd, int arg);

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

cbStrLeds[0] = (CheckBox)findViewById(R.id.checkbox_str_led1);

cbStrLeds[1] = (CheckBox)findViewById(R.id.checkbox_str_led2);

cbStrLeds[2] = (CheckBox)findViewById(R.id.checkbox_str_led3);

cbStrLeds[3] = (CheckBox)findViewById(R.id.checkbox_str_led4);

cbCmdLeds[0] = (CheckBox)findViewById(R.id.checkbox_cmd_led1);

cbCmdLeds[1] = (CheckBox)findViewById(R.id.checkbox_cmd_led2);

cbCmdLeds[2] = (CheckBox)findViewById(R.id.checkbox_cmd_led3);

cbCmdLeds[3] = (CheckBox)findViewById(R.id.checkbox_cmd_led4);

String myString = getText();//调用native方法

//   long z = add(2, 3);

//   String zstr=Long.toString(z);

textView = (TextView)findViewById(R.id.textView);

textView.setText(myString);

}

public void onClick_write(View view)

{

String str="";

for(int i=0;i<4;i++)

{

if(cbStrLeds[i].isChecked())

str +='1';

else

str +='0';

}

TextView textview = (TextView)findViewById(R.id.textView);

textview.setText(str);

writeLeds(str);

}

public void onClick_ioctl(View view)

{

TextView textview = (TextView)findViewById(R.id.textView);

textview.setText("ioctl_test!");

for(int i=0;i<4;i++)

{

if(cbCmdLeds[i].isChecked())

ioctlLeds(1,i);

else

ioctlLeds(0,i);

}

}

@Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if it is present.

getMenuInflater().inflate(R.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();

if (id == R.id.action_settings) {

return true;

}

return super.onOptionsItemSelected(item);

}

static {

System.loadLibrary("leds"); //导入链接库

}

}</span>


需要注意的是,想要编译jni中的文件,必须在电脑上提前装好ndk,这也是一个非常客观的工程量。然后重点是,需要在命令行或者界面形式下编译jni,具体方法上网查查吧。

4.2 JNI/Service程序代码

//LedHalService.cpp

<span style="font-family:KaiTi_GB2312;font-size:18px;">#include <stdlib.h>

#include <string.h>

#include <assert.h>

#include <jni.h>

#include <hardware/led_hal.h>   //用到了和HAL层同一个LED_HARDWARE_MODULE_ID = led_hal

#ifdef __cplusplus

extern "C" {

#endif

struct led_control_device_t *sLedDevice = NULL;

//open the led device  by hal

static inline int led_control_open(struct hw_module_t *module,struct led_control_device_t **device)

{

return module->methods->open(module,LED_HARDWARE_MODULE_ID,(struct hw_device_t**)device);

}

//close the led device  by hal

jboolean Java_com_example_leds_MainActivity_ledClose (JNIEnv* env, jobject obj)

{

if(sLedDevice)

{

sLedDevice->common.close(&(sLedDevice->common));

}

return 0;

}

//turn on the led

jboolean Java_com_example_leds_MainActivity_ledSetOn (JNIEnv* env, jobject obj,jint number)

{

if(sLedDevice)

{

return sLedDevice->set_on(sLedDevice,number);

}

return false;

}

//turn off the led

jboolean Java_com_example_leds_MainActivity_ledSetOff (JNIEnv* env, jobject obj,jint number)

{

if(sLedDevice)

{

return sLedDevice->set_off(sLedDevice,number);

}

return false;

}

//led init

jboolean Java_com_example_leds_MainActivity_ledInit (JNIEnv* env, jobject obj)

{

led_module_t *module;

/*看到了吗,hw_get_module是调用HAL模块的核心函数,其中的LED_HARDWARE_MODULE_ID就是前文一直在提的ID号,JNI模块就是通过这个ID号找到的/system/lib/hw文件中相应的HAL共享库模块*/

int err = hw_get_module(LED_HARDWARE_MODULE_ID,(hw_module_t const**)&module);

if(err == 0)

{

if(led_control_open(&(module->common),&sLedDevice) == 0)

return true;

}

sLedDevice = NULL;

return false;

}

static led_control_device_t * get_device(hw_module_t* module, char const* name)

{

int err;

hw_device_t* device;

err = module->methods->open(module,name,&device);

if(err == 0)

{

return (led_control_device_t*)device;

}

else

{

return NULL;

}

}

/*通过JNINativeMethod数组定义了函数映射表,注册给Java虚拟机,这样JVM就可以用函数映射表来调用相应的函数,该方式属于动态调用共享库*/

static JNINativeMethod method_table[]={

{"led_init","()Z",(void*)Java_com_example_leds_MainActivity_ledInit},

{"led_setOn","(I)Z",(void*)Java_com_example_leds_MainActivity_ledSetOn},

{"led_setOff","(I)Z",(void*)Java_com_example_leds_MainActivity_ledSetOff},

{"led_close","()Z",(void*)Java_com_example_leds_MainActivity_ledClose},

};

int register_android_server_LedService(JNIEnv *env)

{

//This sentence is very important because we will use this class to call the service

static const char* const kClassName = "com/example/leds/LedService";

//static const char* const kClassName = "com/example/leds";

jclass clazz;

/*Look up the class*/

clazz = env->FindClass(kClassName);

if(clazz == NULL)

{

return -1;

}

/*Register all the methods*/

if(env->RegisterNatives(clazz,method_table,sizeof(method_table)/sizeof(method_table[0]))!=JNI_OK)

{

return -1;

}

/*Fill out the rest of the ID cache*/

return 0;

}

/*系统在成功装在JNI共享库后会自动调用JNI_OnLoad函数,该函数一般用与初始化JNI模块*/

jint JNI_OnLoad(JavaVM* vm, void* reserved)

{

JNIEnv* env = NULL;

jint result = -1;

if(vm->GetEnv((void**) &env,JNI_VERSION_1_4)!=JNI_OK)

{

return result;

}

/*使用下面的函数绑定JNI程序库与Java程序库。若想使用静态调用libled_hal_jni.so,则需要将下面的代码屏蔽掉*/

register_android_server_LedService(env);

/*返回JNI_VERSION_1_4,表明只有运行在JDK1.4及以上版本的Java程序才能调用当前的JNI模块*/

return JNI_VERSION_1_4;

}

#ifdef __cplusplus

}

#endif</span>

//Android.mk

<span style="font-family:KaiTi_GB2312;font-size:18px;">LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_PRELINK_MODULE :=false

LOCAL_MODULE_TAGS:=optional

LOCAL_SRC_FILES:=LedHalService.cpp

LOCAL_MODULE:=libled_hal_jni

LOCAL_SHARED_LIBRARIES := \

libandroid_runtime \

libcutils \

libhardware \

libhardware_legacy \

libnativehelper \

libsystem_server \

libutils \

libui \

libsurfaceflinger_client

include $(BUILD_SHARED_LIBRARY)

</span>


五、APP应用程序层

关于如何在APP中调用JNI库文件,上文中4.1节有提到,不过那是在工程中建立了一个jni文件夹,生成的JNI的库文件直接保存到了当前工程的lib文件夹下。当然也可以不用在当前工程下建立jni文件夹,像4.2那样通过Android原生代码进行编译,然后直接通过绝对路径加载模块也能产生相同功能。

5.1 activity_main.xml

<span style="font-family:KaiTi_GB2312;font-size:18px;"><?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:orientation="vertical" >

<LinearLayout

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:orientation="horizontal" >

<CheckBox

android:id="@+id/checkbox_cmd_led1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED1" />

<CheckBox

android:id="@+id/checkbox_cmd_led2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED2" />

<CheckBox

android:id="@+id/checkbox_cmd_led3"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED3" />

<CheckBox

android:id="@+id/checkbox_cmd_led4"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="LED4" />

</LinearLayout>

<Button

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:onClick="onClick_led"

android:text="led_hal_jni" />

</LinearLayout></span>


5.2 LedServer.java

<span style="font-family:KaiTi_GB2312;font-size:18px;">package com.example.leds;

public class LedService {

private static LedService LedService;

public static LedService getInstance()

{

if (LedService == null)

LedService = new LedService();

return LedService;

}

private LedService()

{

init();

}

public boolean init()

{

return led_init();

}

public boolean setOn(int led)

{

return led_setOn(led);

}

public boolean setOff(int led)

{

return led_setOff(led);

}

//  native method

private native boolean led_init();

private native boolean led_setOn(int led);

private native boolean led_setOff(int led);

static

{

System.load("/system/lib/libled_hal_jni.so");

}

}</span>


5.3 MainActivity.java

<span style="font-family:KaiTi_GB2312;font-size:18px;">package com.example.leds;

import com.example.leds.LedService;

import android.app.Activity;

import android.os.Bundle;

import android.view.Menu;

import android.view.MenuItem;

import android.view.View;

import android.widget.CheckBox;

public class MainActivity extends Activity {

private CheckBox[] cbCmdLeds = new CheckBox[4];

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

cbCmdLeds[0] = (CheckBox)findViewById(R.id.checkbox_cmd_led1);

cbCmdLeds[1] = (CheckBox)findViewById(R.id.checkbox_cmd_led2);

cbCmdLeds[2] = (CheckBox)findViewById(R.id.checkbox_cmd_led3);

cbCmdLeds[3] = (CheckBox)findViewById(R.id.checkbox_cmd_led4);

/*

LedService ledService = LedService.getInstance();

ledService.setOn(0);

ledService.setOff(1);

ledService.setOn(2);

ledService.setOff(3);

*/

}

public void onClick_led(View view)

{

LedService ledService = LedService.getInstance();

for(int i=0;i<4;i++)

{

if(cbCmdLeds[i].isChecked())

ledService.setOn(i);

else

ledService.setOff(i);

}

}

@Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if it is present.

getMenuInflater().inflate(R.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();

if (id == R.id.action_settings) {

return true;

}

return super.onOptionsItemSelected(item);

}

}</span>


好了,关于整套程序都已经写完了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: