綁定型服務是什麼?

綁定型服務是服務的一種,可以綁定應用程式元件並透過已定義的介面和元件互動。元件可以透過該介面呼叫服務的方法。
 

實作綁定型服務

透過繼承Service類別來建立綁定型服務,需要實作onBind方法,就是最簡單的綁定型服務。

public class BindService extends Service {
  public BindService() {
  }
  @Override
  public IBinder onBind(Intent intent) {
    // TODO: Return the communication channel to the service.
    throw new UnsupportedOperationException("Not yet implemented");
  }
}

重點為onBind方法,該方法會在元件綁定服務時執行,參數intent會從元件傳遞進來以攜帶需要從元件傳入的資料。
onBinder方法會回傳IBinder,當IBinder回傳到元件之後,元件便可透過IBinder和服務互動。
 

實作IBinder

IBinder是一個介面,其用途為讓元件取得服務的參考以進行和服務之間的互動。
因為Android已提供Binder類別,Binder類別已經實作IBinder介面中重要的方法。所以我們不必自己再實作IBinder介面,只需要建立Binder的子類別,並在該子類別中傳回服務的參考即可。

public class BindService extends Service {
  private IBinder mBinder = new ServiceBinder();
  public BindService() {
  }
  public class ServiceBinder extends Binder {
    public BindService getBindService() {
      return BindService.this;
    }
  }
  @Override
  public IBinder onBind(Intent intent) {
    return mBinder;
  }
  public int getRandom(){
   return new Random().nextInt(100);
  }
}

ServiceBinder類別的getBindService方法會回傳服務本身,元件就是透過這個方法取得服務的參考並與其互動。

實作ServiceConnection

當元件和服務進行綁定時是透過呼叫bindService方法來執行,如下
bindService(IntentToService, serviceConnect, Context.BIND_AUTO_CREATE);
該方法第2個參數為ServiceConnect物件,元件透過ServiceConnect物件取得目前和服務綁定的狀況(已連結或已中斷),我們需要建立ServiceConnect物件並實作2個方法,分別為

@Override
public void onServiceConnected(ComponentName name, IBinder service) {}

onServiceConnected方法會在元件綁定服務時被呼叫,該方法的IBinder參數即為從服務的onBind方法回傳,我們就可以透過該參數來取得服務。

@Override
public void onServiceDisconnected(ComponentName name) {}

onServiceDisconnected方法會在元件和服務中斷時被呼叫。
建立BindServiceActivity如下

public class BindServiceActivity extends AppCompatActivity {
  private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
    }
  };
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_bind_service);
  }
}

接著在ServiceConnection的onServiceConnected方法中實作透過傳入的binder取得服務的參考。

private BindService mBindService;
private ServiceConnection mServiceConnection = new ServiceConnection() {
  @Override
  public void onServiceConnected(ComponentName name, IBinder binder) {
    ServiceBinder serviceBinder = (ServiceBinder) binder;
    mBindService = serviceBinder.getBindService();
  }
  @Override
  public void onServiceDisconnected(ComponentName name) {
  }
};

另外在元件中還需要一個布林變數來記錄目前是否已綁定服務,這個變數主要是方便元件對服務進行綁定或解綁定。

private boolean mIsServiceBound;
private ServiceConnection mServiceConnection = new ServiceConnection() {
  @Override
  public void onServiceConnected(ComponentName name, IBinder binder) {
    ServiceBinder serviceBinder = (ServiceBinder) binder;
    mBindService = serviceBinder.getBindService();
    mIsServiceBound = true;
  }
  @Override
  public void onServiceDisconnected(ComponentName name) {
    mIsServiceBound = false;
  }
};

 

綁定服務或解綁定服務

以Activity來說綁定服務的實作位置通常會在onCreate或onStart,而解綁定的位置會對應綁定的位置。
若Activity移到後台時也要持續從服務收到更新,那就在onCreate綁定然後在onDestroy解綁定。
若Activity只有在前台顯示時需要收到服務的更新,在後台時不需要。就在onStart綁定然後在onStop解綁定。
以下在onCreate綁定服務,在onDestroy解綁定服務。

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_bind_service);
  Intent bindService = new Intent(this, BindService.class);
  if (!mIsServiceBound) {
    bindService(bindService, mServiceConnection, Context.BIND_AUTO_CREATE);
  }
}
@Override
protected void onDestroy() {
  super.onDestroy();
  if (mIsServiceBound) {
    unbindService(mServiceConnection);
    mIsServiceBound = false;
  }
}

