Android有四大组件:
一、Activity
二、Service
三、Broadcast Receiver
四、Content Provider
Service是Android中一个类,它是Android四大组件之一,使用Service可以在后台执行长时间的操作( perform long-running operations in the background ),Service并不与用户产生UI交互。其他的应用组件可以启动Service,即便用户切换了其他应用,启动的Service仍可在后台运行。一个组件可以与Service绑定并与之交互,甚至是跨进程通信(IPC)。例如,一个Service可以在后台执行网络请求、播放音乐、执行文件读写操作或者与 content provider交互 等。
由于上一章JNI直接访问硬件可能导致多个应用同时访问一个驱动,就可能导致驱动出现问题,所以我们可以只让一个应用程序来访问硬件,这个应用程序成为“SystemServer”,APP有应用请求统一发给它,由它统一管理所有的service。而我们这章的目的也是建立一个led service。
SystemServer使用java写的,访问硬件只能用C,所以中间也需要使用JNI。
SystemServer的源码在android目录下:
frameworks\base\services\java\com\android\server\SystemServer.java
这里先概述一下SystemServer的过程:
SystemServer源码解析:
文件:SystemServer.java
SystemServer在源码中是一个类,里面实现了很多类方法,以Vibrator这个service作为参考例子,模仿写一个led service,我们从main这个方法开始解析
想要APP能使用service需要注册添加各种service进service_manager.c,然后通过getService来获取接口,自己添加的驱动就需要在这里面addService,最上面那副图中的几个进程都会涉及到Binder driver,这个驱动程序并不是内核自带的,而是google公司对linux内核做的修改,添加的一个驱动程序,它可以实现更加高效的进程间通信。
接下来以代码的形式描述一下上面的整个过程,从APP—>server—>service—>JNI(其实应该还有个HAL,但这章主要描述service的建立过程,HAL放到下一章讲)。
整个过程文字描述如下:
实现一个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 <目录名>)命令,可能会遇到报错(mmm: command not found),在android源码目录下执行“. build/envsetup.sh”即可解决,执行mmm .后系统会帮我们自动生成ILedService.java文件。编译成功后在out目录下搜索“find –name “ILedService.java””可以找到相应生成的java接口文件
实现LedService.java
里面主要实现调用我们定义的本地方法然后修改SystemServer.java把这个service添加进去,之后APP就可以通过getService获得LedService从而操作LED了
实现com_android_server_LedService.cpp
上面LedService.java的方法都是由这个文件来实现的,即JNI的相关代码,主要将数组里面的方法注册到LedService这个方法里面,并修改Onload.cpp将这个注册函数添加进去
APP使用
ILedService iLedService;
iLedService = ILedService.Stub.asInterface(ServiceManager.getService(“led”));
iLedService.ledCtrl(0, 1);
整个过程代码描述如下:
(1) AIDL
1. 把 ILedService.aidl 放入 frameworks/base/core/java/android/os
aidl代码如下:
package android.os;
/** {@hide} */
interface ILedService
{
int ledCtrl(int which, int status);
}
2. 修改 frameworks/base/Android.mk 添加一行
core/java/android/os/IVibratorService.aidl \
+ core/java/android/os/ILedService.aidl \
3. mmm frameworks/base(这里需要在源码根目录先运行. build/envsetup.sh)
4. 它会生成: ./out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/ILedService.java
5. ILedService.java里面定义了ledCtrl,我们需要去实现这个方法
(2) Server
实现:LedService.java
非常简单,就是提供native方法,这些方法最后需要我们写JNI代码去实现,代码如下:
package com.android.server;
import android.os.ILedService;
/* defind a LedService class */
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);/* this is a native method */
}
public LedService(){
native_ledOpen();
}
public static native int native_ledCtrl(int which, int status);
public static native int 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());//The 'new' can call LedService Constructor
修改完后重新上传至:
frameworks/base/services/java/com/android/server/SystemServer.java
这里我们不需要去修改 frameworks/base/services/core/Android.mk
因为它的内容里已经把该目录下所有JAVA文件自动包含进去了,后面编译会自动重新加载这两个java文件:
LOCAL_SRC_FILES += \
$(call all-java-files-under,java)
(3) JNI
实现:com_android_server_LedService.cpp
上面LedService.java的方法都是由这个文件来实现的,即JNI的相关代码,主要将数组里面的方法注册到LedService这个方法里面,并修改Onload.cpp将这个注册函数添加进去,代码如下:
#define LOG_TAG "LedService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include
#include
#include
#include
#include
#include
#include
#include
namespace android
{
static jint fd;
jint ledOpen(JNIEnv *env, jobject cls)
{
fd = open("/dev/leds_node", O_RDWR);//打开设备LED节点
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;
}
/* 定义JNI字段描述符 */
static const JNINativeMethod methods[] = {//定义一个映射数组
{"native_ledOpen", "()I", (void *)ledOpen},//对应c语言的ledOpen,这个函数没有参数,返回值是int类型
{"native_ledClose", "()V", (void *)ledClose},//对应c语言的ledClose,这个函数没有参数,返回值是void类型
{"native_ledCtrl", "(II)I", (void *)ledCtrl},//对应c语言的ledCtrl,这个函数有两个int参数,返回值是int类型
};
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
修改 frameworks/base/services/core/jni/Android.mk :
编译:
mmm frameworks/base/services
make snod
修改APP使用Service(此APP基于上一章“Android硬件访问服务-JNI”)
原来我们使用HardConrtol这个在JNI里面定义的类来访问,现在我们可以直接使用service来访问
首先在工程的MainActivity.java里面导入:
import android.os.ILedService;
import android.os.ServiceManger;
在MainActivity这个类里面添加如下:
/* 定义一个Service成员 */
private ILedService iLedService = null;
在OnCreate函数里面给service赋值,并且去掉之前的HardControl.ledopen()函数
/* 给service赋值*/
iLedService = ILedService.Stub.asInterface(ServiceManager.getService(“led”));
Ctrl+R将所有的hardcontrol替换为ILedService
工程中需要包含什么:
在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
Android studio软件打开File->Project Structure ->左上角的’+’号添加->import .JAR->找到classed.jar文件导入->打开Modules下app的Dependencies->右边‘+’号添加classes即可,如下图
在build.gradle(Module.app)里面添加如下几项:
android {
.......
defaultConfig {
....
multiDexEnabled true
}
dexOptions{
javaMaxHeapSize "4g"
}
.......
}
dependencies {
....
compile project(':classes')
}
在AndroidManifest.xml文件里面的“application”添加如下
解决以上编译错误的参考文章如下(大部分网址需要访问):
How do I build the Android SDK with hidden and internal APIs available?
http://stackoverflow.com/questions/7888191/how-do-i-build-the-android-sdk-with-hidden-and-internal-apis-available
Creating a module library and adding it to module dependencies
https://www.jetbrains.com/idea/help/configuring-module-dependencies-and-libraries.html
java.lang.OutOfMemoryError: GC overhead limit exceeded
Android Studio Google jar causing GC overhead limit exceeded error
http://stackoverflow.com/questions/25013638/android-studio-google-jar-causing-gc-overhead-limit-exceeded-error
Too many field references
Building Apps with Over 65K Methods
https://developer.android.com/tools/building/multidex.html