Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> [Android的系統移植與平台開發]led HAL案例分析

[Android的系統移植與平台開發]led HAL案例分析

編輯:Android開發實例

通過前兩節HAL框架分析和JNI概述,我們對Android提供的Stub HAL有了比較詳細的了解了,下面我們來看下led的實例,寫驅動點亮led燈,就如同寫程序,學語言打印HelloWorld一樣,如果說打印HelloWorld是一門新語言使用的第一聲吆喝,那麼點亮led燈就是我們學習HAL的一座燈塔,指揮我們在後面的復雜的HAL代碼裡准確找到方向。
 

 

 LedHAL實例架構


 

 

 

上圖描述了我們Led實例的框架層次:

l  LedDemo.java:是我們寫的Android應用程序

l  LedService.java:是根據Led HAL封裝的Java框架層的API,主要用於向應用層提供框架層API,它屬於Android的框架層

l  libled_runtime.so:由於Java代碼不能訪問HAL層,該庫是LedService.java對應的本地代碼部分

l  led.default.so:針對led硬件的HAL代碼

LedDemo通過LedService提供的框架層API訪問Led設備,LedService對於LedDemo應用程序而言是Led設備的服務提供者,LedService運行在Dalvik中沒有辦法直接訪問Led硬件設備,它只能將具體的Led操作交給本地代碼來實現,通過JNI來調用Led硬件操作的封裝庫libled_runtime.so,由HAL Stub框架可知,在libled_runtime.so中首先查找注冊為led的硬件設備module,找到之後保存其操作接口指針在本地庫中等待框架層LedService調用。led.default.so是HAL層代碼,它是上層操作的具體實施者,它並不是一個動態庫(也就是說它並沒有被任何進程加載並鏈接),它只是在本地代碼查找硬件設備module時通過ldopen”殺雞取卵”找module,返回該硬件module對應的device操作結構體中封裝的函數指針。

其調用時序如下:


 

 

Led HAL實例代碼分析

 

我們來看下led實例的目錄結構:


 

 

主要文件如下:

com.hello.LedService.cpp:它在frameworks/services/jni目錄下,是的Led本地服務代碼

led.c:HAL代碼

led.h:HAL代碼頭文件

LedDemo.java:應用程序代碼

LedService.java:Led框架層服務代碼

在Android的源碼目錄下,框架層服務代碼應該放在frameworks/services/java/包名/目錄下,由Android的編譯系統統一編譯生成system/framework/services.jar文件,由於我們的測試代碼屬於廠商定制代碼,盡量不要放到frameworks的源碼樹裡,我將其和LedDemo應用程序放在一起了,雖然這種方式從Android框架層次上不標准。

另外,本地服務代碼的文件名要和對應的框架層Java代碼的名字匹配(包名+類文件名,包目錄用“_“代替)。有源碼目錄裡都有對應的一個Android.mk文件,它是Android編譯系統的指導文件,用來編譯目標module。

1)        Android.mk文件分析

先來看下led源碼中①號Android.mk:

include $(call all-subdir-makefiles)

代碼很簡單,表示包含當前目錄下所有的Android.mk文件

先來看下led_app目錄下的③號Android.mk:

# 調用宏my-dir,這個宏返回當前Android.mk文件所在的路徑

LOCAL_PATH:= $(call my-dir)                                    

# 包含CLEAR_VARS變量指向的mk文件build/core/clear_vars.mk,它主要用來清除編譯時依賴的編譯變量

include $(CLEAR_VARS)                                   

# 指定當前目標的TAG標簽,關於其作用見前面Android編譯系統章節

LOCAL_MODULE_TAGS := user

 

# 當前mk文件的編譯目標模塊

LOCAL_PACKAGE_NAME := LedDemo

 

# 編譯目標時依賴的源碼,它調用了一個宏all-java-files-under,該宏在build/core/definitions.mk中定義

# 表示在當前目錄下查找所有的java文件,將查找到的java文件返回

LOCAL_SRC_FILES := $(callall-java-files-under, src)

 

# 在編譯Android應用程序時都要指定API level,也就是當前程序的編譯平台版本

# 這裡表示使用當前源碼的版本

LOCAL_SDK_VERSION := current

# 最重要的就是這句代碼,它包含了一個文件build/core/package.mk,根據前面設置的編譯變量,編譯生成Android包文件,即:apk文件

include $(BUILD_PACKAGE)

上述代碼中都加了注釋,基本上每一個編譯目標都有類似上述的編譯變量的聲明:

LOCAL_MODULE_TAGS

LOCAL_PACKAGE_NAME

