綁定型服務是什麼?
綁定型服務是服務的一種,可以綁定應用程式元件並透過已定義的介面和元件互動。元件可以透過該介面呼叫服務的方法。
實作綁定型服務
透過繼承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中,沒有另外開啟新的執行緒來執行