Android知識點回顧之Service基礎

星泉毅發表於2019-03-04

一、Service的簡介

Service為Android四大元件之一,和Activity一樣,都是Context的子類,只是它沒有介面,Service很適合去執行那些長時間執行又不需要和使用者互動的任務。由於Service本身是在主執行緒執行的,所以如果需要執行耗時操作還是需要另外開啟子執行緒,否則會出現ANR錯誤。

Service包含三種型別:

  • Foreground:前臺服務。啟動的時候使用通知(Notification),以提示使用者此服務正在執行。需要注意的是此時的Service還是在後臺執行的。
  • Background:後臺服務。執行的時候使用者是不可感知的。
  • Bound:繫結服務。當使用bindService()方法啟動Service的時候,此Service為繫結服務。此時Service可以和與它繫結的元件進行互動。可以繫結多個元件,並且當所有繫結的元件對其解綁時此Service才會被銷燬。

二、Service的生命週期

Service 兩種啟動模式的生命週期

Service 有兩種啟動方式,startService() 和 bindService()。

  • startService() 啟動的生命週期如上圖左邊所示。特別的,會回撥 onStartCommand()方法

    • onCreate()在整個生命週期中只會被呼叫一次
    • onStartCommand()可能會被多次呼叫,包括 Service 重啟,重複呼叫 startService()
    • startService() 啟動的 Service 停止的方法是 Service 呼叫自己的 stopSelf() 方法或是其他其他元件呼叫 stopService(Intent name) 方法,引數中的 name 可以直接 new 一個,只要關聯的 class 為要停止的目標 Service就可以了。
    • stopSelf()還有個過載方法 stopSelf(int startId),可單獨停止 startId 對應的請求。startId 為 onStartCommand() 方法傳入的引數 startId,用來區分每次訪問請求,
  • bindService()啟動的生命週期如上圖右邊所示。特別的,會呼叫 onBind() 和 onUnbind()

    • onCreate() 在整個生命週期中只會被呼叫一次
    • 同一個元件可以繫結多次
    • 當多個元件繫結此服務時 onBind() 只在第一次繫結的時候被呼叫。
    • 用此方式啟動的 Service 使用 Service.stopSelf() 或者是在元件呼叫 stopService() 來停止服務是無效的
    • 此服務會一直執行下去,直到繫結的所有的元件呼叫 unbindService() 方法對其解綁,只要有一個繫結的元件未解綁,則此服務不會停止。

onStartCommand() 方法返回一個整數值,讓系統在系統殺死此 Service 的時候如何處理此 Service

  • START_NOT_STICKY:Service 被系統殺死後不會重啟
  • START_STICKY:Service 被系統殺死後會重啟,此時呼叫 onStartCommand() 傳入的 Intent 的值為null
  • START_REDELIVER_INTENT:Service 被系統殺死後會重啟,此時呼叫 onStartCommand() 傳入的 Intent 值為最後一次呼叫 startService() 時傳入的。

三、建立服務

1、建立後臺服務(無互動)

不可互動的後臺服務的啟動方式為 startService(),

public class MyService extends Service{
    public static final String TAG = "MyService";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG,"onBind");
        return null;
    }

    @Override
    public void onCreate() {
        Log.e(TAG,"onCreate");
        super.onCreate();
        runAfterStop();
    }

    private void runAfterStop(){
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                    Log.e(TAG,"stopSelf()");
                    stopSelf();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand");
        return START_NOT_STICKY;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG,"onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        Log.e(TAG,"onDestroy");
        super.onDestroy();
    }
}
複製程式碼

啟動服務

public class BaseActivity extends AppCompatActivity{
        ...
        Intent intent = new Intent(this, MyService.class);
        startService(intent);
        ...
}
複製程式碼

2、建立前臺服務

前臺服務會在通知欄/狀態列上顯示,並且此時的 Service 的優先順序比較高,

public class ForegroundService extends Service{
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        //id不能為0,否則通知欄/狀態列不會顯示
        int id = 1;
        startForeground(id,createNotification());
    }

    private Notification createNotification(){
        Intent notifiIntent = new Intent(this, FirstActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notifiIntent,0);

        Notification.Builder builder = new Notification.Builder(this)
                .setContentTitle("title")
                .setContentText("Content")
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setContentIntent(pendingIntent);

        return builder.build();
    }
}

