編輯:關於Android編程
前面兩篇博客記錄了實現Linux驅動和使用HAL層訪問Linux驅動的代碼,我們分別對這兩部分做了測試,他們都正常工作。有了前面的基礎,我們就可以實現service層了,我們想系統注冊我們自己的service,在service中訪問HAL層,在HAL層中訪問linux驅動…當然,我們要在應用程序中訪問service,這要留到下一節來實現。
應用程序訪問service設計到了進程間的通信,這要求我們使用(Android Interface definition language)來描述我們的service提供的接口,然後應用程序就可以通過binder來訪問service 了。所以,我們先從aidl開始,逐步搭建我們的service層。
首先在frameworks/base/core/java/android/os/目錄下新建HelloTestService.aidl文件,用來描述我們的service提供的接口:
package android.os; interface IHelloTestService { int wirteString(String str); String readString(); }
我們的service只提供兩個方法,一個寫字符串,另一個讀字符串。寫好aidl文件後,執行mmm framework/base進行編譯,編譯後會在out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/目錄下生成IHelloTestService.java文件。
IHelloTestService.java文件中實現了使用binder通信的一些方法,同時還包含了我們在接口中定義的兩個方法。接下來,我們需要實現這兩個方法。
在framework/base/service/core/java/com/android/server/下新建HelloTestService.java文件,也就是創建HelloTestService類,這個類繼承了IHelloTestService中的Stub內部類,我們需要復寫其中的我們在aidl中定義的兩個方法。
package com.android.server; import android.content.Context; import android.os.IHelloTestService; import android.util.Slog; public class HelloTestService extends IHelloTestService.Stub { private static final String TAG = "HelloTestService"; HelloTestService() { init_native(); } public int wirteString(java.lang.String str){ return wirteString_native(str); } public java.lang.String readString() { return readString_native(); } private static native boolean init_native(); private static native int wirteString_native(String str); private static native String readString_native(); };
這個類中是java中的類,java是不能直接訪問C/C++代碼的,必須使用jni來實現。因此,我們在這裡簡單調用jni中對應的方法即可。
在java對應native代碼中,就可以使用C/C++來訪問C/C++代碼了,回想下我們在上節測試hal層代碼時寫的測試代碼,這部分代碼將與之類似,只不過,因為這部分代碼需要供java層調用,所以它需要遵尋固定的格式。
jni層代碼如下:
#define LOG_TAG "HelloTestService" #include "JNIHelp.h" #include "jni.h" #include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { //jstring to char* char* jstringTostring(JNIEnv* env, jstring jstr); //char* to jstring jstring stoJstring(JNIEnv* env, const char* pat); /*在硬件抽象層中定義的硬件訪問結構體,參考 */ struct hellotest_device_t* device = NULL; /*通過硬件抽象層定義的硬件訪問接口設置硬件寄存器val的值*/ static jstring hellotest_readString(JNIEnv* env, jobject clazz) { if(!device) { ALOGI("HelloTest JNI: device is not open."); return NULL; } char read_str[10]; device->read_string(device, (char **)&read_str); ALOGI("HelloTest JNI: read string %s from hellotest device.", read_str); return stoJstring(env,read_str); } /*通過硬件抽象層定義的硬件訪問接口讀取硬件寄存器val的值*/ static jint hellotest_writeString(JNIEnv* env, jobject clazz,jstring str) { if(!device) { ALOGI("HelloTest JNI: device is not open."); return -1; } char * local_str = jstringTostring(env,str); device->write_string(device, local_str); ALOGI("HelloTest JNI: write string %s to hellotest device.", local_str); return sizeof(local_str); } /*通過硬件抽象層定義的硬件模塊打開接口打開硬件設備*/ static inline int hellotest_device_open(const hw_module_t* module, struct hellotest_device_t** device) { return module->methods->open(module, HELLOTEST_HARDWARE_MODULE_ID, (struct hw_device_t**)device); } /*通過硬件模塊ID來加載指定的硬件抽象層模塊並打開硬件*/ static jboolean hellotest_init(JNIEnv* env, jclass clazz) { hellotest_module_t* module; ALOGI("HelloTest JNI: initializing......"); if(hw_get_module(HELLOTEST_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) { ALOGI("HelloTest JNI: hello Stub found."); if(hellotest_device_open(&(module->common), &device) == 0) { ALOGI("HelloTest JNI: hello device is open."); return 0; } ALOGI("HelloTest JNI: failed to open hello device."); return -1; } ALOGI("HelloTest JNI: failed to get hello stub module."); return -1; } /*JNI方法表*/ static const JNINativeMethod method_table[] = { {"init_native", "()Z", (void*)hellotest_init}, {"readString_native", "()Ljava/lang/String;", (void*)hellotest_readString}, {"wirteString_native", "(Ljava/lang/String;)I", (void*)hellotest_writeString}, }; /*注冊JNI方法*/ int register_android_server_HelloTestService(JNIEnv *env) { ALOGI("SystemServer :register_android_server_HelloTestService."); ALOGI("SystemServer :register_android_server_HelloTestService."); ALOGI("SystemServer :register_android_server_HelloTestService."); ALOGI("SystemServer :register_android_server_HelloTestService."); ALOGI("SystemServer :register_android_server_HelloTestService."); return jniRegisterNativeMethods(env, "com/android/server/HelloTestService", method_table, NELEM(method_table)); } //jstring to char* char* jstringTostring(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = env->FindClass("java/lang/String"); jstring strencode = env->NewStringUTF("utf-8"); jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode); jsize alen = env->GetArrayLength(barr); jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); if (alen > 0) { rtn = (char*)malloc(alen + 1); memcpy(rtn, ba, alen); rtn[alen] = 0; } env->ReleaseByteArrayElements(barr, ba, 0); return rtn; } //char* to jstring jstring stoJstring(JNIEnv* env, const char* pat) { jclass strClass = env->FindClass("java/lang/String"); jmethodID ctorID = env->GetMethodID(strClass, " ", "([BLjava/lang/String;)V"); jbyteArray bytes = env->NewByteArray(strlen(pat)); env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat); jstring encoding = env->NewStringUTF("utf-8"); return (jstring)env->NewObject(strClass, ctorID, bytes, encoding); } };
這部分代碼中,我們把java層傳下來的字符串轉換為c++的char *字符數組,然後通過HAL層把它寫入linux驅動。然後把從linux驅動中讀出來的字符串轉換為java層的字符串並返回給java層。
修改當前目錄下的Android.mk:
添加一行:
$(LOCAL_REL_DIR)/com_android_server_HelloTestService.cpp \
這樣就可以編譯我們的native代碼了,此時可以嘗試編譯並修改錯誤。
我們先要在onload.cpp中添加聲明:
int register_android_server_tv_TvInputHal(JNIEnv* env); int register_android_server_PersistentDataBlockService(JNIEnv* env); int register_android_server_Watchdog(JNIEnv* env); int register_android_server_HelloTestService(JNIEnv* env);
然後再onload.cpp中調用register_android_server_HelloTestService方法向java層的HelloTestService類注冊我們的native方法:
register_android_server_tv_TvInputHal(env); register_android_server_PersistentDataBlockService(env); register_android_server_Watchdog(env); register_android_server_HelloTestService(env);
至此,本地的代碼會在onload.cpp中完成向java層對應類的注冊,同時我們可以編譯我們的service,使用mmm framework/base/service即可。我們已經完成了service層代碼的編寫,但系統還不會使用我們的service,所以,接下來我們要向系統注冊我們的服務。
系統在啟動的時候,會使用SystemServer類啟動或者注冊系統服務,所以我們要在其中注冊我們的服務。
在
frameworks/base/services/java/com/android/server/SystemServer.java中的main函數中有:
public static void main(String[] args) { new SystemServer().run(); }
可見它調用了run方法,run方法中又調用了如下啟動注冊系統服務:
// Start services. try { startBootstrapServices(); startCoreServices(); startOtherServices(); } catch (Throwable ex) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting system services", ex); throw ex; }
我們在startOtherServices方法的最後注冊我們的服務就好了,使用ServiceManager.addService即可:
try { Slog.i("jinwei", "Hello Test Service"); ServiceManager.addService("hellotest", new HelloTestService()); } catch (Throwable e) { Slog.e(TAG, "Failure starting Hello Test Service", e); }
然後再執行mmm frameworks/base/services/ 進行編譯。編譯完成後,使用make snod命令重新打包system.img,然後重寫燒寫system.img,這樣,系統中就有了我們的服務了。然而事情往往沒有想想中順利,我們需要解決一些問題。
前面貼出來的代碼已經是經過測試的代碼,可以正常使用,這裡還是把我在實現service的過程中遇到的問題總結一下。
注意:我們在前面的文章中測試linux驅動和HAL層代碼的時候都是動態安裝linux驅動的,這裡因為我們在android啟動的時候就注冊我們的service,所以,驅動必須編譯進linux kernel。而且,我們在android應用程序訪問Linux驅動第二步-實現並測試hardware層
中編譯出來的hellotest.default.so文件必須拷貝到/system/lib/hw目錄下。
重新燒寫完系統後發現系統無法成功啟動,Log如下:
pid: 6273, tid: 6273, name: system_server >>> system_server <<< 12-31 18:01:02.461 1142 1142 F DEBUG : signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- 12-31 18:01:02.500 1142 1142 F DEBUG : Abort message: 'art/runtime/jni_internal.cc:497] JNI FatalError called: RegisterNatives failed for 'com/android/server/HelloTestService'; aborting...'
這個問題是由於之前代碼"(Ljava/lang/String;)I" 中遺忘了”;”造成的,加上“;”即可解決。
5.2解決devie無法打開
從開機Log可以看到,/dev/hello無法打開:
09-17 17:27:22.416 2193 2193 I HelloTestService: SystemServer :register_android_server_HelloTestService.
09-17 17:27:22.444 2193 2193 D HelloTestService: init_native
09-17 17:27:22.444 2193 2193 I HelloTestService: HelloTest JNI: initializing......
09-17 17:27:22.447 2193 2193 I HelloTestService: HelloTest JNI: hello Stub found.
09-17 17:27:22.447 2193 2193 I HelloTestService: HelloTest JNI: failed to open hello device.
這是因為我們沒有訪問/dev/hello權限問題導致的解決方法:打開Android源代碼工程目錄下,進入到system/core/rootdir目錄,裡面有一個名為ueventd.rc文件,往裡面添加一行:
/dev/hello 0666 root root
最後,我們可以在開機Log中看到
HelloTest JNI: initializing......
HelloTest JNI: hello Stub found.
HelloTest JNI: hello device is open.
等LOG,說明我們的服務已經成功完成注冊。
適配器模式(Adapter):適配器模式是一種行為模式,它可以把一個類的接口轉換成為另一個所期待的另一種接口,這樣可以使原本因接口無法在一起工作的兩個類能夠在一起工作了。
1.2.//使用回調接口,首先初始化pintuview並綁定,實現回調接口的方法 mPintuLayout = (PintuLayout) findViewById
Android組件之間的通信有多種實現方式,Broadcast就是其中一種。在activity和fragment之間的通信,broadcast用的更多本文以一個activ
There are many great advantages to building your own UI components, such as the abili