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

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