## 簡述
前面的博客中,我們直接添加C lib到APK中,然后使用LoadLibrary加載這個庫,同時添加一個class來作為中間層,直接使用這個C庫中的native函數來控制硬件,這種做法將硬件與APK牢牢綁定,如果有多個APP來訪問同一個硬件就會出現問題,代碼也會有很多的重復,在Android中,我們使用Android的SystemServer向ServiceManager來將硬件的功能添加為一個服務,這樣當一個APP需要使用硬件的時候就向SystemServer發出請求service服務,然后由ServiceMnager統一提供服務,提供統一的接口與硬件控制,即相當于多添加了一層,從而實現解耦。
## 詳細原理
先看下圖(圖片來源于韋東山的Android視頻資料)中的③②①,按照順序:
1. SystemServer會加載Cpp lib
1. 在JNI_OnLoad中注冊各個Service,SystemServer向ServiceManager添加服務
1. 這些service就包括像串口/LED等硬件相關的服務

而使用的時候,就是7~5步驟:
1. AddService:SystemServer向ServiceManager添加服務addServeice
1. getservice:通過getservice來從SystemServer注冊了的service中獲取服務所具有的功能,例如ledctrl
1. 使用Service的方法:APP使用一個Interface(即以i開頭的對象)來使用service提供的功能,將服務請求到SystemServer去
APP/SystemServer/ServiceManager三者都是通過Bindler來通訊。
## 添加Service與使用Service的步驟
### 添加serviceAIDL文件,生成Interface java文件
因為系統中其他都aidl文件都放在frameworks/base/core/java/android/os下,所有我們也參考其他的文件添加一個ILedService.aidl:
~~~
package android.os;
/** {@hide} */
interface ILedService
{
int ledCtrl(int which, int status);
}
~~~
可以看到這個interface前面有個@hide的修飾,表明是個hide class。
同時還需要將此aidl文件添加到Android.mk(Makefile)中:
~~~
$ git diff Android.mk
diff --git a/Android.mk b/Android.mk
index 151621c..7bde511 100644
--- a/Android.mk
+++ b/Android.mk
@@ -150,6 +150,7 @@ LOCAL_SRC_FILES += \
core/java/android/os/IUpdateLock.aidl \
core/java/android/os/IUserManager.aidl \
core/java/android/os/IVibratorService.aidl \
+ core/java/android/os/ILedService.aidl \
core/java/android/service/notification/INotificationListener.aidl \
core/java/android/service/dreams/IDreamManager.aidl \
core/java/android/service/dreams/IDreamService.aidl \
~~~
這個這個Android.mk位于frameworks/base/,編譯后就會生成一個ILedService.java
### 添加service的實現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 <hardware_legacy/led.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
namespace android
{
#define ALOGI printf
#define LED_NUM 3
int leds_fd[LED_NUM];
char path_buff[255];
static jint ledCtrl(JNIEnv *env, jobject clazz, jint which, jint status)
{
int ret = -1;
if(status == 1) {
ret = write(leds_fd[which], "255", 3);
} else {
ret = write(leds_fd[which], "0", 1);
}
if(ret < 0){
return -1;
}
ALOGI("Native ctrl fd = [%d]\n", which);
return 0;
}
static jint ledOpen(JNIEnv *env, jobject clazz)
{
int i = 0;
for(i=0; i<LED_NUM; i++){
sprintf(path_buff, "/sys/class/leds/led%d/brightness", i);
printf("path:%s\n",path_buff);
leds_fd[i] = open(path_buff, O_RDWR);
if(leds_fd[i] < 0){
ALOGI("led%d: %s, open failed\n", i, path_buff);
return -1;
} else {
ALOGI("led%d: %s, open success\n", i, path_buff);
}
}
return 0;
}
static void ledClose(JNIEnv *env, jobject clazz)
{
int i = 0;
for(i=0; i< LED_NUM; i++){
close(leds_fd[i]);
}
}
static JNINativeMethod method_table[] = {
{ "native_ledCtrl", "(II)I", (void*)ledCtrl },
{ "native_ledClose", "()V", (void*)ledClose },
{ "native_ledOpen", "()I", (void*)ledOpen }
};
int register_android_server_LedService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/LedService",
method_table, NELEM(method_table));
}
};
~~~
里面定義好了來調用這個native函數的java class名字為com_android_server_LedService:
~~~
jniRegisterNativeMethods(env, "com/android/server/LedService",
method_table, NELEM(method_table));
~~~
還需要添加到編譯中:
~~~
$ git diff services/jni/Android.mk
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index b313d48..fb359cb 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -14,6 +14,7 @@ LOCAL_SRC_FILES:= \
com_android_server_UsbDeviceManager.cpp \
com_android_server_UsbHostManager.cpp \
com_android_server_VibratorService.cpp \
+ com_android_server_LedService.cpp \
com_android_server_location_GpsLocationProvider.cpp \
com_android_server_connectivity_Vpn.cpp \
onload.cpp
~~~
### 添加LedService.java文件
前面有了native c/cpp的實現,接下來就需要用JNI來調用native方法了,因此需要添加LedService.java(frameworks/base/services/java/com/android/server/LedService.java)文件:
~~~
package com.android.server;
import android.os.ILedService;
/**
* Created by hexiongjun on 12/9/15.
* Function:
* Call Native C function to control hardware
*/
public class LedService extends ILedService.Stub{
private static final String TAG = "LedService";
public int ledCtrl(int which, int status) throws android.os.RemoteException {
return native_ledCtrl(which, status);
}
public void LedService(){
native_ledOpen();
}
// Declare the function
public native static int native_ledCtrl(int which, int status);
public native static int native_ledOpen();
public native static void native_ledClose();
}
~~~
內容很簡單:
- 聲明了native函數
- 在構造函數中調用open打開設備
上層的Android.mk會自動將java文件添加到Android編譯中,不需要自己添加。
### 讓SystemServer啟動的時候加載service
~~~
$ git diff services/jni/onload.cpp
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index 423ebd1..83721fe 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -31,6 +31,8 @@ int register_android_server_SerialService(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
+// From com_android_server_LedService.cpp
+int register_android_server_LedService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
@@ -60,6 +62,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
register_android_server_UsbDeviceManager(env);
register_android_server_UsbHostManager(env);
register_android_server_VibratorService(env);
+ register_android_server_LedService(env);
register_android_server_SystemServer(env);
register_android_server_location_GpsLocationProvider(env);
register_android_server_connectivity_Vpn(env);
~~~
這個是因為SystemServer進程啟動的時候會去調用LoadLibrary去加載各個庫,這個加載的過程就在OnLoad.cpp中。
### 添加Service到ServiceManager中
這個是在SystemServer中完成的:
~~~
$ git diff services/java/com/android/server/SystemServer.java
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9455017..1ccf63a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -129,6 +129,7 @@ class ServerThread extends Thread {
PowerManagerService power = null;
DisplayManagerService display = null;
BatteryService battery = null;
+ LedService led = null;
VibratorService vibrator = null;
AlarmManagerService alarm = null;
MountService mountService = null;
@@ -288,6 +289,11 @@ class ServerThread extends Thread {
battery = new BatteryService(context, lights);
ServiceManager.addService("battery", battery);
+ //Add the led service to SystemServer, so others can use
+ Slog.i(TAG, "Led Service");
+ led = new LedService();
+ ServiceManager.addService("led", led);
+
Slog.i(TAG, "Vibrator Service");
vibrator = new VibratorService(context);
ServiceManager.addService("vibrator", vibrator);
~~~
### LED App中使用添加的Service
到了最后就可以使用這些服務了,但是要使用之前還需要添加包含了LedService的模塊,這個模塊其實是framework,但是因為我們是java,而framework屬于dex格式,因此我們需要添加jar格式的包,這個包編譯完成后,位于:
> out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
因此在APP中添加此模塊:

并將此模塊添加到app的依賴中:

然后在代碼中導入Interface與ServiceManager,并使用Service:

## 這種添加Service到ServiceManager方法的問題
現在我們依然將硬件相關的操作放到了一個cpp中,而這個cpp會編譯到系統中,因此如果對硬件的操作有變更,我們就需要修改這個文件,修改了這個文件,那么就需要將整個Android系統重新編譯,因此圖片中還有一個步驟④,這個就是將硬件相關的東西放在一個HAL層,這樣子就避免了修改一個文件就需要編譯整個系統,同時也可以不放出與硬件相關的源碼而僅僅給出一個HAL相關的庫(保密)。
## 遇到的問題
### multidex問題
因為包含了framework的classes.jar,而這個jar中有超過65K個的方法,因此就需要開啟multidex。
~~~
$ git diff app/build.gradle
diff --git a/app/build.gradle b/app/build.gradle
index 131397f..82a2a62 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,6 +10,12 @@ android {
targetSdkVersion 22
versionCode 1
versionName "1.0"
+
+ //Enable multidex
+ multiDexEnabled true
+ }
+ dexOptions {
+ javaMaxHeapSize "4g"
}
sourceSets{
main {
@@ -29,4 +35,6 @@ dependencies {
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:design:22.2.1'
+ compile project(':classes')
+ compile 'com.android.support:multidex:1.0.0'
}
~~~
同時還需要更改xml文件:
~~~
$ git diff app/src/main/AndroidManifest.xml
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a77e7a1..1504514 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
package="com.hexiongjun.led">
<application
+ android:name="android.support.multidex.MultiDexApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
~~~
并對gradle resync。
### jar不匹配的問題
如果重新編譯了Android classes.jar但是在APP中依舊使用的是老的,那么會出現一些奇怪的問題:

此時需要先將老的移除掉然后重新添加,或者直接在app的workspace中替換新的。
### javaHeap size的配置

- 前言
- Freescale IMX6 Android (1): 使用HDMI作為Android顯示輸出的配置
- Freescale IMX6 Android (2): Android NFS啟動問題匯總
- Freescale IMX6 Android (3): 手動制作Android啟動用SD卡 省去MFGTOOLS燒寫
- Freescale IMX6 Android (5): APP通過JNI控制LED
- Freescale IMX6 Android (4): 基于TQIMX6 給Toolbox添加LED控制程序
- Freescale IMX6 Android (6): 向ServerManager中添加Service
- Freescale IMX6 Android (7): Android啟動動畫死循環 Home界面不出來與pid XXX exit 可能的原因匯總