Android Service和IntentService知識點詳細總結

Service 對於廣大的Android開發者來講算是耳熟能詳了,做爲Android的四大組件之一,在咱們的開發中也起着重要的做用,在Android面試中,Service相關的問題也是面試官問得比較多的,當別人問你,Service 究竟是什麼的時候?你可能隨口就能答得上來,Service是一個在後臺執行長時間運行操做而不用提供用戶界面的應用組件,可由其餘組件啓動,即便用戶切換到其餘應用程序,Service 仍然在後臺繼續運行。沒錯,這是Service的概念,做爲Android開發,或多或少都知道一些,可是不是每一個人把全部知識點都瞭解得透測。前段時間因爲項目中有用到Service,所以,本篇文章對Service的用法作一個總結。html

Service 目錄.png

Service

Service 和Activity 同樣同爲Android 的四大組件之一,而且他們都有各自的生命週期,要想掌握Service 的用法,那就要了解Service 的生命週期有哪些方法,而且生命週期中各個方法回調的時機和做用。有一點比較重要,Service 有兩種啓動方式,而且它的兩種啓動方式的生命週期是不同的。接下來分別看一下兩種啓動方式各自的生命週期方法。java

startService方式啓動Service

當應用組件經過startService方法來啓動Service 時,Service 則會處於啓動狀態,一旦服務啓動,它就會在後臺無限期的運行,生命週期獨立於啓動它的組件,即便啓動它的組件已經銷燬了也不受任何影響,因爲啓動的服務長期運行在後臺,這會大量消耗手機的電量,所以,咱們應該在任務執行完成以後調用stopSelf()來中止服務,或者經過其餘應用組件調用stopService 來中止服務。android

startService 啓動服務後,會執行以下生命週期:onCreate() -> onStartCommand() -> onStart()(如今已經廢棄) -> onDestroy() 。具體看一下它的幾個生命週期方法:git

  • onCreate() :首次啓動服務的時候,系統會調用這個方法,在onStartCommand 和 onBind 方法以前,若是服務已經啓動起來了,再次啓動時,則不會調用此方法,所以能夠在onCreate 方法中作一些初始化的操做,好比要執行耗時的操做,能夠在這裏建立線程,要播放音樂,能夠在這裏初始化音樂播放器。github

  • 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標籤下:app

<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;
        }
    }複製代碼

界面.png

如上圖界面所示,有2 個button ,分別是啓動服務和中止服務,分別點擊 startService 和StopService 的button ,看看生命週期回調方法打印的日誌:

生命週期打印日誌.png

小結:經過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 了從服務中獲取的字符串:

綁定服務.png

生命週期方法調用以下:

image.png

能夠看到,綁定服務的生命週期內依次調用了onCreate ,onBind,onUnbind 和 onDestroy 方法,只有中間兩個生命週期方法與startService 啓動服務是不一樣的。一張圖就能看清兩種方式的生命週期的異同:

Service生命週期.png

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);
    }複製代碼

效果以下:

Service下載圖片.gif

如上就完成了使用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 後,就能夠調用接口方法了,好比:播放音樂,暫停、中止、獲取進度等等。
看一下打印的日誌:

模擬播放音樂.png

使用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();
       }
    }
}複製代碼

而後啓動服務,看一下打印的日誌,以下圖:

image.png

判斷了是否爲主線程,結果爲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圖解.png

代碼很簡單,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代碼)

參考:
API Guides 服務
Android 接口定義語言 (AIDL)

相關文章
相關標籤/搜索