前面两篇博客记录了实现Linux驱动和使用HAL层访问Linux驱动的代码,我们分别对这两部分做了测试,他们都正常工作。有了前面的基础,我们就可以实现service层了,我们想系统注册我们自己的service,在service中访问HAL层,在HAL层中访问linux驱动…当然,我们要在应用程序中访问service,这要留到下一节来实现。
应用程序访问service设计到了进程间的通信,这要求我们使用(Android Interface definition language)来描述我们的service提供的接口,然后应用程序就可以通过binder来访问service 了。所以,我们先从aidl开始,逐步搭建我们的service层。
一.aidl
首先在frameworks/base/core/java/android/os/目录下新建HelloTestService.aidl文件,用来描述我们的service提供的接口:
package android.os;
interface IHelloTestService {
int wirteString(String str);
String readString();
}
我们的service只提供两个方法,一个写字符串,另一个读字符串。写好aidl文件后,执行mmm framework/base进行编译,编译后会在out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/目录下生成IHelloTestService.java文件。
IHelloTestService.java文件中实现了使用binder通信的一些方法,同时还包含了我们在接口中定义的两个方法。接下来,我们需要实现这两个方法。
二.HelloTestService.java
在framework/base/service/core/java/com/android/server/下新建HelloTestService.java文件,也就是创建HelloTestService类,这个类继承了IHelloTestService中的Stub内部类,我们需要复写其中的我们在aidl中定义的两个方法。
package com.android.server;
import android.content.Context;
import android.os.IHelloTestService;
import android.util.Slog;
public class HelloTestService extends IHelloTestService.Stub {
private static final String TAG = "HelloTestService";
HelloTestService() {
init_native();
}
public int wirteString(java.lang.String str){
return wirteString_native(str);
}
public java.lang.String readString() {
return readString_native();
}
private static native boolean init_native();
private static native int wirteString_native(String str);
private static native String readString_native();
};
这个类中是java中的类,java是不能直接访问C/C++代码的,必须使用jni来实现。因此,我们在这里简单调用jni中对应的方法即可。
三.实现jni访问HAL
在java对应native代码中,就可以使用C/C++来访问C/C++代码了,回想下我们在上节测试hal层代码时写的测试代码,这部分代码将与之类似,只不过,因为这部分代码需要供java层调用,所以它需要遵寻固定的格式。
jni层代码如下:
#define LOG_TAG "HelloTestService"
#include "JNIHelp.h"
#include "jni.h"
#include <utils/Log.h>
#include <utils/misc.h>
#include <utils/String8.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <linux/ioctl.h>
#include <linux/rtc.h>
#include <hardware/hellotest.h>
namespace android
{
//jstring to char*
char* jstringTostring(JNIEnv* env, jstring jstr);
//char* to jstring
jstring stoJstring(JNIEnv* env, const char* pat);
/*在硬件抽象层中定义的硬件访问结构体,参考<hardware/hellotest.h>*/
struct hellotest_device_t* device = NULL;
/*通过硬件抽象层定义的硬件访问接口读字符串*/
static jstring hellotest_readString(JNIEnv* env, jobject clazz) {
if(!device) {
ALOGI("HelloTest JNI: device is not open.");
return NULL;
}
char read_str[10];
device->read_string(device, (char **)&read_str);
ALOGI("HelloTest JNI: read string %s from hellotest device.", read_str);
return stoJstring(env,read_str);
}
/*通过硬件抽象层定义的硬件访问接口写字符串*/
static jint hellotest_writeString(JNIEnv* env, jobject clazz,jstring str) {
if(!device) {
ALOGI("HelloTest JNI: device is not open.");
return -1;
}
char * local_str = jstringTostring(env,str);
device->write_string(device, local_str);
ALOGI("HelloTest JNI: write string %s to hellotest device.", local_str);
return sizeof(local_str);
}
/*通过硬件抽象层定义的硬件模块打开接口打开硬件设备*/
static inline int hellotest_device_open(const hw_module_t* module, struct hellotest_device_t** device) {
return module->methods->open(module, HELLOTEST_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}
/*通过硬件模块ID来加载指定的硬件抽象层模块并打开硬件*/
static jboolean hellotest_init(JNIEnv* env, jclass clazz) {
hellotest_module_t* module;
ALOGI("HelloTest JNI: initializing......");
if(hw_get_module(HELLOTEST_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) {
ALOGI("HelloTest JNI: hello Stub found.");
if(hellotest_device_open(&(module->common), &device) == 0) {
ALOGI("HelloTest JNI: hello device is open.");
return 0;
}
ALOGI("HelloTest JNI: failed to open hello device.");
return -1;
}
ALOGI("HelloTest JNI: failed to get hello stub module.");
return -1;
}
/*JNI方法表*/
static const JNINativeMethod method_table[] = {
{"init_native", "()Z", (void*)hellotest_init},
{"readString_native", "()Ljava/lang/String;", (void*)hellotest_readString},
{"wirteString_native", "(Ljava/lang/String;)I", (void*)hellotest_writeString},
};
/*注册JNI方法*/
int register_android_server_HelloTestService(JNIEnv *env) {
ALOGI("SystemServer :register_android_server_HelloTestService.");
ALOGI("SystemServer :register_android_server_HelloTestService.");
ALOGI("SystemServer :register_android_server_HelloTestService.");
ALOGI("SystemServer :register_android_server_HelloTestService.");
ALOGI("SystemServer :register_android_server_HelloTestService.");
return jniRegisterNativeMethods(env, "com/android/server/HelloTestService", method_table, NELEM(method_table));
}
//jstring to char*
char* jstringTostring(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
//char* to jstring
jstring stoJstring(JNIEnv* env, const char* pat)
{
jclass strClass = env->FindClass("java/lang/String");
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = env->NewByteArray(strlen(pat));
env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}
};
这部分代码中,我们把java层传下来的字符串转换为c++的char *字符数组,然后通过HAL层把它写入linux驱动。然后把从linux驱动中读出来的字符串转换为java层的字符串并返回给java层。
3.1编译
修改当前目录下的Android.mk:
添加一行:
$(LOCAL_REL_DIR)/com_android_server_HelloTestService.cpp \
这样就可以编译我们的native代码了,此时可以尝试编译并修改错误。
3.2向HelloTestService中注册本地方法
我们先要在onload.cpp中添加声明:
int register_android_server_tv_TvInputHal(JNIEnv* env);
int register_android_server_PersistentDataBlockService(JNIEnv* env);
int register_android_server_Watchdog(JNIEnv* env);
int register_android_server_HelloTestService(JNIEnv* env);
然后再onload.cpp中调用register_android_server_HelloTestService方法向java层的HelloTestService类注册我们的native方法:
register_android_server_tv_TvInputHal(env);
register_android_server_PersistentDataBlockService(env);
register_android_server_Watchdog(env);
register_android_server_HelloTestService(env);
至此,本地的代码会在onload.cpp中完成向java层对应类的注册,同时我们可以编译我们的service,使用mmm framework/base/service即可。我们已经完成了service层代码的编写,但系统还不会使用我们的service,所以,接下来我们要向系统注册我们的服务。
四.向系统注册我们的java服务
系统在启动的时候,会使用SystemServer类启动或者注册系统服务,所以我们要在其中注册我们的服务。
在
frameworks/base/services/java/com/android/server/SystemServer.java中的main函数中有:
public static void main(String[] args) {
new SystemServer().run();
}
可见它调用了run方法,run方法中又调用了如下启动注册系统服务:
// Start services.
try {
startBootstrapServices();
startCoreServices();
startOtherServices();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
}
我们在startOtherServices方法的最后注册我们的服务就好了,使用ServiceManager.addService即可:
try {
Slog.i("jinwei", "Hello Test Service");
ServiceManager.addService("hellotest", new HelloTestService());
} catch (Throwable e) {
Slog.e(TAG, "Failure starting Hello Test Service", e);
}
然后再执行mmm frameworks/base/services/ 进行编译。编译完成后,使用make snod命令重新打包system.img,然后重写烧写system.img,这样,系统中就有了我们的服务了。然而事情往往没有想想中顺利,我们需要解决一些问题。
五.解决问题
前面贴出来的代码已经是经过测试的代码,可以正常使用,这里还是把我在实现service的过程中遇到的问题总结一下。
注意:我们在前面的文章中测试linux驱动和HAL层代码的时候都是动态安装linux驱动的,这里因为我们在android启动的时候就注册我们的service,所以,驱动必须编译进linux kernel。而且,我们在
android应用程序访问Linux驱动第二步-实现并测试hardware层
中编译出来的hellotest.default.so文件必须拷贝到/system/lib/hw目录下。
5.1系统无法成功启动
重新烧写完系统后发现系统无法成功启动,Log如下:
pid: 6273, tid: 6273, name: system_server >>> system_server <<<
12-31 18:01:02.461 1142 1142 F DEBUG : signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
12-31 18:01:02.500 1142 1142 F DEBUG : Abort message: 'art/runtime/jni_internal.cc:497] JNI FatalError called: RegisterNatives failed for 'com/android/server/HelloTestService'; aborting...'
这个问题是由于之前代码
"(Ljava/lang/String;)I"
中遗忘了”;”造成的,加上“;”即可解决。
5.2解决devie无法打开
从开机Log可以看到,/dev/hello无法打开:
09-17 17:27:22.416 2193 2193 I HelloTestService: SystemServer :register_android_server_HelloTestService.
09-17 17:27:22.444 2193 2193 D HelloTestService: init_native
09-17 17:27:22.444 2193 2193 I HelloTestService: HelloTest JNI: initializing......
09-17 17:27:22.447 2193 2193 I HelloTestService: HelloTest JNI: hello Stub found.
09-17 17:27:22.447 2193 2193 I HelloTestService: HelloTest JNI: failed to open hello device.
这是因为我们没有访问/dev/hello权限问题导致的解决方法:打开Android源代码工程目录下,进入到system/core/rootdir目录,里面有一个名为ueventd.rc文件,往里面添加一行:
/dev/hello 0666 root root
最后,我们可以在开机Log中看到
HelloTest JNI: initializing......
HelloTest JNI: hello Stub found.
HelloTest JNI: hello device is open.
等LOG,说明我们的服务已经成功完成注册。