通过前面的学习,我们知道怎么通过andriod硬件访问服务控制硬件,再次贴出框图如下 其中红色圈出部分我们已经完成,我们知道在andriod系统中,java程序是无法直接访问硬件的,他访问硬件需要service_manager.c发出请求,才能操控硬件,之前我们通过com_android_server_LedService.cpp直接访问硬件,这种方法一般不被推荐,主要有以下几点
1.单我们需要修改对硬件的操作C函数时,必须修改com_android_server_LedService.cpp,每次都要编译整个framework重新生成system.img。
2.linux是遵循GPL协议的,则会公开你的com_android_server_xxxService.cpp文件,倒是很多厂家并不想公开自己对硬件操作的代码。
那么为了解决以上问题,我们可以让com_android_server_xxxService.cpp文件。仅仅注册本地C函数,对硬件的操作我们把他封装成.so文件导入工程。这样我们以上的两个问题就得以解决了,需要修改对硬件的操作函数时,我们只需要修改.生成.so的文件即可。
源码解读既然我们的要把对硬件操作部分分离出来,那么 1.com_android_server_xxxService.cpp:成了一个纯粹的JIN文件,主要有两个作用,向上提供本地C函数,向下加载hal文件(Hardware Abstraction Layer:硬件抽象层)并且调用HAL函数 2.HAL(Hardware Abstraction Layer:硬件抽象层):负责访问驱动程序,执行硬件操作。 在android中,com_android_server_xxxService.cpp加载hal文件,并不是直接声明函数,指定库就可以了,他内部有一套封装的方法。即调用dlopen,那么他是怎么调用dlopen的呢? 调用过程如下
hw_get_module("led")
//1.模块名==>文件名
hw_get_module_by_class("led",NULL)
name = "led"
property_get //获取属性值
hw_module_exists //判断是否存在led.xxx.so
//2.加载
load
dlopen(filename)
dlsym("HMI")//从so文件中获取HMI的hw_module_t结构体
strcmp(id,hmi->id)//判断名字是否一致(hmi->id,"led")
上述是一个大概的过程,现在进行详细讲解,我们先在源码中查看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);
}
传入了两个参数,module为我们自己定义的hw_module_t **类型对象,调用hw_get_module_by_class,其中参数id为“led”,他可以看到其中多次调用hw_module_exists函数,他主要用来判断开发板以下3个路径下面是否存在led.xxx.so文件
1.HAL_LIBRARY_PATH 环境变量
2./vendor/lib/hw
3./system/lib/hw
那么led.xxx.so中的xxx具体是什么内容?我们查看hw_get_module_by_class中的property_get函数如下:
if (property_get(variant_keys[i], prop, NULL) == 0)
其中传入的参数variant_keys如下所示
static const char *variant_keys[] = {
"ro.hardware", /* This goes first so that it can pick up a different
file on the emulator. */
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
我们在开板上分别输入
rk3399_all:/ $ getprop ro.hardware
rk30board
rk3399_all:/ $ getprop ro.product.board
rk30sdk
rk3399_all:/ $ getprop ro.board.platform"
rk3399
rk3399_all:/ $ getprop ro.arch
rk3399_all:/ $
故此,他查找的分别为led.rk30board.so, led.rk30sdk .so,led.rk30sdk .so,led…so文件,如果都没有查找到,则查找led.default .so文件。那么他为什么是在 1.HAL_LIBRARY_PATH 环境变量2./vendor/lib/hw 3./system/lib/hw 下查找呢?因为在hw_module_exists函数中,有如下代码
char *hal_library_path = getenv("HAL_LIBRARY_PATH");
if (hal_library_path) {
---------------------------------------------------
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH2, name, subname);
if (access(path, R_OK) == 0)
---------------------------------------------------
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH1, name, subname);
然后调用
load
dlopen(filename)
dlsym("HMI")//从so文件中获取HMI的hw_module_t结构体
strcmp(id,hmi->id)//判断名字是否一致(hmi->id,"led")
其中hw_module_t结构体会赋值给hw_get_module()传入的第二个参数。
总结:我们调用hw_get_module()函数,传入“led”,会获得一个和led相关的hw_module_t结构体,这个结构体在后续中,会多次使用。
JNF使用HAL前面做了那么多的讲解,都是为我们的JNF使用HAL做铺垫,那么我们的JNF应该怎么使用HAL呢? 我们参考源代码com_android_server_lights_LightsService文件,在 init_native函数中,先调用hw_get_module()获得一个hw_module_t module结构体,然后把module当作实参传入到get_device,我们可以发现get_device()被多次调用,说明一个moudle可能支持多个device。get_device()实现如下(参考)
static light_device_t* get_device(hw_module_t* module, char const* name)
{
int err;
hw_device_t* device;
err = module->methods->open(module, name, &device);
if (err == 0) {
return (light_device_t*)device;
} else {
return NULL;
}
}
可以知get_device()函数中,调用传入参数module中的方法,获得一个hw_device_t结构体(前面提到过hw_module_t中应该包括多个hw_device_t),并且强制装换为light_device_t(设备自定义的结构体)类型返回,那么他为什么可以直接这样强制装换呢?我们再源码中查看light_device_t是怎么定义的,在/sdk/hardware/libhardware/include/hardware/ lights.h中可以看到如下定义:
struct light_device_t {
struct hw_device_t common;
int (*set_light)(struct light_device_t* dev,
struct light_state_t const* state);
};
可以看到,最前面包含了struct hw_device_t ,所有能强制转化为light_device_t。
HAL实现根据前面的分析,com_android_server_xxxService文件中,需要一个hw_module_t module结构体,并且调用module->methods->open(module, name, &device)。那我们编写HAL需要实现两点
1.实现一个名为HMI(HAL_MODULE_INFO_SYM:一个宏)的hw_module_t结构体
2.实现一个open函数,他会根据反回name一个设备自定义的结构体,这个设备自定义的结构体,第一个成员是hw_device_t结构体
还可以定义一些设备相关的成员,比如一些操控硬件函数。
下面我们开始编写led_hal.c文件,代码如下(参考hardware/libhardware/modules/vivrator/vivrator.c):
#define LOG_TAG "LedHal"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int fd;
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_drv", O_RDWR);
ALOGE("ledOpen : %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, which, status);
ALOGE("ledCtrl : %d, %d, %d", which, status, ret);
return ret;
}
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 hw_module_t* module, const char* id,
hw_device_t** device)
{
*device = &led_dev;
return 0;
}
static struct hw_module_methods_t led_module_methods = {
/*璇ュ嚱鏁帮紝com_android_server_LedService.cpp鏂囦欢涓皟鐢?
hw_get_module鏃朵細琚皟鐢紝鍏朵富瑕佺洰鐨勬槸杩斿洖鎴戜滑鑷畾涔夌殑
缁撴瀯浣擄紝渚汮NI鎿嶄綔*/
.open = led_device_open,
};
//浣跨敤HAL_MODULE_INFO_SYM瀹氫箟涓€涓粨鏋勪綋锛圚MI锛?
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.id = "led",
.methods = &led_module_methods,
};
led_hal.h
#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H
#include
#include
#include
#include
__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
除此之外我们还需修改com_android_server_LedService.cpp如下:
#define LOG_TAG "LedService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace android
{
static led_device_t* led_device;
jint ledOpen(JNIEnv *env, jobject cls)
{
int err;
hw_module_t* module;
hw_device_t* device;
/*1. hw_get_module*/
err = hw_get_module("led", (hw_module_t const**)&module);
ALOGE("native ledOpen");
if(err == 0){
/*2. get device: module->methods->open*/
err = module->methods->open(module, NULL, &device);
if (err == 0) {
led_device = (led_device_t*)device;
/*3. call led_open*/
return led_device->led_open(led_device);
} else {
return -1;
}
}
return -1;
}
void ledClose(JNIEnv *env, jobject cls)
{
ALOGE("native ledClose");
}
jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
{
ALOGE("native ledCtrl : %d, %d", which, status);
return led_device->led_ctrl(led_device, which, status);
}
static const JNINativeMethod method_table[] = {
{"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",
method_table, NELEM(method_table));
}
};
该文件主要通过hw_get_module获得与led对应的module,再通过module->methods->open调用获得我们自定义的结构体struct led_device_t.
系统编译编写完以上代码之后,我们需要编译 进系统
1. JNI上传
SDK/frameworks/base/services/core/jni/com_android_server_LedService.cpp
2. HAL上传
hardware/libhardware/include/hardware/led_hal.h
hardware/libhardware/modules/led/led_hal.c
hardware/libhardware/modules/led/Android.mk
其中没有的目录和文件就自己创建,Android.mk的内容如下
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := led.default
# HAL module implementation stored in
# hw/.default.so
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := led_hal.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := userdebug
include $(BUILD_SHARED_LIBRARY)
该文档参考hardware/libhardware/modules/virator/Android.mk 编写,其中LOCAL_MODULE_TAGS := userdebug是因为我们在编译源码的时候选择的是lunch rk3399_all-userdebug。 然后执行编译指令 source build/envsetup.sh lunch rk3399_all-userdebug mmm frameworks/base/services/ mmm hardware/libhardware/modules/led/ make snod -j3 生成system.img烧写到开发板 运行APP我们局可以操控led了
打印简介1.在android中有三类打印信息:app,system,radio,程序里使用ALOGx,SLOGx,RLOGx来打印
2.其中x代表打印级别,有如下可选:
V Verbose
D Debug
W Info
E Warn
F Fatal
比如:
#define LOG_TAG "LedHal"
ALOGI("led_open" :%d",fd);
3 打印出来的格式如下
I/Led (1987):led_open :65
(级别)LOG_TAG 进程号 打印信息
4.那我们怎么查看打印信息呢?
可以在串口终端上执行logcat命令
筛选想要的信息:
1. logcat | grep “LedHal”
2. logcat LedHal:I *:s(该处死两层过滤,可以过滤最多层)
编译优化
我们APP需要导入自己编译生成classes.jar包,该文件接近16M左右,编译生成的app/build/outpus/apk/debug/app-arm64-v8a-debug.apk也达到了8.8M,仅仅是点亮LED却占用了这么大的空间,当然并不是我们想要的结果,那么我们怎么对其进行优化呢?
打开AS工程,选择File->Projiect Structure 点击app->Dpendencies,选择classes.jar为compile模式
然后重新编译,我么可以看到此次APK大约1M左右。
本小节我们讲解了一下几点:
- 参考源码com_android_server_lights_LightsService,模仿使用hw_get_module与module->methods->open实现JNI
- 参考hardware/libhardware/modules/vivrator/vivrator.c文件编写led_hal.c
- 打印调试信息相关了解
下小节将修改APP,使用反射的方式操控硬件。