app通过hal调用访问led

开发环境:Android stdio 3.5.3
开发板:tiny4412开发板
软件版本:Android5.0.2 + kernel3.0.86

app通过hal调用访问led

Android5.0app调用hal架构介绍

上一章节介绍了如何使用app通过 jni接口调用我们的动态库实现对具体硬件的访问,但是这种操作方式仅限于特定硬件,对于我们通用的器件比如说LCD Android都有通用的hal层接口来访问,可以实现对硬件访问的统一管理。对于我们tiny4412开发板使用的Android5.0版本的基础架构如下图所示:
hal注册架构
1.在Android中对硬件的操作权限只在systemserver这里,所有的APP想要访问或者操作硬件都需要将请求发送给service_manager,然后由systemserver统一处理访问硬件,systemserver通过loadlibrary来加载C库,在C库的JNIonload里面来注册本地方法。在JNIonload里面分别调用各个硬件的函数来注册本地方法。
systemserver对每一个硬件构造一个service(使用上面提到的本地方法),然后使用addservice添加到系统。

2.app想要访问硬件先通过getService来获得各种服务,比如振动器服务接口调用就在IVibratorService.java里面,获取到service以后就可以使用service访问硬件了。

以led为例向系统添加hal接口

一、添加app访问的ILedService接口

如上图所示,app是通过ILedService.java来向系统获取ledservice的,所以我们首先应该实现该接口。在Android中这个java文件并不是我们直接编写的,而是通过实现一个aidl文件然后由系统帮助我们自动生成的。
下面我们来编写我们的ILedService.aidl文件,如下所示,比较简单只提供一个ledCtrl供app使用,我witch控制哪盏灯,status亮灭状态。

package android.os;

/* led hide*/
interface ILedService
{
	int ledCtrl(int which, int status);
}

将我们编写的ILedService.aidl文件放到如下目录:
/home/tangtao/work/tiny_4412/android-5.0.2/frameworks/base/core/java/android/os
修改frameworks/base/下的Android.mk文件,加上我们自己添加的aidl文件

LOCAL_SRC_FILES += \
	core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl \
	core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl \
	core/java/android/accounts/IAccountManager.aidl \
	core/java/android/os/IVibratorService.aidl \
+	core/java/android/os/ILedService.aidl \

添加以后就可以使用mmm命令编译frameworks/base,编译成功会生成ILedService.java,生成文件在
out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/ILedService.java目录下

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: frameworks/base/core/java/android/os/ILedService.aidl
 */
package android.os;
/* led hide*/
public interface ILedService extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements android.os.ILedService
{
private static final java.lang.String DESCRIPTOR = "android.os.ILedService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
.......
	public int ledCtrl(int which, int status) throws android.os.RemoteException;
}

生成了ILedService.java以后app如何调用呢

  1. ILedService iLedService;
  2. iLedService = ILedService.Stub.asInterface(ServiceManager.getService(“led”));
  3. iLedService.ledCtrl(0, 1);

二、向系统添加ledservice

这里iLedService.ledCtrl(0, 1);并不会调用到我们的功能函数,而是通过binder驱动将操作请求发送给我们的LedService,所以我们还要实现我们的LedService.java,在这里面去调用我们的本地方法去操作led,下面编写我们的LedService.java
放到android-5.0.2/frameworks/base/services/core/java/com/android/server目录下

package com.android.server;
import android.os.ILedService;

public class LedService extends ILedService.Stub {
	private static final String TAG = "LedService";
	public int ledCtrl(int which, int status) throws android.os.RemoteException{
		return native_ledCtrl(which, status);
	}
	public LedService() {
		native_ledOpen();
	}
	public static  native int  native_ledOpen();
	public static  native void  native_ledClose();
	public static  native int  native_ledCtrl(int which, int status);
}

然后在android-5.0.2/frameworks/base/services/java/com/android/server/SystemServicer.java中添加我们的LedService

            Slog.i(TAG, "Vibrator Service");
            vibrator = new VibratorService(context);
            ServiceManager.addService("vibrator", vibrator);

+            Slog.i(TAG, "Led Service");
+            ServiceManager.addService("led", new LedService());

至此app侧调用的类和service侧的类都已经实现了,LedService这个类会调用很多的本地方法,所以下一步就是实现我们的jni调用com_android_server_LedService.cpp,在这里面注册本地方法,供LedService.java使用。

三、JNI 提供本地方法

JNI向上提供本地方法,向下通过hw_get_module加载HAL文件然后获取到我们hal提供的device,接下来就可以调用具体的功能函数了。

#define LOG_TAG "LedService"

#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <hardware/led_hal.h>

