Android平臺讀寫i2c裝置開發筆記二
二、 使用JNI在應用程式框架層新增服務訪問介面
APP應用不能直接訪問HAL層,需要JNI層訪問HAL模組並向上提供API介面。可以直接提供介面,但建議最好使用服務的方式提供訪問。
我們先看JNI如何訪問剛才的HAL模組。
進入原始碼根目錄下的frameworks/base/service/jni目錄,新建com_android_server_IICService.cpp,程式碼如下:
#include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include <utils/misc.h> #include <cutils/log.h> #include <hardware/hardware.h> #include <hardware/iic.h> #include <stdio.h> namespace android { /*在硬體抽象層中定義的硬體訪問結構體,參考<hardware/iic.h>*/ struct iic_device_t* iic_device = NULL; /*通過硬體抽象層定義的硬體訪問介面設定硬體暫存器val的值*/ static void iic_setVal(JNIEnv* env, jobject clazz, jstring val, jint slaveAddr, jint subAddr, jint len) { const char *str = env->GetStringUTFChars(val, NULL); LOGI("iic JNI: set value %s to device.", str); if(!iic_device) { LOGI("iic JNI: device is not open."); return; } iic_device->iic_write(iic_device, (unsigned char*)str, slaveAddr, subAddr, len); env->ReleaseStringUTFChars(val, str); //注意釋放資源 } /*通過硬體抽象層定義的硬體訪問介面讀取硬體暫存器val的值*/ static jstring iic_getVal(JNIEnv* env, jobject clazz, jint slaveAddr, jint len) { unsigned char* data = (unsigned char*)malloc(len); iic_device->iic_read(iic_device, data, slaveAddr, len); if(!iic_device) { LOGI("iic JNI: device is not open."); } int i = 0; for(;i<strlen((const char*)data);i++){ LOGI("data: %c ", data[i]); } //LOGI("iic JNI: get value %s from device @ %x address!", data, subAddr); jstring tmp = env->NewStringUTF((const char*)data); free(data); data = NULL; return tmp; } /*通過硬體抽象層定義的硬體模組open介面開啟硬體裝置*/ static inline int iic_device_open(const hw_module_t* module, struct iic_device_t** device) { return module->methods->open(module, IIC_HARDWARE_MODULE_ID, (struct hw_device_t**)device); } /*通過硬體模組ID來載入指定的硬體抽象層模組並開啟硬體*/ static jboolean iic_init(JNIEnv* env, jclass clazz) { iic_module_t* module; LOGI("iic JNI: initializing......"); if(hw_get_module(IIC_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) { LOGI("iic JNI: iic Stub found."); if(iic_device_open(&(module->common), &iic_device) == 0) { LOGI("eeprom JNI: iic device is opening..."); return 0; } LOGE("eeprom JNI: failed to open iic device."); return -1; } LOGE("eeprom JNI: failed to get iic stub module."); return -1; } /*JNI方法表*/ static const JNINativeMethod method_table[] = { {"init_native", "()Z", (void*)iic_init}, {"setVal_native", "(Ljava/lang/String;III)V", (void*)iic_setVal}, {"getVal_native", "(III)Ljava/lang/String;", (void*)iic_getVal}, }; /*註冊JNI方法*/ int register_android_server_IICService(JNIEnv *env) { return jniRegisterNativeMethods(env, "com/android/server/IICService", method_table, NELEM(method_table)); } };
然後需要讓android啟動時載入此jni模組
在同目錄下修改onload.cpp:
在namespace android中新增一行 int register_android_server_IICService(JNIEnv *env);
在JNI_onLoad方法中新增一行 register_android_server_IICService(env);
在同目錄下修改Android.mk:
LOCAL_SRC_FILES增加一行 com_android_server_IICService \
編譯命令:mmm frameworks/base/services/jni
注意: HAL是根據iic_init中的IIC_HARDWARE_MODULE_ID載入相應模組。然後,使用AIDL進行程序間通訊,使APP能訪問自定義的硬體服務。
我們需要在frameworks/base/core/java/android/os中新建IIICService.aidl(注意是III)
package android.os;
interface IIICService {
void setVal(String val, int slaveAddr, int regAddr, int len);
String getVal(int slaveAddr, int len);
}
它定義了服務的介面,介面在IICService中實現並關聯到jni本地方法中。
同時我們需要修改frameworkd/base下的Android.mk編譯檔案,在LOCAL_SRC_FILES中增加 core/java/android/os/IIICService.aidl
編譯命令: mmm frameworks/base
下面是AIDL的實現方法類:com.android.server.IICService 位置為:frameworks/base/services/java/com/android/server 程式碼如下:
package com.android.server;
import android.content.Context;
import android.os.IIICService;
import android.util.Slog;
public class IICService extends IIICService.Stub {
private static final String TAG = "IICService";
IICService() {
init_native();
}
public void setVal(String val,int slaveAddr, int regAddr, int len) {
setVal_native(val, slaveAddr, regAddr, len);
}
public String getVal(int slaveAddr,int len) {
return getVal_native( slaveAddr, len);
}
//本地方法
private static native boolean init_native();
private static native void setVal_native(String val, int slaveAddr, int regAddr, int len);
private static native String getVal_native(int slaveAddr, int len);
};
從程式碼中我們可以看到它繼承了IIICService.Stub,實現兩個介面方法。因為硬體訪問一般需要放在一個獨立的執行緒中,這裡使用了代理的方法來處理app與硬體服務的通訊。最後需要把新增的IICService服務加入到ServiceManager中,這樣就可以通過ServiceManager進行呼叫。
修改frameworks/base/services/java/com/android/server下的SystemServer.java 在run()方法中新增
try{
Slog.i(TAG, "IIC SERVICE");
ServiceManager.addService("iic", new IICService());
}catch(Throwable e){
Slog.e(TAG, "Failure starting IIC Service", e);
}
編譯命令:mmm frameworks/base/services/java
或者使用另一種形式來呼叫服務:如同使用binder機制繫結service一樣的方法, 具體就不詳細寫了。
注意:有可能會編譯不通過,因為這裡修改了android的官方api, 需要執行make update-api更新frameworks/base/api/current.xml
打包後,app就可以使用IICService介面來訪問硬體了。
下一節發上app相關程式碼
(待續)