概念:
android 四大組件之一,提供在後臺運行的服務,屬於計算型組件。
特點:
在後臺運行,無用戶界面,生命週期長。
啓動方式
startservice:
不與Activity綁定,啓動之後會無限期的運行下去,除非遇到內存低情況被回收,需要調用stopService或stopSelf纔會停止。
- **生命週期 :**onCreate(只執行一次)-onStartCommand-服務運行-onDestory()
- onCreate 只調用一次,onStartCommand可以調用多次(調用的次數是startService的次數),其他方法只能調用一次。
- onStartCommand必須返回一個整數= 描述系統在殺死服務後如何繼續運行。
- START_STICK:重建服務,調用onStartCommand,但不會傳入上次未發送完的intent,而是使用null intent (所以需要檢查)。除非還有啓動服務的intent未發送完會繼續發送。適用於媒體播放器,不需要執行命令但需要一直運行並隨時待命。
- START_NOT_STICK:不會重建服務,除非還存在未發送的intent。但服務不再必需的時候,這個是避免重啓服務的最安全的方法。
- START_REDELIVER_INTENT:重建服務,並且任何未傳入的intent的都會被依次送入。 適用於需要立即回覆工作的活躍服務,比如下載文件。
- **操作:**創建一個Service繼承自service,在onStartCommand操作。在context中通過intent方式是啓動服務。
- 只能開啓或停止服務,無法操作服務。
- 調用者退出後,服務仍然存在。
bindservice
與Activity綁定,綁定之後在後臺運行,除非調用unBindService或綁定的Context被銷燬。
-
**生命週期:**onCreate(只執行一次)-onBind-onUnbind-onDestory ,如果先調用了startservice,已經onCreate,也不會再次調用。
-
**操作:**創建一個Binder繼承Binder,通過onBind返回Binder對象,在context中通過serviceConnection取到binder對象並調用bindner的方法,bindService中傳入ServiceConnection建立連接。
//在service中自定義Binder class MyBinder extends Binder{ public void startDownload(){ Log.d(TAG, "startDownload: "); } } //在onBind方法中返回Binder private MyBinder myBinder = new MyBinder(); @Override public IBinder onBind(Intent intent) { Log.d(TAG, "onBind: "); // TODO: Return the communication channel to the service. return myBinder; } //在Activity 中創建serviceconnection private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { myBinder = (MyService.MyBinder) service; myBinder.startDownload(); } @Override public void onServiceDisconnected(ComponentName name) { } }; //綁定服務 bindService(new Intent(MainActivity.this,MyService.class),connection,BIND_AUTO_CREATE);
-
除了可以開啓或停止服務,還可以獲得Service對象,對Service進行操作。
-
調用者退出後Service隨着調用者退出而銷燬。
服務的銷燬方式:
- 如果是startservice啓動的調用stop service就可以銷燬,或者在內存極低的情況下,被回收銷燬。
- 如果是通過bindservice啓動的服務調用unbindservice 銷燬服務;但是同時startservice和bindservice需要unbindservice及再次調用stopservice纔會銷燬服務,即當service與activity綁定的情況下,service不再綁定且service處於靜止狀態時。 另外當service的調用者推出時也會銷燬服務。
前臺服務:
如果需要service一直保持運行狀態(service保活),則可以考慮前臺service。效果類似於通知。在service的onStartCommand方法中修改.(需要添加FOREGROUND_SERVICE權限)
//8.0 適配通知欄
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
NotificationChannel channel = new NotificationChannel("service","test", NotificationManager.IMPORTANCE_DEFAULT);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
assert manager != null;
manager.createNotificationChannel(channel);
NotificationCompat.Builder service = new NotificationCompat.Builder(this, "service");
service.setContentTitle("執行前臺服務的通知");
service.setContentText("執行前臺服務的內容");
service.setSmallIcon(R.mipmap.ic_launcher);
notification = service.getNotification();
}else {
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
Notification.Builder builder = new Notification.Builder(this);
builder.setContentTitle("執行前臺服務的通知");
builder.setContentText("執行前臺服務的內容");
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentIntent(pendingIntent);
notification = builder.getNotification();
}
//設置爲前臺服務:參數1:唯一的通知標識。參數二:通知
startForeground(1,notification);
service與Thread的區別
兩者無聯繫。雖然都是在後臺執行一下耗時的操作,但是service是運行在主線程的,Thread是開啓的子線程運行。
遠程服務跨進程通信
遠程服務的創建
在註冊服務的地方添加屬性:
android:process=":remote"
遠程服務是 運行在另一個進程。此時服務需要與Activity綁定的話需要使用AIDL。
同一個工程下使用:
-
創建一個AIDL文件,在此文件中定義方法,構建項目會自動生成一個接口文件。改文件是IBinder的子類。
-
在service文件獲取該子類,並在onBinder方法返回。(進程S)
@Override public IBinder onBind(Intent intent) { Log.d(TAG, "onBind: "); // TODO: Return the communication channel to the service. return mBinder; } IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() { @Override public String toUppercase(String aString) throws RemoteException { if (!TextUtils.isEmpty(aString)){ return aString.toUpperCase(); } return null; } };
-
在activity中(進程C)修改
serviceconnection
-
private IMyAidlInterface iMyAidlInterface; //是IBinder的子類 ,AIDL ,mainactivity 和myservice 是不同的進程,此時實現了跨進程通信 private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service); try { String hello_world = iMyAidlInterface.toUppercase("hello world"); Log.d(TAG, "onServiceConnected: "+hello_world); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } };
因此實現了跨進程通信。
不同工程下使用:
把AIDL文件和Activity的內容移至另一個工程即可。使用隱式跳轉。在service中添加過濾器,在
Intent intent = new Intent(定義的action);
bindService(intent, connection, BIND_AUTO_CREATE);
Binder機制
概念:
Binder實現IBinder接口,Android 中實現跨進程通信的機制。
跨進程通信的原因:
爲了數據的獨立性和安全性,一個進程不能訪問另一個進程的數據,即Android的進程是相互獨立、隔離的。如果需要讀取另一個進程的數據就需要IPC機制。
IPC機制基本原理
- 進程的空間分爲用戶空間及系統空間,系統空間是全部進程公用的,用戶空間是每個進程私有的,當需要跨進程通信時,進程1通過系統調用,將需要傳遞的數據複製到系統空間,由系統空間喚醒進程2的接收線程,通過系統調用將數據發送到進程2的用戶空間(第二次複製),從而完成跨進程通信。
Binder機制優點:
傳統的跨進程(socket)通信缺點:1)複製兩次,費時間。 2)接收數據的緩存有接收方提供,但接收方不知道需要提供多大合適。
而Binder機制調用系統函數mmap()內存映射,只需要複製一次即可。
Binder機制原理
利用Binder驅動創建接收緩存區並實現地址映射關係:根據需映射的接收進程信息,實現內核緩存區和接收進程用戶空間地址同時映射到同1個共享接收緩存區中。
Binder機制模型步驟
- 向驅動申請SM,驅動同意後成爲SM,管理service。
- Client與Server與SM的通信都是通過Binder驅動,他們不可以直接與SM交互。
- 註冊服務:
- Server進程向Binder驅動發起註冊服務請求。
- Binder驅動將註冊請求發送給service manager進程。
- service manager進程添加該service進程,即註冊服務。
- 獲取服務:
- client進程傳遞需要獲取的服務名稱,向Binder驅動發起獲取服務請求。
- Binder驅動將請求轉發給SM。
- SM查找到client需要的Server對應的服務信息。
- 通過Binder驅動將上述信息返回給client進程。
- 使用服務:
- Binder驅動爲實現跨進程做準備(調用系統mmap()函數)實現內存映射。
- Binder驅動創建一塊接收緩存區
- 實現地址映射關係:通過SM進程裏的server信息找到server進程,實現內核緩存區 和 server進程用戶空間地址 同時映射到同一接收緩存中。
- client進程將參數數據發送到server進程:
- client進程通過系統調用將數據發送到內核緩存區。 (存在內存映射關係,相當於也發送到了server進程的用戶空間地址)
- Binder驅動通知server進程進行解包。
- server進程根據client進程要求調用目標方法:
- 收到Binder驅動通知後,server進程從線程池中取出線程,進行數據解包和調用目標方法。
- 將最終執行結果寫入到自己的共享內存中。
- server進程將目標方法結果返回給client進程:
- 由於存在內存映射關係,當server將結果寫入自己的內存中,Binder驅動通知client進程獲取返回結果(沒有起用新線程,之前發送數據的線程被掛起)
- client進程通過系統調用從內核緩存區接收server進程返回的數據。
- Binder驅動爲實現跨進程做準備(調用系統mmap()函數)實現內存映射。
- 註冊服務:
服務的保活方式
- 在onStartCommand方法中返回START_STICK,在服務被殺死的時候會重新啓動。
- 把service的優先級(1000)是最高優先級,也可以把服務改爲前臺服務,在系統內存不足時不會被回收。
- 使用AIDL跨進程機制雙進程保護。
- 使用JobService
- 使用自定義廣播,在應用退出時(發送廣播啓動服務)
- 使用系統廣播,比如開機的時候,點擊home鍵的時候啓動廣播。
IntentService
定義:
- Intent Service是繼承自service並處理異步請求的服務。內部有一個工作線程處理耗時操作。
- 啓動Intent Service 執行完成後會自動停止,不需要調用stopself。
- 可以多次啓動Intent service,每一個耗時操作會以工作隊列的方式在intentservice的onHandleIntent回調中執行,並且是依次執行。
- 內部封裝了Handler Thread 和Handler實現的。
**生命週期:**onCreate(只執行一次)—onStartCommand—onStart–onHandleIntent–onDestory
優勢與作用:
- 內部有一個繼承於Thread的HandleThread線程,綁定了一個Looper,支持串行執行消息隊列中的消息。
- IntentService比一般線程優先級高,適用於處理優先級較高的耗時操作。也因爲具有較高的優先級,不容易被殺死,可靠性強。
- 省去了在service裏開啓線程的麻煩,也不用手動關閉服務。
使用場景:
- 一項任務需要幾個子任務進行,幾個子任務按順序進行纔算完成。如在後臺默默進行耗時的上傳和下載操作。
源碼分析
當調用IntentService時,會回調到onCreate方法中,在該方法中創建了一個HandleThread,並啓動這個線程,也創建了一個servicehandler。
HandleThread,繼承於Thread類,是一個線程,當線程處於執行狀態時,創建了一個looper,並調用loop方法開始無限消息輪詢過程。
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
//....省略部分代碼....
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
//核心代碼
@Override
public void run() {
mTid = Process.myTid();
//創建looper
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
//開始無限循環消息
Looper.loop();
mTid = -1;
}
//....省略部分代碼....
}
創建了一個servicehandler處理消息
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//調用onHandleIntent
onHandleIntent((Intent)msg.obj);
//並停止服務---因此不用手動停止服務
stopSelf(msg.arg1);
}
}
接着調用onStartCommand 喝onStart,在自定義的intentservice中不用重寫這兩個方法。
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
//獲取到intent,調用handler的發送消息
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
發送消息之後,loop輪詢到消息並分發給目標處理消息,調用onHandleIntent.是一個抽象方法,必須重寫處理耗時任務。
問題
-
啓動intent service不需要創建新的線程?
在onCreate方法裏創建 了HandlerThread,這是一個繼承自Thread的類,在onCreate中也獲取了Looper進行工作。本來有一個線程,無需創建線程。
-
爲什麼不建議通過 bindService() 啓動 IntentService?
intent service源碼中的onBind方法默認返回null,不會回調到onHanldeIntent方法中,沒有使用到intent service的優點,與普通service無區別。
-
爲什麼多次啓動 IntentService 會順序執行事件,停止服務後,後續的事件得不到執行?
內部使用的是handler機制,多次啓動intent service不會重新創建新的線程和服務,而是把消息加到消息隊列裏,消息隊列是一個單鏈表,消息入列時的操作是根據時間入列,所以會按順序執行。停止服務後,會將消息隊列的消息清空,因此後續的事件得不到執行。
-
如何實現讓IntentService任務可以並行執行?
創建多個intent service。
demo地址祥見:github:https://github.com/MarinaTsang/sqliteAndContentprovider/tree/master/sevicetest