namespace android
{
static led_device_t* led_device;

jint ledOpen(JNIEnv *env, jobject cls)
{
    jint err;
    hw_module_t* module;
    hw_device_t* device;

    ALOGI("native ledOpen ...");
    /* 1. hw_get_module */
    err = hw_get_module("led", (hw_module_t const**)&module);
    if (err == 0) {
		/* 2. get device : module->methods->open */
	    err = module->methods->open(module, NULL, &device);
	    if (err == 0) {
			/* 3. call led_open */
	        led_device = (led_device_t *)device;
			return led_device->led_open(led_device);
	    } else {
	        return -1;
    	}
    }
    return -1;	
}

void ledClose(JNIEnv *env, jobject cls)
{
}

jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
{
	ALOGI("native ledCtrl %d, %d", which, status);
	return led_device->led_ctrl(led_device, which, status);
}

static const JNINativeMethod methods[] = {
	{"native_ledOpen", "()I", (void *)ledOpen},
	{"native_ledClose", "()V", (void *)ledClose},
	{"native_ledCtrl", "(II)I", (void *)ledCtrl},
};

int register_android_server_LedService(JNIEnv *env)
{
    return jniRegisterNativeMethods(env, "com/android/server/LedService",
            methods, NELEM(methods));
}
};

通过上述代码可以发现JNI调用hal的过程:
1.通过 hw_get_module(“led”, (hw_module_t const**)&module)来获取我们的led module,得到一个hw_module_t结构体
2.调用module->methods->open(module, device_name, &device)来获取一个hw_device_t结构体
3.将hw_device_t转化为和device_name对应的设备自定义的结构体再调用特有的函数完成功能。
注意:正常一个module支持多个设备,可以根据device_name参数来获取特定的devices,因为我们演示demo,只支持一个device,所以device_name参数置为NULL。

四、分析JNI 是如何获取到我们的HAL模块

这部分并没有代码示例,只是通过追踪代码分析hw_get_module 是如何找到我们的hal文件并且加载到系统中的,对整体架构理解有帮助。从上小节jni到吗来看关键就是hw_get_module这个函数了,下面我们来看看这个函数里面做了什么事情。

int hw_get_module(const char *id, const struct hw_module_t **module)
{
    return hw_get_module_by_class(id, NULL, module); //id = led
}

int hw_get_module_by_class(const char *class_id, const char *inst,
                           const struct hw_module_t **module) {
	......
	strlcpy(name, class_id, PATH_MAX); //name = led
    if (property_get(prop_name, prop, NULL) > 0) {
        if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
	}
	......
	found:
    return load(class_id, path, module);
}
static int load(const char *id,const char *path,  const struct hw_module_t **pHmi){
	......
	dlopen(path, RTLD_NOW);
    const char *sym = “HMI”;
    hmi = (struct hw_module_t *)dlsym(handle, sym);
    strcmp(id, hmi->id) != 0
    *pHmi = hmi;
}

简单分析上述代码,核心操作都在hw_get_module_by_class这个函数里面:
property_get:这个函数是通过获取一些属性的键值
hw_module_exists(char *path, size_t path_len, const char name, const char subname)hw_module_exists:
这个函数是功能是在
/vendor/lib/hw和/system/lib/hw**两个路径下面查找"name"(led).“subname”.so文件是否存在。
结合上述代码hw_get_module_by_class这个函数会在/vendor/lib/hw和/system/lib/hw这两个路径下去查找
led.tiny4412.so
led.exynos4.so
led.default.so
这三个文件是否存在,如果在以上两个路径找到任意一个so文件就像这个文件路径传递给load函数,这里面就会从so文件中获得名为HMI的hw_module_t的结构体指针hmi,再判断 hmi->id和我们传进来的名称是否相同,如果相同则这个hmi就是我们想要的hw_module_t,传递回最初的调用,返回到jni文件中的调用者。
至此比较清晰的看到我们的HAL应该怎么编写:
1.实现一个名为HMI的hw_module_t结构体
2.实现一个open函数,返回一个设备自定义的结构体
注意:结构体的第一个成员必须是hw_device_t结构体,这样才可以根据device_name不同进行不同的类型转换。结构体里面可以添加这个device特定的函数操作方法。

五、编写我们的HAL文件

下面就开始编写我们的HAL文件。
1.编写我们的hardware/libhardware/include/hardware/led_hal.h文件,里面包含我们特定设备的结构体定义。

#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H
#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/types.h>
#include <hardware/hardware.h>

__BEGIN_DECLS
struct led_device_t {
    struct hw_device_t common;
 