最後在Activity加入按鈕,按鈕按下時就會從服務取得亂數

@Override
public void onClick(View v) {
  int uiID = v.getId();
  switch (uiID) {
    case R.id.bind_service_get_service_random_number:
      if (mIsServiceBound) {
        Toast.makeText(this, "get Service random number:" + mBindService.getRandom(), Toast.LENGTH_SHORT).show();
      }
      break;
  }
}

 
以下為完整程式碼

public class BindServiceActivity extends AppCompatActivity implements OnClickListener {
  private BindService mBindService;
  private boolean mIsServiceBound;
  private Button mGetServiceRandomNumber;
  private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
      ServiceBinder serviceBinder = (ServiceBinder) binder;
      mBindService = serviceBinder.getBindService();
      mIsServiceBound = true;
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
      mIsServiceBound = false;
    }
  };
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_bind_service);
    mGetServiceRandomNumber = findViewById(R.id.bind_service_get_service_random_number);
    mGetServiceRandomNumber.setOnClickListener(this);
    Intent bindService = new Intent(this, BindService.class);
    if (!mIsServiceBound) {
      bindService(bindService, mServiceConnection, Context.BIND_AUTO_CREATE);
    }
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
    if (mIsServiceBound) {
      unbindService(mServiceConnection);
      mIsServiceBound = false;
    }
  }
  @Override
  public void onClick(View v) {
    int uiID = v.getId();
    switch (uiID) {
      case R.id.bind_service_get_service_random_number:
        if (mIsServiceBound) {
          Toast.makeText(this, "get Service random number:" + mBindService.getRandom(), Toast.LENGTH_SHORT).show();
        }
        break;
    }
  }
}
public class BindService extends Service {
  private IBinder mBinder = new ServiceBinder();
  public BindService() {
  }
  public class ServiceBinder extends Binder {
    public BindService getBindService() {
      return BindService.this;
    }
  }
  @Override
  public IBinder onBind(Intent intent) {
    return mBinder;
  }
  public int getRandom(){
   return new Random().nextInt(100);
  }
}

 

綁定型服務的生命週期

在BindService複寫onCreate, onBind, onStartCommand, onUnbind, onDestroy加入log來觀察生命週期。

public class BindService extends Service {
…
  public BindService() {
    Log.d(TAG, "BindService: ");
  }
  @Override
  public void onCreate() {
    Log.d(TAG, "onCreate: ");
    super.onCreate();
  }
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d(TAG, "onStartCommand: ");
    return super.onStartCommand(intent, flags, startId);
  }
  @Override
  public IBinder onBind(Intent intent) {
    Log.d(TAG, "onBind: ");
    return mBinder;
  }
  @Override
  public boolean onUnbind(Intent intent) {
    Log.d(TAG, "onUnbind: ");
    return super.onUnbind(intent);
  }
  @Override
  public void onDestroy() {
    Log.d(TAG, "onDestroy: ");
    super.onDestroy();
  }
…
}

在BindServiceActivity複寫onCreate, ServiceConnection的onServiceConnected, onServiceDisconnected加入log觀察生命週期

public class BindServiceActivity extends AppCompatActivity implements OnClickListener {
  private static final String TAG = BindServiceActivity.class.getSimpleName();
  private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
      ServiceBinder serviceBinder = (ServiceBinder) binder;
      mBindService = serviceBinder.getBindService();
      mIsServiceBound = true;
      Log.d(TAG, "onServiceConnected: ");
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
      mIsServiceBound = false;
      Log.d(TAG, "onServiceDisconnected: ");
    }
  };
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_bind_service);
    mGetServiceRandomNumber = findViewById(R.id.bind_service_get_service_random_number);
    mGetServiceRandomNumber.setOnClickListener(this);
    Intent bindService = new Intent(this, BindService.class);
    if (!mIsServiceBound) {
      Log.d(TAG, "Activity call bindService: ");
      bindService(bindService, mServiceConnection, Context.BIND_AUTO_CREATE);
    }
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
    if (mIsServiceBound) {
      unbindService(mServiceConnection);
      Log.d(TAG, "Activity call unbindService: ");
      mIsServiceBound = false;
    }
  }
  @Override
  public void onClick(View v) {
    int uiID = v.getId();
    switch (uiID) {
      case R.id.bind_service_get_service_random_number:
        if (mIsServiceBound) {
          Toast.makeText(this, "get Service random number:" + mBindService.getRandom(), Toast.LENGTH_SHORT).show();
        }
        break;
    }
  }
}

