如何讓android的service一直在後臺執行
阿新 • • 發佈:2019-01-23
1. 把service和activity分開,讓service開機啟動。設定一個broadcast receiver接受開機訊號, 使用RECEIVE_BOOT_COMPLETED的permission, 然後啟動service。activity啟動後繫結到service上, 通過ipc機制通訊,acitivity結束後鬆綁。注意安裝後要手動啟動service,不會自動啟動,之後重啟手機後才會隨開機啟動。
2. 在記憶體低的時候系統會自動清理程序,這時候後臺service可能會被殺掉。可以在onStartCommand中返回START_STICKY,這樣系統有足夠多資源的時候,就會重新開啟service。
按使用方式分類:
類別 區別
startService 啟動的服務 主要用於啟動一個服務執行後臺任務,不進行通訊。停止服務使用stopService
bindService 啟動的服務 該方法啟動的服務要進行通訊。停止服務使用unbindService
startService 同時也 bindService 啟動的服務 停止服務應同時使用stepService與unbindService
以上面三種方式啟動的服務其生命週期也有區別,將在隨後給出。
2、Service 與 Thread 的區別
很多時候,你可能會問,為什麼要用 Service,而不用 Thread 呢,因為用 Thread 是很方便的,比起 Service
也方便多了,下面我詳細的來解釋一下。
1). Thread:Thread 是程式執行的最小單元,它是分配CPU的基本單位。可以用 Thread 來執行一些非同步的操作。
2). Service:Service 是android的一種機制,當它執行的時候如果是Local Service,那麼對應的 Service
是執行在主程序的 main 執行緒上的。如:onCreate,onStart 這些函式在被系統呼叫的時候都是在主程序的 main 執行緒上執行的。如果是Remote
Service,那麼對應的 Service 則是執行在獨立程序的 main 執行緒上。因此請不要把 Service
理解成執行緒,它跟執行緒半毛錢的關係都沒有!
既然這樣,那麼我們為什麼要用 Service 呢?其實這跟 android 的系統機制有關,我們先拿 Thread 來說。Thread 的執行是獨立於
Activity 的,也就是說當一個 Activity 被 finish 之後,如果你沒有主動停止 Thread 或者 Thread 裡的 run
方法沒有執行完畢的話,Thread 也會一直執行。因此這裡會出現一個問題:當 Activity 被 finish 之後,你不再持有該 Thread
的引用。另一方面,你沒有辦法在不同的 Activity 中對同一 Thread 進行控制。
舉個例子:如果你的 Thread 需要不停地隔一段時間就要連線伺服器做某種同步的話,該 Thread 需要在 Activity
沒有start的時候也在執行。這個時候當你 start 一個 Activity 就沒有辦法在該 Activity 裡面控制之前建立的
Thread。因此你便需要建立並啟動一個 Service ,在 Service 裡面建立、執行並控制該 Thread,這樣便解決了該問題(因為任何
Activity 都可以控制同一 Service,而系統也只會建立一個對應 Service 的例項)。
因此你可以把 Service 想象成一種訊息服務,而你可以在任何有 Context 的地方呼叫
Context.startService、Context.stopService、
Context.bindService,Context.unbindService,來控制它,你也可以在 Service 裡註冊
BroadcastReceiver,在其他地方通過傳送 broadcast 來控制它,當然這些都是 Thread 做不到的。
3、Service的生命週期
onCreate onStart onDestroy onBind
1). 被啟動的服務的生命週期:如果一個Service被某個Activity 呼叫 Context.startService
方法啟動,那麼不管是否有Activity使用bindService繫結或unbindService解除繫結到該Service,該Service都
在後臺執行。如果一個Service被startService
方法多次啟動,那麼onCreate方法只會呼叫一次,onStart將會被呼叫多次(對應呼叫startService的次數),並且系統只會建立
Service的一個例項(因此你應該知道只需要一次stopService呼叫)。該Service將會一直在後臺執行,而不管對應程式的
Activity是否在執行,直到被呼叫stopService,或自身的stopSelf方法。當然如果系統資源不足,android系統也可能結束服 務。
2). 被繫結的服務的生命週期:如果一個Service被某個Activity 呼叫 Context.bindService 方法繫結啟動,不管呼叫
bindService 呼叫幾次,onCreate方法都只會呼叫一次,同時onStart方法始終不會被呼叫。當連線建立之後,Service將會一直執行,除非呼叫
Context.unbindService 斷開連線或者之前呼叫bindService 的 Context
不存在了(如Activity被finish的時候),系統將會自動停止Service,對應onDestroy將被呼叫。
3).
被啟動又被繫結的服務的生命週期:如果一個Service又被啟動又被繫結,則該Service將會一直在後臺執行。並且不管如何呼叫,onCreate
始終只會呼叫一次,對應startService呼叫多少次,Service的onStart便會呼叫多少次。呼叫unbindService將不會停止
Service,而必須呼叫 stopService 或 Service的 stopSelf 來停止服務。
4). 當服務被停止時清除服務:當一個Service被終止(1、呼叫stopService;2、呼叫stopSelf;3、不再有繫結的連線(沒有被啟
動))時,onDestroy方法將會被呼叫,在這裡你應當做一些清除工作,如停止在Service中建立並執行的執行緒。
特別注意:
1、你應當知道在呼叫 bindService 繫結到Service的時候,你就應當保證在某處呼叫 unbindService 解除繫結(儘管
Activity 被 finish 的時候繫結會自 動解除,並且Service會自動停止);
2、你應當注意 使用 startService 啟動服務之後,一定要使用
stopService停止服務,不管你是否使用bindService;
3、同時使用 startService 與 bindService 要注意到,Service
的終止,需要unbindService與stopService同時呼叫,才能終止 Service,不管 startService 與 bindService
的呼叫順序,如果先呼叫 unbindService 此時服務不會自動終止,再呼叫 stopService 之後服務才會停止,如果先呼叫 stopService
此時服務也不會終止,而再呼叫 unbindService 或者 之前呼叫 bindService 的 Context 不存在了(如Activity 被
finish 的時候)之後服務才會自動停止;
4、當在旋轉手機螢幕的時候,當手機螢幕在“橫”“豎”變換時,此時如果你的 Activity 如果會自動旋轉的話,旋轉其實是 Activity
的重新建立,因此旋轉之前的使用 bindService 建立的連線便會斷開(Context 不存在了),對應服務的生命週期與上述相同。
5、在 sdk 2.0 及其以後的版本中,對應的 onStart 已經被否決變為了 onStartCommand,不過之前的 onStart
任然有效。這意味著,如果你開發的應用程式用的 sdk 為 2.0 及其以後的版本,那麼你應當使用 onStartCommand 而不是 onStart。
4、startService 啟動服務
想要用 startService 啟動服務,不管Local 還是 Remote 我們需要做的工作都是一樣簡單。當然要記得在
Androidmanifest.xml 中註冊 service。
根據上面的生命週期,我們便會給出 Service 中的程式碼框架:
package com.newcj.test;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class LocalService1 extends Service {
/**
* onBind 是 Service 的虛方法,因此我們不得不實現它。
* 返回 null,表示客服端不能建立到此服務的連線。
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
對應生命週期系統回撥函式上面已經說明,在對應地方加上適當的程式碼即可。下面是啟動與停止 Service 的程式碼:
// 啟動一個 Activity
startActivity(new Intent(this, LocalService1.class));
...
// 停止一個 Activity
stopService(new Intent(this, LocalService1.class));
對應的 Intent 為標誌服務類的 Intent。
5、Local 與 Remote 服務繫結
同樣記得在 Androidmanifest.xml 中註冊 service
1). Local 服務繫結:Local 服務的繫結較簡單,首先在 Service 中我們需要實現 Service 的抽象方法
onBind,並返回一個實現 IBinder 介面的物件。
Service 中的程式碼:
package com.newcj.test;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
public class LocalService extends Service {
/**
* 在 Local Service 中我們直接繼承 Binder 而不是 IBinder,因為 Binder 實現了 IBinder
介面,這樣我們可以少做很多工作。
* @author newcj
*/
public class SimpleBinder extends Binder{
/**
* 獲取 Service 例項
* @return
*/
public LocalService getService(){
return LocalService.this;
}
public int add(int a, int b){
return a + b;
}
}
public SimpleBinder sBinder;
@Override
public void onCreate() {
super.onCreate();
// 建立 SimpleBinder
sBinder = new SimpleBinder();
}
@Override
public IBinder onBind(Intent intent) {
// 返回 SimpleBinder 物件
return sBinder;
}
}
上面的程式碼關鍵之處,在於 onBind(Intent) 這個方法 返回了一個實現了 IBinder 介面的物件,這個物件將用於繫結Service 的
Activity 與 Local Service 通訊。下面是 Activity 中的程式碼:
package com.newcj.test;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
public class Main extends Activity {
private final static String TAG = "SERVICE_TEST";
private ServiceConnection sc;
private boolean isBind;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
sc = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LocalService.SimpleBinder sBinder = (LocalService.SimpleBinder)service;
Log.v(TAG, "3 + 5 = " + sBinder.add(3, 5));
Log.v(TAG, sBinder.getService().toString());
}
};
findViewById(R.id.btnBind).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
bindService(new Intent(Main.this, LocalService.class), sc,
Context.BIND_AUTO_CREATE);
isBind = true;
}
});
findViewById(R.id.btnUnbind).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(isBind){
unbindService(sc);
isBind = false;
}
}
});
}
}
在 Activity 中,我們通過 ServiceConnection 介面來取得建立連線 與
連線意外丟失的回撥。bindService有三個引數,第一個是用於區分 Service 的Intent 與 startService 中的 Intent
一致,第二個是實現了 ServiceConnection 介面的物件,最後一個是 flag 標誌位。有兩個flag,BIND_DEBUG_UNBIND 與
BIND_AUTO_CREATE,前者用於除錯(詳細內容可以檢視javadoc 上面描述的很清楚),後者預設使用。unbindService
解除繫結,引數則為之前建立的 ServiceConnection 介面物件。另外,多次呼叫 unbindService
來釋放相同的連線會丟擲異常,因此我建立了一個 boolean 變數來判斷是否 unbindService 已經被呼叫過。
執行結果:
2). Remote 服務繫結:Remote 的服務繫結由於服務是在另外一個程序,因此需要用到 android 的 IPC
機制。這將又是一個很長的話題,因此,我打算寫另外一篇 android 的 IPC 機制分析 ,並在其中進行詳述,然後在這裡更新連結,敬請關注。
特別注意:
1、Service.onBind如果返回null,則呼叫 bindService 會啟動 Service,但不會連線上 Service,因此
ServiceConnection.onServiceConnected 不會被呼叫,但你任然需要使用 unbindService 函式斷開它,這樣
Service 才會停止。
6、建立前臺服務
前臺服務的優點上面已經說明,但設定服務為前臺服務,我們需要注意在 sdk 2.0 及其以後版本使用的方法是 startForeground 與
stopForeground,之前版本使用的是 setForeground ,因此如果你應用程式的最低執行環境要求是
2.0,那麼這裡可以直接運用新方法,如果執行環境是2.0以下,那麼為了保證向後相容性,這裡必須使用反射技術來呼叫新方法。
下面是我仿照 ApiDemos 重新敲的程式碼,對某些地方進行了修改,因此更具有說明性:
特別注意:
1、使用 startForeground ,如果 id 為 0 ,那麼 notification 將不會顯示。
7、在什麼情況下使用 startService 或 bindService 或 同時使用startService 和 bindService
如果你只是想要啟動一個後臺服務長期進行某項任務那麼使用 startService 便可以了。如果你想要與正在執行的 Service
取得聯絡,那麼有兩種方法,一種是使用 broadcast ,另外是使用 bindService ,前者的缺點是如果交流較為頻繁,容易造成效能上的問題,並且
BroadcastReceiver 本身執行程式碼的時間是很短的(也許執行到一半,後面的程式碼便不會執行),而後者則沒有這些問題,因此我們肯定選擇使用
bindService(這個時候你便同時在使用 startService 和 bindService 了,這在 Activity 中更新 Service
的某些執行狀態是相當有用的)。另外如果你的服務只是公開一個遠端介面,供連線上的客服端(android 的 Service
是C/S架構)遠端呼叫執行方法。這個時候你可以不讓服務一開始就執行,而只用 bindService ,這樣在第一次 bindService
的時候才會建立服務的例項執行它,這會節約很多系統資源,特別是如果你的服務是Remote Service,那麼該效果會越明顯(當然在 Service
建立的時候會花去一定時間,你應當注意到這點)。
8、在 AndroidManifest.xml 裡 Service 元素的常見選項
android:name ------------- 服務類名
android:label -------------- 服務的名字,如果此項不設定,那麼預設顯示的服務名則為類名
android:icon -------------- 服務的圖示
android:permission ------- 申明此服務的許可權,這意味著只有提供了該許可權的應用才能控制或連線此服務
android:process ---------- 表示該服務是否執行在另外一個程序,如果設定了此項,那麼將會在包名後面加上這段字串表示另一程序的名字
android:enabled ---------- 如果此項設定為 true,那麼 Service 將會預設被系統啟動,不設定預設此項為
false
android:exported --------- 表示該服務是否能夠被其他應用程式所控制或連線,不設定預設此項為 false