本文主要内容,在硬件访问的基础上添加了权限控制以及app调用新增API的方法。
1编写HAL库控制硬件##
以Android5.0为例在如下目录创建一个目录添加一个C文件和一个Android.mk文件
hardware/libhardware/modules/leds/Android.mk //编译HAL库的规则
hardware/libhardware/modules/leds/leds.c //生成HAL库的逻辑代码
hardware/libhardware/modules/include/hardware/leds.h
如何实现对应的代码这里首先需要分析一下HAL的架构。首先是三个结构体。对应目录
hardware/libhardware/include/hardware/hardware.h
未说明的结构体成员在这个hardware.h文件下有很详细的说明了。
/*这个结构体主要是为了通过id成员找到methods成员,methods成员通过调用该methods->open函数打开对硬件操作的一个类似句柄一样的东西*/
struct hw_module_t{
uint32_t tag;
uint16_t version_major;
uint16_t version_minor;
const char *id;
const char *name;
const char * author;
struct hw_module_methods_t* methods; //指向封装有open函数指针的结构体这个重点说明一下看下一段代码片。
void* dso;
uint32_t reserved[32-7];
};
/*
硬件对象的open方法描述结构体,通过函数参数将hw_device_t的指针分配的空间返回回来。这个又是何方神圣?接下来再看。
*/
struct hw_module_methods_t{
// 只封装了open函数指针
int (*open)(const struct hw_module_t* module,
const char * id,
struct hw_device_t** device);
};
/*
这个hw_device_t内容是不是让你看的一头雾水?没什么主要的功能呀,除了一个close方法可能联想到硬件操作。那其他方法呢?这里实际上是用C语言来实现一“个面向对象”“继承”这么一个概念。对于具体的硬件可以继承这个结构体。接下来看下一个代码片如何继承。
*/
struct hw_device_t{
uint32_t tag;
uint32_t version;
struct hw_module_t* module; // 这里头可是封装了methods与id号的。
uint32_t reserved[12];
int (*close)(struct hw_device_t* device); // 该设备的关闭函数指针,可以看做硬件的close方法
};
typedef struct leds_device {
struct hw_device_t common; //这里所谓继承,必须写在第一个成员里。因为内存对齐,不被编译器优化的情况下,leds_device的第一个成员的地址就是leds_device的地址。看不懂的请回去恶补C语言结构体内存分布。methods下的open函数hw_device_t**这个参数传入的时候是需要用这个派生类进行强制转化成基类(hw_device_t的)。
int (*leds_control)( int status, int which);//这个是派生类扩充的功能。也就是我们具体硬件led的操作函数了。
} leds_device_t;
以上是HAL三个结构体的内容。HAL库的调用端利用
int hw_get_module(const char *id, const struct hw_module_t **module); 获取module,这个获取过程不再详述。再利用这个module的methods方法从传入的参数中获取到leds_device_t结构体。这样就能获取到leds_device_t结构体里的leds_control方法了。关于以上三个结构体填充与具体实现过程如下。
leds.c文件
#include <hardware/leds.h>
#include <hardware/hardware.h>
#include <cutils/log.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define LED_DEVICE_PATH "/dev/leds"
static int leds_exists(void) {
int fd;
ALOGE("Leds leds_exists");
fd = open(LED_DEVICE_PATH, O_RDWR);
if ( fd < 0 )
return 0;
close(fd);
return 1;
}
/*led具体硬件操作实现*/
static int leds_ctl (int status, int whitch) {
int ret,fd;
ALOGE("Leds leds_ctl");
fd = open(LED_DEVICE_PATH, O_RDWR);
ret = ioctl(fd,status,whitch);
close(fd);
return ret;
}
/*释放硬件句柄分配的空间*/
static int leds_close (hw_device_t *dev) {
if(dev != NULL)
free(dev);
return 0;
}
/*该函数实现调用获取硬件句柄方法。*/
static int leds_open_exists(const hw_module_t* module, const char* id __unused, hw_device_t** device __unused) {
if (!leds_exists()) {
ALOGE("Leds device does not exist. Cannot start leds");
return -ENODEV;
}
//对具体实现的派生类分配空间。
leds_device_t *ledsdev = calloc(1, sizeof(leds_device_t));
if (!ledsdev) {
ALOGE("Can not allocate memory for the leds device");
return -ENOMEM;
}
memset(ledsdev, 0, sizeof(leds_device_t));
ledsdev->common.tag = HARDWARE_DEVICE_TAG;
ledsdev->common.module = (hw_module_t *) module;
ledsdev->common.version = HARDWARE_DEVICE_API_VERSION(1,0);
ledsdev->common.close = leds_close;
ledsdev->leds_control = leds_ctl;//led操作函数的实现
//传回的是以基类返回。印证上面的知识点结构体内存分布的内容。
*device = (hw_device_t *) ledsdev;
return 0;
}
/*===========================================================================*/
/* Default leds HW module interface definition */
/*===========================================================================*/
static struct hw_module_methods_t leds_module_methods = {
.open = leds_open_exists,//打开获取硬件操作句柄的具体实现。
};
struct hw_module_t HAL_MODULE_INFO_SYM = {//必须这个值看hardware.h解说
.tag = HARDWARE_MODULE_TAG,//必须这个值
.module_api_version = LEDS_API_VERSION,
.hal_api_version = HARDWARE_HAL_API_VERSION,
.id = LEDS_HARDWARE_MODULE_ID,//led 设备的id。
.name = "Default leds HAL",
.author = "JerryRowe",
.methods = &leds_module_methods,//methods获取句柄的方法
};
对应的leds.h文件
#ifndef _HARDWARE_LEDS_H
#define _HARDWARE_LEDS_H
#include <hardware/hardware.h>
__BEGIN_DECLS
#define LEDS_API_VERSION HARDWARE_MODULE_API_VERSION(1,0)
/**
* The id of this module
*/
#define LEDS_HARDWARE_MODULE_ID "leds"
/**
* The id of the main vibrator device
*/
#define LEDS_DEVICE_ID_MAIN "main_leds"
typedef struct leds_device {
/**
* Common methods of the leds device. This *must* be the first member of
* leds_device as users of this structure will cast a hw_device_t to
* leds_device pointer in contexts where it's known the hw_device_t references a leds_device.
*/
struct hw_device_t common;
/** Turn on/off leds
*
* @param status on/off 0-1
*
* @param which which led to on/off 0-3
*
* @return 0 in case of success, negative errno code else
*/
int (*leds_control)( int status, int which);
} leds_device_t;
/*这里简单对methods的open进行了封装,这样在jni层要调用该方法时候只要include该头文件即可*/
static inline int leds_open(
const struct hw_module_t* module,
leds_device_t** device)//注意这里,传入的依旧是派生类
{
return module->methods->open(module, LEDS_DEVICE_ID_MAIN, (struct hw_device_t**)device);//而调用open时,向父类转化了。
}
__END_DECLS
#endif // _HARDWARE_LEDS_H
对应Android.mk文件
#本地当前
LOCAL_PATH := $(call my-dir)
#清除变量
include $(CLEAR_VARS)
LOCAL_MODULE := leds.default
# HAL module implementation stored in
# hw/<LEDS_HARDWARE_MODULE_ID>.default.so
LOCAL_MODULE_RELATIVE_PATH := hw
#C语言头文件路径
LOCAL_C_INCLUDES := hardware/libhardware
#C源文件
LOCAL_SRC_FILES := leds.c
#由于用了log打印,链接的动态库名
LOCAL_SHARED_LIBRARIES := liblog
#这个不用多说了吧。
LOCAL_MODULE_TAGS := optional
#调用动态库编译规则
include $(BUILD_SHARED_LIBRARY)
查看编译log可以知道生成的so路径在哪里。
总结:框架中主要难以理解可能是利用C语言实现继承的过程。
##2、添加jni访问HAL库 ##
这里主要是实现对应java层的native方法的功能,并且加载调用HAL层leds库的功能。对应实现如下。
frameworks/base/services/core/jni/com_android_server_LedService.cpp
frameworks/base/services/core/jni/onload.cpp
frameworks/base/services/core/jni/Android.mk
这里没什么框架难度。主要还是业务逻辑的代码,还有就是找到对应位置添加注册,以及添加业务逻辑的代码编译选项。
com_android_server_LedService.cpp
#define LOG_TAG "native_LedService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
/*注意这里添加leds头文件*/
#include <hardware/leds.h>
#include <hardware/hardware.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
namespace android
{
/*声明需要获取的硬件操作句柄类型*/
leds_device_t *leds_device = NULL;
static jint ledControl(JNIEnv *env, jobject clazz,jint which, jint status)
{
//通过硬件句柄控制led。
return leds_device->leds_control(status,which);
}
static jint ledOpen(JNIEnv *env, jobject clazz)
{
hw_module_t* leds_module = NULL;
ALOGI("ledOpen ");
/*利用LEDS的id获取leds_module,这样就能获取methods->open方法,从而获取leds_device的硬件句柄*/
if ( hw_get_module(LEDS_HARDWARE_MODULE_ID, (const hw_module_t **)&leds_module) == 0 ) {
ALOGI("HAL leds.default.so find!!");
leds_open(leds_module, &leds_device);//这个是leds头文件里inline封装的函数第一节已经提到过。
return 0;
}
else {
ALOGI("HAL leds.default.so no exists!!");
return -1;
}
}
//jni函数映射表。
static JNINativeMethod method_table[] = {
{ "native_ledControl", "(II)I", (void*)ledControl },
{ "native_ledOpen", "()I", (void*)ledOpen },
};
//注册jni到对应的java文件。
int register_android_server_LedService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/LedService",
method_table, NELEM(method_table));
}
};
onload.cpp添加jni注册位置。
namespace android {
………………
//注册jni服务函数声明
int register_android_server_LedService(JNIEnv* env);
………………
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
………………
//注册jni服务函数调用
register_android_server_LedService(env);
………………
}
Android.mk添加com_android_server_LedService.cpp的编译
LOCAL_SRC_FILES += \
…………
$(LOCAL_REL_DIR)/com_android_server_LedService.cpp \
…………
3、java调用jni的实现以及AIDL
LED服务涉及到android进程间通信aidl编程。不熟悉的朋友请自行恶补一下。
涉及一下文件
frameworks/base/core/java/android/os/ILedService.aidl
frameworks/base/Android.mk
frameworks/base/services/core/java/com/android/server/LedService.java
frameworks/base/services/java/com/android/server/SystemServer.java
首先来看aidl,对于应用层操作硬件来说。越简单越好。因此对应用层只管亮灭。因此api也要设计的简洁。
package android.os;
/** {@hide} */
interface ILedService
{
int ledControl(int which, int status);
}
在frameworks/base/Android.mk添加编译
LOCAL_SRC_FILES += \
…………
core/java/android/os/ILedService.aidl \
…………
此处编译完后会自动生成对应的ILedService.java自动实现进程间通信,out/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/src/android/os/ILedService.java
再看看LedService.java会继承该Stub
package com.android.server;
import android.os.ILedService;
public class LedService extends ILedService.Stub {
//app访问的api。aidl定义的api需要实现
public int ledControl(int which, int status) {
int ret;
ret = native_ledControl(which,status);
return ret;
}
//打开设备实际上这里最终会调用初始化和加载HAL的led库。
public LedService() {
native_ledOpen();
}
// 这里两个native方法。在上一节jni实现了。
native private int native_ledControl(int which, int status);
native private int native_ledOpen();
}
最后要在SystemServer.java添加我们自己建立的led服务。
public final class SystemServer {
…………
Slog.i(TAG, "Led Service");
ServiceManager.addService("led", new LedService());
…………
4、实现管理类来对java的访问服务进行操作以及APK调用方法
实际上第三节过后,app已经具备可以调用操作ILedService的能力了。只是为了符合标准,我们应当像
context.getSystemService(Context.XXX_SERVICE);
这样获取服务进行操才符合应用人员开发调用服务的思路。本节就是扩充一个LedManager来实现管理类。
涉及到的文件
frameworks/base/core/java/android/app/ContextImpl.java
frameworks/base/core/java/android/content/Context.java
frameworks/base/core/java/android/service/led/LedManager.java
先来看看管理类。LedManager.java
package android.service.led;
import android.content.Context;
import android.os.ILedService;
import android.os.RemoteException;
import android.content.pm.PackageManager;
import android.util.Log;
public class LedManager {
private Context mContext;//保存调用的进程上下文
private ILedService mService;//访问ILedService服务
public LedManager(Context context, ILedService service) {//这两个参数是在getSystemService里传入的。
mContext = context;//保存进程上下文
mService = service;//保存获取到的服务
}
public int ledControl(int which, int status) {
try{
//调用控制led
return mService.ledControl(which, status);
} catch (RemoteException e) {
e.printStackTrace();
return -1;
}
}
}
getSystemService是一个Context的抽象方法,具体实现的地方
来看看ContextImpl.java
…………
import android.service.led.LedManager;
import android.os.ILedService;
…………
@Override //抽象方法实现处,从ServiceFetcher下获取对应的方法。并且返回fetcher由获取这个服务。由于篇幅较多不继续深入。
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
/*最终这里我们需要注册一个LED的服务让etcher拿到这个服务。*/
registerService(LED_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(LED_SERVICE);//此处LED_SERVICE是在Context添加的。
ILedService service = ILedService.Stub.asInterface(b);
return new LedManager(ctx, service);//这里调用LedManager构造函数
}});
最后在Context.java添加LED_SERVICE的字符串
…………
public static final String LED_SERVICE = "led";//添加led的字符串
…………
@StringDef({
…………
LED_SERVICE,//添加led资源描述,不了解请参看这里:http://www.linuxidc.com/Linux/2015-08/121993.htm
…………
})
…………
//这里是文档描述依赖应该不用加也可,并且make update-api了
* @see #LED_SERVICE
* @see android.service.LedManager
…………
public abstract Object getSystemService(@ServiceName @NonNull String name);
编译后生成的jar包路径在out/target/common/obj/JAVA_LIBRARIES,Android studio导入这个包依赖后,可以使用apk调用LedManager。但是还是找不到LED_SERVICE这个字符串符号。这里提供两种解决办法。
1、在android源码树里make sdk出来的android.jar替换Android studio sdk路径下的android.jar。如何make sdk出来请移步http://www.360doc.com/content/14/0214/23/9075092_352577757.shtml
2、利用反射。由于本次反射过程不复杂,因此本例采用反射。app代码如下。
package com.example.jerryrowe.led;
import android.content.Context;
import android.service.led.LedManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.Toast;
import java.lang.reflect.Field;
public class LedMainActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener {
private CheckBox[] checkBoxes = new CheckBox[5];
private int[] checkBoxId = {R.id.led0, R.id.led1, R.id.led2, R.id.led3, R.id.ledall};
LedManager ledManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_led_main);
for(int i = 0; i < checkBoxes.length; i++) {
checkBoxes[i] = (CheckBox) findViewById(checkBoxId[i]);
checkBoxes[i].setOnCheckedChangeListener(LedMainActivity.this);
}
ledManager = getLedManager();//该函数利用反射获取server
}
private LedManager getLedManager() {
Class mContext = Context.class;//获取class
Field fields[] = (mContext.getDeclaredFields());//class拥有的所有属性
for(int i = 0; i < fields.length; i++) {
//fields[i].setAccessible(true);对于private成员使用。
if("LED_SERVICE".equals(fields[i].getName())){//利用成员变量名反射出自己需要的属性。
try {
String led_service = (String) fields[i].get(mContext);
Toast.makeText(getApplicationContext(), fields[i].getName()+ ":" + led_service, Toast.LENGTH_SHORT).show();//打印出属性值是否为leds
return (LedManager) getApplicationContext().getSystemService(led_service);
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
}
return null;
}
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (compoundButton.getId() == R.id.led0) {
ledManager.ledControl(0,b ? 1:0);
}
if (compoundButton.getId() == R.id.led1) {
ledManager.ledControl(1,b ? 1:0);
}
if (compoundButton.getId() == R.id.led2) {
ledManager.ledControl(2,b ? 1:0);
}
if (compoundButton.getId() == R.id.led3) {
ledManager.ledControl(3,b ? 1:0);
}
if (compoundButton.getId() == R.id.ledall) {
ledManager.ledControl(0,b ? 1:0);
ledManager.ledControl(1,b ? 1:0);
ledManager.ledControl(2,b ? 1:0);
ledManager.ledControl(3,b ? 1:0);
}
}
}
至此,从HAL到app调用的整个过程已经打通。已经能正常访问硬件服务了。
5、对硬件访问服务进行访问硬件的权限检查,在framework添加新的权限
仔细思考apk开发流程中,是不是涉及到对每个资源的访问都要在AndroidManifest.xml 中进行权限声明呢?比如
<uses-permission android:name="android.permission.XXX"/>这样本节内容就是对自己添加的硬件访问服务进行权限的限制。没有声明权限的则同样无法访问led
涉及到的文件
frameworks/base/core/res/AndroidManifest.xml
frameworks/base/core/res/res/values/strings.xml
frameworks/base/core/res/res/values-xx-xx/strings.xml
frameworks/base/core/java/android/service/led/LedManager.java
首先添加对应的权限描述在AndroidManifest.xml添加一下permission
<!-- Allows access to the led -->
<permission android:name="android.permission.LED"
android:permissionGroup="android.permission-group.AFFECTS_BATTERY"
android:protectionLevel="normal"
android:label="@string/permlab_led"
android:description="@string/permdesc_led" />
然后在strings.xml下添加对应引用的@string/xxx以及各国语言下的values-xx-xx/strings.xml各国语言的我就省略了。
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_led">control led</string>
<!-- Description of an application permission, listed so the user can c hoose whether they want to allow the application to do this. -->
<string name="permdesc_led">Allows the app to control the led.</string>
最后在LedManager.java下添加控制权校验的方法。不贴源码了只贴出增加的部分。
package android.service.led;
import android.content.Context;
import android.os.ILedService;
import android.os.RemoteException;
import android.content.pm.PackageManager;
import android.util.Log;
public class LedManager {
private Context mContext;
private ILedService mService;
public LedManager(Context context, ILedService service) {
mContext = context;
mService = service;
}
public int ledControl(int which, int status) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.LED)!= PackageManager.PERMISSION_GRANTED) {//这句if就是判断是否声明了该权限。如果错误打印错误信息。
Log.e("LedManager","Permission Dneial: can't control led from" +
mContext.getApplicationInfo());
return -1;
}
try{
return mService.ledControl(which, status);
} catch (RemoteException e) {
e.printStackTrace();
return -1;
}
}
}
编译LedManager.java可能出现找不到android.Manifest.permission.LED,这个错误在于我们更改了framework的res,却还没编译生成新的res因此要先编译res。mmm frameworks/base/core/res/ 这样再编译LedManager.java就不会报错了。
最后应用要访问ledControl的函数需要在应用的AndroidManifest.xml文件下增加一行,
apk的java访问代码在第4节就已经贴出来了。