Android 8.1 源碼_啓動篇 -- 探討 Service 的使用方法

開篇

核心源碼

關鍵類 路徑
Service.java frameworks/base/core/java/android/app/Service.java

服務是什麼?

服務(Service)是 Android 中實現程序後臺運行的解決方案,它非常適合去執行那些不需要和用戶交互而且還需要長期進行的任務。服務的運行不依賴於任何用戶界面,即使程序被切換到後臺,或者用戶打開了另外一個應用程序,服務仍然能夠保持正常運行。

不過需要注意的是,服務並不是運行在一個獨立的進程當中的,而是依賴於創建服務時所在的應用程序進程。當某個應用程序進程被殺掉時,所有依賴於該進程的服務也會停止運行。

Service 使用方法

定義一個服務

首先,我們定義一個MyService.java類,當然作爲一個服務類,必須要繼承 Service(android.app.Service),看代碼:

// 源碼路徑:frameworks/base/core/java/android/app/Service.java
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
    private static final String TAG = "Service";
    ... ...

    @Nullable
    public abstract IBinder onBind(Intent intent);
    ... ...
}

Service 定義了一個抽象方法 onBind,子類繼承它,必須複寫此方法。

public class MyService extends Service{
  @Nullable
  @Override
  public IBinder onBind(Intent intent) {        // 這是一個抽象方法,那麼子類是必須要重寫的
        return null;
  }
}

服務既然已經定義好了,自然應該在服務中去處理一些事情,那處理事情的邏輯應該寫在哪裏?我們需要在服務裏重寫 Service 中的另一些常用的方法:

public class MyService extends Service{
  @Nullable
  @Override
  public IBinder onBind(Intent intent) {        // 這是一個抽象方法,那麼子類是必須要重寫的
        return null;
  }

    @Override
    public void onCreate() {                                                // 服務創建時調用
      super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {      // 服務啓動時調用
      return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {                                               // 服務銷燬時調用
      super.onDestroy();
    }
}

和添加 Activity 一樣,我們添加了一個服務,那麼在 AndroidManifest.xml 文件中必須進行註冊才能生效!

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.xin02ma.myapplication">
  <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    ......
    <activity android:name=".MainActivity">
      ......
    </activity>
    <service android:name=".MyService"
                 android:enabled="true"
                 android:exported="true" />
  </application>
</manifest>

啓動和停止服務

服務定義好了,接下來就應該考慮如何去啓動以及停止這個服務了。

(1)先添加兩個Button(activity_main.xml)

<Button
  android:id="@+id/start_service"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/start_service" />

<Button
  android:id="@+id/stop_service"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/stop_service" />

添加兩個Button.png

  (2)接下來,修改主函數 MainActivity 的代碼:

public class MainActivity extends Activity implements View.OnClickListener{
  private Button startService;                                              
  private Button stopService;

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);                               // 採用佈局
    startService = (Button) super.findViewById(R.id.start_service);       // 取得Button實例
    stopService = (Button) super.findViewById(R.id.stop_service);         // 取得Button實例
    startService.setOnClickListener(this);                                // 監控Button,註冊按鈕事件
    stopService.setOnClickListener(this);                                 // 監控Button,註冊按鈕時間
  }

  public void onClick(View v) {
    switch (v.getId()) {
      case R.id.start_service:
        Intent startIntent = new Intent(this, MyService.class);
        startService(startIntent);                                          // 啓動服務
        break;
      case R.id.stop_service:
        Intent stopIntent = new Intent(this, MyService.class);
        stopService(stopIntent);                                            // 停止服務
        break;
      default:
        break;
    }
  }
}

上面的代碼很簡單,主要作了以下工作:

(1)取得 startService 和 stopService 兩個按鈕實例,並且註冊了點擊事件;

(2)通過 Intent 對象,調用 Activity 的 startService() 和 stopService() 方法來啓動和停止服務。

【Notice】

這裏的活動的啓動和停止完全是由活動本身控制的,如果我們 start 了服務,但是沒有點擊 stop,那麼服務會一直處於運行狀態,此時服務如何讓自己停止下來?