LOCAL_SRC_FILES

由於所有的Android.mk最終被編譯系統包含,所以在編譯每個目標模塊時,都要通過LOCAL_PATH:= $(call my-dir)指定當前目標的目錄,然後調用include $(CLEAR_VARS)先清除編譯系統依賴的重要的編譯變量,再生成新的編譯變量。

讓我們來看看LedDemo目標對應的源碼吧。

2)        LedDemo代碼分析

學習過Android應用的同學對其目錄結構很熟悉,LedDemo的源碼在src目錄下。

@ led_app/src/com/farsight/LedDemo.java:

package com.hello;

 import com.hello.LedService;

 import com.hello.R;

 importandroid.app.Activity;

 importandroid.os.Bundle;

 importandroid.util.Log;

 importandroid.view.View;

 import android.view.View.OnClickListener;

 importandroid.widget.Button;

 

 public classLedDemo extends Activity {

     privateLedService led_svc;

     private Buttonbtn;

     private booleaniflag = false;

     private Stringtitle;

 

     /** Calledwhen the activity is first created. */

     @Override

     public voidonCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        Log.i("Java App", "OnCreate");

         led_svc =new LedService();

         btn =(Button) this.findViewById(R.id.Button01);

        this.btn.setOnClickListener(new OnClickListener() {

            public void onClick(View v) {

                Log.i("Java App", "btnOnClicked");

                if (iflag) {

                    title = led_svc.set_off();

                    btn.setText("Turn On");

                    setTitle(title);

                    iflag = false;

                } else {

                     title = led_svc.set_on();

                    btn.setText("Turn Off");

                    setTitle(title);

                    iflag = true;

                }

             }

         });

     }

 }

代碼很簡單,Activity上有一個按鈕,當Activity初始化時創建LedService對象,按鈕按下時通過LedService對象調用其方法set_on()和set_off()。

 

3)        LedService代碼分析

我們來看下LedService的代碼:

@led_app/src/com/farsight/LedService.java:

package com.hello;

import android.util.Log;

 

public class LedService {

    /*

     * loadnative service.

     */

    static {         // 靜態初始化語言塊,僅在類被加載時被執行一次,通常用來加載庫

        Log.i ("Java Service" , "Load Native Serivce LIB" );

       System.loadLibrary ( "led_runtime" );

    }

    // 構造方法

    publicLedService() {     

        int icount ;

        Log.i ("Java Service" , "do init Native Call" );

        _init ();        

        icount =_get_count ();

        Log.d ("Java Service" , "led count = " + icount );

        Log.d ("Java Service" , "Init OK " );

    }

 

    /*

     * LED nativemethods.

     */

    public Stringset_on() {

        Log.i ("com.hello.LedService" , "LED On" );

        _set_on();

        return"led on" ;

     }

 

     publicString set_off() {

         Log.i ("com.hello.LedService" , "LED Off" );

         _set_off();

         return"led off" ;

     }

 

     /*

     * declareall the native interface.

     */

     privatestatic native boolean _init();

     privatestatic native int _set_on();

     privatestatic native int _set_off();

     privatestatic native int _get_count();

 

 }

通過分析上面代碼可知LedService的工作:

l   加載本地服務的庫代碼

l   在構造方法裡調用_init本地代碼,對Led進行初始化,並調用get_count得到Led燈的個數

l   為LedDemo應用程序提供兩個API:set_on和set_off,這兩個API方法實際上也是交給了本地服務代碼來操作的

由於Java代碼無法直接操作底層硬件,通過JNI方法將具體的操作交給本地底層代碼實現,自己只是一個API Provider,即:服務提供者。

讓我們來到底層本地代碼,先看下底層代碼的Android.mk文件:

@ frameworks/Android.mk:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

 

LOCAL_MODULE_TAGS := eng

LOCAL_MODULE:= libled_runtime                    # 編譯目標模塊

LOCAL_SRC_FILES:= \

       services/jni/com_farsight_LedService.cpp

 

LOCAL_SHARED_LIBRARIES := \                       # 編譯時依賴的動態庫

       libandroid_runtime \

       libnativehelper \

        libcutils\

        libutils\

       libhardware

 

LOCAL_C_INCLUDES += \                                  #編譯時用到的頭文件目錄

       $(JNI_H_INCLUDE)

 

LOCAL_PRELINK_MODULE := false                            # 本目標為非預鏈接模塊

include $(BUILD_SHARED_LIBRARY)                # 編譯生成共享動態庫

