通过上一节的介绍,对于android硬件访问服务器有了大致的了解,根据上一节的框图,我们在来梳理一下要点。一个饭店要正常的营业,顾客,菜单菜式,厨师缺一不可。我们的要点亮LED也是同样,APP(应用层),硬件访问服务器,LED服务,本地函数注册都不能缺少。总的来说,我们需要实现以下几点 1.JNI(用于注册本地C函数,转化为java方法)和LED_HAL(本地C函数,对硬件的操作-注意:不是内核驱动) 2.修改onload.cpp,调用com_android_server_LedService.cpp中的register_android_server_LedService。(厨师会做的菜式,当然要上报给厨师总管) 3.修改SystemServer.java。*(厨师总管整理好菜单以后,要提交给老板娘,老板娘负责给顾客提供可以选择的菜式) 4.LedService.java(顾客需要的菜式,即我们要实现的LED服务) 5.ILedService.java(提供给应用层使用的接口) 初步看起来不好理解,别急,下面将围绕着五个点。进行详细的讲解
接口生成 Vibrator举例内容比较多,我们需要找到一个突破口,就从用户需求开始吧,现在用户的需求是能够点亮或者熄灭LED,那么我们肯定需要构造一个java class给顾客使用,因为顾客是不能直接调用C函数的,不知大家有没有注意到上小节的框图,再次截图如下: 比如图中红色圈出部分Binder driver可以理解为一个进程,该进程负责管理各个进程的通信工作,几乎各个进程都和他存在联系,其中内部实现过于复杂。但是又非实现不可。那么怎么办呢?系统提供了一套方法,我们使用该方法,只需要我们做和硬件相关的工作即可。话不多说,直接先查看源码例子, SDK/frameworks/base/core/java/android/os/IVibratorService.aidl 代码如下
package android.os;
/** {@hide} */
interface IVibratorService
{
boolean hasVibrator();
void vibrate(int uid, String opPkg, long milliseconds, int usageHint, IBinder token);
void vibratePattern(int uid, String opPkg, in long[] pattern, int repeat, int usageHint, IBinder token);
void cancelVibrate(IBinder token);
}
只是简单的几个方法,都是vivrator服务的一些具体函数,在系统编译的时候,该文件会自动生成接口文件 out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/IVibratorService.java(文件名I代表接口的意思) 为了博文的整洁,在此只粘贴部分代码,我们可以看到文件中,有很多的方法,这些方法都是系统自动生成,前面提到过,一个服务程序是需要和其他很多进程进行交互的,这些交互全都由我们自己编写,即复杂,又耗时,故此就交给系统,在代码的最后,我们可以看到如下方法定义:
public boolean hasVibrator() throws android.os.RemoteException;
public void vibrate(int uid, java.lang.String opPkg, long milliseconds, int usageHint, android.os.IBinder token) throws android.os.RemoteException;
public void vibratePattern(int uid, java.lang.String opPkg, long[] pattern, int repeat, int usageHint, android.os.IBinder token) throws android.os.RemoteException;
public void cancelVibrate(android.os.IBinder token) throws android.os.RemoteException;
这些定义和之前IVibratorService.aidl文件中我们编写的是一样的,我们可以称xxx.aidl文件为接口描述文件,根据自己需要的编写简单的接口定义,然后加入到系统中进行编译生成xxx.java文件,他们的差别在于:xxx.java文件添加了很多系统相关需要的函数,xxx.java存在等目的就是为了生成xxx.java。下面我们使用LED实例进行模仿
LED实例仿照SDK/frameworks/base/core/java/android/os/IVibratorService.aidl文件,在相同目录下,创建文件ILedService.aidl,代码如下:
package android.os;
/** {@hide} */
interface ILedService
{
int ledCtrl(int which,int status);
}
对于java此程序员,并不需要所谓的ledOpen,ledClose,他们只需要点亮或者熄灭Led即可,故此我们只需要编写ledCtrl函数即可。 下面我们进行系统编译,源码中我们可以知道SDK/frameworks/base/core/java/android/os/目录下没有Android.mk文件,故此我们不能执行mmm(详细信息请查阅其他资料))命令,往上一级目录逐渐查找,直到SDK/frameworks/base目录下我们可以看到Android.mk文件。执行: mmm 即可,当然在这之前需要在SDK目录下执行 source build/envsetup.sh lunch rk3399_all-userdebug 否则会报错,找不到mmm命令。 编译完成之后,我们可以在out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/目录下找到I系统生成的文件ILedService.java文件,打开该文件可以看到
public static abstract class Stub extends android.os.Binder implements android.os.ILedService
得知,ILedService类中,定义了一个Stub抽象类,该类的父类是Binder(后续章节会对其进行详细的讲解),那么他肯定实现了进程间的通信。 其中还定义了
public static android.os.ILedService asInterface(android.os.IBinder obj)
暂且我们放一边,继续往下可以看到还定义了
public int ledCtrl(int which, int status) throws android.os.RemoteException;
那么ILedService这个接口类我们怎么使用呢?
接口使用 Vibrator举例我们还是参考Vibrator,回到IVibratorService.java文件,我们在源码中搜索IVibratorService,看看这个接口在哪里被调用了,是怎么被使用的。在SystemVIbrator.java文件中
private final IVibratorService mService;
private final Binder mToken = new Binder();
public SystemVibrator() {
mService = IVibratorService.Stub.asInterface(
ServiceManager.getService("vibrator"));
}
定义了一个IVibratorService类mService,通过IVibratorService.Stub.asInterface(ServiceManager.getService(“vibrator”))获得实例化,继续往下看,他调用了
mService.hasVibrator();
mService.vibrate()
mService.vibratePattern()
mService.cancelVibrate()
这些都是在IVibratorService.iald中编写的方法,在系统中Vibrator是这样使用的,那么我们需要的Led应该怎么使用呢?
Led实例照葫芦画瓢,根据前面的例子,Led 软件APP程序也是类似的原理,通过
ILedService.Stub.asInterface(ServiceManager.getService("led"));
从ServiceManager中获取一个Led服务的实例化,然后调用该实例中的ledCtrl就可以控制led的熄灭和点亮了,当然在这之前我们肯定需要先调用ledOpen(),并且软件退出的时候调用ledClose。但是到目前为止虽然ledCtrl接口虽然已经定义,但是并未实现,没有注册本地方法,所以接下来我们将开始led本地函数的注册— 实现 L e d S e r v i c e . j a v a \color{red}{实现LedService.java} 实现LedService.java
本地方法定义 Vibrator举例这个本地方法程序该怎么编写呢,我们还是参看参考 Vibrator,在SystemServer.java文件中,有如下代码:
traceBeginAndSlog("StartVibratorService");
vibrator = new VibratorService(context);
ServiceManager.addService("vibrator", vibrator);
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
在系统启动的时候,SystemServer.java的run函数会被调用,进而会执行上诉代码,向ServiceManager进程中添加服务,由内部机制实现(注意其中的"vibrator"与接口中ServiceManager.getService的参数是一一对应关系),让其其维护,这里会实例化一个VibratorService对象,提供给应用层使用。 既然实例化了一个VibratorService对象,那么我么来查看一下VibratorService.java文件,可以找到如下代码
native static boolean vibratorExists();
native static void vibratorInit();
native static void vibratorOn(long milliseconds);
native static void vibratorOff();
从native关键字可以知道,这些是本地方法,这些本地方法都在内部被其他方法调用,那么我们就开始模仿,编写我们的led相关代码。
Led实例首先我们打开SystemServer.java文件,在
if(!isBox){
traceBeginAndSlog("StartVibratorService");
代码前面添加
/*-------------------------------------添加ledService--------------------------------------*/
traceBeginAndSlog("StartLedService");
ServiceManager.addService("Led", new LedService());
/*-----------------------------------------------------------------------------------------*/
然后在SDK/frameworks/base/services/core/java/com/android/server/目录下创建文件LedService.java,内容如下
package com.android.server;
import android.os.ILedService;
public class LedService extends ILedService.Stub{
private static final String TAG = "LedService";
/*call native C function to access hardware*/
public int ledCtrl(int which, int status) throws android.os.RemoteException{
return native_ledCtrl(which, status);
}
public LedService()
{
native_ledOpen();
}
public static native int native_ledOpen();
public static native void native_ledClose();
public static native int native_ledCtrl(int which, int status);
}
在构造函数中我们调用了native_ledOpen()函数,前面提到过,java程序员,他只需要给他ledCtrol接口就可以了,其他的ledOpen,ledClose他们并不需要理会,所以我们可以在构造函数中调用native_ledOpen()函数。这些方法都定义了,但是却没有实现,下面我们通过JNI实现具体方法
在上一小节中,我们有五个需要实现的要点,但是我们只实现了三个。如下框图 红色圈出部分我们已经完成,下面我们来实现剩余部分
在LedService.java文件中,定义了
public static native int native_ledOpen();
public static native void native_ledClose();
public static native int native_ledCtrl(int which, int status);
那么他是由最终谁实现的呢?根据框图,可以知道是由com_android_server_xxxService.cpp实现,我们还是先来参考vibrator的相关文件
vibrator举例打开com_android_server_VibratorService.cpp文件,为了博客不显得臃肿,只粘贴部分代码
static const JNINativeMethod method_table[] = {
{ "vibratorExists", "()Z", (void*)vibratorExists },
{ "vibratorInit", "()V", (void*)vibratorInit },
{ "vibratorOn", "(J)V", (void*)vibratorOn },
{ "vibratorOff", "()V", (void*)vibratorOff }
};
int register_android_server_VibratorService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/VibratorService",
method_table, NELEM(method_table));
}
我们可以看到该cpp文件向com/android/server/VibratorService类中注册了4个方法,我们也仿照其,编写Led相关代码
Led实例创建文件frameworks/base/services/core/jni/com_android_server_LedService.cpp代码如下(如果你看过第一章节的博文,可以把hardcontrol.c稍作修改移植过来):
#define LOG_TAG "LedService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace android
{
static jint fd;
jint ledOpen(JNIEnv *env, jobject cls)
{
fd = open("/dev/leds_drv", O_RDWR);
ALOGE("native ledOpen : %d", fd);
if (fd >= 0)
return 0;
else
return -1;
}
void ledClose(JNIEnv *env, jobject cls)
{
ALOGE("native ledClose : %d", fd);
close(fd);
}
jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
{
int ret = ioctl(fd, which, status);
ALOGE("native ledCtrl : %d, %d, %d", which, status, ret);
return ret;
}
static const JNINativeMethod method_table[] = {
{"native_ledOpen", "()I", (void *)ledOpen},
{"native_ledClose", "()V", (void *)ledClose},
{"native_ledCtrl", "(II)I", (void *)ledCtrl},
};
int register_android_server_LedService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/LedService",
method_table, NELEM(method_table));
}
};
该代码在在第一章节已经进行过讲解,故此不做二次讲解了,有需要可以参考之前的博文
onload.cpp修改
编写com_android_server_LedService.cpp之后,应该怎么使用它呢?我们参看SystemServer.java文件,在该文件中有如下代码
System.loadLibrary("android_servers");
之前有提到过,在run方法中会被调用,他的实质是加载onload.cpp编译生成的库,在frameworks/base/services/Android.mk文件中,可以看到如下
include $(wildcard $(LOCAL_PATH)/*/jni/Android.mk)
LOCAL_MODULE:= libandroid_servers
libandroid_servers实质是frameworks/base/services/*/jni/目录下所有文件集成的库。打开onload.cpp文件添加如下代码。
namespace android中:
/*------------------------------增加代码--------------------------------------*/
int register_android_server_LedService(JNIEnv* env);
/*----------------------------------------------------------------------------*/
JNI_OnLoad中:
/*------------------------------增加代码--------------------------------------*/
register_android_server_LedService(env);
/*----------------------------------------------------------------------------*/
然后在SDK目录执行 source build/envsetup.sh lunch rk3399_all-userdebug mmm frameworks/base/services make snod -j3 生成新的system.img影像文件烧写到开发板
小节回顾该小节实现主要做了一下内容:
1.编写ILedService.aidl文件,通过系统编译生成ILedService.java。
2.修改SystemServer.java文件,添加ServiceManager.addService("Led", new LedService()),向ServiceManager注册服务
3.编写LedService.java,定义本地方法。
4.编写com_android_server_LedService.cpp,实现本地方法注册
5.onload.cpp修改,关联com_android_server_LedService.cpp
该小节我们通过com_android_server_LedService.cpp直接访问硬件了,其实这是不被推荐的方法,特别是硬件操作程序复杂的时候,具体细节,下小节讲解
希望大家多多关注我的博客,一起讨论技术,争取做一个不脱轨的技术员。
源码下载[系统移植相关源码] (https://github.com/944284742/android7.1Transplant.git) [AndriodStudioAPP] (https://github.com/944284742/andriod7.1APP.git)