啟動Activity後的log為

D/BindServiceActivity: Activity call bindService:
D/BindService: BindService:
D/BindService: onCreate:
D/BindService: onBind:
D/BindServiceActivity: onServiceConnected:

需要注意的是onServiceConnected方法是在服務呼叫onBind方法之後呼叫
點擊back key之後的log為

D/BindServiceActivity: Activity call unbindService:
D/BindService: onUnbind:
D/BindService: onDestroy:

需要注意的是當元件透過呼叫unbindService去解綁定服務時, ServiceConnection的onServiceDisconnected方法不會被呼叫。
 

多個元件綁定和解綁定服務

綁定型服務可以和多個元件一起綁定。和多個元件綁定之後只有當所有的元件都解綁定該服務才會銷毀。
為了測試多個元件綁定服務,新增AnotherBindServiceActivity,如下

public class AnotherBindServiceActivity extends AppCompatActivity implements OnClickListener {
  private static final String TAG = AnotherBindServiceActivity.class.getSimpleName();
  private Button mBindServiceBtn;
  private Button mUnbindServiceBtn;
  private Button mGetRandomNumberBtn;
  private Button mLaunchBindServiceActivityBtn;
  private boolean mIsServiceBound;
  private BindService mBindService;
  private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
      ServiceBinder serviceBinder = (ServiceBinder) binder;
      mBindService = serviceBinder.getBindService();
      mIsServiceBound = true;
      Log.d(TAG, "onServiceConnected: ");
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
      mIsServiceBound = false;
      Log.d(TAG, "onServiceDisconnected: ");
    }
  };
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_another_bind_service);
    mLaunchBindServiceActivityBtn = findViewById(R.id.another_bind_service_start_bind_service_activity);
    mLaunchBindServiceActivityBtn.setOnClickListener(this);
    mBindServiceBtn = findViewById(R.id.another_bind_service_bind_service);
    mBindServiceBtn.setOnClickListener(this);
    mUnbindServiceBtn = findViewById(R.id.another_bind_service_unbind_service);
    mUnbindServiceBtn.setOnClickListener(this);
    mGetRandomNumberBtn = findViewById(R.id.another_bind_service_get_service_random_number);
    mGetRandomNumberBtn.setOnClickListener(this);
  }
  @Override
  protected void onResume() {
    super.onResume();
    Log.d(TAG, "AnotherBindServiceActivity is:"+this.toString());
  }
  @Override
  public void onClick(View v) {
    int uiID = v.getId();
    switch(uiID){
      case R.id.another_bind_service_start_bind_service_activity:
        Intent launchBindServiceActivity = new Intent(this, BindServiceActivity.class);
        launchBindServiceActivity.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        startActivity(launchBindServiceActivity);
        break;
      case R.id.another_bind_service_bind_service:
        Intent bindService = new Intent(this, BindService.class);
        if (!mIsServiceBound) {
          Log.d(TAG, "Activity call bindService: ");
          bindService(bindService, mServiceConnection, Context.BIND_AUTO_CREATE);
        }
        break;
      case R.id.another_bind_service_unbind_service:
        if (mIsServiceBound) {
          unbindService(mServiceConnection);
          Log.d(TAG, "Activity call unbindService: ");
          mIsServiceBound = false;
        }
        break;
      case R.id.another_bind_service_get_service_random_number:
        if (mIsServiceBound) {
          Toast.makeText(this, "get Service random number:" + mBindService.getRandom()+" Service:"+mBindService.toString(), Toast.LENGTH_SHORT).show();
        }else{
          Toast.makeText(this, "Service not bind:", Toast.LENGTH_SHORT).show();
        }
        break;
    }
  }
}

重點在於AnotherBindServiceActivity也可綁定BindService,目前有BindServiceActivity和AnotherBindServiceActivity可綁定BindService。
另外修改BindServiceActivity新增按鍵用來綁定和解綁定以及啟動AnotherBindServiceActivity,如下

