作为基于Linux内核的操作系统,Android系统自推出以来,就以“开源”特性逐渐占据了嵌入式设备操作系统的主要市场份额,成为现有主要的嵌入式系统如iOS、Windows Phone、Android等市场占有率最高的操作系统。Android SDK (software development kit )中指定使用Java作为第三方应用开发语言[1],且Java应用程序运行在Dalvik虚拟机中。运行在虚拟机中的Java代码可以充分利用Java语言的平台无关性,但是一定程度上造成了Android应用程序很难操作底层硬件的缺点。为此Android系统中引入了JNI机制——利用Java的JNI机制,使用Android NDK( native development kit )编译环境,应用程序可以透过Android系统的应用框架层,直接在Linux的文件系统对设备进行操作[2]。文中讨论了在Android系统中通过应用程序控制3G模块的常用接口——复位、飞行模式等,讨论了JNI技术在Android系统中的应用价值和意义。
1 Android JNI和NDK介绍
Android系统架构采用了分层结构,保证了层与层之间相互分离,当某一层发生变化时,其他层受影响很少[3]。如图 1所示,从底层到顶层分别是Linux内核层、系统库和Android运行时库、应用框架层和应用程序层[4]。
JNI是JDK( Java native kit )的一部分,可以允许Java代码和其他语言写的代码进行交互,以实现代码在不同的平台上移植。通过JNI,可以使得运行于JVM( Java virtual machine )的代码调用C、C++等[5]语言编写的应用程序或库[6],同时也可以通过调用相应的接口函数将Java虚拟机内嵌到本地应用程序中[7]。JNI机制调用本地的C/C++代码库,可以充分利用C/C++代码的高效性,来提高应用程序的运行效率。
Android NDK本质上是一系列的工具集,用来完善Android应用更加便捷的使用本地代码,如C或C++代码[8]。因此,用户可以使用NDK将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率,而且可避免Java包都是可以反编译的不安全性[9]。Android系统的层次结构如图 1所示。
2 MU739模块控制引脚及驱动实现
MU739为华为公司推出的支持WCDMA、HSPA+的LGA 3G模块,采用USB接口。在HSPA+模式下,上行速率可以达到5.76 Mbit/s,下行速率可以达到21.6 Mbit/s。在Linux内核中,对USB 3G模块的驱动位于USB设备驱动层[10, 11],如图 2所示。
在3G模块的驱动移植完成后,为实现对3G模块的控制,文中选取了MU739模块的上电控制引脚(ON1、ON2_N)、复位控制引脚(PWRDWN_N)、飞行模式控制引脚(W_DISABLE_N)和休眠控制引脚(WAKEUP_IN)来实现控制3G模块的工作状态。下面依次介绍这几个引脚的主要功能。
1) ON1和ON2_N引脚用于打开3G模块,其中ON1引脚设置为默认高电平,ON2_N通过控制引脚提供开机时序,使3G模块在上电之后处于工作状态;
2) PWRDWN_N引脚用于复位整个模块系统,使基带、电源管理单元、RF单元进入初始状态;
3) W_DISABLE_N引脚用于使模块进入飞行模式,关闭模块的RF单元;
4) WAKEUP_IN引脚用于CPU来控制3G模块的睡眠状态,当设置为高电平时,CPU唤醒MU739,当电平为低时,CPU使MU739进入睡眠模式。
2.1 配置控制引脚为GPIO模式
设计中采用的CPU为飞思卡尔公司的iMX536多媒体处理器,该处理器采用ARM CortexTM-A8 内核,在车载环境中可以实现800 Hz的工作频率。为了实现对片内多种功能模块的支持,iMX53x系列处理器采用IOMUX机制实现对IO引脚的复用分配。
对3G模块的控制,主要通过iMX536处理器的IO控制实现。为此,需要在平台设备的配置文件中,配置对应的IO引脚为GPIO模式:
MX53_PAD_GPIO_19__GPIO4_5,//ON2
MX53_PAD_PATA_DA_2__GPIO7_8,// PWRDWN
MX53_PAD_PATA_CS_0__GPIO7_9,// W_DISABLE
MX53_PAD_PATA_CS_1__GPIO7_10,// WAKEUP_IN
并给与ON2_N的控制引脚实现模块的开启时序(见图 3所示)。
gpio_direction_output(ON2,1);
udelay(40);
gpio_direction_output(ON2,0);
其中,ON2为宏定义——#define ON2(3×32+5) /*GPIO_4_5*/
2.2 控制驱动程序的编写
为实现对底层设备的操作,需要编写对应的控制驱动程序。因此,为实现对上述IO引脚的控制,需要编写对应的驱动代码,即字符型设备驱动。需要首先实例化Linux内核中描述设备文件操作的结构体file_operations(位于Linux内核include/linux/下的Fs.h文件),其中定义了操作驱动设备的主要操作接口函数指针。下面所示代码为在本驱动代码中实例化结构体file_operations:
static struct file_operations mu739_cont_fops={.owner = THIS_MODULE,
.open = mu739_cont_open,
.read = mu739_cont_read,
.write = mu739_cont_write,
.ioctl = mu739_cont_ioctl,
.release = mu739_cont_release,
};上述函数指针中,主要实现功能的函数为mu739_cont_ioctl,该函数的实现原理是通过获取打开设备时传入的参数值,来执行对应的IO控制输出,以实现对应控制引脚的控制时序(见图 4)。
static int mu739_cont_ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg){
if(arg > 4){
return -EINVAL;
}
printk(KERN_ERR"mu739_cont_ioctl --CMD=%x /n",cmd);
switch(cmd){
case IOSIGNAL_REST:
……// reset the module
return 0;
case IOSIGNAL_AIRMOD://
……// set the module in airplane mode
return 0;
case IOSIGNAL_UAIRMOD:
……// set the module in normal mode
return 0;
case IOSIGNAL_WAKEUP:
……// wake up the module
return 0;
case IOSIGNAL_UWAKEUP:
……// set the module in sleep mode
return 0;
default: break;
}
}在驱动代码最后,实现模块的初始和注销函数如下:
static int __init MU739_cont_init(void){int ret;
//调用register_chrdev方法注册设备驱动
ret=register_chrdev(MU739_MAJOR,DEVICE_NAME,&mu739_cont_fops);
if(ret < 0){
printk(DEVICE_NAME"cant register major No.\\n");
return ret;
}
……
return 0;
} static void __exit MU739_cont_exit(void){// 调用unregister_chrdev方法注销设备
unregister_chrdev(MU739_MAJOR,DEVICE_NAME);
……
} module_init(MU739_cont_init); module_exit(MU739_cont_exit);最后,将该驱动静态添加进内核中。编译烧写后,会在Android系统的dev/目录下查看到该设备mu739_cont_dev。
3 应用程序编写
在Eclipse开发环境中新建一个Android应用工程mu739_control,用来作为控制3G模块的Android应用程序。
3.1 编写调用本地方法
在mu739_control工程目录下新建包com.example.myjni,用于声明native方法以及给应用程序调用的静态库名字:
public static native int open3G(int flag);
static{
System.loadLibrary("ContModule");
}
其中native关键字用于告知编译器该方法为本地方法;static关键字包括的语句System.loadLibrary("ContModule"),表示调用本地库文件为libContModule.so(其命名规则是libFileName.so)。
之后,在工程目录中,新建空文件夹jni,用于存放jni文件。并通过终端进入工程的根目录下,运行命令javah,生成JNI头文件:
$ javah -classpath bin/classes -d jni/ com.example.myjni.MyNative
其中,javah命令为NDK开发环境中带有的工具,其用法如图 5所示。
javah命令生成的头文件名字为com_example_myjni_MyNative.h,其中包含了上述本地方法的声明方式:
JNIEXPORT jint JNICALL Java_com_example_ myjni_MyNative_open3G(JNIEnv *,jclass,jint);之后,在jni/目录下新建C文件myfile.c,实现上述函数的功能:
JNIEXPORT jint JNICALL Java_com_example_ myjni_MyNative_open3G(JNIEnv *env,jclass thiz,jint flag){int temp,ledstatus = flag;
int fd;
// 调用open函数打开设备mu739_cont_dev
fd = open("/dev/mu739_cont_dev",O_RDWR);
if(fd < 0){
LOGE("Open 3G device error,fd = %d .",fd);
exit(1);
}
// 打开设备后,调用ioctl函数,操作设备并传入控制参数
temp = ioctl(fd,ledstatus,0);
sleep(10);
if(temp < 0 ){
LOGE("ioctl the device error,lestatus = %d",ledstatus);
exit(1);
}
// 完成操作后,调用close函数,关闭设备
……close(fd);
return 1;
}然后,在jni/目录下编写Android.mk文件,用来指导编译工具编译上述C文件:
LOCAL_PATH := $(call my-dir) #include $(CLEAR_VARS) LOCAL_LDLIBS := -llog LOCAL_MODULE := ContModule LOCAL_MODULE_FILENAME := libContModule LOCAL_SRC_FILES := myfile.c include $(BUILD_SHARED_LIBRARY)最后,在终端运行命令# ndk-build编译,即可在libs/armeabi/目录下生成.so库文件libContModule.so。即为System.loadLibrary()中引用的静态库文件。
3.2 JAVA代码实现
编写Android应用代码,实现对上述本地方法的调用。
在Android应用工程中开发一个Android应用界面(如图 6所示),界面上的按键采用监听的方式处理对3G设备的操作。
在代码中,实现对上述按键的监听,为减少函数调用时引起的延时,响应按键的函数在新建的线程中实现。监听按键的onClick函数代码如下:
@Override public void onClick(View arg0) { switch(arg0.getId()){case R.id.Button_Rest: // 对复位按键处理
Log.d(TAG,"Rest the 3G module");// Log调试信息
new Thread(new Runnable() { // 新线程中处理
public void run() {
MyNative.open3G(Rest_Flag);
}
}).start();
break;
case R.id.Button_Normal: // 正常模式Log.d(TAG,"Set the module Normal Mode");
new Thread(new Runnable() {
public void run() {
MyNative.open3G(Norm_Flag);
}
}).start();
break;
case R.id.Button_Airmode: // 飞行模式Log.d(TAG,"Set the module Airmode");
new Thread(new Runnable() {
public void run() {
MyNative.open3G(Airm_Flag);
}
}).start();
break;
case R.id.Button_Wakeup:Log.d(TAG,"WakeUP 3G module");
new Thread(new Runnable() {
public void run() {
MyNative.open3G(Wake_Flag);
}
}).start();
break;
case R.id.Button_Sleep: // 对睡眠模式处理Log.d(TAG,"Set the module sleep");
new Thread(new Runnable() {
public void run() {
MyNative.open3G(Slep_Flag);
}).start();
break;
}// switch(arg0)}// onClick
4 结束语
通过介绍Android系统应用层代码对3G模块复位等控制引脚的操作,讨论了Android应用中使用JNI技术控制底层设备的实现方法和步骤,对于涉及到底层设备操作(如串口等)的应用设计具有指导意义。在下一步的研究中,可以结合硬件抽象层实现底层和应用层的隔离,讨论及实现应用层对底层设备的操作。
[1] | 金智义, 张戟. 基于Android平台的串口通信实现[J]. 电脑知识与技术, 2011, 7(13): 2983-2990. |
[2] | 高海彬. JNI在Android系统下串口控制的应用[J]. 信息技术, 2013(10): 173-176. |
[3] | 李刚. 疯狂Android讲义[M]. 2版. 北京: 电子工业出版社, 2011: 78-79. |
[4] | 高海彬. JNI在Android系统下串口控制的应用[J]. 信息技术, 2011(10): 173-176 |
[5] | 丁海洋, 姚佳楠, 王明飞. 基于移动平台的印刷网点检测技术[J]. 北京印刷学院学报, 2014(8): 50-65. |
[6] | 张华平, 玄光哲,于贵平, 等. 基于JNI技术应用框架的分析和实现[J]. 吉林大学学报: 信息科学, 2003, 21(2): 188-191. |
[7] | 任俊伟, 林东岱. JNI技术实现跨平台开发的研究[J]. 计算机应用研究, 2005(7): 180-184. |
[8] | Google. Android Developer website[EB/OL]. [2012-09-02]. http://developer.android.com. |
[9] | 王二伟. 基于Android平台人脸检测与识别研究[D]. 西安: 西安电子科技大学, 2013: 11-22. |
[10] | 刘淑峰. 基于 Android 的多媒体与 3G 上网子系统设计与实现[D]. 哈尔滨: 哈尔滨工业大学, 2012: 63-64. |
[11] | 宋世磊, 刘晓平, 应怀樵. 基于ARM-Linux的USB 3G模块设备驱动的研究[J]. 计算机工程与应用, 2011: 175-178. |