第二章:硬件访问服务(2)-系统编写

通过上一节的介绍,对于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}

本地方法定义

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实现具体方法

在上一小节中,我们有五个需要实现的要点,但是我们只实现了三个。如下框图
在这里插入图片描述
红色圈出部分我们已经完成,下面我们来实现剩余部分

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 <utils/misc.h>
#include <utils/Log.h>
#include <hardware/vibrator.h>

#include <stdio.h>

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>

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)

猜你喜欢

转载自blog.csdn.net/weixin_43013761/article/details/86755824