Service 的開發(fā)精要

Service的2個分類以及各自的生命周期
service.png

通過startService() 啟動的服務(wù)叫做本地服務(wù), 即Local Service, 客戶端只是能啟動service, service一般新建線程干自己的事, 無法調(diào)用service的方法進(jìn)行交互.
通過bindService() 啟動的服務(wù)叫做遠(yuǎn)程服務(wù)惨奕,即Remote Service, 也叫做基于aidl的服務(wù), 支持客戶端和服務(wù)端可以相互調(diào)用對方的方法.

關(guān)鍵方法暗示的信息

小技巧: 看繼承自Service的類時会烙,如果onBind()返回null, 那就知道這個Service就是本地服務(wù)婿着, 客戶端只能通過startService()啟動, 不能通過bindService() 啟動.

eg.
PrecacheService.java
public class PrecacheService extends Service {
    /** PrecacheService does not support binding. */
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
本地服務(wù)的啟動和停止, 以及int onStartComand()的返回值.

啟動:

Intent intent = new Intent(context, DownloadService.class);
context.startService(intent);

停止:

Intent intent = new Intent(context, DownloadService.class);
context.stopService(intent);

或 Service自己調(diào)用stopSelf() API.

    private void shutdownPrecaching() {
        mIsPrecaching = false;
        releasePrecachingWakeLock();
        stopSelf();
    }

int onStartComand()的返回值含義:

START_STICKY:
包含Service的進(jìn)程被異常kill掉谷徙,系統(tǒng)會自動重啟該服務(wù), 但是傳遞給onStartComand()的intent為null.

START_NOT_STICKY:
包含Service的進(jìn)程被異常kill掉,系統(tǒng)不會自動重啟該服務(wù).

START_REDELIVER_INTENT:
包含Service的進(jìn)程被異常kill掉幔亥,系統(tǒng)會自動重啟該服務(wù), 并且把之前保留的intent傳遞給onStartComand().

另外:

START_STICKY和 START_NOT_STICKY:當(dāng)進(jìn)程被殺死后onDestroy()是不會被執(zhí)行的粪般!
START_REDELIVER_INTENT :當(dāng)進(jìn)程被殺死后onDestroy()會被執(zhí)行!
遠(yuǎn)程服務(wù)的基本使用方法

客戶端和服務(wù)端共享完全相同內(nèi)容的aidl文件闸拿,里面定義服務(wù)端暴露給客戶端可以調(diào)用的API空盼, 各自通過IDE生成其對應(yīng)的java文件.
服務(wù)端代碼:

  1. 在Service中, 聲明一個
private Binder mBinder = new IBookManager.Stub() {
//實現(xiàn)對aidl中的方法
//IBookManager是通過aidl生成的class新荤, Stub是生成class中的內(nèi)部類
}
  1. 在onBind()方法中把這個mBinder返回給客戶端使用.
@Override
public IBinder onBind(Intent intent) {
    return mBinder;
}

客戶端代碼:

  1. 創(chuàng)建一個實現(xiàn)了ServiceConnection接口的對象揽趾, 實現(xiàn)里面的2個方法,
    onServiceConnected()和onServiceDisconnected().
    在onServiceConnected()中把服務(wù)端返回的mBinder對象, 轉(zhuǎn)換為aidl生成類的對象苛骨, 之后就可以通過這個對象訪問服務(wù)端方法了.
public void onServiceConnected(ComponentName className, IBinder service) {
    IBookManager bookManager = IBookManager.Stub.asInterface(service);
    mRemoteBookManager = bookManager;
}
其中篱瞎, IBookManager就是aidl文件生成的類.
  1. 使用ServiceConnection的對象和Intent對象, 連接服務(wù)端Service.
Intent intent = new Intent(this, BookManagerService.class);
//綁定Service, 成功后ServiceConnection mConnection的onServiceConnected()方法被回調(diào).
bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 
  1. 解除綁定調(diào)用unbindService()
context.unbindService(mConnection);
不要在onServiceConnected中直接調(diào)用遠(yuǎn)程方法

因為onServiceConnected()運行在UI線程苟呐, 調(diào)用的遠(yuǎn)程方法可能是個耗時操作,無法預(yù)知,
因此不能直接調(diào)用俐筋, 避免ANR的發(fā)生.

如何實現(xiàn)服務(wù)端調(diào)用客戶端的方法

使用RemoteCallbackList類
在aidl中提供注冊API牵素, 客戶端把自己的一個對象傳遞給服務(wù)端,
服務(wù)端通過RemoteCallbackList類型的一個對象保存所有的客戶端對象,
通過遍歷RemoteCallbackList對象就可以調(diào)用客戶端的方法.

private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList

這樣就實現(xiàn)了雙向調(diào)用.

服務(wù)端意外停止后, 客戶端應(yīng)該如何處理

創(chuàng)建一個IBinder.DeathRecipient接口的對象澄者, 并實現(xiàn)里面的binderDied()方法.
在客戶端里去調(diào)用IBinder的linkToDeath()注冊笆呆,
這樣服務(wù)端意外中止后, binderDied()方法會被回調(diào).

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
    Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());
    if (mRemoteBookManager == null)
        return;
    mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
    mRemoteBookManager = null;
    // TODO:這里重新綁定遠(yuǎn)程Service
}
};

mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);

同時粱挡, 客戶端的onServiceDisconnected()在服務(wù)端意外中止時也會被回調(diào),
只是區(qū)別在于onServiceDisconnected()運行在UI線程, binderDied()運行在Binder線程池中的線程.
服務(wù)端意外中止時, 客戶端的處理方式一般是重新去bindService().

權(quán)限認(rèn)證

保證服務(wù)端的service只準(zhǔn)許被公司內(nèi)部應(yīng)用連接.

  1. 可以在Service的onBind()中調(diào)用checkCallingOrSelfPermission()檢查客戶端的AndroidManifest.xml是否聲明了特定權(quán)限
@Override
public IBinder onBind(Intent intent) {
    int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
    Log.d(TAG, "onbind check=" + check);
    if (check == PackageManager.PERMISSION_DENIED) {
        return null;
    }
    return mBinder;
}
<uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"/>

檢查沒有權(quán)限的話赠幕, 返回null.

2.也可以在服務(wù)端的onTransact()中檢查權(quán)限和客戶端的uid是否是公司內(nèi)部應(yīng)用
查看通過aidl生成的java類, 可以看到所有客戶端調(diào)用服務(wù)端的方法都是首先進(jìn)入到onTransact()询筏,
在onTransact()中再通過switch case調(diào)用到真正的aidl中的方法榕堰,所以可以通過復(fù)寫onTransact()進(jìn)行權(quán)限認(rèn)證.

//雙重安全性檢查.
Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
    int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
    Log.d(TAG, "check=" + check);
    if (check == PackageManager.PERMISSION_DENIED) {
        return false;
    }

    //通過getCallingUid()得到客戶端的uid, 再通過PackageManager根據(jù)uid查到package name進(jìn)行檢查.
    String packageName = null;
    String[] packages = getPackageManager().getPackagesForUid(
            getCallingUid());
    if (packages != null && packages.length > 0) {
        packageName = packages[0];
    }
    Log.d(TAG, "onTransact: " + packageName);
    if (!packageName.startsWith("com.ryg")) {
        return false;
    }

    //驗證都通過后屈留,才在super.onTransact()中調(diào)用真正的aidl方法.
    return super.onTransact(code, data, reply, flags);
}
項目中的應(yīng)用場景

本地服務(wù): DownloadService.java 實現(xiàn)下載功能
遠(yuǎn)程服務(wù): IUIAdapter.aidl 實現(xiàn)調(diào)用手機(jī)助手的方法

IntentService

作為本地服務(wù)來說, Service的幾個生命周期方法都是運行在UI線程测蘑,
所以通常的做法是再開一個新線程執(zhí)行實際的工作. 為簡化這個操作灌危,
framework提供了IntentService, 關(guān)鍵方法是onHandleIntent(Intent intent)碳胳,
在一個worker thread中處理傳進(jìn)來的intent.
使用IntentService的目的是為了簡化直接繼承Service作為本地服務(wù)時的操作.

chromium的MinidumpUploadService extends IntentService勇蝙,
用于上傳造成crash時的dump文件.

代碼實踐

完整的示例代碼可以看之前的總結(jié):
http://www.reibang.com/p/7e97076d8613 (IPC機(jī)制)
https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_2/src/com/ryg/chapter_2/aidl/BookManagerService.java
自己寫了一個app, 用于監(jiān)測指定進(jìn)程的cpu占用情況挨约, 并實現(xiàn)了雙向調(diào)用操作.
完整app代碼在:
https://github.com/AandK/PerformanceMonitor

生命周期的調(diào)用參考圖:
http://www.th7.cn/Program/Android/201411/307510.shtml

------------DONE---------

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末味混,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子诫惭,更是在濱河造成了極大的恐慌翁锡,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夕土,死亡現(xiàn)場離奇詭異馆衔,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)怨绣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進(jìn)店門角溃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人篮撑,你說我怎么就攤上這事减细。” “怎么了赢笨?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵未蝌,是天一觀的道長驮吱。 經(jīng)常有香客問我,道長树埠,這世上最難降的妖魔是什么糠馆? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮怎憋,結(jié)果婚禮上又碌,老公的妹妹穿的比我還像新娘。我一直安慰自己绊袋,他們只是感情好毕匀,可當(dāng)我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著癌别,像睡著了一般皂岔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上展姐,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天躁垛,我揣著相機(jī)與錄音,去河邊找鬼圾笨。 笑死教馆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的擂达。 我是一名探鬼主播土铺,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼板鬓!你這毒婦竟也來了悲敷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤俭令,失蹤者是張志新(化名)和其女友劉穎后德,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抄腔,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡探遵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了妓柜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片箱季。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖棍掐,靈堂內(nèi)的尸體忽然破棺而出藏雏,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布掘殴,位于F島的核電站赚瘦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏奏寨。R本人自食惡果不足惜起意,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望病瞳。 院中可真熱鬧揽咕,春花似錦、人聲如沸套菜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逗柴。三九已至蛹头,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間戏溺,已是汗流浹背渣蜗。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留旷祸,地道東北人耕拷。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像肋僧,于是被迫代替她去往敵國和親斑胜。 傳聞我的和親對象是個殘疾皇子控淡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容