結合前面分析的Android.mk不難看懂這個mk文件。之前的mk文件是編譯成Android apk文件,這兒編譯成so共享庫,所以LOCAL_MODULE和include $(BUILD_SHARED_LIBRARY)與前面mk文件不同,關於Android.mk文件裡的變量作用,請查看Android編譯系統章節。

總而言之,本地代碼編譯生成的目標是libled_runtime.so文件。

 

4)        Led本地服務代碼分析

我們來看下本地服務的源碼:

@ frameworks/services/jni/com_hello_LedService.cpp:

#define LOG_TAG "LedService"

#include "utils/Log.h"

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <assert.h>

#include <jni.h>

#include "../../../hardware/led.h"

 

static led_control_device_t *sLedDevice = 0;

static led_module_t* sLedModule=0;

 

static jint get_count(void)

{

    LOGI("%sE", __func__);

   if(sLedDevice)

        returnsLedDevice->get_led_count(sLedDevice);

    else

       LOGI("sLedDevice is null");

    return 0;

}

 

static jint led_setOn(JNIEnv* env, jobject thiz) {

    LOGI("%sE", __func__);

    if(sLedDevice) {

       sLedDevice->set_on(sLedDevice);

    }else{

       LOGI("sLedDevice is null");

     }

     return 0;

 }

 

 static jintled_setOff(JNIEnv* env, jobject thiz) {

    LOGI("%s E", __func__);

     if(sLedDevice) {

        sLedDevice->set_off(sLedDevice);

     }else{

         LOGI("sLedDevice is null");

     }

     return 0;

 }

 

