為什麼要開發系統服務(system service)或Java API ?
有時候我們會需要修改手機作業系統的運作邏輯,甚至是取得只有system有權限可以存取的資訊,在這篇文章中我們就以一個實際的例子來說明我們要做的事情. 手機APP的啟動總是可能會花費許多的等待時間,這段時間我們稱之為launch time, 根據現有的Android API, 這個資訊是無從取得的,我們就把目標放在launch time上, 從系統中擷取每一隻APP的launch time並記錄在一隻system service中, 並且讓APP開發者能夠透過一套新的API與此service溝通並取得任一隻APP最近一次的launch time.
第一章 system service的撰寫與編譯
在這段裡面,我們撰寫一支空的service,並且在其建構子中印出一行log訊息以確定是否正常被執行.
/* * framework/base/services/java/com/android/server/MyService.java */ package com.android.server; import android.util.Log; import android.os.IMyManager; class MyService extends IMyManager.Stub { final String TAG = "MyService"; public MyService() { Log.i(TAG,"MyService is constructed!"); } }
為了要讓這個service註冊進開機流程中,我們必須在新增幾行指令在以下檔案:
/* * framework/base/services/java/com/android/server/SystemServer.java */ power = new PowerManagerService(); ServiceManager.addService(Context.POWER_SERVICE, power); //Add two lines MyService myservice = new MyService(); ServiceManager.addService("myservice", myservice);
其中myservice字串代表這個系統服務的代號,未來要使用到此service都會使用到這個字串,為了避免未來混淆,建議可以在framework/base/core/java/android/content/Context.java定義一個此字串對應的值.
此外,手動新增一個檔案,IMyManager.aidl讓MyService可以extends,至於此aidl用途後續會敘述.為了讓此aidl檔案能夠被編譯,在makefile中加入一行:
/* * framework/base/core/java/android/os/IMyManager.aidl */ package android.os; interface IMyManager { }
/* * framework/base/Android.mk */ core/java/android/os/IPowerManager.aidl \ #Add the following one line: core/java/android/os/IMyManager.aidl \
到此為止第一步驟完成了,你已經完成了一隻system service,接下來照以下步驟就可以編譯出系統映像檔(system image),把此映像檔用模擬器啟動即可在ddms中看到你的service在被初始化時所產生的訊息了:
~/android$ . build/envsetup.sh ~/android$ lunch full-eng ~/android$ make -j4
第二章 ServiceManager的API實作與編譯
接下來我們先修改一下MyService讓他能夠提供一些簡單的運算服務,以一個最簡單的例子,我們希望這個service可以提供app一個加法的服務,讓app可以透過一套API來存取此加法服務,以下為修改過的MyService.java
/* * framework/base/services/java/com/android/server/MyService.java */ package com.android.server; import android.util.Log; import android.os.IMyManager; class MyService extends IMyManager.Stub { final String TAG = "MyService"; public MyService() { Log.i(TAG,"MyService is constructed!"); } //A functionality of the service public int add(int a, int b) { Log.i(TAG,"MyService performs the add()"); return a+b; } }
由於app本身無法直接跟system service傳遞資料,因此我們需要透過Android Interface Defination Language來作為兩個processes之間溝通的介面(Inter-Process Communication),詳細的IPC機制是透過IBinder來實作的,但這裡我就簡單帶過了.
首先修改上一步驟中幾乎為空的IMyManager.aidl:
/* * framework/base/core/java/android/os/IMyManager.aidl */ package android.os; interface IMyManager { int add(int a, int b); }
為了讓app開發者可以很輕鬆的呼叫這個介面上所定義的函式,我們需要提供一個service manager讓app開發者可以import,因此新增以下檔案:
/* * framework/base/core/java/android/os/MyManager.java */ package android.os; import android.util.Log; public class MyManager { final String TAG = "MyManger"; private IMyManager mService; public MyManager(IMyManager service) { mService = service; } public int getSum(int a, int b) { try{ Log.i(TAG, "Try to call the add function of MyService..."); return mService.add(a,b); } catch(RemoteException e) { e.printStackTrace(); return 0; } } }
MyManager提供app開發者一套API,讓開發者可以直接透過此manager來呼叫遠端service的sum函式,以此程式為例子,app透過MyManager的getSum API直接呼叫MyService的sum功能.
然而,此manager尚未定義其被建構之處,以及如何取得IMyManager的reference,因此我們還需要註冊此Manager讓他能夠透過AIDL與service來溝通,修改如下檔案:
/* * framework/base/core/java/android/app/ContextImpl.java */ //add the packages import android.os.IMyManager; import android.os.MyManager; registerService(POWER_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(POWER_SERVICE); IPowerManager service = IPowerManager.Stub.asInterface(b); return new PowerManager(service, ctx.mMainThread.getHandler()); }}); //Add the following codes registerService("myservice", new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService("myservice"); IMyManager service = IMyManager.Stub.asInterface(b); return new MyManager(service); }});
到此為止已經成功把service manager的API新增了,接下來就要編譯出兩樣產出,分別是模擬器用的系統映像檔,以及修改過API的android.jar,透過以下指令來編譯:
~/android$ . build/envsetup.sh ~/android$ make update-api ~/android$ lunch sdk-eng ~/android$ make sdk -j4
這裡有幾點需要說明的,由於我們有動到framework的API,因此編譯之前必須先下make update-api, 此外,在4.0以後若是要編譯SDK,則必須透過新的lunch來達成(這點讓我相當不習慣,舊版直接下make sdk就可以了),經過make sdk後,android.jar和system.img都會被編譯產生!!但建議sdk編譯出來後就改回用make -j4了,避免odex開機失敗,要make兩次
編譯完成後,會產生out/host/linux-x86/sdk/android-sdk/platforms/android-4.0/android.jar,我們將可以替換舊的android.jar,如此一來便能在APP中呼叫MyManager並透過他向MyService進行呼叫,以下為一個Android APP,呼叫了我們定義的加法函式:
package cs.nthu; import android.util.Log; import android.os.MyManager;//Our inserted package import android.app.Activity; import android.os.Bundle; public class MyApp extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //Access the MyManager MyManager mm = (MyManager)this.getSystemService("myservice"); //Call MyService through MyManager Log.i("MyApp", "1+3=" + mm.getSum(1, 3)); } }
第三章 郵包(parcel)與IPC
到目前為止,我們已經擁有了撰寫基本系統服務的能力了,但是只會這樣其實還不夠,由於Android採用郵包來傳遞不同process之間的Java物件,只有實作了(implements)可郵包化(parcelable)的物件才可以定義在AIDL當中,亦即只有可郵包化的物件才能夠進行IPC.然而我們在實作一個service時,往往有必要傳遞一些自定義型別的物件,因此接下來的練習中,我們將示範如何實作一個可郵包化的類別.
首先我們先訂出一個MyType,並且會讓此型別的物件在AIDL中作為參數傳遞的型別:
/* * framework/base/core/java/android/os/MyType.java */ package android.os; public class MyType { private int value = 0; public MyType(int n) { value = n; } public int getValue() { return value; } public void setValue(int n) { value = n; } }
接下來我們在MyService當中新增一個函式addMyType, 在此函式中會將兩個自訂類別的物件的value相加,並回傳一個加總後的自訂類別物件:
/* * framework/base/services/java/com/android/server/MyService.java */ package com.android.server; import android.util.Log; import android.os.IMyManager; import android.os.MyType; class MyService extends IMyManager.Stub { final String TAG = "MyService"; public MyService() { Log.i(TAG,"MyService is constructed!"); } //A functionality of the service public int add(int a, int b) { Log.i(TAG,"MyService performs the add()"); return a+b; } //Self-defined type functions public MyType addMyType(MyType a, MyType b) { Log.i(TAG, "MyService performs the addMyType()"); return new MyType(a.getValue() + b.getValue()); } }
為了可以遠端呼叫此函式,我們當然要修改AIDL讓mananger可以看的見此函式:
/* * framework/base/core/java/android/os/IMyManager.aidl */ package android.os; import android.os.MyType; interface IMyManager { int add(int a, int b); MyType addMyType(in MyType a, in MyType b); }
再傳入參數的地方有一個in,這個意思代表說傳入的物件不需要回傳,相對的如果設定成out的話,代表此物件只須被傳出不需傳入,如果要同時傳入傳出的話就要用inout.(這個地方很重要,像這個例子其實可以把a或b設定成inout,然後就把a+b後的值透過a回傳)
接下來再service manager當中新增此函式的API,以便讓APP開發者可以呼叫此函式:
/* * framework/base/core/java/android/os/MyManager.java */ public MyType getMyTypeSum(MyType a, MyType b) { try{ Log.i(TAG, "Try to call the addMyType function of MyService..."); return mService.addMyType(a,b); } catch(RemoteException e) { e.printStackTrace(); return new MyType(0); } }
到目前為止看似很順利,但當你實際編譯的時候會出錯原因就在於MyType這個型別並未implements parcelable,所以是無法順利完成IPC的,因此我們新增幾個method於MyType當中:
/* * framework/base/core/java/android/os/MyType.java */ package android.os; import android.os.Parcelable; public class MyType implements Parcelable { private int value = 0; public MyType(int n) { value = n; } public int getValue() { return value; } public void setValue(int n) { value = n; } //Implements the "unpacking" procedure public static final Parcelable.Creator<MyType> CREATOR = new Parcelable.Creator<MyType>(){ public MyType createFromParcel(Parcel in){ return new MyType(in); } public MyType[] newArray(int size){ return new MyType[size]; } }; //Implements the "packing" procedure public void writeToParcel(Parcel out, int flags) { out.writeInt(value); } //Implements the private constructor for "unpacking" private MyType(Parcel in) { value = in.readInt(); } public int describeContents(){ return 0; } }
後面新增的四個method都是為了要implements parcelable所增加的,主要是透過parcel.write和parcel.read來寫入(讀取)郵包,藉以達成IPC的需求,這裡要特別注意的是讀寫郵包的時候,先寫入的要先讀(FIFO),這個例子只有包入一個int,實際上各種primarily type都是可以直接讀寫郵包的.
最後為了要讓IMyManager.aidl可以認識我們自定義的類別,所以必須額外寫一個檔案讓他辨認:
/* * framework/base/core/java/android/os/MyType.aidl */ package android.os; parcelable MyType;
到目前為止已經完成了新的函式功能,這個章節最後我們就以一個APP來做結尾吧:
package cs.nthu; import android.util.Log; import android.app.Activity; import android.os.Bundle; //Our inserted package import android.os.MyManager; import android.os.MyType; public class MyApp extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //Access the MyManager MyManager mm = (MyManager)this.getSystemService("myservice"); MyType ret = mm.getMyTypeSum(new MyType(1), new MyType(3)); Log.i("MyApp", "MyType(1) + MyType(3) = " + ret.getValue()); } }
第四章 範例: 實作launch time 服務 (待補)