上一章JNI直接访问硬件可能导致多个应用同时访问一个驱动,就可能导致驱动出现问题,所以我们可以只让一个应用程序来访问硬件,这个应用程序成为“SystemServer”,APP有应用请求统一发给它,由它统一管理所有的service。而我们这章的目的也是建立一个led service。 SystemServer使用java写的,访问硬件只能用C,所以中间也需要使用JNI。 SystemServer的源码在android目录下: frameworks\base\services\java\com\android\server\SystemServer.java
这里先概述一下SystemServer的过程:
- 通过LoadLibrary来加载C库,
- C库的JNI_Onload函数里面注册本地方法,分别调用各个硬件的函数来注册本地方法,比如LED、振动器、串口等等
- SystemServer:对每个硬件addService(Server里面有很多个Service,由Server提供Service),每个硬件都需构造Service(即注册本地方法)
- APP使用:
首先获得服务:get Service 然后使用服务:执行Service方法
SystemServer源码解析: 文件:SystemServer.java SystemServer在源码中是一个类,里面实现了很多类方法,以Vibrator这个service作为参考例子,模仿写一个led service,我们从main这个方法开始解析
Main() – SystemServer().run() -----System.loadLibrary(“android_servers”)—>初始化native service,即加载C库,对应文件是 onload.cpp,里面有JNI_OnLoad,用于注册各种本地方法。 比如: --------Register_android_server_VibratorService(env)—>对应com_ android_server_VibratorService(对应文件名),里面负责实现native方法 -----StartOtherServices()—>里面定义了各种service—>new VibratorService—>addService,在VibratorService中调用各种native方法。在native层中不会直接调用操作硬件的接口,而是通过一层hal层进行了封装,native层通过加载hal层的库,去调用hal层的接口,达到更好的封装效果。 想要APP能使用service需要注册添加各种service进service_manager.c,然后通过getService来获取接口,自己添加的驱动就需要在这里面addService,最上面那副图中的几个进程都会涉及到Binder driver,这个驱动程序并不是内核自带的,而是google公司对linux内核做的修改,添加的一个驱动程序,它可以实现更加高效的进程间通信。 接下来以代码的形式描述一下上面的整个过程,从APP—>server—>service—>JNI(其实应该还有个HAL,但这章主要描述service的建立过程,HAL放到下一章讲)。
整个过程文字描述如下:
1.实现一个aidl(Android接口定义语言)文件,写出这个文件后用Android系统里面的编译命令让它帮我们自动生成那个ILedService.java(接口)文件,怎么写参考源码目录framework里面的振动器代码(IVibratorService.aidl,位于frameworks目录),这个aidl文件位于源码目录下的frameworks/base/core/java/android/os,然后修改它的Android.mk (位于frameworks/base/),vi Android.mk,全局搜索关键字“Virbrator”可以找到相应的aidl文件定义,添加上我们自己的Led(core/ java/android/os /ILedService.aidl)文件即可,修改完后在目录(frameworks/base/)下执行mmm .(mmm <目录名>,这会根据目录名下的mk文件编译)命令,可能会遇到报错(mmm: command not found),在android源码目录下执行“. setenv”,然后选择对应的单板即可。执行mmm .后系统会帮我们自动生成ILedService.java文件。编译成功后在out目录下搜索“find –name “ILedService.java””可以找到相应生成的java接口文件。
aidl文件内容如下:
package android.os;
interface ILedService
{
int ledCtrl(int which, int status);
}
2.实现LedService.java 里面主要实现调用我们定义的本地方法然后修改SystemServer.java把这个service添加进去,之后APP就可以通过getService获得LedService从而操作LED了。
package com.android.server;
import android.os.ILedService;
public class LedService extends ILedService.Stub {
private static final String TAG = "LedService";
public LedService() {
native_ledOpen();
}
public int ledCtrl(int which, int status) throws android.os.RemoteException
{
return native_ledCtrl(which, status);
}
public static native int native_ledCtrl(int which, int status);
public static native void native_ledOpen();
public static native void native_ledClose();
}
把写好的service上传到服务器编译,上传到目录: frameworks/base/services/core/java/com/android/server/LedService.java 由于添加了新的service,所以我们需要去修改下SystemServer.java,参考vibrator的这个service写即可,添加如下代码到startOtherServices:
Slog.i(TAG, "Led Service");
ServiceManager.addService("led", new LedService());
修改完后重新上传至: frameworks/base/services/java/com/android/server/SystemServer.java
**注意:**虽然新增了LedService.java文件,这里我们不需要去修改 frameworks/base/services/core/Android.mk 因为它的内容里已经把该目录下所有JAVA文件自动包含进去了,后面编译会自动重新加载这个LedService.java文件:
LOCAL_SRC_FILES += \
$(call all-java-files-under,java)
3.JNI 实现com_android_server_LedService.cpp 上面LedService.java的native方法都是由这个文件来实现的,即JNI的相关代码,主要将数组里面的方法注册到LedService这个方法里面,并修改Onload.cpp将这个注册函数添加进去,代码如下:
#define LOG_TAG "LedService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.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_node", O_RDWR);
ALOGI("native ledOpen : %d", fd);
if (fd >= 0)
return 0;
else
return -1;
}
void ledClose(JNIEnv *env, jobject cls)
{
ALOGI("native ledClose ...");
close(fd);
}
jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
{
int ret = ioctl(fd, which, status);
ALOGI("native ledCtrl : %d, %d, %d", which, status, ret);
return ret;
}
static const JNINativeMethod methods[] = {
{"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",
methods, NELEM(methods));
}
}
onload.cpp添加如下代码:
namespace android {
........
int register_android_server_LedService(JNIEnv* env);
}
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
........
register_android_server_LedService(env);
}
把新文件上传到服务器, 所在目录: frameworks/base/services/core/jni/onload.cpp frameworks/base/services/core/jni/com_android_server_LedService.cpp
由于增加了com_android_server_LedService.cpp文件,需要修改 frameworks/base/services/core/jni/Android.mk :
$(LOCAL_REL_DIR)/com_android_server_LedService.cpp
4.编译 要将上面的cpp文件和java文件一起编译,怎么编译呢? 发现在frameworks/base/services/Android.mk中包含了所涉及的所有的Android.mk文件
include $(wildcard $(LOCAL_PATH)
所以编译命令为: mmm frameworks/base/services //生成libandroid_servers库以及class文件 make snod //生成system.img镜像 或者使用./gen-img.sh //生成所有镜像,宝库 5.APP使用 ILedService iLedService; iLedService = ILedService.Stub.asInterface(ServiceManager.getService(“led”)); iLedService.ledCtrl(0, 1);
工程中需要包含什么: 在linux系统下Android源码目录使用指令:mmm frameworks/base show commands > log.txt 2>&1可以看到aidl文件修改后会涉及哪些文件,从这个文件里面我们可以看出最后会生成一个framework.jar文件,由于我们的ledservice是一个隐藏类(使用/** {@hide} */关键字描述的部分),所以我们是不是需要导入这个framework.jar呢?经搜索后发现我们应该导入classes.jar文件,因为framework.jar文件是dex格式文件,我们的android运行的程序并不是原原本本的java程序,它是把这些java目标文件转换为了dex格式的文件,dex文件是对java文件做了一些优化 jar文件目录:out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar 将classes.jar复制到D:\project\android studio\APP_0001_LEDDemo\app\libs\armeabi目录下。 Android studio软件打开File->Project Structure 在build.gradle(Module.app)里面添加如下几项:
defaultConfig {
...
multiDexEnabled true
}
dexOptions{
javaMaxHeapSize "4g"
}
并将implementation files(‘libs\armeabi\classes.jar’)改为
compileOnly files(‘libs\armeabi\classes.jar’),如图所示:
|