目錄
基本環境
- 開發板:hikey960
- 代碼:aosp,Android R
- 開發環境:64bit ubuntu 16.04,
一、build
添加新的lunch選項(新產品):賦值COMMON_LUNCH_CHOICES
,PRODUCT_MAKEFILES
如下 (參考device/sample/products/AndroidProducts.mk
,實例參考device/linaro/hikey/AndroidProducts.mk
)
PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/sample_addon.mk
COMMON_LUNCH_CHOICES := sample_addon-userdebug
二、編寫Android.mk
Android.mk模板如下所示:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
hello.c
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := hello_elf_arm64
include $(BUILD_EXECUTABLE)
- 編譯報unused parameters錯時,需加上:
LOCAL_CFLAGS := -Wno-unused-parameter
以忽略掉該報錯。
三、LOG系統
- log系統的設備節點:
hikey960:/ # ls /dev/socket/log* -l
srw-rw-rw- 1 logd logd 0 1970-01-01 00:00 /dev/socket/logd
srw-rw-rw- 1 logd logd 0 1970-01-01 00:00 /dev/socket/logdr
s-w--w--w- 1 logd logd 0 1970-01-01 00:00 /dev/socket/logdw
-
轉載來自文章《Android Log系統介紹 (基於Android N)》的log系統核心圖如下所示,更多細節請參考此文。
圖1 Android log系統 -
Android Q、R上面log的緩衝區有:main,system,radio,events,crash
-
c/c++中打印調試信息:
① 宏定義標籤及添加頭文件:
#define LOG_TAG "hello"
#include <log/log.h>
② Android.mk中聲明所依賴的動態庫:
LOCAL_SHARED_LIBRARIES := \
liblog
③ 使用宏ALOGV()
,ALOGD(“xxx”)
,ALOGI()
,ALOGW()
,ALOGE()
,進行打印log -
java中打印log信息:
① 導入包:import android.util.Log;
② 在類內部定義標籤:private final String TAG = "listview";
③ 使用方法Log.d(TAG, "xxx");
,Log.i(TAG, "xxx");
,Log.w(TAG, "xxx");
,Log.e(TAG, "xxx");
進行打印
四、init.rc
參考system/core/init/init.cpp
中如下代碼,可知/init
祖先進程通過解析/init.rc
的內容來進行啓動各種本地服務
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
Parser parser = CreateParser(action_manager, service_list);
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");
- android init語言說明文檔:
system/core/init/README.md
- 在廠商
.rc
中添加規則,實例:device/linaro/hikey/init.common.rc
五、JNI
以控制LED燈爲背景,進行JNI編程技術的介紹。aosp中JNI技術參考代碼:development/samples/SimpleJNI/
1. java調用jni接口
將native接口的聲明組織到LedControl\app\src\main\java\com\example\lowlevel\LedNative.java
package com.example.lowlevel;
public class LedNative {
/* 1) 加載jni動態庫 */
static {
System.loadLibrary("led_jni");
}
/* 2) 使用關鍵字native聲明jni接口 */
public native int openDev();
public native int closeDev();
public native int devOn();
public native int devOff();
}
然後在class MainActivity
中進行調用:
/* 3) 使用上面定義的LedNative類生成對象後調用接口 */
LedNative ledNative = new LedNative();
ledNative.devOn();
- 注意:普通app沒有權限加載jni動態庫依賴的庫,需將apk預置或傳入到手機的
/system/app/
下作爲system_app
2. aosp中開發native代碼
① 定義JAVA和C/C++間的映射表:
static JNINativeMethod ledMethod[] = {
{"openDev", "()I", (void*)openLed},
{"closeDev", "()I", (void*)closeLed},
{"devOn", "()I", (void*)ledOn},
{"devOff", "()I", (void*)ledOff},
};
② 通過定義jni層的回調函數jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
,來將上述映射表註冊到java虛擬機:
static const char *classPathName = "com/example/lowlevel/LedNative";
jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
{
jint ret;
JNIEnv* env = NULL;
ALOGD("------%s", __FUNCTION__);
ret = vm->GetEnv((void**)&env, JNI_VERSION_1_4);
if (ret != JNI_OK) {
ALOGE("ERROR: GetEnv failed");
return -1;
}
jclass cls = env->FindClass(classPathName);
if (cls == NULL) {
ALOGE("Native registration unable to find class '%s'", classPathName);
return JNI_FALSE;
}
ret = env->RegisterNatives(cls, ledMethod, sizeof(ledMethod)/sizeof(ledMethod[0]));
if (ret < 0) {
ALOGE("RegisterNatives failed for '%s'", classPathName);
return JNI_FALSE;
}
return JNI_VERSION_1_4;
}
③ Android.mk
中指定生成的是動態庫和編譯工具分別如下:
LOCAL_MODULE:= libled_jni
include $(BUILD_SHARED_LIBRARY)
- 注意:測試時,需要修改設備節點權限:
# chmod 0666 /sys/devices/platform/leds/leds/user_led3/brightness
,以及關閉selinux:# setenforce 0
3. Android Studio中開發native代碼
參考我的另一篇文章有詳細開發步驟:Android Studio開發NDK代碼。
六、傳統HAL
本章介紹開發傳統HAL代碼和調用HAL的主要流程,如下出現的代碼場景是控制LED燈。
1. 接口聲明
開發HAL在頭文件,定義相應硬件模塊和設備結構,以繼承基礎模塊結構(struct hw_module_t
)和基礎設備結構(struct hw_device_t
)。另外還有暴露接口給調用者的目的。具體開發流程如下所示:
#ifndef __LED_HAL_H__
#define __LED_HAL_H__
#include <hardware/hardware.h>
__BEGIN_DECLS
/* 1) 定義生成的動態庫前綴的名稱, xxx.default.so */
#define LED_HARDWARE_MODULE_ID "led_hal"
/* 2) 定義專有硬件模塊,繼承 hw_module_t,並擴展要暴露的模塊控制接口 */
typedef struct led_module {
struct hw_module_t common;
} led_module_t;
/* 3) 定義專有硬件硬件,繼承 hw_device_t */
typedef struct led_device {
struct hw_device_t common;
/* 4) 擴展要暴露出來的設備控制接口 */
int (*control)(int enable);
} led_device_t;
/* 5) 用來調用 hw_module_t.methods->open() */
static inline int led_hal_open(const struct hw_module_t* module,
led_device_t** device) {
return module->methods->open(module, NULL,
TO_HW_DEVICE_T_OPEN(device));
}
/* 6) 用來調用 hw_device_t.close() */
static inline int led_hal_close(led_device_t* device) {
return device->common.close(&device->common);
}
__END_DECLS
#endif
2. 接口實現
① 實例化自己派生的專有硬件模塊結構led_module_t
;
led_module_t HAL_MODULE_INFO_SYM = { /* 1.實例名必須爲 HAL_MODULE_INFO_SYM */
.common = {
.tag = HARDWARE_MODULE_TAG, /* 2.基礎結構標籤必爲 HARDWARE_MODULE_TAG */
.module_api_version = HARDWARE_MODULE_API_VERSION(1,0), /* 3.模塊版本爲1.0 */
.hal_api_version = HARDWARE_HAL_API_VERSION, /* 4.固定 */
.id = LED_HARDWARE_MODULE_ID, /* 5.綁定最後生成的動態庫名:[id].default.so */
.name = "Hikey960 led HAl",
.author = "[email protected]",
.methods = &led_module_methods, /* 6.指定激活模塊下設備的方法,下文中介紹 */
},
};
② 實例化struct hw_module_methods_t
,該結構下成員只有open
;
struct hw_module_methods_t led_module_methods = {
.open = led_module_open, /* 在下文中定義 */
};
③ 定義hw_module_methods_t.open()
,它的任務是實例化struct hw_device_t
的派生結構體led_device_t
;
int led_module_open(const struct hw_module_t* module, const char* id,
struct hw_device_t** device)
{
led_device_t* led_dev = (led_device_t*)calloc(1, sizeof(led_device_t)); /* 1.申請內存實例化派生結構 */
led_dev->common.tag = HARDWARE_DEVICE_TAG; /* 2.HAL設備標籤,值固定 */
led_dev->common.version = HARDWARE_DEVICE_API_VERSION(1, 0);
led_dev->common.module = (struct hw_module_t*)module;
led_dev->common.close = led_device_close; /* 2.close函數與本函數任務相反,用於釋放相關資源 */
led_dev->control = led_control; /* 3.對接自定義的接口 */
*device = (hw_device_t*) led_dev; /* 4.通過參數返回實例化後設備指針,HAL調用者通過此指針調用設備的控制接口 */
return 0;
};
④ 定義hw_device_t.close()
,HAL調用者調用此函數用於關閉設備和釋放hw_module_methods_t.open()
中申請的資源;
int led_device_close(struct hw_device_t* device)
{
led_device_t* priv = (led_device_t*) device;
if (priv)
free(priv);
return 0;
}
⑤ 實現暴露給HAL調用者的接口,本場景要實現int led_control(int enable)
;
3. 添加編譯規則
① 動態庫名應爲[id].default.so
,所以LOCAL_MODULE:= led_hal.default
;
② HAL動態庫應部署到/system/lib64/hw/
和/system/lib/hw/
下,需加上規則LOCAL_MODULE_RELATIVE_PATH := hw
;
③ 導出暴露給調用者的接口頭文件。部署到system
分區的HAL庫,使用LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
;部署到vendor
分區的HAL庫,應使用LOCAL_HEADER_LIBRARIES := libhardware_headers
。
- 若要HAL動態庫部署到
/vendor/lib64/hw/
和/vendor/lib/hw/
,還需加上規則LOCAL_PROPRIETARY_MODULE := true
HAL參考代碼:hardware/libhardware/modules/gralloc
4. 使用HAL的接口
① 加載硬件的HAL模塊,使用 int hw_get_module(const char *id, const struct hw_module_t **module)
;
id
——HAL動態庫名字的前綴,本場景值爲led_hal
;module
——通過此參數獲得hw_module_t
實例
#include <hardware/hardware.h>
#include <led_hal.h>
static led_module_t* pModule = NULL; /* 全局變量 */
int ret = hw_get_module(LED_HARDWARE_MODULE_ID, (const struct hw_module_t **)&pModule);
if (ret != 0) {
ALOGE("get hardware module '%s' failed", LED_HARDWARE_MODULE_ID);
return -1;
}
② 通過hw_module_t
的派生結構體led_module_t
,獲取hw_device_t
的派生結構體led_device_t
,方法如下所示。但爲了方便調用者,HAL接口頭文件一般會將下面的代碼實現到內聯函數中,如調用前文的led_hal_open((const struct hw_module_t*)pModule, &pDevice);
;
static led_device_t* pDevice = NULL;
pModule->common.methods->open((const struct hw_module_t *)pModule, NULL, (struct hw_device_t**)&pDevice);
③使用HAL接口進行硬件控制的形式:pDevice->foo(xxx);
;如本場景使用:pDevice->control(1);
來點亮LED;
④ 在需要關閉設備和釋放有關內存資源時,需調用hw_device_t.close()
,所以調用方法如下所示。但爲了方便調用者,HAL接口頭文件一般會將下面的代碼實現到內聯函數中,如調用前文的led_hal_close(pDevice);
;
pDevice->common.close((struct hw_device_t*)pDevice);
- 注:調用HAL動態庫的代碼,需要鏈接動態庫
libhardware
,故Android.mk
有LOCAL_SHARED_LIBRARIES := libhardware
。
aosp中調用HAL的參考代碼:frameworks/native/services/surfaceflinger/tests/hwc2/Hwc2Test.cpp
參考文獻
[1] moasm. Android Log系統介紹 (基於Android N)[EB/OL].簡書,2019