三、使用AndroidNDK测试Linux驱动
在 Android系统中Linux驱动主要的使用者是APK程序。因此,Linux驱动做完后必须要用APK程序进行测试才能说明Linux驱动可以正常使 用。由于上一节在Android虚拟机上使用C语言编写的可执行程序测试了Linux驱动,因此很容易想到可以利用Android NDK来测试Linux驱动,
由于Android NDK也使用C/C++来编写程序,因此可以利用上一节的C语言代码,当然,还得加上一些AndroidNDK特有的代码。在使用AndroidNDK测试Linux驱动之前需要做如下两件事。
1. 由于Linux驱动模块不会随Android系统启动而装载,因此必须执行build.sh脚本文件安装word_count驱动。
2. 不能使用默认方式启动Android模拟器,而要使用我们自己编译的Linux内核启动Android模拟器,启动模拟器的命令如下:
# emulator-avd myavd -kernel /root/kernel/goldfish/arch/arm/boot/zImage
为了方便,读者也可以在随书光盘带的Ubuntu Linux虚拟环境中直接执行如下的命令来异步启动Android模拟器。其中emulator.sh文件在/root/drivers目录中。
# sh emulator.sh &
本节的例子已经包含在随书光盘和虚拟环境中,路径如下:
随书光盘:/sources/ch06/word_count/word_count_ndk
虚拟环境:/root/drivers/ch06/word_count/word_count_ndk
word_count_ndk工程的代码部分由WordCountNDKTestMain.java和ndk_test_word_count.c文件组成。工程结构如图6-17所示。
ndk_test_word_count.c文件用于访问word_count驱动。该文件包含两个供Java访问的函数,分别用来读取/dev /wordcount设备文件中的单词数和向/dev/wordcount设备文件写入字符串。下面先看看ndk_test_word_count.c文 件的完整代码。
#include
#include
#include
#include
#include
#include
#include
#include
// JNI函数:readWordCountFromDev
// 用于从/dev/wordcount设备文件读取单词数
jint Java_mobile_android_word_count_ndk_WordCountNDKTestMain_readWordCountFromDev(
JNIEnv* env, jobject thiz)
{
int dev; // open函数打开/dev/wordcount设备文件后返回的句柄,打开失败返回-1
jint wordcount = 0; // 单词数
unsigned char buf[4]; // 以4个字节形式存储的单词数
// 以只读方式打开/dev/wordcount设备文件
dev = open("/dev/wordcount", O_RDONLY);
// 从dev/wordcount设备文件中读取单词数
read(dev, buf, 4);
int n = 0; // 存储单词数的int类型变量
// 将由4个字节表示的单词数转换成int类型的值
n = ((int) buf[0]) << 24 | ((int) buf[1]) << 16 | ((int) buf[2]) << 8 | ((int) buf[3]);
// 将int类型的单词数转换成jint类型的单词数
wordcount = (jint) n;
// 关闭/dev/wordcount设备文件
close(dev);
// 返回单词数
return wordcount;
}
// 将jstring类型的值转换成char *类型的值
char* jstring_to_pchar(JNIEnv* env, jstring str)
{
char* pstr = NULL;
// 下面的代码会调用Java中的String.getBytes方法获取字符串的字节数
// 获取java.lang.String类
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
// 将字符串“utf-8”转换成jstring类型的值
jstring strencode = (*env)->NewStringUTF(env, "utf-8");
// 获取java.lang.String.getBytes方法
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
// 调用String.getBytes方法将str变量的值转换成jbytearray类型的值
jbyteArray byteArray = (jbyteArray)( (*env)->CallObjectMethod(env, str, mid, strencode));
// 获取字节长度
jsize size = (*env)->GetArrayLength(env, byteArray);
// 将jbytearray类型的值转换成jbyte*类型的值
jbyte* pbyte = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE);
if (size > 0)
{
// 为char*类型变量pstr分配空间
pstr = (char*) malloc(size);
// 将pbyte变量中的值复制到pstr变量中
memcpy(pstr, pbyte, size);
}
// 返回转换后的值
return pstr;
}
// JNI函数:writeStringToDev
// 用于向/dev/wordcount设备文件写入字符串
void Java_mobile_android_word_count_ndk_WordCountNDKTestMain_writeStringToDev(
JNIEnv* env, jobject thiz, jstring str)
{
int dev; // open函数打开/dev/wordcount设备文件后返回的句柄,打开失败返回-1
// 以只写方式打开/dev/wordcount设备文件
dev = open("/dev/wordcount", O_WRONLY);
// 将jstring类型字符串转换成char* 类型的值
char* pstr = jstring_to_pchar(env, str);
if (pstr != NULL)
{
// 向/dev/wordcount设备文件写入字符串
write(dev,pstr, strlen(pstr));
}
// 关闭/dev/wordcount设备文件
close(dev);
}
编写上面的代码有一个重点就是jstring_to_pchar函数。该函数可以将jstring类型的数据转换成char*类型的数据。转换的基 本思想就是调用Java方法String.getBytes,获取字符串对应的字节数组(jbyteArray)。由于write函数需要的是char *类型的数据,因此,还必须将jbyteArray类型的数据转换成char *类型的数据。采用的方法是先将jbyteArray类型的数据转换成jbyte类型的数据,然后调用memcpy函数将jbyte类型的数据复制到使用 malloc函数分配的char *指针空间中。在jstring_to_pchar函数中有如下的一行代码。
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B"];
看到getMethodID方法最后一个参数的值是"(Ljava/lang/String;)[B",可能Android NDK初学者会对此感到困惑,以为是写错了。实际上这是JNI(Android NDK程序实际上就是遵循JNI规则的程序)对方法参数和返回类型的描述。在JNI程序中为了方便描述Java数据类型,将简单类型使用了一个大写英文字 母表示,如表6-1所示。
除了表6-1所示的Java简单类型外,还有一些数据类型需要在JNI代码中与其对应。表6-2是这些数据类型在JNI中的描述符。
从表6-2所示的数据类型对照关系很容易想到本例中的"(Ljava/lang/String;)[B"是什么意思。jstring_to_pchar函数调用的是如下的getBytes方法的重载形式。
public byte[] getBytes(String charsetName) throwsUnsupportedEncodingException
在JNI中调用Java方法需要指定方法参数和返回值的数据类型。在JNI中的格式如下:
"(参数类型)返回值类型"
getBytes方法的参数类型是String,根据表6-2的描述,String类型中JNI在的描述符是"Ljava/lang/String; "。getBytes方法的返回值类型是byte[]。这里就涉及到一个数组的表示法。在JNI中数组使用左中括号([]表示,后面是数组中元素的类型。 每一维需要使用一个“[”。byte[]是一维字节数组,所以使用"[B"表示。如果是byte[][][],应使用"[[[B"表示。如果Java方法 未返回任何值(返回值类型是void),则用V表示。如void mymethod(int value)的参数和返回值类型可表示为"(I)V"。
Android NDK程序还需要一个Android.mk文件,代码如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ndk_test_word_count
LOCAL_SRC_FILES := ndk_test_word_count.c
include $(BUILD_SHARED_LIBRARY)
在编写Java代码调用JNI函数之前,先看一下本例的界面,如图6-18所示。
读者需要先在PC上运行build.sh脚本文件安装word_count驱动。然后单击“从/dev/wordcount读取单词数”按钮,会在按钮下 方输出当前/dev/wordcount设备文件中统计出的单词数。读者也可以在输入框中输入一个由空格分隔的字符串,然后单击“向/dev /wordcount写入字符串”按钮,再单击“从/dev/wordcount读取单词数”按钮,就会统计出字符串中包含的单词数,效果如图6-19所 示。
下面看一下本例中Java部分(WordCountNDKTestMain.java)的完整代码。
package mobile.android.word.count.ndk;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class WordCountNDKTestMain extends Activity
{
private TextView tvWordCount;
private EditText etString;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tvWordCount = (TextView) findViewById(R.id.textview_wordcount);
etString = (EditText) findViewById(R.id.edittext_string);
}
// “从/dev/wordcount读取单词数”按钮的执行代码
public void onClick_ReadWordCountFromDev(View view)
{
// 显示单词数
tvWordCount.setText("单词数:" + String.valueOf(readWordCountFromDev()));
}
// “向/dev/wordcount写入字符串”按钮的执行代码
public void onClick_WriteStringToDev(View view)
{
// 向/dev/wordcount设备文件写入字符串
writeStringToDev(etString.getText().toString());
Toast.makeText(this, "已向/dev/wordcount写入字符串", Toast.LENGTH_LONG).show();
}
// native方法
public native int readWordCountFromDev();
public native void writeStringToDev(String str);
static
{
System.loadLibrary("ndk_test_word_count");
}
}
WordCountNDKTestMain.java中的代码只是简单地调用了JNI函数来操作/dev/wordcount文件。其他的代码都是常规的Android应用级别的代码。如果读者对这部分不熟悉,可以参阅笔者所著的《Android开发权威指南》。
四、使用Java代码直接操作设备文件来测试Linux驱动
如果Android拥有root权限,完全可以直接使用Java代码操作/dev/wordcount设备文件(没有root权限,Linux驱动模块是 无法安装的)。本节将介绍如何使用Java代码来测试Linux驱动(测试程序不使用一行C/C++代码)。本节示例的路径如下:
随书光盘:/sources/ch06/word_count/word_count_java
虚拟环境:/root/drivers/ch06/word_count/word_count_java
word_count_java工程中只有一个源代码文件WordCountJavaTestMain.java。该文件的内容如下:
package mobile.android.word.count.java;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class WordCountJavaTestMain extends Activity
{
private TextView tvWordCount;
private EditText etString;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tvWordCount = (TextView) findViewById(R.id.textview_wordcount);
etString = (EditText) findViewById(R.id.edittext_string);
}
// “从/dev/wordcount读取单词数”按钮的执行代码
public void onClick_ReadWordCountFromDev(View view)
{
// 显示单词数
tvWordCount.setText("单词数:" + String.valueOf(readWordCountFromDev()));
}
// “向/dev/wordcount写入字符串”按钮的执行代码
public void onClick_WriteStringToDev(View view)
{
// 向/dev/wordcount设备文件写入字符串
writeStringToDev(etString.getText().toString());
Toast.makeText(this, "已向/dev/wordcount写入字符串", Toast.LENGTH_LONG).show();
}
// 下面是用Java实现的操作/dev/wordcount设备文件的代码
// 读取/dev/wordcount设备文件中的单词数
private int readWordCountFromDev()
{
int n = 0;
byte[] buffer = new byte[4];
try
{
// 打开/dev/wordcount设备文件
FileInputStream fis = new FileInputStream("/dev/wordcount");
// 从设备文件中读取4个字节
fis.read(buffer);
// 将4个字节转换成int类型的值
n = ((int) buffer[0]) << 24 | ((int) buffer[1]) << 16
| ((int) buffer[2]) << 8 | ((int) buffer[3]);
fis.close();
}
catch (Exception e)
{
}
return n;
}
// 向/dev/wordcount设备文件中写入字符串
private void writeStringToDev(String str)
{
try
{
// 打开/dev/wordcount设备文件
FileOutputStream fos = new FileOutputStream("/dev/wordcount");
// 写入字符串
fos.write(str.getBytes("iso-8859-1"));
fos.close();
}
catch (Exception e)
{
}
}
}
本例的运行效果和使用方法与上一节的例子类似。读者可以运行随书光盘或虚拟环境中的例子与上一节的例子进行比较。
本文节选至《Android深度探索(卷1):HAL与驱动开发》,
接下来几篇文章将详细阐述如何开发ARM架构的Linux驱动,并分别利用android程序、NDK、可执行文件测试Linux驱动。可在ubuntu
Linux、Android模拟器和S3C6410开发板(可以选购OK6410-A开发板,需要刷Android)