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版本的基础架构如下图所示:
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如何调用呢
- ILedService iLedService;
- iLedService = ILedService.Stub.asInterface(ServiceManager.getService(“led”));
- 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;
- ILedService iLedService;
- iLedService = ILedService.Stub.asInterface(ServiceManager.getService(“led”));
- 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即可,在重新编译运行成功。