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 
#include 
#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 
#include 
#include 

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 
#include 
#include 
#include 

#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,开发,C语言开发)