	int (*led_open)(struct led_device_t* dev);
	int (*led_ctrl)(struct led_device_t* dev, int which, int status);
};
__END_DECLS

#endif  // ANDROID_LED_INTERFACE_H

2.编写实际hal功能函数hardware/libhardware/modules/led/led_hal.c

#define LOG_TAG "LedHal"
#include <hardware/vibrator.h>
#include <hardware/hardware.h>
#include <cutils/log.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <hardware/led_hal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <utils/Log.h>

static int fd;
/** Close this device */
static int led_close(struct hw_device_t* device)
{
	close(fd);
	return 0;
}

static int led_open(struct led_device_t* dev)
{
	fd = open("/dev/leds", O_RDWR);
	ALOGI("led_open : %d", fd);
	if (fd >= 0)
		return 0;
	else
		return -1;
}

static int led_ctrl(struct led_device_t* dev, int which, int status)
{
	int ret = ioctl(fd, status, which);
	ALOGI("led_ctrl : %d, %d, %d", which, status, ret);
	return ret;
}
static struct led_device_t led_dev = {
	.common = {
		.tag   = HARDWARE_DEVICE_TAG,
		.close = led_close,
	},
	.led_open  = led_open,
	.led_ctrl  = led_ctrl,
};

static int led_device_open(const struct hw_module_t* module, const char* id,
        struct hw_device_t** device)
{
	*device = &led_dev;
	return 0;
}

static struct hw_module_methods_t led_module_methods = {
    .open = led_device_open,
};

struct hw_module_t HAL_MODULE_INFO_SYM = {
	.tag = HARDWARE_MODULE_TAG,
    .id = "led",
    .methods = &led_module_methods,
};

从上代码可以看见实现了一个HAL_MODULE_INFO_SYM结构体,而且id是led。也实现了.methods.open函数,直接返回我们自己 定义的led_dev(led_device_t)结构体,完全按照JNI调用hal的流程对接的。
下面再添加一个hardware/libhardware/modules/led//Android.mk文件,将我们的HAL文件编译成led.default.so文件,至此我们的jni调用HAL整个流程结束。

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := led.default
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := led_hal.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := eng
include $(BUILD_SHARED_LIBRARY)

六、向系统注册JNI 实现的native方法

com_android_server_LedService.cpp中实现了我们的native方法,下一步还要向系统注册上才可以。

namespace android {
...
    int register_android_server_VibratorService(JNIEnv* env);
+  int register_android_server_LedService(JNIEnv* env);
...
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) {
	...
    register_android_server_VibratorService(env);
  +  register_android_server_LedService(env);
    ...
}

Android,mk中添加编译支持

    $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
+    $(LOCAL_REL_DIR)/com_android_server_LedService.cpp \

至此基本告一段落。使用使用mmm frameworks/base/services进行编译,编译成功可以看到重新生成了libandroid_servers.so services.jar等内容

Install: out/target/product/tiny4412/system/lib/libandroid_servers.so
Install: out/target/product/tiny4412/system/framework/services.jar

然后使用make snod重新生成镜像文件, ./gen-img.sh会在代码路径下生成新的system.img
烧写system.img到我们的tiny4412开发板。
至此系统已经更新完毕,而且已经包含我们的LedService了。

七、APP调用

下面修改我们上一节的app工程。使用原则就是
import android.os.ILedService;

  1. ILedService iLedService;
  2. iLedService = ILedService.Stub.asInterface(ServiceManager.getService(“led”));
  3. iLedService.ledCtrl(0, 1);
    需要注意的是我们添加的ILedService是hide的,也就是Android的隐藏类,Android stdio是标准的Android SDK所以不能直接调用,需要添加我们自己编译的framework jar包。首先将
    android-5.0.2/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
    导入到android stdio工程:
    file->project structure -> + Import JAR
    file->project structure ->Dependencies app + ->add module
    经过如上两部ILedService就可以被我们的工程识别到了。然后在编译系统发现运行时报错:
Error: Cannot fit requested classes in a single dex file (# methods: 149346 > 65536)

解决办法:
修改app下的build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"
    defaultConfig {
	...
     +   multiDexEnabled true
     +  dexOptions {
     +       javaMaxHeapSize "4g"
     +   }
}
dependencies {
  +  implementation 'com.android.support:multidex:1.0.3'
}

AndroidManifest.xml

    <application
       + android:name="androidx.multidex.MultiDexApplication"
      >

MainActivity.java

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        +MultiDex.install(this);

经过如上修改编译运行还是会报错:

java.lang.RuntimeException: Unable to instantiate application Multi dex Application

解决办法:
在build选项选择clean project,然后再重新rebuild project即可,在重新编译运行成功。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章