Service的2個分類以及各自的生命周期
通過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ù)端代碼:
- 在Service中, 聲明一個
private Binder mBinder = new IBookManager.Stub() {
//實現(xiàn)對aidl中的方法
//IBookManager是通過aidl生成的class新荤, Stub是生成class中的內(nèi)部類
}
- 在onBind()方法中把這個mBinder返回給客戶端使用.
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
客戶端代碼:
- 創(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文件生成的類.
- 使用ServiceConnection的對象和Intent對象, 連接服務(wù)端Service.
Intent intent = new Intent(this, BookManagerService.class);
//綁定Service, 成功后ServiceConnection mConnection的onServiceConnected()方法被回調(diào).
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- 解除綁定調(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)用連接.
- 可以在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---------