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

  • Post author:
  • Post category:linux


前面两篇博客记录了实现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,说明我们的服务已经成功完成注册。



版权声明:本文为u011913612原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。