Service 對於廣大的Android開發者來說算是耳熟能詳了,作為Android的四大元件之一,在我們的開發中也起著重要的作用,在Android面試中,Service相關的問題也是面試官問得比較多的,當別人問你,Service 到底是什麼的時候?你可能隨口就能答得上來,Service是一個在後臺執行長時間執行操作而不用提供使用者介面的應用元件,可由其他元件啟動,即使使用者切換到其他應用程式,Service 仍然在後臺繼續執行。沒錯,這是Service的概念,作為Android開發,或多或少都知道一些,但是不是每個人把所有知識點都瞭解得透測。前段時間由於專案中有用到Service,因此,本篇文章對Service的用法做一個總結。
Service
Service 和Activity 一樣同為Android 的四大元件之一,並且他們都有各自的生命週期,要想掌握Service 的用法,那就要了解Service 的生命週期有哪些方法,並且生命週期中各個方法回撥的時機和作用。有一點比較重要,Service 有兩種啟動方式,並且它的兩種啟動方式的生命週期是不一樣的。接下來分別看一下兩種啟動方式各自的生命週期方法。
startService方式啟動Service
當應用元件通過startService方法來啟動Service 時,Service 則會處於啟動狀態,一旦服務啟動,它就會在後臺無限期的執行,生命週期獨立於啟動它的元件,即使啟動它的元件已經銷燬了也不受任何影響,由於啟動的服務長期執行在後臺,這會大量消耗手機的電量,因此,我們應該在任務執行完成之後呼叫stopSelf()來停止服務,或者通過其他應用元件呼叫stopService 來停止服務。
startService 啟動服務後,會執行如下生命週期:onCreate() -> onStartCommand() -> onStart()(現在已經廢棄) -> onDestroy() 。具體看一下它的幾個生命週期方法:
onCreate() :首次啟動服務的時候,系統會呼叫這個方法,在onStartCommand 和 onBind 方法之前,如果服務已經啟動起來了,再次啟動時,則不會呼叫此方法,因此可以在onCreate 方法中做一些初始化的操作,比如要執行耗時的操作,可以在這裡建立執行緒,要播放音樂,可以在這裡初始化音樂播放器。
onStartCommand(): 當通過startService 方法來啟動服務的時候,在onCreate 方法之後就會回撥這個方法,此方法呼叫後,服務就啟動起來了,將會在後臺無限期的執行,直到通過stopService 或者 stopSelf 方法來停止服務。
onDestroy():當服務不再使用且將被銷燬時,系統將呼叫此方法。服務應該實現此方法來清理所有資源,如執行緒、註冊的偵聽器、接收器等。 這是服務接收的最後一個呼叫。
瞭解了這幾個生命週期方法後,我們就來寫一個簡單Service 。
要使用Service 就要通過繼承Service類(或者繼承IntentService ,侯文會講)來實現,程式碼如下:
public class SimpleService extends Service {
public static final String TAG = "SimpleService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"call onBind...");
return null;
}
@Override
public void onCreate() {
Log.i(TAG,"call onCreate...");
}
@Override
public void onStart(Intent intent, int startId) {
Log.i(TAG,"call onStart...");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"call onStartCommand...");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG,"call onDestroy...");
}
}複製程式碼
Service類寫好了之後,我們需要在清單檔案中註冊一下,在application標籤下:
<service android:name=".service.SimpleService"
android:exported="false"
/>複製程式碼
寫好了Service並且在清單檔案註冊之後,我們就可以啟動Service了,啟動Service和啟動Activity 差不多,通過Intent 來啟動,程式碼如下:
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
Intent intent = new Intent(this,SimpleService.class);
// 啟動服務
startService(intent);
break;
case R.id.stop_service:
Intent service = new Intent(this,SimpleService.class);
// 停止服務
stopService(service);
break;
}
}複製程式碼
如上圖介面所示,有2 個button ,分別是啟動服務和停止服務,分別點選 startService 和StopService 的button ,看看生命週期回撥方法列印的日誌:
小結:通過startService 方式啟動的服務,服務會無限期的在後臺執行,直到通過stopService 或 stopSelf 來終止服務。服務獨立於啟動它的元件,也就是說,當元件啟動服務後,元件和服務就在也沒有關係了,就算啟動它的元件被銷燬了,服務照樣在後臺執行。通過這種方式啟動的服務不好與元件之間通訊。
bindService 方式啟動服務
除了startService 來啟動服務之外,另外一種啟動服務的方式就是通過bindService 方法了,也就是繫結服務,其實通過它的名字就容易理解,繫結即將啟動元件和服務繫結在一起。前面講的通過startService 方式啟動的服務是與元件相獨立的,即使啟動服務的元件被銷燬了,服務仍然在後臺執行不受干擾。但是通過bindSerivce 方式繫結的服務就不一樣了,它與繫結元件的生命週期是有關的。如下:
多個元件可以繫結到同一個服務上,如果只有一個元件繫結服務,當繫結的元件被銷燬時,服務也就會停止了。如果是多個元件繫結到一個服務上,當繫結到該服務的所有元件都被銷燬時,服務才會停止。
bindService 繫結服務 和startService 的生命週期是不一樣,bindServie 的生命週期如下:onCreate -> onBind -> onUnbind ->onDestroy。其中重要的就是onBind 和onUnbind 方法。
onBind(): 當其他元件想通過bindService 與服務繫結時,系統將會回撥這個方法,在實現中,你必須返回一個IBinder介面,供客戶端與服務進行通訊,必須實現此方法,這個方法是Service 的一個抽象方法,但是如果你不允許繫結的話,返回null 就可以了。
onUnbind(): 當所有與服務繫結的元件都解除繫結時,就會呼叫此方法。
瞭解了這2個方法後,我們來看一下怎麼繫結一個服務。
1,首先,新增一個類 繼承 Binder ,在Binder 類中新增其他元件要與服務互動的方法,並在onBind() 方法中返回IBinder 例項物件:
public class SimpleService extends Service {
public static final String TAG = "SimpleService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"call onBind...");
//返回IBinder 介面物件
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG,"call onUnbind...");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
Log.i(TAG,"call onCreate...");
}
@Override
public void onStart(Intent intent, int startId) {
Log.i(TAG,"call onStart...");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"call onStartCommand...");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG,"call onDestroy...");
}
// 新增一個類繼承Binder
public class MyBinder extends Binder{
// 新增要與外界互動的方法
public String getStringInfo(){
return "呼叫了服務中的方法";
}
}
}複製程式碼
2, 繫結服務的時候,需要提供一個ServiceConnection 介面,在介面回撥中獲取Binder 物件,與服務進行通訊。
private SimpleService.MyBinder mMyBinder;
// 繫結/解除繫結 Service 回撥介面
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 繫結成功後回撥
//1 ,獲取Binder介面物件
mMyBinder = (SimpleService.MyBinder) service;
//2, 從服務獲取資料
String content = mMyBinder.getStringInfo();
// 3,介面提示
Toast.makeText(ServiceSimpleActivity.this,content,Toast.LENGTH_LONG).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 解除繫結後回撥
mMyBinder = null;
}
};複製程式碼
3,繫結和解除繫結服務
case R.id.bind_service:
Intent intent = new Intent(this,SimpleService.class);
// 繫結服務
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
break;
case R.id.unbind_service:
// 解除繫結服務
unbindService(mConnection);
break;複製程式碼
點選繫結按鈕,即繫結服務,並且在onServiceConnected 中得到MyBinder 物件,就可以通過這個物件和服務通訊了,例子中我們Toast 了從服務中獲取的字串:
生命週期方法呼叫如下:
可以看到,繫結服務的生命週期內依次呼叫了onCreate ,onBind,onUnbind 和 onDestroy 方法,只有中間兩個生命週期方法與startService 啟動服務是不同的。一張圖就能看清兩種方式的生命週期的異同:
tips: Service 的生命週期方法不同於Activity ,不需要呼叫超類的生命週期方法,如:不用呼叫 super.onCreate()
多個元件繫結同一服務
Service 是支援多個元件繫結在同一個服務的,第一個元件繫結是會回撥 onCreate 生命週期方法,後續的繫結只會呼叫onBind方法,返回IBinder給客戶端。當繫結在服務上的元件都呼叫unbindService 解除服務或者元件本身就已經被系統回收,那麼服務也就會被停止回收了,會回撥onUnbind 和 onDestroy 方法。
Service 與應用元件通訊的幾種方式
1,BroadcastReceiver
通過前文我們知道,startService方式啟動的服務在後臺,無限期地執行,並且與啟動它的元件是獨立的,啟動Service 之後也就與啟動它的元件沒有任何關係了。因此它是不能與啟動它的元件之間相互通訊的。雖然Service 沒有提供這種啟動方式的通訊方法,我們還是可以通過其他方式來解決的,這就用到了BroadcastReceiver。
場景描述:通過startService 啟動一個長期在後臺執行的下載圖片服務,然後在介面上點選下載按鈕,通過intent 傳遞一個下載連結給Service,在下載完成後,通過BroadcastReceiver 通知Activity 介面顯示圖片。看一下程式碼實現:
Service程式碼如下:
public class DownloadService extends Service {
public static final String IMAGE = "iamge_url";
public static final String RECEIVER_ACTION = "com.zhouwei.simpleservice";
private static final String TAG = "DownloadService";
public static final String ACTION_START_SERVICER = "com.zhouwei.startservice";
public static final String ACTION_DOWNLOAD = "com.zhouwei.startdownload";
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper){
super(looper);
}
@Override
public void handleMessage(Message msg) {
// 工作執行緒做耗時下載
String url = (String) msg.obj;
Bitmap bitmap = null;
try {
bitmap = Picasso.with(getApplicationContext()).load(url).get();
Intent intent = new Intent();
intent.putExtra("bitmap",bitmap);
intent.setAction(RECEIVER_ACTION);
// 通知顯示
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
} catch (IOException e) {
e.printStackTrace();
}
//工作完成之後,停止服務
stopSelf();
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
// 開啟一個工作執行緒做耗時工作
HandlerThread thread = new HandlerThread("ServiceHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// 獲取工作執行緒的Looper
mServiceLooper = thread.getLooper();
// 建立工作執行緒的Handler
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"call onStartCommand...");
if(intent.getAction().equals(ACTION_DOWNLOAD)){
handleCommand(intent);
}else if(intent.getAction().equals(ACTION_START_SERVICER)){
//do nothing
}
return START_STICKY;
}
private void handleCommand(Intent intent){
String url = intent.getStringExtra(IMAGE);
// 傳送訊息下載
Message message = mServiceHandler.obtainMessage();
message.obj = url;
mServiceHandler.sendMessage(message);
}
}複製程式碼
新建了一個DownloadService ,在裡面啟動了一個工作執行緒,線上程裡下載圖片,然後通過BroadcastReceiver 通知Activity顯示。
Activity的程式碼很簡單,註冊BroadcastReceiver,在onReceiver中顯示圖片就好了,程式碼如下:
private ImageView mImageView;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 顯示圖片
Bitmap bitmap = intent.getParcelableExtra("bitmap");
mImageView.setImageBitmap(bitmap);
}
};複製程式碼
/**
* 啟動下載
*/
private void startDownload(){
Intent intent = new Intent(this,DownloadService.class);
// 啟動服務
intent.putExtra(DownloadService.IMAGE,"http://www.8kmm.com/UploadFiles/2012/8/201208140920132659.jpg");
intent.setAction(DownloadService.ACTION_DOWNLOAD);
startService(intent);
}複製程式碼
效果如下:
如上就完成了使用BroadcastReceiver 完成和元件和Service的通訊。
2, LocaService 使用Binder 和 服務通訊
既然通過startService 啟動的服務與啟動它的元件是獨立的。相互通訊比較麻煩,那麼Google也提供了兩者之間的通訊方法,那就是元件繫結服務,也就是上文講的通過bindService 將元件和服務繫結到一起。元件可以獲取Service 通過onBind返回的一個IBinder介面,這樣兩者就可以通訊了,這也是Service 應用類通訊比較常用的方式。
下面就模擬一個用服務播放音樂的例子來講一下元件通過Binder 介面和服務之間通訊。
首先定義一個通訊的介面 IPlayer:
/**
* Created by zhouwei on 17/5/11.
*/
public interface IPlayer {
// 播放
public void play();
// 暫停
public void pause();
// 停止
public void stop();
// 獲取播放進度
public int getProgress();
// 獲取時長
public int getDuration();
}複製程式碼
然後新增一個MusicService 類,繼承Service 實現 Iplayer 介面:
public class MusicService extends Service implements IPlayer{
public static final String TAG = "MusicService";
private LocalService mBinder = new LocalService();
public class LocalService extends Binder{
public MusicService getService(){
return MusicService.this;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void play() {
Log.i(TAG,"music play...");
}
@Override
public void pause() {
Log.i(TAG,"music pause...");
}
@Override
public void stop() {
Log.i(TAG,"music stop...");
}
@Override
public int getProgress() {
return 100;
}
@Override
public int getDuration() {
return 10240;
}
}複製程式碼
其中比較重要的就是內部類LocalService ,繼承Binder ,裡面提供一個getService 方法,返回MusicService 例項,元件通過IBinder 獲取到Music 例項後,就可以和Service之間相互通訊啦!
Activity中程式碼如下:
private MusicService.LocalService mLocalService;
private MusicService mMusicService;
// 繫結/解除繫結 Service 回撥介面
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//1 ,獲取Binder介面物件
mLocalService = (MusicService.LocalService) service;
//2, 獲取MusicService 例項
mMusicService = mLocalService.getService();
// 只要拿到Music Service 例項之後,就可以呼叫介面方法了
// 可以通過它來播放/暫停音樂,還可以通過它來獲取當前播放音樂的進度,時長等等
mMusicService.play();
mMusicService.pause();
mMusicService.stop();
int progress = mMusicService.getProgress();
Log.i(MusicService.TAG,"progress:"+progress);
int duration = mMusicService.getDuration();
Log.i(MusicService.TAG,"duration:"+duration);
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 解除繫結後回撥
mMusicService = null;
mLocalService = null;
}
};複製程式碼
獲取到MusicService 後,就可以呼叫介面方法了,比如:播放音樂,暫停、停止、獲取進度等等。
看一下列印的日誌:
使用AIDL 通訊 (跨進城通訊IPC)
AIDL(Android 介面定義語言)執行所有將物件分解成原語的工作,作業系統可以識別這些原語並將它們編組到各程式中,以執行 IPC。 如果不是多執行緒訪問服務的話,也可以使用Messenger來跨進城通訊,Messenger
方法實際上是以 AIDL 作為其底層結構。Messenger
會在單一執行緒中建立包含所有客戶端請求的佇列,以便服務一次接收一個請求。 不過,如果您想讓服務同時處理多個請求,則可直接使用 AIDL。 在此情況下,您的服務必須具備多執行緒處理能力,並採用執行緒安全式設計。如需直接使用 AIDL,您必須建立一個定義程式設計介面的 .aidl
檔案。Android SDK 工具利用該檔案生成一個實現介面並處理 IPC 的抽象類,您隨後可在服務內對其進行擴充套件。
由於篇幅有限,AIDL 實現程式間的通訊後面單獨出一篇文章講,這裡就不再囉嗦了。
注意:只有允許不同的客戶端用IPC 方式訪問服務,並且想要在服務中處理多執行緒時,採用AIDL,如果不想通過IPC實現不同應用的訪問,直接用前面所講的繼承Binder ,用介面通訊就可以了。
Service 總結
Service 有2種啟動方式,startService 啟動服務,服務啟動起來後,在後臺無限期執行,直到通過stopService 或者 stopSelf 停止服務,服務與元件獨立,通訊比較困難(但還是有辦法的,通過BroadcastReceiver )。另一種方式就是 bindService 即繫結服務,元件和服務繫結在一起,服務的生命後期受元件影響,如果繫結到服務的元件全部被銷燬了,那麼服務也就會停止了。繫結服務的方式通常用於元件和服務之間 需要相互通訊。startService 這種 方式一般用於在後臺執行任務,而不需要返回結果給元件。 這兩種方式並非完全獨立,也就是說,你可以繫結已經通過 startService 啟動起來的服務,可以通過在Intent 中新增Action 來標示要執行的動作。比如:通過Intent Action 標記要播放的音樂,呼叫startService 來啟動音樂服務播放音樂,在介面需要顯示播放進度的時候,可以通過binderService 來繫結服務,從而獲取歌曲資訊。這種情況下,Service 需要實現兩種方式的生命週期。這種情況下,除非所有客戶端都已經取消繫結,否則通過stopService 或者 stopSelf 是不能停止服務的。
Service 是執行在主執行緒中的,因此不能執行耗時的或者密集型的任務,如果要執行耗時操作或者密集型計算任務,請在服務中開啟工作執行緒,線上程中執行。或者使用下面一節將要講的IntentService。
IntentService
IntentService 是Service 的子類,它使用工作執行緒逐一處理所有啟動請求,果您不要求服務同時處理多個請求,這是最好的選擇。 您只需實現 onHandIntent方法即可,該方法會接收每個啟動請求的 Intent,使您能夠執行後臺工作。
IntentService 示例
IntentService 預設為我們開啟了一個工作執行緒,在任務執行完畢後,自動停止服務,因此在我們大多數的工作中,使用IntentService 就夠了,並且IntentService 比較簡單,只要實現一個方法OnHandleIntent,接下來看一下示例:
IntentService 擴充套件類:
public class MyIntentService extends IntentService {
public static final String TAG ="MyIntentService";
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 這裡已經是工作執行緒,在這裡執行操作就行
boolean isMainThread = Thread.currentThread() == Looper.getMainLooper().getThread();
Log.i(TAG,"is main thread:"+isMainThread);
// 執行耗時下載操作
mockDownload();
}
/**
* 模擬執行下載
*/
private void mockDownload(){
try {
Thread.sleep(5000);
Log.i(TAG,"下載完成...");
}catch (Exception e){
e.printStackTrace();
}
}
}複製程式碼
然後啟動服務,看一下列印的日誌,如下圖:
判斷了是否為主執行緒,結果為false ,說明是開啟了一個工作執行緒,5s 之後,列印了下載完成,並且自動停止了服務。
IntentService 原始碼淺析
IntentService 自動為我們開啟了一個執行緒來執行耗時操作,並且在任務完成後自動停止服務,那麼它是怎麼做的呢?我們看一下原始碼一探究竟。其實IntentService 的原始碼非常簡單,就一百多行。一起看一下:
// 1,有一個Looper 變數和一個ServiceHandler 變數,ServiceHander 繼承Handler 處理訊息
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// 在工作執行緒中呼叫onHandleIntent,子類根據Intent傳遞的資料執行具體的操作
onHandleIntent((Intent)msg.obj);
// 任務執行完畢後,自動停止Service
stopSelf(msg.arg1);
}
}
//2, 在OnCreate 方法中,建立了一個執行緒HandlerThread ,並啟動執行緒
// 然後獲取工作執行緒的Looper ,並用Looper 初始化Handler(我們都知道Handler 的建立需要一依賴Looper)
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
//3, 在onStart()方法中傳送訊息給Handler,並且把Intent 傳給了Handler 處理
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
// 4,onStartCommand 直接呼叫的是onStart方法
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
// 5 最後就是一個子類需要實現的抽象方法,這個方法在handleMessage中呼叫,也就是在工作執行緒中執行。
protected abstract void onHandleIntent(@Nullable Intent intent);複製程式碼
上面程式碼中註釋得很清楚,下面用一張圖來看一下整個過程.
程式碼很簡單,IntentService的原始碼看著是不是很熟悉?當然很熟悉,前面使用Service的時候,在Service 裡面開啟工作執行緒其實就和Intent Service的程式碼差不多。在onCreate中建立執行緒,啟動,初始化Handler和Looper ,然後在onStartCommand中傳送訊息給Handler 處理任務。
IntentService 總結
IntentService是Service 的子類,預設給我們開啟了一個工作執行緒執行耗時任務,並且執行完任務後自 動停止服務。擴充套件IntentService比較簡單,提供一個構造方法和實現onHandleIntent 方法就可了,不用重寫父類的其他方法。但是如果要繫結服務的話,還是要重寫onBind 返回一個IBinder 的。使用Service 可以同時執行多個請求,而使用IntentService 只能同時執行一個請求。
以上就是對Service 和IntentService的詳細總結,如果問題,歡迎討論。 所有Demo程式碼已上傳Github:github.com/pinguo-zhou…(包含我所有部落格的所講的知識點的Demo程式碼)