Android-服务跨进程通信(Binder/Messenger/AIDL)

官方文档中国版: https://developer.android.google.cn/guide/components/bound-services.html 谷歌2016年底为中国开发者提供中国版,方便访问!

绑定服务简介

Android服务与客户端相互调用(传递消息),必须创建绑定服务bindService()提供IBinder接口()!
服务与客户端交互方式(传递消息)有三种:

1.使用Binder类(客户端与服务在同一进程)
如果服务与客户端在同一进程中运行,通过扩展Binder类并从onBind()返回它的一个实例来创建接口。
客户端收到Binder后,可利用它直接访问Binder实现中乃至Service中可用的公共方法!

2.使用Messenger(客户端与服务在不同进程,服务只能是单线程)
不同进程工作,可用Messenger为服务创建接口。Message对象内含Handler。
随后可与客户端分享一个IBinder,从而让客户端能利用 Message 对象向服务发送命令。
此外,客户端还可定义自有 Messenger,以便服务回传消息!
这是进程间通信(IPC)的最简单方法(AIDL的简化),因为Messenger会在服务单一线程创建所有请求队列。

3.使用AIDL(客户端与服务在不同进程,服务可以是多线程)
AIDL(Android 接口定义语言)执行所有将对象分解成原语的工作,
操作系统可以识别这些原语并将它们编组到各进程中以执行IPC。
之前的Messenger实际是以AIDL作为其底层结构。如果服务同时处理多个请求(多线程),应该用AIDL。

注:大多数应用“都不会”使用AIDL来创建绑定服务,因为它可能要求具备多线程处理能力,
	并可能导致实现的复杂性增加。因此,AIDL并不适合大多数应用!
	
4.此外,还可用广播这个万金油组件进行传递消息,无论是不同进程,还是不同APP应用,都可用广播传递消息!
但是需要注意的是, 广播有可能泄露数据、恶意程序发送广播等安全性问题,
所以应该限制广播只在本应用内传播:
	1.设置permission或Intent.setPackage,不把广播发送到应用外!
	2.使用LocalBroadcastManager或设置android:exported="false"不接收应用外广播!

一.使用Binder类(客户端与服务在同一进程)


// 1.本地服务————————————————————————————————————
public class LocalService extends Service {  
    private final IBinder mLocalBinder = new LocalBinder();
	
    public class LocalBinder extends Binder {
        LocalService getService() {           
            return LocalService.this;
        }
    }

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

    public int getRandomNumber() {
      return new Random().nextInt(100);
    }
}

// 2.客户端————————————————————————————————————
public class BindingActivity extends Activity {    
    boolean mBound = false;
	
	 @Override
    protected void onStart() {
        super.onStart();
		// 绑定本地服务(同一进程)
        bindService(new Intent(this, LocalService.class), 
					mConnection, 
					Context.BIND_AUTO_CREATE);
    }
	
	private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
			mBound = true;
			// 获取本地服务的实例,并调用方法,获取随机数
            LocalService localService = ((LocalBinder) service).getService();            
			int num = localService.getRandomNumber();				
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };

    @Override
    protected void onStop() {
        super.onStop();       
        if (mBound) {
			// 解绑本地服务
            unbindService(mConnection);
            mBound = false;
        }
    }   
}

二.使用Messenger(客户端与服务在不同进程,服务只能是单线程)


// 1.注册远程服务————————————————————————————————————
	<service  
		android:name=".MessengerService"  
		android:process=":remote"> (process指定服务在另一个进程,名叫remote)
	</service>

// 2.远程服务————————————————————————————————————
public class MessengerService extends Service {

    final Messenger mServiceMessenger = new Messenger(new Handler(){
        @Override
        public void handleMessage(Message msg) {
			// 接收客户端消息
            switch (msg.what) {
                case 1:					
                    Toast.makeText(getApplicationContext(), "service hello!", Toast.LENGTH_SHORT).show();					
					Thread.sleep(3000);
					// 向客户端发消息
					Messenger messenger = msg.replyTo;
					messenger.send(Message.obtain(null, 1, 0, 0))
                    break;               
            }
        }
    });

    @Override
    public IBinder onBind(Intent intent) {        
        return mServiceMessenger.getBinder();
    }
}

// 3.客户端————————————————————————————————————
public class ActivityMessenger extends Activity { 
    boolean mBound = false;
	
	private Messenger mActivityMessenger = new Messenger(new Handler(){
        @Override
        public void handleMessage(Message msg){
			// 接收远程服务消息
            switch (msg.what){
                case 1:
                    Toast.makeText(ActivityMessenger.this, "activity hello!", Toast.LENGTH_SHORT).show();
                    break;				
            }
        }
    });

    private ServiceConnection mConnection = new ServiceConnection(){
        public void onServiceConnected(ComponentName className, IBinder service) {       
            mBound = true;
			// 向远程服务发消息				
			Messenger messenger = new Messenger(service);
			Message msg = Message.obtain(null, 1, 0, 0); 
			msg.replyTo = mActivityMessenger
            messenger.send(msg);			
        }

        public void onServiceDisconnected(ComponentName className) {
            mBound = false;
        }
    };

    @Override
    protected void onStart() {
        super.onStart();  
		// 绑定远程服务(不同进程)
        bindService(new Intent(this, MessengerService.class),
				mConnection,
				Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();        
		// 解绑远程服务
        if (mBound) {
            unbindService(mConnection);           
        }
    }
}

三.使用AIDL(客户端与服务在不同进程,服务可以是多线程)

1.创建AIDL接口

默认情况下AIDL支持下列数据类型
int、long、char、boolean、String、CharSequence、List、Map
List中元素都必须是以上类型、其他AIDL接口、可打包类型(即实现Parcelable接口).
Map中元素都必须是以上类型、其他AIDL接口、可打包类型(即实现了Parcelable接口)
不支持通用Map(如Map<String,Integer>形式的Map)

// IRemoteService.aidl文件
interface IRemoteService {
	// 注册回调
	void registerCallback(IRemoteServiceCallBack cb);     
	void unregisterCallback(IRemoteServiceCallBack cb);  
}

// IRemoteServiceCallBack.aidl文件
interface IRemoteServiceCallBack{
	void valueChanged(int value);
}

2.在远程服务中实现AIDL接口


public class RemoteService extends Service {
	// 一个服务会绑定多个客户端, 需要集合来存放客户端的回调接口
	private RemoteCallbackList<IRemoteServiceCallBack> mCallbackList 
				= new RemoteCallbackList<IRemoteServiceCallBack>();
				
	// 实现IRemoteService.AIDL接口
    private IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        @Override
        public void registerCallback(IRemoteServiceCallBack cb)throws RemoteException {
            if (cb != null) {
                mCallbackList.register(cb);
            }
        }
		
        @Override
        public void unRegisterCallback(IRemoteServiceCallBack cb)throws RemoteException {
            if (cb != null) {
                mCallbackList.unregister(cb);
            }
        }
    };
	
	// 通知所有客户端的回调接口
	public void sendMsg(){		
		int N = mCallbackList.beginBroadcast();
		for(int i=0;i<N;i++){
			try {
				// 回调通知客户端
				mCallbackList.getBroadcastItem(i).valueChanged(mValue);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}		
		mCallbackList.finishBroadcast(); // 通知完成
	}

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

3.客户端


public class BindActivity extends Activity {
    IRemoteService mService = null;
    private boolean mIsBound = false;	
	
	// 实现IRemoteServiceCallback.AIDL接口
	private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {     
		/**
		* 来自远程服务的回调通知,
		* 此方法不在UI线程中, 更新UI需小心!
		*/
        public void valueChanged(int value) {
			...
        }
    };
	
	private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,IBinder service){
			mIsBound = true;
            mService = IRemoteService.Stub.asInterface(service);			
			// 注册回调接口
            mService.registerCallback(mCallback);		
        }

        public void onServiceDisconnected(ComponentName className) {           
			mIsBound = false;
            mService = null;
        }
    };
	
    @Override
    protected void onStart() {
        super.onStart();
		// 绑定远程服务
		bindService(new Intent(BindActivity.this, RemoteService.class),
					mConnection,
					Context.BIND_AUTO_CREATE);	
    }

	@Override
    protected void onStop() {
        super.onStop();  
		if (mIsBound){
			// 取消回调接口
			mService.unregisterCallback(mCallback);
			// 解绑远程服务
			unbindService(mConnection);
        }
	}
}

总结:

综上比较, AIDL实现客户端与远程服务通信太繁琐, 相互通知调用至少需要两个AIDL接口文件
在远程服务中通知客户端, 需要循环通知, 相当繁琐!
除非迫不得以(如服务可以是多线程运行), 实在不建议使用AIDL!

客户端与服务在不同进程时, 建议服务单线程运行,
使用Messenger通信, 这也是Android官方推荐的!

简书: http://www.jianshu.com/p/aec29a98bc1e
CSDN博客: http://blog.csdn.net/qq_32115439/article/details/72760479
GitHub博客:http://lioil.win/2017/05/25/Android_bindService.html
Coding博客:http://c.lioil.win/2017/05/25/Android_bindService.html