只需要在 MyService 的任何一個位置調用 stopSelf() 這個方法就能讓服務停下來!

Log測試

添加Log,查看Service是如何運作的:

public class MyService extends Service{

    private static final String TAG = "MyService";
    
  @Nullable
  @Override
  public IBinder onBind(Intent intent) {        // 這是一個抽象方法,那麼子類是必須要重寫的
        return null;
  }
 
    public void onCreate() {                                                // 服務創建時調用
      super.onCreate();
      Log.d(TAG, "onCreate executed");
    }

    public int onStartCommand(Intent intent, int flags, int startId) {      // 服務啓動時調用
      Log.d(TAG, "onStartCommand executed");
      return super.onStartCommand(intent, flags, startId);
    }

    public void onDestroy() {                                               // 服務銷燬時調用
      super.onDestroy();
      Log.d(TAG, "onDestroy executed");
    }
}

添加了3行Log,目的就是看:在我們點擊兩個按鈕的時候,整個Service什麼時候創建,什麼時候啓動,什麼時候毀滅!

我們來看一下執行結果,運行程序,查看Logcat中的打印日誌:
image

(1)第一次點擊 StartService按鈕 後,MyService中的 onCreate()onStartCommand() 方法都執行了,圖中黃色箭頭所示!
此時,我們可以在 手機 --> 設置 --> 應用 --> 運行中 可以看到這個服務:
Service.png

(2)然後我們點擊 stopService按鈕 後,MyService中的 onDestory() 方法被執行,圖中藍色箭頭所示!

(3)此時可能你會有一個疑問?當我們點擊了 startService按鈕 以後,onCreate()onStartCommand()方法同時被執行,這兩個方法有什麼區別?

圖中的 紅色箭頭 給了我們答案:onCreat() 方法是在服務第一次創建的時候調用的,而 onStartCommand() 方法則在每次啓動服務的時候都會被調用。

當我們在 服務未啓動 的時候,點擊 startService 按鈕,則此時會 執行兩個方法

但是 服務啓動完成 之後,再次點擊(隨便你點幾次)startService按鈕,你會發現 只有onStartCommand()方法被執行

Service生命週期

上面介紹完 Service 的使用方法,接下來看看 Service 的 生命週期 :跟Activity相比,Service的生命週期很簡單:onCreate()->onStart()->onDestroy()

我們以如下的方式展開這章節的討論工作!

【主題】:Activity 與 Service之間的 Communication

【問題】:由上貼我們知道,當我們點擊 START SERVICE 按鈕後,服務的 onCreate() 和 onStartCommand() 方法會得到執行,此後 Service 是一直存在於後臺運行的,Activity 無法控制 Service 中具體的邏輯運行,那麼這樣 Activity 只相當於起到一個通知的作用,除了告訴 Service 你可以開始工作了。那麼這樣顯然會分離兩者之間的關聯性,這也不是我們需要的結果!

【後果】:如果出現以上的問題,那麼在我們平時的項目開發過程中,一直存在的 Service 很有可能會引起功耗的問題,可能影響手機的運行效率!

【要求】:我們能否將 Activity 與 Service 建立一種聯繫,當 Activity 終結之時,Service 也銷燬,也就是有沒有辦法讓 Activity 和 Service 能夠“不求同生,但求共死”

答案是肯定的!這就涉及到 Service 的另一個重要知識點:綁定解綁

還是以代碼爲例:

MyService

MyService.java

public class MyService extends Service{

  private static final String TAG = "MyService";

  private DownloadBinder mBinder = new DownloadBinder();              // 定義一個 DownloadBinder 類

  class DownloadBinder extends Binder {                               // 讓 DownloadBinder 成爲 Binder 的子類
        public void startDownload() {                                   // 定義開始下載的方法
      Log.d(TAG, "startDownload executed");
    }
    public int getProgress() {                                     // 定義一個查看下載進度的方法
      Log.d(TAG, "getProgress executed");
      return 0;                                                 
    }
  }

