Android系統服務編寫例項-Binder(Java層AIDL)

瀟瀟獨行俠發表於2018-06-14

此篇涉及系統服務編寫流程,主要就是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);

 

相關文章