Android系統服務編寫例項-Binder(Java層AIDL)
此篇涉及系統服務編寫流程,主要就是Java層AIDL實現Binder跨程式通訊;JNI的編寫;ioctrl的學習
C/C++層實現可參考另一篇文章:Android Binder實現示例(C/C++層)
最近開發專案中,涉及到一個訊飛硬體降噪模組的功能除錯;在與底層驅動溝通後,被告知底層已經實現好了ioctl,需要上層編寫service直接呼叫;作為一個聽都沒聽過ioctl的小白,簡直是懵X狀態。提前瞭解的上層到底層的呼叫關係,經過HAL層,現在被告知不涉及HAL層,於是參考專案經驗,跟了幾天終於搞定了。下面介紹下一整個對訊飛硬體降噪模組的除錯
一、硬體訪問服務開發
歸根到底,不論是HAL還是ioctl,都需要先建立一個硬體訪問服務,為上層提供訪問支援
1、定義硬體訪問服務介面
1.1定義介面
在這裡和基本的AIDL定義一樣,先定義一個AIDL的介面檔案,宣告需要使用的方法;
frameworks/base/core/java/android/chipctrl/IChipCtrl.aidl
package android.chipctrl;
/** @hide */
interface IChipCtrl
{
int openDevice(String devName);
void closeDevice(int handle);
byte[] loadInfo(in byte []orig, int handle);
boolean setDenoiseMode(int mode);//主要使用此方法切換硬體降噪模組的功能
}
1.2新增到編譯指令碼
由於此服務介面使用AIDL語言描述,我們需要將其新增到編譯指令碼中,這樣系統才能將其轉換為Java檔案,然後再對它進行編譯。進入到framework/base下的Android.mk,修改LOCAL_SRC_FILES的值,新增此檔案
LOCAL_SRC_FILES += \
core/java/android/chipctrl/IChipCtrl.aidl \
然後使用mmm命令對其編譯:mmm ./framework/base/ 編譯出的framework.jar檔案中就包含有IChipCtrl介面。AIDL是程式間通訊,Server端和Client端通過Binder來互動,Server通過Binder.Stub來監聽Client程式傳送的程式間通訊請求,而Client端則需要先獲得一個Binder代理物件介面(Proxy),然後通過這個Binder代理物件介面向它傳送程式間通訊請求。關於Binder這方面的知識後續會繼續研究,此處重點關注JNI。
2、實現硬體訪問服務
實現AIDL介面定義的服務,此處是真正的服務,AIDL介面只是供跨程式通訊使用;
frameworks/base/services/java/com/android/server/chipctrlservice/ChipCtrlService.java
package com.android.server.chipctrlservice;
import android.chipctrl.IChipCtrl;
public class ChipCtrlService extends IChipCtrl.Stub {
public static final String TAG = "ChipCtrlService";
public static final String Name = "chipctrlservice";
public int openDevice(String devName) {
return nativeOpenDevice(devName);
}
public void closeDevice(int handle) {
nativeCloseDevice(handle);
}
public byte []loadInfo(byte []orig, int handle) {
return nativeLoadInfo(orig, handle);
}
public boolean setDenoiseMode(int mode) {
return nativeSetDenoiseMode(mode);
}
public native byte[] nativeLoadInfo(byte[] msg, int handle);//這裡就是宣告的Native方法,供JNI層實現
public native int nativeOpenDevice(String devName);
public native void nativeCloseDevice(int handle);
public native boolean nativeSetDenoiseMode(int mode);
}
編寫完成後進行編譯 mmm ./frameworks/base/services/java/ 將其編譯到services.jar中。
3、實現硬體訪問服務的JNI方法
通常把硬體訪問服務的JNI方法實現放在frameworks/base/services/jni目錄下com_android_server_chipctrlservice_ChipCtrlService.cpp
#define LOG_TAG "chipctrljni native.cpp"
#include <utils/Log.h>
#include <stdio.h>
#include "jni.h"
#include "JNIHelp.h"
extern "C" {
#include "chipctrl/chip204.h"
}
namespace android {
static jint nativeOpenDevice(JNIEnv *env, jclass clazz, jstring devName)
{
const char *rawDevName = env->GetStringUTFChars(devName, NULL);
int handle = open_i2c_device(rawDevName);
env->ReleaseStringUTFChars(devName, rawDevName);
if (handle < 0) {
ALOGE("open chip %s failed!", devName);
} else {
ALOGI("open chip %s success!", devName);
}
return handle;
}
static void nativeCloseDevice(JNIEnv *env, jclass clazz, jint handle)
{
if (handle > 0) {
close_i2c_device(handle);
}
}
static jbyteArray
nativeLoadInfo(JNIEnv *env, jclass clazz, jbyteArray msg, jint handle) {
jbyte *cValues = env->GetByteArrayElements(msg, NULL);
jbyte result[32];
BOOL success = chip_loadinfo(handle, (const unsigned char *)cValues, (unsigned char *)result);
env->ReleaseByteArrayElements(msg, cValues, 0);
if (!success) {
ALOGE("chip_loadinfo failed!");
return NULL;
}
jbyteArray jValue = env->NewByteArray(32);
env->SetByteArrayRegion(jValue, 0, 32, result);
return jValue;
}
static jboolean nativeSetDenoiseMode(JNIEnv *env, jclass clazz, jint mode) {
BOOL success = false;
success = set_denoise_mode(mode);
return success;
}
static const char *classPathName = "com/android/server/chipctrlservice/ChipCtrlService";
static JNINativeMethod methods[] = {
{"nativeLoadInfo", "([BI)[B", (void*)nativeLoadInfo },
{"nativeOpenDevice", "(Ljava/lang/String;)I", (void*)nativeOpenDevice },
{"nativeCloseDevice", "(I)V", (void*)nativeCloseDevice },
{"nativeSetDenoiseMode", "(I)Z", (void*)nativeSetDenoiseMode },
};
/*
* Register native methods for all classes we know about.
*
* returns JNI_TRUE on success.
*/
int register_android_server_chipctrlservice_ChipCtrlService(JNIEnv* env)
{
return jniRegisterNativeMethods(env, classPathName, methods, NELEM(methods));
}
}
我們在編寫完JNI實現後,需要把它註冊到系統JNI中(java虛擬機器),方法是修改此路徑下的onload.cpp檔案,
#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputApplicationHandle(JNIEnv* env);
int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_AssetAtlasService(JNIEnv* env);
int register_android_server_chipctrlservice_ChipCtrlService(JNIEnv* env);//add for iflytek by bruce
};
using namespace android;
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("GetEnv failed!");
return result;
}
ALOG_ASSERT(env, "Could not retrieve the env!");
register_android_server_PowerManagerService(env);
register_android_server_SerialService(env);
register_android_server_InputApplicationHandle(env);
register_android_server_InputWindowHandle(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);
register_android_server_AlarmManagerService(env);
register_android_server_UsbDeviceManager(env);
register_android_server_UsbHostManager(env);
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);
register_android_server_location_GpsLocationProvider(env);
register_android_server_location_FlpHardwareProvider(env);
register_android_server_connectivity_Vpn(env);
register_android_server_AssetAtlasService(env);
register_android_server_ConsumerIrService(env);
register_android_server_chipctrlservice_ChipCtrlService(env);//add for iflytek by bruce
return JNI_VERSION_1_4;
}
int register_android_server_chipctrlservice_ChipCtrlService(JNIEnv* env);//add for iflytek by bruce
};
using namespace android;
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("GetEnv failed!");
return result;
}
ALOG_ASSERT(env, "Could not retrieve the env!");
register_android_server_PowerManagerService(env);
register_android_server_SerialService(env);
register_android_server_InputApplicationHandle(env);
register_android_server_InputWindowHandle(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);
register_android_server_AlarmManagerService(env);
register_android_server_UsbDeviceManager(env);
register_android_server_UsbHostManager(env);
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);
register_android_server_location_GpsLocationProvider(env);
register_android_server_location_FlpHardwareProvider(env);
register_android_server_connectivity_Vpn(env);
register_android_server_AssetAtlasService(env);
register_android_server_ConsumerIrService(env);
register_android_server_chipctrlservice_ChipCtrlService(env);//add for iflytek by bruce
return JNI_VERSION_1_4;
}
之後,需要將其編譯進系統,方法是開啟此路徑下的Android.mk檔案,要注意,一定要把下面的C語言實現編譯出的lib庫也加入到mk檔案中,不然編譯會報錯的。
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
..........
com_android_server_chipctrlservice_ChipCtrlService.cpp \
onload.cpp
.............
LOCAL_SHARED_LIBRARIES := \
.....
libchipctrl \
...
在這個JNI中,實現了Service服務中宣告的Native方法;它的實現方法就是呼叫C/C++編寫的C層程式碼,從Include的標頭檔案我們可以看到chip204.h,frameworks/base/include/chipctrl下的三個標頭檔案中宣告瞭JNI呼叫的方法,主要看下面的實現就行了
#include <stddef.h>
#include <stdio.h>
#include <string.h>
typedef int HANDLE;
#define INVALID_HANDLE_VALUE (-1)
typedef int BOOL;
#define FALSE 0
#define TRUE 1
typedef int DE_NOISE_MODE;
//下面是對四種模式定義ioctrl的引數,這個定義要與驅動層完全一致,這是ioctrl使用的關鍵點。
#define VCDRIVER_CMD_FUNC_MODE_PHONE _IOW('U', 0x10, unsigned long)
#define VCDRIVER_CMD_FUNC_MODE_NOISECLEAN _IOW('U', 0x11, unsigned long)
#define VCDRIVER_CMD_FUNC_MODE_PASSBY _IOW('U', 0x14, unsigned long)
#define VCDRIVER_CMD_FUNC_MODE_WAKEUP _IOW('U', 0x15, unsigned long)
//下面是對四種模式的常量值定義
#define MODE_PHONE 1
#define MODE_NOISECLEAN 2
#define MODE_PASSBY 3
#define MODE_WAKEUP 4
//開啟裝置節點
HANDLE open_device(const char * devname);
//控制選擇降噪模組的模式
BOOL control_denoise_device(HANDLE handle, DE_NOISE_MODE mode);
//關閉裝置節點
void close_device(HANDLE handle);
BOOL set_denoise_mode(DE_NOISE_MODE mode);
#endif
下面是C語言的實現,我們需要將其編譯成lib庫,供JNI層呼叫。在framework/base/libs/chipctrl/下新增Android.mk編譯檔案
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
# This is the target being built.
LOCAL_MODULE:= libchipctrl//將其編譯為此庫,上面的jni編譯時是會呼叫此庫的。
# All of the source files that we will compile.
LOCAL_SRC_FILES:= chip204.c I2CDevice.c DeNoise.c
# All of the shared libraries we link against.
LOCAL_SHARED_LIBRARIES := \
libutils liblog
# No special compiler flags.
LOCAL_CFLAGS +=
include $(BUILD_SHARED_LIBRARY)
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <linux/ioctl.h>
#include "chipctrl/DeNoise.h"
#include "cutils/log.h"
#define LOG_NDEBUG 0
#define LOG_TAG "DeNoise"
HANDLE open_device(const char * devname)
{
HANDLE fd;
//open device
fd = open(devname, O_RDWR);
if (fd < 0) {
ALOGD("%s: open %s failed", __func__, devname);
return INVALID_HANDLE_VALUE;
} else {
ALOGD("%s: open %s success handle = %d", __func__, devname, fd);
}
return fd;
}
BOOL control_denoise_device(HANDLE handle, DE_NOISE_MODE mode)
{
int ret = -1;
ALOGD("%s: handle %d, denoise mode %d", __func__, handle, mode);
switch (mode) {//根據設定的不同模式,來匹配傳入不同的引數,傳入的引數就是供ioctrl匹配用的,它是一種cmd命令值,其定義已在.h中宣告瞭,這個定義要保證驅動層與呼叫層完全一致,因為這是ioctrl使用的重點重點重重點
case MODE_PHONE:
ret = ioctl(handle, VCDRIVER_CMD_FUNC_MODE_PHONE, 0);
break;
case MODE_NOISECLEAN:
ret = ioctl(handle, VCDRIVER_CMD_FUNC_MODE_NOISECLEAN, 0);
break;
case MODE_PASSBY:
ret = ioctl(handle, VCDRIVER_CMD_FUNC_MODE_PASSBY, 0);
break;
case MODE_WAKEUP:
ret = ioctl(handle, VCDRIVER_CMD_FUNC_MODE_WAKEUP, 0);
break;
default:
ALOGE("%s: mode invaild", __func__);
}
if (ret != 0){
ALOGD("%s: ret %d errno type %s", __func__, ret, strerror(errno));
}
return ret == 0;
}
void close_device(HANDLE handle)
{
if (handle != INVALID_HANDLE_VALUE) {
close(handle);
}
}
BOOL set_denoise_mode(DE_NOISE_MODE mode)
{
const char *devName = "/dev/xf6000ye_CV";//底層提供的裝置節點
HANDLE mFD;
BOOL success;
mFD = open_device(devName);
if (mFD < 0) {
ALOGE("%s: open device failed", __func__);
return FALSE;
}
success = control_denoise_device(mFD, mode);
ALOGD("%s: success %d", __func__, success);
close_device(mFD);
return success;
}
寫到這裡就到所謂的ioctrl了,我們可以從上面的c語言實現中的control_denoise_device方法中看到對不同的值匹配後,都會呼叫ioctrl函式,這個就是ioctrl的使用了。具體驅動層是如何實現的,待後續再學習,本篇重點不在這。
4、啟動硬體訪問服務
上面已經完成硬體訪問服務的一整套實現,我們需要把它加入到系統程式System中啟動它,才能供上層呼叫;我們找到SystemServer.java檔案,參考其他服務,將此服務加入其中即可;
//Add ChipCtrlService for iflytek ,by bruce
Slog.i(TAG,"chipctrlservice");
ServiceManager.addService("chipctrlservice",new ChipCtrlService());
重新編譯 mmm ./frameworks/base/services/java/
最後,需要提升下硬體裝置節點的許可權,在device/下的init.rc中
新增chmod 0666 /dev/xxxxx即可
5、Client呼叫方法
5.1 第一種
完成上述步驟後,便可以編寫client端來呼叫server進行通訊;如果按照上述第4步,把chipctrlService新增到SystemService中後,可以通過如下方法獲取代理proxy;
import android.chipctrl.IChipCtrl;
IChipCtrl mProxy;
mProxy = IChipCtrl.Stub.asInterface(ServiceManager.getService("chipctrlservice"));
下面是對在framework層對Client端做了封裝供應用層使用,因為應用層無法直接訪問ServiceManager(@hide),就不能直接獲取服務的代理物件。
package android.chipctrl;
import android.os.RemoteException;
import android.util.Log;
import android.os.ServiceManager;
public class ChipCtrl
{
private final static String TAG = "ChipCtrl";
//此處就是採用直接獲取一個遠端代理物件,Client端可以使用此管理類進行呼叫;呼叫方法就是,new一個此管理類即可。
private IChipCtrl mProxy = IChipCtrl.Stub.asInterface(ServiceManager.getService("chipctrlservice"));
public int openDevice(String devName) {
int handle = -1;
if (mProxy == null) {
Log.e(TAG, "get chipctrlservice failed!");
return handle;
}
try {
handle = mProxy.openDevice(devName);
} catch (RemoteException e) {
}
return handle;
}
public void closeDevice(int handle) {
if (mProxy == null) {
Log.e(TAG, "get chipctrlservice failed!");
return;
}
try {
mProxy.closeDevice(handle);
} catch (RemoteException e) {
}
}
public byte[] loadInfo(byte[] orig, int handle) {
if (mProxy == null) {
Log.e(TAG, "get chipctrlservice failed!");
return null;
}
if (handle < 0) {
Log.e(TAG, "invalid handle!" + handle);
return null;
}
byte []result = null;
try {
result = mProxy.loadInfo(orig, handle);
if (result != null) {
Log.i(TAG, "loadInfo result is " + new String(result));
} else {
Log.e(TAG, "loadInfo result failed!");
}
} catch (RemoteException e) {
}
return result;
}
public boolean setDenoiseMode(int mode) {
if (mProxy == null) {
Log.e(TAG, "get chipctrlservice failed!");
return false;
}
try {
return mProxy.setDenoiseMode(mode);
} catch(RemoteException e) {
return false;
}
}
}
5.2 第二種
可以把chipctrlService再封裝一下,提供一個Manager類chipctrlManager供Client使用;
採用類系統服務方式
可以參考一些系統服務的呼叫方式:
WifiManager mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
我們需要把ChipCtrl進行改寫;
package android.chipctrl;
import android.os.RemoteException;
import android.util.Log;
import android.os.ServiceManager;
public class ChipCtrlManager
{
private final static String TAG = "ChipCtrlManager";
//private IChipCtrl mService = IChipCtrl.Stub.asInterface(ServiceManager.getService("chipctrlservice"));
final Context mContext;
private IChipCtrl mService;
//主要是此處新增了一個構造器,用於獲取代理
public ChipCtrlManager(Context context, IChipCtrl mChipCtrl) {
mContext = context;
mService = mChipCtrl;
}
public int openDevice(String devName) {
int handle = -1;
if (mService == null) {
Log.e(TAG, "get chipctrlservice failed!");
return handle;
}
try {
handle = mService.openDevice(devName);
} catch (RemoteException e) {
}
return handle;
}
public void closeDevice(int handle) {
if (mService == null) {
Log.e(TAG, "get chipctrlservice failed!");
return;
}
try {
mService.closeDevice(handle);
} catch (RemoteException e) {
}
}
public byte[] loadInfo(byte[] orig, int handle) {
if (mService == null) {
Log.e(TAG, "get chipctrlservice failed!");
return null;
}
if (handle < 0) {
Log.e(TAG, "invalid handle!" + handle);
return null;
}
byte []result = null;
try {
result = mService.loadInfo(orig, handle);
if (result != null) {
Log.i(TAG, "loadInfo result is " + new String(result));
} else {
Log.e(TAG, "loadInfo result failed!");
}
} catch (RemoteException e) {
}
return result;
}
public boolean setDenoiseMode(int mode) {
if (mService == null) {
Log.e(TAG, "get chipctrlservice failed!");
return false;
}
try {
return mService.setDenoiseMode(mode);
} catch(RemoteException e) {
return false;
}
}
}
接下來,需要把服務新增進系統的Context中;
1、將字串標誌加入frameworks/base/core/java/android/content/Context.java
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.os.CanUtilManager} for transmitting infrared
* signals from the device.
*
* @see #getSystemService
* @see android.os.CanUtilManager
*/
public static final String CHIPCTRL_SERVICE = "chipctrlservice";//add by bruce for ChipCtrl
2、註冊此服務進系統frameworks/base/core/java/android/app/ContextImpl.java
/*add begin by bruce*/
registerService(CHIPCTRL_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(CHIPCTRL_SERVICE);
IChipCtrl service = ICanUtilManager.Stub.asInterface(b);
//其實我們看上述兩步,就是獲取遠端的代理,只是這裡又進行了一次封裝;通過Manager類的構造器,建立一個管理類例項來處理。
return new ChipCtrlManager(ctx.getOuterContext(), service);
}});
/*add end*/
這樣我們就可以通過如下方式呼叫了。
ChipCtrlManager mChipCtrlManager = (ChipCtrlManager)mContext.getSystemService(CHIPCTRL_SERVICE);
相關文章
- Android Binder原理(三)系統服務的註冊過程Android
- 藉助 AIDL 理解 Android Binder 機制——Binder 來龍去脈AIAndroid
- 藉助 AIDL 理解 Android Binder 機制——AIDL 的使用和原理分析AIAndroid
- Android探索之旅 | AIDL原理和例項講解AndroidAI
- Android多程式之手動編寫Binder類Android
- Binder機制之AIDLAI
- Binder Java層分析Java
- 對AIDL和Binder的理解AI
- Binder + AMS + AIDL大雜燴AI
- Android Input子系統-含例項原始碼Android原始碼
- Android native程式間通訊例項-binder結合共享記憶體Android記憶體
- Java 例項 - 檔案寫入Java
- 理解 Android Binder 機制(二):C++層AndroidC++
- Android Binder實現示例(C/C++層)AndroidC++
- Binder Java層的實現原理分析Java
- 雙層 for 例項
- android native service編寫及兩個服務程式通訊Android
- Android硬體服務框架例項之Vibrator(驅動到應用)Android框架
- oracle監聽不到例項服務Oracle
- Android C++層使用Binder通訊的方法AndroidC++
- Android 系統原始碼-2:Binder 通訊機制Android原始碼
- Android AIDL原理AndroidAI
- 7 Android中常用的系統服務Android
- znai: 使用Markdown編寫Java文件系統AIJava
- 編碼式事務管理使用例項
- 由淺入深 學習 Android Binder(三)- java binder深究(從java到native)AndroidJava
- grpc套路服務端編寫RPC服務端
- Laravel 服務提供者業務使用例項Laravel
- Laravel 服務容器、服務提供器、契約例項講解Laravel
- JAVA服務例項記憶體高問題排查及解決Java記憶體
- Android IPC 之AIDLAndroidAI
- Binder系列6—獲取服務(getService)
- Android Binder之旅Android
- 【 karle 專欄 】Android 初探底層知識系列——Binder原理。Android
- 雲容器例項服務入門必讀
- systemd 編寫服務管理指令碼指令碼
- Android6.0 顯示系統(五) SurfaceFlinger服務Android
- Java編寫定時任務Java