此篇涉及系统服务编写流程,主要就是Java层AIDL实现Binder跨进程通信;JNI的编写;ioctrl的学习
C/C++层实现可参考另一篇文章:Android Binder实现示例(C/C++层)
最近开发项目中,涉及到一个讯飞硬件降噪模块的功能调试;在与底层驱动沟通后,被告知底层已经实现好了ioctl,需要上层编写service直接调用;作为一个听都没听过ioctl的小白,简直是懵X状态。提前了解的上层到底层的调用关系,经过HAL层,现在被告知不涉及HAL层,于是参考项目经验,跟了几天终于搞定了。下面介绍下一整个对讯飞硬件降噪模块的调试
归根到底,不论是HAL还是ioctl,都需要先创建一个硬件访问服务,为上层提供访问支持
在这里和基本的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);//主要使用此方法切换硬件降噪模块的功能
}
由于此服务接口使用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。
实现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中。
通常把硬件访问服务的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的使用了。具体驱动层是如何实现的,待后续再学习,本篇重点不在这。
上面已经完成硬件访问服务的一整套实现,我们需要把它加入到系统进程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即可
完成上述步骤后,便可以编写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;
}
}
}
可以把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);