public class BindServiceActivity extends AppCompatActivity implements OnClickListener {
  private static final String TAG = BindServiceActivity.class.getSimpleName();
  private BindService mBindService;
  private boolean mIsServiceBound;
  private Button mGetServiceRandomNumberBtn;
  private Button mtBindServiceBtn;
  private Button mtUnBindServiceBtn;
  private Button mStartAnotherBindServiceActivityBtn;
  private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
      ServiceBinder serviceBinder = (ServiceBinder) binder;
      mBindService = serviceBinder.getBindService();
      mIsServiceBound = true;
      Log.d(TAG, "onServiceConnected: ");
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
      mIsServiceBound = false;
      Log.d(TAG, "onServiceConnected: ");
    }
  };
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_bind_service);
    mGetServiceRandomNumberBtn = findViewById(R.id.bind_service_get_service_random_number);
    mGetServiceRandomNumberBtn.setOnClickListener(this);
    mtBindServiceBtn = findViewById(R.id.bind_service_bind_service);
    mtBindServiceBtn.setOnClickListener(this);
    mtUnBindServiceBtn = findViewById(R.id.bind_service_unbind_service);
    mtUnBindServiceBtn.setOnClickListener(this);
    mStartAnotherBindServiceActivityBtn = findViewById(R.id.bind_service_start_another_bind_service_activity);
    mStartAnotherBindServiceActivityBtn.setOnClickListener(this);
  }
  @Override
  protected void onResume() {
    super.onResume();
    Log.d(TAG, "BindServiceActivity is:"+this.toString());
  }
  @Override
  protected void onDestroy() {
    super.onDestroy();
  }
  @Override
  public void onClick(View v) {
    int uiID = v.getId();
    switch (uiID) {
      case R.id.bind_service_get_service_random_number:
        if (mIsServiceBound) {
          Toast.makeText(this, "get Service random number:" + mBindService.getRandom()+" Service:"+mBindService.toString(), Toast.LENGTH_SHORT).show();
        }else{
          Toast.makeText(this, "Service not bind:", Toast.LENGTH_SHORT).show();
        }
        break;
      case R.id.bind_service_bind_service:
        Intent bindService = new Intent(this, BindService.class);
        if (!mIsServiceBound) {
          Log.d(TAG, "Activity call bindService: ");
          bindService(bindService, mServiceConnection, Context.BIND_AUTO_CREATE);
        }
        break;
      case R.id.bind_service_unbind_service:
        if (mIsServiceBound) {
          unbindService(mServiceConnection);
          Log.d(TAG, "Activity call unbindService: ");
          mIsServiceBound = false;
        }
        break;
      case R.id.bind_service_start_another_bind_service_activity:
        Intent launchAnotherBindServiceActivity = new Intent(this, AnotherBindServiceActivity.class);
        launchAnotherBindServiceActivity.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        startActivity(launchAnotherBindServiceActivity);
        break;
    }
  }
}

注意BindServiceActivity和AnotherBindServiceActivity是透過FLAG_ACTIVITY_REORDER_TOFRONT來互相切換,並不是產生新的Activity實體。
現在先啟動BindServiceActivity並點擊bind service按鈕,Log如下

D/BindServiceActivity: Activity call bindService:
D/BindService: BindService:
D/BindService: onCreate:
D/BindService: onBind:
D/BindServiceActivity: onServiceConnected:

可以看出服務已被綁定,接著點擊按鈕啟動AnotherBindServiceActivity並在該Activity中點擊bind service按鈕,Log如下

D/AnotherBindServiceActivity: Activity call bindService:
D/AnotherBindServiceActivity: onServiceConnected:

AnotherBindServiceActivity綁定服務之後服務不會再呼叫建構式以及onCreate, onBind方法,因為該服務已啟動。
接著點擊AnotherBindServiceActivity的unbind service按鈕,Log如下

D/AnotherBindServiceActivity: Activity call unbindService:

可以看到該服務並未銷毀,只是和AnotherBindServiceActivity解綁定。
接著按下start BindServiceActivity按鈕來啟動BindServiceActivity,啟動之後點擊unbind service按鈕,Log如下。

D/BindServiceActivity: Activity call unbindService:
D/BindService: onUnbind:
D/BindService: onDestroy:

這時候因為所有和該服務綁定的元件都解綁定服務,服務銷毀。
 

綁定型服務的優缺點

綁定型服務的優點在於可以和多個元件互動,傳遞訊息。
缺點在於服務本身的執行也是在UI Thread中,沒有另外開啟新的執行緒來執行