static inline int led_control_open(const structhw_module_t* module,

     structled_control_device_t** device) {

    LOGI("%s E ", __func__);

     returnmodule->methods->open(module,

        LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);

 }

 

 static jintled_init(JNIEnv *env, jclass clazz)

 {

     led_module_tconst * module;

    LOGI("%s E ", __func__);

     if(hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0){

        LOGI("get Module OK");

        sLedModule = (led_module_t *) module;

         if(led_control_open(&module->common, &sLedDevice) != 0) {

            LOGI("led_init error");

             return-1;

         }

     }

 

    LOGI("led_init success");

     return 0;

 }

 

 /*

  *

  * Array ofmethods.

  * Each entryhas three fields: the name of the method, the method

  * signature,and a pointer to the native implementation.

  */

 static constJNINativeMethod gMethods[] = {

    {"_init",    "()Z",(void*)led_init},

     {"_set_on",   "()I",(void*)led_setOn },

     {"_set_off", "()I",(void*)led_setOff },

     {"_get_count", "()I",(void*)get_count },

 };

 

 static intregisterMethods(JNIEnv* env) {

     static constchar* const kClassName = "com/hello/LedService";

     jclassclazz;

     /* look upthe class */

     clazz =env->FindClass(kClassName);

     if (clazz ==NULL) {

        LOGE("Can't find class %s\n", kClassName);

         return-1;

     }

 

     /* registerall the methods */

     if(env->RegisterNatives(clazz, gMethods,

            sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)

     {

        LOGE("Failed registering methods for %s\n", kClassName);

         return -1;

     }

     /* fill outthe rest of the ID cache */

     return 0;

 }

 

 /*

  * This iscalled by the VM when the shared library is first loaded.

  */

 jintJNI_OnLoad(JavaVM* vm, void* reserved) {

     JNIEnv* env= NULL;

     jint result= -1;

    LOGI("JNI_OnLoad");

     if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {

        LOGE("ERROR: GetEnv failed\n");

         gotofail;

     }

     assert(env!= NULL);

     if(registerMethods(env) != 0) {

         LOGE("ERROR: PlatformLibrary nativeregistration failed\n");

         gotofail;

     }

     /* success-- return valid version number */

     result =JNI_VERSION_1_4;

 

 fail:

     returnresult;

 }

         這兒的代碼不太容易讀,因為裡面是JNI的類型和JNI特性的代碼,看代碼先找入口。LedService.java框架代碼一加載就調用靜態初始化語句塊裡的System.loadLibrary ( "led_runtime" ),加載libled_runtime.so,該庫剛好是前面Android.mk文件的目標文件,也就是說LedService加載的庫就是由上面的本地代碼生成的。當一個動態庫被Dalvik加載時,首先在Dalvik會回調該庫代碼裡的JNI_OnLoad函數。也就是說JNI_OnLoad就是本地服務代碼的入口函數。

         JNI_OnLoad的代碼一般來說是死的,使用的時候直接拷貝過來即可,vm->GetEnv會返回JNIEnv指針,而這個指針其實就是Java虛擬機的環境變量,我們可以通過該指針去調用JNI提供的方法,如FindClass等,調用registerMethods方法,在方法裡通過JNIEnv的FindClass查找LedService類的引用,然後在該類中注冊本地方法與Java方法的映射關系,上層Java代碼可以通過這個映射關系調用到本地代碼的實現。RegisterNatives方法接收三個參數:

l  第一個參數jclass:要注冊哪個類裡的本地方法映射關系

l  第二個參數JNINativeMethod*:這是一個本地方法與Java方法映射數組,JNINativeMethod是個結構體,每個元素是一個Java方法到本地方法的映射。

typedef struct {

         constchar* name;

         constchar* signature;

         void*fnPtr;

} JNINativeMethod;

         name:表示Java方法名

         signature:表示方法的簽名

         fnPtr:Java方法對應的本地方法指針

l  第三個參數size:映射關系個數

由代碼可知,Java方法與本地方法的映射關系如下:

Java方法

本地方法

void _init()

jint led_init(JNIEnv *env, jclass clazz)

int _set_on()

jint led_setOn(JNIEnv* env, jobject thiz)

int _set_off()

jint led_setOff(JNIEnv* env, jobject thiz)

int _get_count()

jint get_count(void)

通過上表可知,本地方法參數中默認會有兩個參數:JNIEnv* env, jobject thiz,分別表示JNI環境和調用當前方法的對象引用,當然你也可以不設置這兩個參數,在這種情況下你就不能訪問Java環境中的成員。本地方法與Java方法的簽名必須一致,返回值不一致不會造成錯誤。

現在我們再來回顧下我們的調用調用流程:

l  LedDemo創建了LedService對象

l  LedService類加載時加載了對應的本地服務庫,在本地服務庫裡Dalvik自動調用JNI_OnLoad函數,注冊Java方法和本地方法映射關系。

根據Java語言特點,當LedDemo對象創建時會調用其構造方法LedService()。

// 構造方法

    publicLedService() {     

        int icount ;

        Log.i ("Java Service" , "do init Native Call" );

        _init ();        

        icount =_get_count ();

        Log.d ("Java Service" , "led count = " + icount );

        Log.d ("Java Service" , "Init OK " );

    }

在LedService構造方法裡直接調用了本地方法_init和_get_count(通過native保留字聲明),也就是說調用了本地服務代碼裡的jint led_init(JNIEnv *env, jclass clazz)和jintget_count(void)。

在led_init方法裡的內容就是我們前面分析HAL框架代碼的使用規則了。

l  通過hw_get_module方法查到到注冊為LED_HARDWARE_MODULE_ID,即:”led”的module模塊。

l  通過與led_module關聯的open函數指針打開led設備,返回其device_t結構體,保存在本地代碼中,有的朋友可能會問,不是本地方法不能持續保存一個引用嗎?由於device_t結構是在open設備時通過malloc分配的,只要當前進程不死,該指針一直可用,在這兒本地代碼並沒有保存Dalvik裡的引用,保存的是mallco的分配空間地址,但是在關閉設備時記得要將該地址空間free了,否則就內存洩漏了。

l  拿到了led設備的device_t結構之後,當LedDemo上的按鈕按下時調用LedService對象的set_on和set_off方法,這兩個LedService方法直接調用了本地服務代碼的對應映射方法,本地方法直接調用使用device_t指向的函數來間接調用驅動操作代碼。

好吧,讓我們再來看一個詳細的時序圖:
 


 

 

不用多解釋了。

最後一個文件,HAL對應的Android.mk文件:

@ hardware/Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_C_INCLUDES += \

         include/

LOCAL_PRELINK_MODULE := false

LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw

LOCAL_SHARED_LIBRARIES := liblog

LOCAL_SRC_FILES := led.c

LOCAL_MODULE := led.default

include $(BUILD_SHARED_LIBRARY)

注:LOCAL_PRELINK_MODULE:= false要加上,否則編譯出錯

指定目標名為:led.default

目標輸入目錄LOCAL_MODULE_PATH為:/system/lib/hw/,不指定會默認輸出到/system/lib目錄下。

根據前面HAL框架分析可知,HAL Stub庫默認加載地址為:/vendor/lib/hw/或/system/lib/hw/,在這兩個目錄查找:硬件id名.default.so,所以我們這兒指定了HAL Stub的編譯目標名為led.default,編譯成動態庫,輸出目錄為:$(TARGET_OUT_SHARED_LIBRARIES)/hw,TARGET_OUT_SHARED_LIBRARIES指/system/lib/目錄。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved