在Activity和Service之間使用Binder和回撥介面進行通訊
Activity和Service之間的通訊方式有多種:通過broadcast、擴充套件Binder類、使用Messenger、使用AIDL等。
擴充套件Binder類使用場景
如果你的服務僅供本地應用使用,不需要跨程序工作,則可以實現擴充套件Binder 類,讓你的客戶端通過該類直接訪問服務中的公共方法。此方法只有在客戶端和服務位於同一應用和程序內這一最常見的情況下方才有效。例如,對於需要將 Activity 繫結到在後臺播放音樂的自有服務的音樂應用,此方法非常有效。
下面講一下怎麼樣擴充套件Binder類實現Activity和Service的通訊。
1、在Service中建立一個Binder例項,然後再onBind()方法中返回這個例項。
2、在客戶端中使用bindService從 onServiceConnected() 回撥方法接收Binder例項,這個Binder例項就是Service的回撥方法onBind()方法返回的Binder,然後使用這個Binder提供的方法呼叫繫結服務,得到Service例項。
3、然後就可以使用這個Service例項來呼叫其方法,這樣就可以對Sevice服務端進行操作。
4、如果Service端想要給客戶端返回資料咋辦呢?這個簡單,既然已經獲得了Service例項,那麼就可以在Service當中設定一個介面,然後在客戶端實現這個介面,這樣Service端就可以通過這個介面給客戶端傳送訊息了!
以下是Service類:
package com.easyliu.demo.binderdemo; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.util.Log; import android.widget.Toast; import java.util.Random; import java.util.TimerTask; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class LocalService extends Service { private static final String TAG = LocalService.class.getSimpleName(); private final IBinder mBinder = new LocalBinder(); // Random number generator private final Random mGenerator = new Random(); private OnDataArrivedListener mOnDataArrivedListener; private ScheduledExecutorService mThreadPool;//定時器執行緒池 private int mCount = 0; /** * Class for clients to access. Because we know this service always * runs in the same process as its clients, we don't need to deal with * IPC. */ public class LocalBinder extends Binder { LocalService getService() { return LocalService.this; } } @Override public void onCreate() { } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("LocalService", "Received start id " + startId + ": " + intent); // We want this service to continue running until it is explicitly // stopped, so return sticky. return START_STICKY; } @Override public void onDestroy() { Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show(); } /** * 定時任務 */ final TimerTask task = new TimerTask() { @Override public void run() { if (mOnDataArrivedListener != null) { mCount++; if (mCount == 1000) { mCount = 0; } mOnDataArrivedListener.onDataArrived(mCount); } } }; @Override public IBinder onBind(Intent intent) { //開啟定時任務 mThreadPool = Executors.newScheduledThreadPool(1); mThreadPool.scheduleAtFixedRate(task, 0, 1000, TimeUnit.MILLISECONDS); return mBinder; } @Override public boolean onUnbind(Intent intent) { //關閉定時器 if (mThreadPool != null) { mThreadPool.shutdownNow(); } return true; } /** * method for clients */ public int getRandomNumber() { return mGenerator.nextInt(100); } /** * 設定監聽 * * @param onDataArrivedListener */ public void setOnDataArrivedListener(OnDataArrivedListener onDataArrivedListener) { this.mOnDataArrivedListener = onDataArrivedListener; } }
在以上的Service當中,在onBind()方法中開啟了一個定時器,定時器的任務就是每隔一段時間通過介面:
public interface OnDataArrivedListener {
public void onDataArrived(int data);
}
傳送資料,在客戶端獲取到此Service例項之後呼叫以下方法,就可以接收到Service返回的資料了。
/**
* 設定監聽
*
* @param onDataArrivedListener
*/
public void setOnDataArrivedListener(OnDataArrivedListener onDataArrivedListener) {
this.mOnDataArrivedListener = onDataArrivedListener;
}
下面是客戶端程式碼實現:
package com.easyliu.demo.binderdemo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class LocalServiceActivity extends AppCompatActivity {
private boolean mIsBound;
private LocalService mBoundService;
private Button btn_bound;
private Button btn_unbound;
private Button btn_call_func;
private TextView tv_display;
private IncomingHandler mHandler;
private static final int MSG_FROM_SERVICE = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
mHandler = new IncomingHandler();
}
private void initViews() {
btn_bound = (Button) findViewById(R.id.btn_bound);
btn_unbound = (Button) findViewById(R.id.btn_ubound);
btn_call_func = (Button) findViewById(R.id.btn_call_func);
btn_bound.setOnClickListener(mListener);
btn_unbound.setOnClickListener(mListener);
btn_call_func.setOnClickListener(mListener);
tv_display = (TextView) findViewById(R.id.tv_display);
}
/**
* 接收資訊
*/
private final class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_FROM_SERVICE:
tv_display.setText(msg.obj + "");
break;
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mBoundService = ((LocalService.LocalBinder) service).getService();
//回撥監聽
mBoundService.setOnDataArrivedListener(new OnDataArrivedListener() {
@Override
public void onDataArrived(int data) {
//不是在主執行緒,不能直接操作主UI,切換到主執行緒
mHandler.obtainMessage(MSG_FROM_SERVICE, data).sendToTarget();
}
});
// Tell the user about this for our demo.
Toast.makeText(LocalServiceActivity.this, R.string.local_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
mBoundService = null;
Toast.makeText(LocalServiceActivity.this, R.string.local_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
/**
* 繫結服務
*/
void doBindService() {
bindService(new Intent(LocalServiceActivity.this,
LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
/**
* 解除繫結
*/
void doUnbindService() {
if (mIsBound) {
unbindService(mConnection);
mIsBound = false;
}
}
private OnClickListener mListener = new OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_bound:
doBindService();
break;
case R.id.btn_ubound:
doUnbindService();
break;
case R.id.btn_call_func:
Toast.makeText(LocalServiceActivity.this, "number:" + mBoundService.getRandomNumber(), Toast.LENGTH_SHORT).show();
break;
}
}
};
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
}
在onServiceConnected回撥方法中實現以下介面,當接收到訊息之後就把訊息傳送給主UI進行顯示。注意在這裡不能直接操作主UI的控制元件,因為回撥介面是在Service中的執行緒池裡面執行的,也就是以下方法onDataArrived的執行不在主UI,所以不能直接操作主UI的控制元件。
mBoundService.setOnDataArrivedListener(new OnDataArrivedListener() {
@Override
public void onDataArrived(int data) {
//不是在主執行緒,不能直接操作主UI,切換到主執行緒
mHandler.obtainMessage(MSG_FROM_SERVICE, data).sendToTarget();
}
});
下面是介面,當點選繫結之後,下面的數字就會隔一秒加1(這個可以用來音樂播放器後臺Service更新主UI的進度條),可以點選解除繫結。同時可以點選呼叫服務中的方法,就會Toast一個隨機數字。
關於繫結服務
繫結服務是客戶端-伺服器介面中的伺服器。繫結服務可讓元件(例如 Activity)繫結到服務、傳送請求、接收響應,甚至執行程序間通訊 (IPC)。繫結服務通常只在為其他應用元件服務時處於活動狀態,不會無限期在後臺執行。
繫結服務是 Service 類的實現,可讓其他應用與其繫結和互動。要提供服務繫結,你必須實現 onBind() 回撥方法。該方法返回的 IBinder 物件定義了客戶端用來與服務進行互動的程式設計介面。
繫結到已啟動服務
正如服務文件中所述,你可以建立同時具有已啟動和繫結兩種狀態的服務。也就是說,可通過呼叫 startService() 啟動該服務,讓服務無限期執行;此外,還可通過呼叫 bindService() 使客戶端繫結到服務。
如果你確實允許服務同時具有已啟動和繫結狀態,則服務啟動後,系統“絕對不會”在所有客戶端都取消繫結時銷燬服務。為此,你必須通過呼叫 stopSelf() 或 stopService() 顯式停止服務。
儘管你通常應該實現 onBind() 或 onStartCommand(),但有時需要同時實現這兩者。例如,音樂播放器可能發現讓其服務無限期執行並同時提供繫結很有用處。這樣一來,Activity 便可啟動服務進行音樂播放,即使使用者離開應用,音樂播放也不會停止。然後,當用戶返回應用時,Activity 可繫結到服務,重新獲得回放控制權。
客戶端可通過呼叫 bindService() 繫結到服務。呼叫時,它必須提供 ServiceConnection 的實現,後者會監控與服務的連線。bindService() 方法會立即無值返回,但當 Android 系統建立客戶端與服務之間的連線時,會呼叫 ServiceConnection 上的 onServiceConnected(),向客戶端傳遞用來與服務通訊的 IBinder。
多個客戶端可同時連線到一個服務。不過,只有在第一個客戶端繫結時,系統才會呼叫服務的 onBind() 方法來檢索 IBinder。系統隨後無需再次呼叫 onBind(),便可將同一 IBinder 傳遞至任何其他繫結的客戶端。
當最後一個客戶端取消與服務的繫結時,系統會將服務銷燬(除非 startService() 也啟動了該服務)。
當你實現繫結服務時,最重要的環節是定義你的 onBind() 回撥方法返回的介面。