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即可,在重新編譯運行成功。