02、默默無聞的 Service (一):Service概述

Service.png

版權聲明:本文為博主原創文章,未經博主允許不得轉載

PS:轉載請注明出處
作者: TigerChain
地址: http://www.lxweimin.com/p/1443fa4036dc
本文出自 TigerChain 簡書 Android 系列

教程簡介

  • 1、閱讀對象
    本篇教程適合新手閱讀,老手直接略過
  • 2、教程難度
    初級
  • 3、Demo 地址
    稍后 提供

正文

Service 是一個神奇的東西,它可以執行長時間后臺任務,并且在后臺默默無聞的工作著,沒有顯示界面(也不露臉去表現自己),說到這里大家可能會想到,fk–Thread 也能干這樣的事呀?那 Service 存在有必要嗎?在這一節里這些問題都會一一解答

一、什么是 Service

在講解 Service 之前我們先要知道什么是 Service,按照慣例,直接拿官方的解釋來看

Service is an application component that can perform long-running operations in the 
background and does not provide a user interface ...

大體就是說 Service 是一個 Android 系統的組件可以在后臺執行一些長時間操作并且沒有用戶界面

Service一個進程嗎?是一個線程嗎?

Service 既不是一個進程,也不是一個線程,而且它默認是工作在主線程中的,有的同鞋會想運行在主線程?我們都知道在主線程進行耗時操作會 ANR 的,Service 是用來執行耗時操作的難道不會 ANR 嗎,如果不會 難道 ANR 的機制還有兩套,如果會,那么 Service 為什么還能執行耗時操作,別急這些疑問慢慢解答

我們從官方描述可以知道 Service 是不依賴 UI 運行有后臺用來執行耗時操作的一個的系統組件,我們知道 Thread 就是用來執行耗時操作的,那么 Android 有必要要有 Service 這個東西嗎?Thread 就夠了呀,答案是非常有必要有,因為存在就有必要(靠~~ 說了等于沒有說 _ )

  • Service 和 Thread 的關系
    Service 和 Thread 沒有半毛錢關系,如果非要說有關系,也就是組合使用的關系,我們對Service 和 Thread 產生混淆的主要一點是由于官方說了 Service 是后臺任務來處理一些長時間的操作,這和 Thread 的功能非常類似,懂Handler的朋友尤其明白。其實 Thread 和 Service 的后臺意思不太一樣,前者是指不依賴UI后者運行在一個工作線程的后臺任務,而且 Service 是運行在主線程中的,一個主線程一個子線程程肯定兩者之間沒有半毛錢關系(除非用于線程間通信),至于為什么 Service 運行在主線程,我們后面再說

  • Service 的特點
    (1)、優先級高于 Acitivity,一般情況下即使 Activity 銷毀了 Service 任然可以運行,Service 優先級也高于 Activity 所創建的 Thread,優先級高就不會輕易被系統殺死,除非非常有必要
    (2)、Service 有自己的生命周期,這樣的話就很好控制,而 Thread 的生命周期一般是依賴它被啟動的環境中

二、Service 的創建和啟動方式

Service 的創建

/**
 * Created by TigerChain.
 */
public class MyService extends Service {
    private static final String TAG = MyService.class.getSimpleName();
    @Override
    public void onCreate() {
        Log.e(TAG,"onCreate") ;
        super.onCreate();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand") ;
        return super.onStartCommand(intent, flags, startId);
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onDestroy() {
        Log.e(TAG,"onDestroy") ;
        super.onDestroy();
    }
}

Service 是系統組件,既然是系統四大組件之一,那么就要在 Mainfest 中去聲明

<service android:name="com.jun.servicedemo.MyService"></service>

在MainActivity中聲明一個按鈕并用綁定事件

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private static final String TAG = MainActivity.class.getSimpleName();
    private Button start_service ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView() ;
    }
    private void initView() {
        start_service = (Button) this.findViewById(R.id.start_service) ;
        start_service.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
           case  R.id.start_service:
               Intent startServiceIntent = new Intent(MainActivity.this,MyService.class) ;
               startService(startServiceIntent) ;
               break ;
        }
    }
}

然后通過startService()來啟動Service

Intent startServiceIntent = new Intent(MainActivity.this,MyService.class) ;
startService(startServiceIntent) ;

然后我們運行并查看結果,如下圖所示

start_service_first.png

我們清楚的看到了,首次點擊 startService 按鈕的時候會依次調用 MyService 的 onCreate–>onStartCommand 方法

我們再點擊 startService 按鈕,圖如下

start_service_secondandmore.png

以后不管再調用多少次 StartService 方法都只會調用 onStartCommand 方法

我們在 Myservice 和 MainActivity 的 onCreate 中分別加入一條 Log 信息

//用來獲取當前類所在線程
 Log.e(TAG,Thread.currentThread().getId()+"") ;

再次運行,查看 Log

service_thread.png

神奇吧,居然 Service 和 Activity 是在同一個線程中,Activity 是在 UI 線程(主線程中),所以Service 默認也是在主線程中的,這就回答了上面的 Service 不是線程,也不是進程,它運行在主線程中,所以它執行耗時操作肯定會 ANR ,如何解決,答案是在 Service 中開啟一個 Thread 來執行耗時操作,當然還可以有別的辦法,我們后面再說

Service 的 ANR

我們來模擬一個 Service ANR 的效果,我們在 MyService 的 onStartCommand() 方法中添加如入代碼

try {
 //模擬耗時操作
    Thread.sleep(80000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

當我們點擊 startService 按鈕的時候會發現先卡頓一小會作,然后就會報 ANR,典型的主線程進行耗時操作所帶來的問題,如下圖:

service_anr.png

那么如何解決呢,肯定要開子線程去處理耗時操作,代碼修改

@Override
   public int onStartCommand(Intent intent, int flags, int startId) {
       Log.e(TAG,"onStartCommand") ;
      //這里啟用一個子線程用來處理耗時操作
       new Thread(new Runnable() {
           @Override
           public void run() {
               try {
                   //模擬耗時操作
                   Thread.sleep(80000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }).start();
       return super.onStartCommand(intent, flags, startId);
   }

這樣處理以后,就永遠不會 ANR 了

總結:Service 運行在主線程中,如果想要操作耗時操作必須在 Service 中開啟子線程去處理。

調用 startService 以后,如果想要停止 Service 一定要手動調用 stopService 來停止,我們可看看 Service 的啟動方式

Service的啟動方式

  • 1、 startService
    通過上面的例子,我們已經了解了如何使用 startService 來啟動一個 Service 了

  • 2、bindService
    按照官網的說法就是通過 bindService 可以創建一個客戶端接口來和 Service 交互,并且還可以通過 aidl 來實現進程間通訊。

下面我們我們用實例來看看 bindService

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private static final String TAG = MainActivity.class.getSimpleName();
    private Button start_service ,stop_service ,bind_service,unbind_service;
    private MyService.MyBinder myBinder ;
    //服務是否綁定的標志位
    private boolean isBind ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView() ;
        Log.e(TAG,"所在的線程id:"+Thread.currentThread().getId()+"") ;
    }
    private void initView() {
        start_service = (Button) this.findViewById(R.id.start_service) ;
        start_service.setOnClickListener(this);
        stop_service = (Button) this.findViewById(R.id.stop_service) ;
        stop_service.setOnClickListener(this);
        bind_service = (Button) this.findViewById(R.id.bind_service) ;
        bind_service.setOnClickListener(this);
        unbind_service = (Button) this.findViewById(R.id.unbind_service) ;
        unbind_service.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
           case  R.id.start_service:
               Intent startServiceIntent = new Intent(MainActivity.this,MyService.class) ;
               startService(startServiceIntent) ;
               break ;
            case  R.id.stop_service:
                Intent stopServiceIntent = new Intent(MainActivity.this,MyService.class) ;
                stopService(stopServiceIntent) ;
                break ;
            case  R.id.bind_service:
                Intent bindServiceIntent = new Intent(MainActivity.this,MyService.class) ;
                bindService(bindServiceIntent,myServiceConnection, BIND_AUTO_CREATE) ;
                break ;
            case  R.id.unbind_service:
                if(isBind){
                    unbindService(myServiceConnection);
                    isBind = false;
                }
                break ;
        }
    }
    private ServiceConnection myServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //和服務連接的時候調用
            myBinder = (MyService.MyBinder) service ;
            myBinder.progressLongTimeTask();
            isBind = true ;
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            //服務斷開的時候調用(由于異常時斷開) Service被停止或被系統殺死的時候調用
            Log.e(TAG,"service 斷開") ;
            myBinder = null ;
        }
    } ;
}

從代碼中可以看到,我們添加了兩個按鈕 bind_service 和 unbind_service 并添加相應的點擊事件,我們在調用 bindService(Intent service, ServiceConnection conn,int flags) 的時候需要傳入三個參數,第一個是 Intent,第二個是服務的連接類,第三個是標志位,這里傳入 BIND_AUTO_CREATE 表示 Activty 和 Service 建立關聯后自動創建 Service

相應的我們的 MyService 也要添加代碼

/**
 * @author TigerChain
**/
public class MyService extends Service {
    private static final String TAG = MyService.class.getSimpleName();
    private MyBinder myBinder = new MyBinder() ;
    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate") ;
        Log.e(TAG,"所在的線程id:"+Thread.currentThread().getId()+"") ;
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand") ;
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模擬耗時操作
                    Thread.sleep(80000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"IBinder") ;
        return myBinder;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy") ;
    }
     
    class  MyBinder extends Binder{
          //這里模擬耗時任務
        public void progressLongTimeTask(){
            Log.e(TAG,"處理耗時任務") ;
        }
    }
    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG,"onUnbind") ;
        return true ;
    }
    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
        Log.e(TAG,"unbindService") ;
    }
    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        Log.e(TAG,"onRebind") ;
    }
}

先來大概解釋一下,在這里我們定義了一個 MyBinder 類來繼承自 Binder,Binder 是 IBinder 的一個實現類,然后在 MyService 的 onBind() 方法中返回這個類的實例,當我們調用 bindService 的方法的時候就會觸發 onBind() 方法把這個 MyBinder 類的實例返回去,返回到那里呢?就是MainActivity 中的 onServiceConnected 中的 IBinder 中,然后我們就可以在 Activity 中拿到 Binder 了,然后就可以為所欲為了…

private ServiceConnection myServiceConnection = new ServiceConnection() {
      @Override
      public void onServiceConnected(ComponentName name, IBinder service) {
          //和服務連接的時候調用
          myBinder = (MyService.MyBinder) service ;
          myBinder.progressLongTimeTask();
          isBind = true ;
      }
      @Override
      public void onServiceDisconnected(ComponentName name) {
          //服務斷開的時候調用(由于異常時斷開) Service被停止或被系統殺死的時候調用
          Log.e(TAG,"service 斷開") ;
          myBinder = null ;
      }
  } ;

廢話少說,我們點擊 bindService 和 unbindService 按鈕來看看 Log 信息

bind_service.gif

從 gif 圖中我們清楚的看到了點擊 bindservice 按鈕依次調用 MyService 的 oncreate()->onBind()方法,而點擊 unbindservice 按鈕會依次調用 MyService 的 onUnbind()-> onDestroy() 方法,unbindservice 按鈕如果我們點擊多次就會報錯,說沒有注冊 Service,我們是程序員當然對這種異常是0容忍的,解決辦法上面代碼中已經體現,在 MainActivity 中添加一個標志位 isBind 然后判斷一下即可,當然你也可以有自己的解決方案

細心的朋友們發現我們重寫了 Service 的 onRebind 方法,那么這個方法有什么卵用,何時調用,首先 onRebind 方法的調用必須滿足兩個條件,我們來看這個方法注釋中雜說

/**
   * Called when new clients have connected to the service, after it had
   * previously been notified that all had disconnected in its
   * {@link #onUnbind}.  This will only be called if the implementation
   * of {@link #onUnbind} was overridden to return true.
   * 
   * @param intent The Intent that was used to bind to this service,
   * as given to {@link android.content.Context#bindService
   * Context.bindService}.  Note that any extras that were included with
   * the Intent at that point will <em>not</em> be seen here.
   */
  public void onRebind(Intent intent) {
  }