  @Nullable
  @Override
  public IBinder onBind(Intent intent) {      // onBind()方法,這個方法將在綁定後調用
    return mBinder;                         // 返回 IBinder 的實例 --> DownloadBinder 類的實例
  }

    public void onCreate() {                                               
      super.onCreate();
      Log.d(TAG, "onCreate executed");
    }

    public int onStartCommand(Intent intent, int flags, int startId) {      
      Log.d(TAG, "onStartCommand executed");
      return super.onStartCommand(intent, flags, startId);
    }

    public void onDestroy() {                                               
      super.onDestroy();
      Log.d(TAG, "onDestroy executed");
    }
}

BIND SERVICE / UNBIND SERVICE

我們在Layout中添加兩個按鈕 BIND SERVICE 和 UNBIND SERVICE

image

MainActivity.java

public class MainActivity extends Activity implements View.OnClickListener{

  private ServiceConnection connection = new ServiceConnection() {                       // 創建一個 ServiceConnection 的匿名內部類
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {              // 重寫 onServiceConnected() 方法
      MyService.DownloadBinder downloadBinder = (MyService.DownloadBinder) service;  // 向下轉型取得 downloadBinder 實例
      downloadBinder.startDownload();                                                // 在 Activity 中調用 Service 的方法
      downloadBinder.getProgress();                                                  // 在 Activity 中調用 Service 的方法
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {       // 重寫onServiceDisconnected()方法
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Button startService = (Button) super.findViewById(R.id.start_service);
    Button stopService = (Button) super.findViewById(R.id.stop_service);
    Button bindService = (Button) super.findViewById(R.id.bind_service);
    Button unbindService = (Button) super.findViewById(R.id.unbind_service);        
    startService.setOnClickListener(this);
    stopService.setOnClickListener(this);
    bindService.setOnClickListener(this);
    unbindService.setOnClickListener(this);
  }

  @Override
  public void onClick(View v) {
    switch (v.getId()) {
      case R.id.start_service:
        Intent startIntent = new Intent(this, MyService.class);        // START 服務 --> onCreate() --> onStartCommand()
        startService(startIntent);
        break;
      case R.id.stop_service:
        Intent stopIntent = new Intent(this, MyService.class);         // STOP 服務 --> onDestroy()
        stopService(stopIntent);
        break;
      case R.id.bind_service:                                            // 綁定 --> ?
        Intent bindIntent = new Intent(this, MyService.class);
        bindService(bindIntent, connection, BIND_AUTO_CREATE);         // 💥 💥 💥 💥 重點分析
        break;
      case R.id.unbind_service:                                          // 解綁 --> ?
        unbindService(connection);
        break;
      default:
        break;
    }
  }
}

bindService

看一下 bindService(bindIntent, connection, BIND_AUTO_CREATE) 這個方法:

bindService 接收了 3 個參數:

bindIntent:這個參數傳入的就是我們的 intent,目的就是調用 MyService 這個服務。

connection:這個參數傳入的就是創建好的 ServiceConnection 的實例,這個參數代表着我們的 Activity 是要和 Service 綁定在一起的!

BIND_AUTO_CREATE:這是一個 FLAG,表示在活動和服務進行綁定後 自動創建服務。注意!是自動創建服務,也就是說 MyService 會執行 onCreate() 方法,但是不會執行 onStartCommand() 方法!

接下來,直接看代碼最終的效果:
Service_buttons.png</left>   <left>Service_Logcat.png

通過排列組合,對按鈕進行點擊,Log分 3 種情況:

START SERVICE + STOP SERVICE:

1、當我們先點擊 START SERVICE :此時服務啓動,調用 onCreat() 和 onStartCommand() 方法;

2、當我們後點擊 STOP SERVICE :此時,服務被銷燬,調用 onDestroy() 方法。

BIND SERVICE + UNBIND SERVICE:

1、當我們先點擊 BIND SERVICE :此時服務僅僅是創建,並未啓動!所以調用的只是 onCreate() 方法。此時 Activity 與 Service 綁定,會同時調用 onBind() 方法,此時 onServiceConnected() 方法會被執行,還記的 onBind() 方法的返回類型不?我們通過 Log 可以很明顯發現,Activity 調用了服務內部的兩個自定義方法。

2、當我們後點擊 UNBIND SERVICE :由於服務還未啓動,而 BIND SERVICE 只是將服務創建好並與活動進行綁定,那麼解綁後,勢必會銷燬這個 Service,所以 onDestroy() 被執行!

START SERVICE + BIND SERVICE + UNBIND SERVICE + STOP SERVICE:

1、我們先點擊 START SERVICE :onCreat() 和 onStartCommand() 方法被執行,這個就不用多說了;

2、然後點擊 BIND SERVICE :這個時候其實活動已經在後臺運行了,我們此時將活動和服務綁定,那麼 onCreate() 不會再執行,只會執行 onServiceConnected() 方法,Log 裏面打出來看的很清楚。

3、此時你如果手賤,想 STOP SERVICE:那麼恭喜你,毫無反應!爲什麼?因爲你都沒解綁,你怎麼銷燬?

4、OK,那我們先解綁,我們點擊 UNBIND SERVICE :此時一個奇怪的現象發生了,LOG 日誌沒有打印出 Destroy() 這個方法啊?沒有被執行啊!不是說 bind 了 Service 之後,unbind 就會銷燬這個服務嗎?這跟我們之前分析的不符合啊。

5、好吧,我們來看看爲什麼。其實原因很簡單:我們先 start 了 Service,那麼此時服務已經在後臺運行了,這個時候你 bind,讓 Service 和 Activity 綁定,其實是沒有什麼意義的。但是既然綁定了,你如果不解綁,那麼 Destroy() 毫無用武,所以,這種情況和(2)中分析的還是有區別的,此是解綁完後,服務還是舒舒服服的在後臺運行,所以,要想幹掉這個服務,你必須要 STOP SERVICE。

6、那我們解綁後,再 STOP SERVICE :這個時候 Service 就被槍斃了!

Service 兩個實用小技巧

Forground Service

服務幾乎都是在後臺運行的,一直一來它都是默默地做着辛苦的工作。但是服務的系統優先級還是比較低的,當系統出現內存不足的情況時,就有可能會回收掉正在後臺運行的服務。如果你希望服務可以一直保持運行狀態,而不是由於系統內存不足的原因導致被回收掉,就可以考慮使用前臺服務。前臺服務和普通服務最大的區別就在於,它會一直有一個正在運行的圖標在系統的狀態欄顯示,下拉狀態欄後可以看到更加詳細的信息,非常類似於通知的效果。當然有時候你也可能不僅僅是爲了防止服務被回收掉才使用前臺服務,有些項目由於特殊的需求會要求必須使用前臺服務,比如說天氣類軟件,它的服務在後臺更新天氣數據的同時,還會在系統狀態欄一直顯示當前的天氣信息。

【問題】:我們都知道服務是運行在後臺的,如果系統出現內存不足的情況,那麼此時,系統就可能回收後臺的服務,那麼我們如何保證服務可以一直運行?

【解決】:在服務中,有一個 前臺服務 的概念,調用 startForground() 方法可以實現。

如何創建一個前臺服務,看代碼:

public class MyService extends Service{

  ......

  @Override
  public void onCreate() {
    super.onCreate();
        Log.d("MyService", "onCreate executed");
    Intent intent = new Intent(this, MainActivity.class);
    PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
    Notification notification = new Notification.Builder(this)     // 啓動服務後,在前臺添加一個Notification
        .setContentTitle("This is a content title")
        .setContentText("This is content text")
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
        .setContentIntent(pi)
        .build();
    startForeground(1, notification);
  }
    ... ...
}

以上的代碼是在 Service 的創建中添加了一個 Notification,調用 startForground() 就可以保證:只要服務一直存在,那麼在前臺就會一直顯示這個 Notification。

如果我們在 onDestroy() 中調用 stopForground() 方法,會銷燬這個 Notification,但是 Service 還是存活的,此時 Service 就會面臨被 System 幹掉的風險。

如果直接 STOP SERVICE,那麼 Notification 和 Service 都會銷燬。 

IntentService

【問題】:我們知道服務的代碼邏輯是在主線程中執行的,如果我們在主線程中需要執行一些耗時的操作,那麼很有可能出現ANR(程序暫無響應)的狀況。

這個時候,我們可以採用 Android 的多線程編程的方式,我們應該在服務的每個具體的方法裏開啓一個子線程,然後在這裏去處理那些耗時的邏輯。所以,一個比較標準的服務就可以寫成如下形式:

public class MyService extends Service{
  @Nullable
  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(new Runnable() {      // 開啓一個線程處理耗時操作
      @Override
      public void run() {
        // 處理具體的邏輯                     
      }
    }).start();
    return super.onStartCommand(intent, flags, startId);
  }
}

現在,服務可以啓動起來了。但是如果不調用 StopService()stopSelf() 方法,服務會一直運行,所以我們需要修改一下代碼:

public class MyService extends Service{
  @Nullable
  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(new Runnable() {
      @Override
      public void run() {
        // 處理具體的邏輯                  // 開啓一個線程處理耗時操作
        stopSelf();                        // 讓服務執行完邏輯後自行停止
      }
    }).start();
    return super.onStartCommand(intent, flags, startId);
  }
}