...
//啟動服務
public class BaseActivity extends AppCompatActivity{
        ...
        Intent intent = new Intent(this, MyService.class);
        startService(intent);
        ...
}
複製程式碼

如果要取消前臺服務,可以呼叫 stopForeground(boolean removeNotification) ,

引數 removeNotification 為 true 表示取消前臺服務通知也移除掉通知欄/狀態列的圖示,false 表示不移除

呼叫此方法不會導致服務停止,只是把前臺轉到後臺

當服務停止的時候,通知欄/狀態列的圖示也會同時被移除

3、建立繫結服務(可互動的)

繫結服務(Bound Services)為客戶端-伺服器模式。由於需要互動,所以需要有一箇中間代理物件,此物件需要服務端建立然後返回給客戶端持有,型別為 Binder,由 onBind() 進行返回。

服務端程式碼:

public class BoundService extends Service {

    private IBinder mBinder = new LocalBinder();

    public class LocalBinder extends Binder{
        public int add(int a,int b){
            return BoundService.this.add(a,b);
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private int add(int a,int b){
        return a + b;
    }
}
複製程式碼

客戶端程式碼:

public class BoundActivity extends Activity{
    //持有繫結服務返回的 Binder 物件
    private BoundService.LocalBinder mBinder;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        serviceBind();
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(mServiceConn );
    }

    //繫結服務
    private void serviceBind() {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);
    }

    //點選事件
    public void addClick(View view){
        add(2,3);
    }

    //呼叫繫結服務的方法
    private void add(int a,int b){
        Log.(TAG,mBinder.add(a,b));
    }

    //繫結服務後的回撥介面
    private ServiceConnection mServiceConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG,"bing Service Connected");
            mBinder = (BoundService.LocalBinder) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG,"unBind Service disConnected");
            mBinder = null;
        }
    };

}
複製程式碼

這個只是基於簡單相同程式內呼叫,如果在 Manifest 檔案中給 BoundService 新增上 process=”:remote” 變成遠處服務,則上面的程式碼就會報錯,丟擲 java.lang.ClassCastException 異常,因為此種情況下返回的是 Binder 的代理物件 BInderProxy,所以丟擲型別轉換錯誤。程式間的通訊要用到 AIDL ,這個只能另外寫篇回顧了。

4、IntentService

IntentService 是 Service 的子類,用來處理非同步請求
特點:
1、會建立一個預設的工作子執行緒處理所有的請求
2、會有一個佇列逐個處理所有的 Intent 並會在 onHandleIntent() 實現
3、當 Service 處理所有的工作後會自動結束 Service,不需要手動呼叫 stopSelf()
4、預設實現返回值為 null 的 onBind() 方法
5、預設實現 onStartCommand(),會把請求的 Intent 放到工作佇列裡

所以我們不需要去管理 IntentService 的生命週期和管理執行緒。並且 IntentService 在處理完所以任務後會自動關閉。當業務不需要涉及到多執行緒任務時,IntentService 就能夠滿足大多數的需求了。

實現 IntentService 的方式很簡單,只需要提供一個構造方法和實現 onHandleIntent() 方法

public class TestIntentService extends IntentService {
    publicTestIntentService() {
        super("TestIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        //模擬耗時操作
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
複製程式碼

IntentService 的是使用 HandlerThread + Handler 來實現的。HandlerThread 為Thread的子類,Handler 執行在 HandlerThread 執行緒中,處理耗時操作。

四、Service 和執行緒 Thread

Service 是 Android 的一種機制,執行在主執行緒中,如果進行耗時操作,需要建立一個子執行緒執行。

Service 的優先順序高於後臺掛起的 Activity 和其所建立的子執行緒 Thread。系統可能會在記憶體不足的時候,優先殺死後臺掛起的 Activity 或 Thread,而不會輕易殺死 Service。

Thread 的執行是獨立於 Activity 的,當 Activity 被 finish 掉的時候,如果沒有主動停止 Thread 或未執行完任務,那麼它還會繼續執行。此時程式將不再持有這個 Thread 的引用,此時將控制不了此 Thread。

所以當需要長期穩定的在後臺執行某個任務時,需要使用 Service,而當 Service 在執行這個任務時要處理耗時操作時,要另外建立子執行緒來執行。

相關文章