尼瑪,啥求意思呀,總結起來就兩點,也就是 onRebind() 調用滿足的條件

  • 服務被綁定后沒有銷毀
  • onUnbind 方法必須返回值為 true

從上面的代碼中可知,我們第二個條件是滿足了,我們給 onUnbind 方法手動的返回了 true,第一種情況就要配合 startService 了,我們來看這種情況,為了清楚的看日志信息,我把無關的 Log 注釋掉了,如下:

onrebind_service.gif

如 gif 圖我們依次調用了 bindService->startService,然后再調用 unBindService 然后再調用 bindService,這樣就調用了 ServiceonRebind 方法,以后只要不調用 stopService 方法,重復調用 unBindService 和 bindService 都會執行 onRebind 方法。在這里我們獲取到了一個重要信息,就是當調用了 startServcie 再調用 bindService 的時候,如果再調用 unBindService 是沒有銷毀 SerVice 的,不然的話 onRebind 方法是不會調用的,關于 startServicebindService調用同一個 Service 的情況我們后面討論

三、當 bindService 遇上 startService

  • 1、先看先 startService->bindService->unbindService->stopServicestartService->bindService->stopService->unBindService 這兩種情況

無圖無直相,直接上圖

startandbind_service.gif

圖中我們把上面說的兩種情況都實現了:

(1)、首先看 startService->bindService->unbindService->stopService這種情況:我們清楚的看到依次調用 Service 的
onCreateonStartCommandonBindonUnbindonDestory 方法,以下分別是 startServicebindService 對應的方法

startService: onCreate,onStartCommand onDestory
bindService:onBind,onUnbind

先調用 unbindService 再調用 stopService,會分別調用 ServiceonUnbind 方法和 onDestory 方法

(2)、其次看 startService->bindService->stopService->unBindService 這種情況:分別依次調用了 Service 的 onCreateonStartCommandonBindonUnbindonDestory 我肋個去和上面一毛一樣,別急我們慢慢看,以下分別是 startServicebindService 對應的方法

startService: onCreate,onStartCommand
bindService:onBind,onUnbind onDestory

看到區別了沒,如果先調用 stopService 再調用 unBindService 前者任何 log 都不打,只是把 Service 暫停了,再調用 unBindService 的時候會依次調用 Service 的 onUnbindonDestory 方法

  • 2、再看 bindService->startService->unbindService->stopServicebindService->startService->stopService->unBindService 這兩種情況,小二,上圖:
bindsandstart_service.gif

圖中我們看到兩種情況都實現了

(1)、首先行看 bindService->startService->unbindService->stopService 情況會依次調用 Service 的 onCreate->onBind->onStardCommand->onUnbind->onDestory

以下分別是 bindServicestartService 調用方法

bindService: onCreate, onBind ,onUnbind
startService: onStardCommand , onDestory

解釋一下,先調用 bindService 會觸發 Service 的 onCreate 方法和 onBind 方法,再調用 startService 會調用 onStardCommand 方法,再調用 unbindService 方法會調用 Service 的 onUnbind 方法,最后調用 stopService 會調用 Service 的 onDestory 方法

(2)、再看 bindService->startService->stopService->unBindService 情況會依次調用 Service 的 onCreate->onBind->onStardCommand->onUnbind->onDestory

以下分別是 bindServicestartService 調用方法

indService: onCreate, onBind ,onUnbind,onDestory
startService: onStardCommand

區別就是先調用 stopService 什么方法都沒有調用沒有日志信息,只是把 Service 暫停了,再調用 unBindService 方法會依次調用 onUnbindonDestory 方法

四、總結:

  • 1、startServicebindService 可以啟動同一個 Service
  • 2、startServicebindService 啟動同一個 Service 的時候如果想銷毀 Service 就既要調用stopService 又要調用 unBindService 方法,先后順序無關,但是最后成對調用

到此為止,我們就就把 Service 的基本用法說說完,一定要親自試試哦

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容