上面的代碼就是一個標準的 Service 的書寫形式,主要包含兩個知識點:Thread子線程的創建 和 stopSelf() 方法的調用。

雖說這種寫法並不複雜,但是總會有人忘記開啓線程,或者忘記調用 stopSelf(),那麼有沒有更好的辦法能夠實現上面兩個需求呢?

【解決】:在 Android 中,專門提供了一個 IntentService 類(android.app.IntentService),這個類就能很好的滿足我們的需求!我們直接通過代碼來看:

新建一個 MyIntentService 類繼承自 IntentService,代碼如下:

public class MyIntentService extends IntentService{

  public MyIntentService() {
    super("MyIntentService");     // 調用父類的有參構造函數
  }

  @Override
  protected void onHandleIntent(Intent intent) {
        // 打印當前線程的 id
    Log.d("MyIntentService", "MyIntentServiceThread id is " + Thread.currentThread().getId());
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    Log.d("MyIntentService", "onDestroy executed");
  }
}

以上代碼做了幾件事:

1、提供了一個無參的構造方法,並且調用了父類的有參構造函數;

2、子類實現父類的 onHandleIntent() 抽象方法,這個方法好就好在,它是一個已經運行在子線程中的方法。也就是說,服務調用了它,那麼執行的邏輯就如同 Thread 子線程;

3、根據 IntentService 的特性,這個服務在運行結束後應該是會自動停止的,所以我們又重寫了 onDestroy()方法,在這裏也打印一行日誌,以證實服務是不是停止掉了。

我們在 xml 文件中,創建一個 MyIntentService 服務按鈕:

<Button
  android:id="@+id/start_intent_service"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/intent_service"/>

然後修改 MainActivity 中的代碼:

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
        ... ...
        
    Button startIntentService = (Button) super.findViewById(R.id.start_intent_service);

    startIntentService.setOnClickListener(new View.OnClickListener() {

      @Override
      public void onClick(View v) {
        Log.d("MyIntentService", "MainActivity Thread id is " + Thread.currentThread().getId());            // 查看主線程的id
        Intent intentService = new Intent(getBaseContext(), MyIntentService.class);                   
        startService(intentService);
      }
    });
  }
}

最後,在AndroidMainfest中註冊服務:

<service android:name=".MyIntentService" />

【結果】
image

從打出的LOG可以看出:

1、MyIntentService 和 MainActivity 所在進程的 id是不一樣的

2、onHandleIntent() 方法在執行完邏輯後確實銷燬了服務,效果等同於 stopSelf()。

從上面的分析可以看出 onHandleIntent() 方法確實相當的好用!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章