【Android 系統開發】_「四大組件」篇 -- Service(用法)

開篇

服務是什麼?

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

不過須要注意的是,服務並非運行在一個獨立的進程當中的,而是依賴於建立服務時所在的應用程序進程。當某個應用程序進程被殺掉時,全部依賴於該進程的服務也會中止運行。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 中的另外一些經常使用的方法:app

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 文件中必須進行註冊才能生效!ide

<?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" />

<center>添加兩個Button.png</center>測試

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

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   Service_Logcat.png

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

START SERVICE + STOP SERVICE:

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

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

BIND SERVICE + UNBIND SERVICE:

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

二、當咱們後點擊 UNBIND SERVICE :因爲服務還未啓動,而 BIND SERVICE 只是將服務建立好並與活動進行綁定,那麼解綁後,勢必會銷燬這個 Service,因此 onDestroy() 被執行!

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

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

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

三、此時你若是手賤,想 STOP SERVICE:那麼恭喜你,毫無反應!爲何?由於你都沒解綁,你怎麼銷燬?

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

五、好吧,咱們來看看爲何。其實緣由很簡單:咱們先 start 了 Service,那麼此時服務已經在後臺運行了,這個時候你 bind,讓 Service 和 Activity 綁定,實際上是沒有什麼意義的。可是既然綁定了,你若是不解綁,那麼 Destroy() 毫無用武,因此,這種狀況和(2)中分析的仍是有區別的,此是解綁完後,服務仍是舒舒服服的在後臺運行,因此,要想幹掉這個服務,你必需要 STOP SERVICE。

六、那咱們解綁後,再 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");
  }
}

以上代碼作了幾件事:

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

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

三、根據 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能夠看出:

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

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

從上面的分析能夠看出 onHandleIntent() 方法確實至關的好用!

相關文章
相關標籤/搜索