您的位置:首页 > 运维架构 > Linux

Android应用程序访问linux驱动第三步:实现并向系统注册Service

2017-06-24 21:26 405 查看
在学习Android应用程序访问linux驱动时,原博主在第一、二步写得具体详细,但我学到第三步实现并向系统注册Service时,发觉内迷惑和发现几处错误,这里我将我的理解和修改记录下来和大家分享。希望和原博主能帮到爱学习的小伙伴们,再次由衷感谢原博主的分享!
原文链接:http://blog.csdn.net/u011913612/article/details/52549477
为了方便大家查找,我将原博主关于Android应用程序访问linux驱动的文档汇总到一块儿,大家学习时结合源码好好分析,实验。
Android应用程序访问linux驱动第一步:实现并测试Linux驱动 http://blog.csdn.net/u011913612/article/details/52516303 Android应用程序访问linux驱动第二步:实现并测试hardware层 http://blog.csdn.net/u011913612/article/details/52518186 Android应用程序访问linux驱动第三步:实现并向系统注册Service http://blog.csdn.net/u011913612/article/details/52549477 Android应用程序访问linux驱动第四步:实现android应用程序 http://blog.csdn.net/u011913612/article/details/52571481 Android应用程序访问linux驱动第五步:回顾hw_get_module http://blog.csdn.net/u011913612/article/details/52576831
前面两篇博客记录了实现Linux驱动和使用HAL层访问Linux驱动的代码,我们分别对这两部分做了测试,他们都正常工作。有了前面的基础,我们就可以实现service层了,我们想系统注册我们自己的service,在service中访问HAL层,在HAL层中访问linux驱动…当然,我们要在应用程序中访问service,这要留到下一节来实现。 

应用程序访问service设计到了进程间的通信,这要求我们使用(Android Interface definition language)来描述我们的service提供的接口,然后应用程序就可以通过binder来访问service 了。所以,我们先从aidl开始,逐步搭建我们的service层。


一.aidl

在Android系统中,硬件服务一般是运行在一个独立的进程中为各种应用程序提供服务。因此,调用这些硬件服务的应用程序与这些硬件服务之间的通信需要通过代理来进行。为此,我们要先定义好通信接口。进入到frameworks/base/core/java/android/os/目录下新建IHelloTestService.aidl文件,用来描述我们的service提供的接口:
package android.os;

interface IHelloTestService {
int wirteString(String str);
String readString();
}
1
2
3
4
5
6
1
2
3
4
5
6

我们的service只提供两个方法,一个写字符串,另一个读字符串。返回到frameworks/base目录,打开Android.mk文件,修改LOCAL_SRC_FILES变量的值,增加IHelloTestService.aidl源文件:

 LOCAL_SRC_FILES += \

core/java/android/os/IHelloTestService.aidl \

编译IHelloTestService.aidl接口:mmm
frameworks/base  这样,就会根据IHelloService.aidl生成相应的IHelloTestService.Stub接口。会在./out/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/os/目录下生成IHelloTestService.java文件。IHelloTestService.java文件中实现了使用binder通信的一些方法,同时还包含了我们在接口中定义的两个方法。接下来,我们需要实现这两个方法。


二.HelloTestService.java

在frameworks/base/services/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();
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

这个类中是java中的类,java是不能直接访问C/C++代码的,必须使用jni来实现。因此,我们在这里简单调用jni中对应的方法即可。HelloTestService主要是通过调用JNI方法init_native、wirteString_native和readString_native(见在Ubuntu为Android硬件抽象层(HAL)模块编写JNI方法提供Java访问硬件服务接口一文)来提供硬件服务。


三.实现jni访问HAL

进入到frameworks/base/services/jni/目录,新建com_android_server_HelloService.cpp文件:在com_android_server_HelloTestService.cpp文件中,实现JNI方法。注意文件的命令方法,com_android_server前缀表示的是包名,表示硬件服务HelloTestService是放在frameworks/base/services/java目录下的com/android/server目录的,即存在一个命令为com.android.server.HelloTestService的类。这里HelloTestService类是一个提供Java接口的硬件访问服务类。

备注:在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);
}

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127

这部分代码中,我们把java层传下来的字符串转换为c++的char *字符数组,然后通过HAL层把它写入linux驱动。然后把从linux驱动中读出来的字符串转换为java层的字符串并返回给java层。


3.1向HelloTestService中注册本地方法

修改frameworks/base/services/jni/目录下的onload.cpp文件,首先在namespace android增加register_android_server_HelloTestService函数声明:
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);
1
2
3
4
1
2
3
4

然后再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);
1
2
3
4
1
2
3
4


3.2编译

修改/frameworks/base/services/jni/目录下的Android.mk: 

添加一行:
$(LOCAL_REL_DIR)/com_android_server_HelloTestService.cpp \
1
1

这样就可以编译我们的native代码了,此时可以尝试编译并修改错误。至此,本地的代码会在onload.cpp中完成向java层对应类的注册,同时我们可以编译我们的service,使用mmm framework/base/service即可。我们已经完成了service层代码的编写,但系统还不会使用我们的service,所以,接下来我们要向系统注册我们的服务。


四.向系统注册我们的java服务

系统在启动的时候,会使用SystemServer类启动或者注册系统服务,所以我们要在其中注册我们的服务。 

进入frameworks/base/services/java/com/android/server/SystemServer.java中在ServerThread::run函数中增加加载HelloService的代码,最后注册我们的服务就好了,使用ServiceManager.addService即可:

public void run() {

...................................................................................

(贴出要添加前面示例代码,方便定位。)

 try {

Slog.i(TAG, "DiskStats Service");

ServiceManager.addService("diskstats", new DiskStatsService(context));

}  catch (Throwable e)  {

Slog.e(TAG, "Failure starting DiskStats Service",e);

}      (贴出要添加前面示例代码,方便定位。)
try {
Slog.i("jinwei", "Hello Test Service");
ServiceManager.addService("hellotest", new HelloTestService());
} catch (Throwable e) {
Slog.e(TAG, "Failure starting Hello Test Service", e);
}
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8

然后再执行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...'
1
2
3
1
2
3

这个问题是由于之前代码
"(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.
1
2
3
4
5
1
2
3
4
5

这是因为我们没有访问/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.
1
2
3
1
2
3

等LOG,说明我们的服务已经成功完成注册。

至此,本地的代码会在onload.cpp中完成向java层对应类的注册,同时我们可以编译我们的service,使用mmm
framework/base/service即可。我们已经完成了service层代码的编写,但系统还不会使用我们的service,所以,接下来我们要向系统注册我们的服务。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: