【IPC通信】Server如何知道Client崩溃

我们知道,Android是Google基于linux开发的操作系统,沙盒机制的存在使运行在用户空间的各应用程序之间无法直接感知对方并访问对方进程空间,以保护各app敏感数据和文件不受影响。而各进程之间常需要进行数据通信。所以Android提供了Binder机制来解决通常的进程间通信的问题。Binder驱动运行在内核空间,拥有更高的权限,来充当进程间的信使来传递数据有天然优势,它通过代理对象的方式在进程之间进行传话,使得app之间能够进行数据共享甚至方法互调。为了简化进行IPC通信的书写方式,Google使用aidl自动构建代理类。我们只需要一套方法接口,修改文件后缀.java为.aidl。Android Studio识别aidl后会自动在build/generated/source/aidl/下构建代理类。然后接收方继承此Stub代理,实现其中的接口方法,发送方就可以使用Stub调用来实现通信。
在Binder使用过程中,我遇到一个业务场景。应用A作为Client在启动时,需要通过Binder对Server应用进行UUID注册,Server以此UUID来进行任务调度。当应用A退出时需要对Server的UUID进行unregister操作。然而如果应用A意外崩溃或者被kill时,不能正常调用反注册方法。那么会导致Server的UUID管理混乱,进而导致任务调度紊乱。所以当A Client意外崩溃没有进行反注册操作时,Server如何感知此事件,并清除失效的UUID呢?
我们先新建adil类IInterface.aidl(相当于Server的代理)和ICallBack.aidl(Client端给Server的回调接口,用于告诉是否注册成功等信息)。
    1.IInterface.aidl:
package com.why.ipcbinddeath;
import com.why.ipcbinddeath.ICallBack;
interface IInterface {
    void registerCallBack(String UUID,in ICallBack callback);
    void unRegisterCallBack(String UUID);
}
        2.ICallBack.aidl:
package com.why.ipcbinddeath;
interface ICallBack {
    void replyMessage(String message);
}
    首先,我们先构建Server端。我们需要继承Service并重写onBind方法,返回IInterface代理类,此代理类告诉Binder驱动Client的调用方式。client可以通过调用registerCallBack和unRegisterCallBack方法告诉Service注册和反注册信息。
public class IPCService extends Service {
	private static final String TAG = "IPCService";
	public static final int DO_SOMETHING = 1;
	private Map<String, ICallBack> mUUIDs = new HashMap<>();
	private Handler mHandler = new Handler() {


		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
				case DO_SOMETHING:
					doSomething();
					removeMessages(DO_SOMETHING);
					sendEmptyMessageDelayed(DO_SOMETHING, 1000);
					break;
			}
		}
	};

	@Override
	public void onCreate() {
		super.onCreate();
		mHandler.sendEmptyMessage(DO_SOMETHING);
	}

	@Override
	public IBinder onBind(Intent intent) {

		return new IPCBinder();
	}

	public class IPCBinder extends IInterface.Stub {

		@Override
		public void registerCallBack(String UUID, ICallBack callback) throws RemoteException {
			if (!mUUIDs.containsKey(UUID)) {
				mUUIDs.put(UUID, callback);
			} else {
				ICallBack iCallBack = mUUIDs.remove(UUID);
				if (iCallBack != null) {
					iCallBack.replyMessage("当前iCallBack已被替代");
				}
				mUUIDs.put(UUID, callback);
			}
			callback.replyMessage("UUID:" + UUID + "注册成功");
		}

		@Override
		public void unRegisterCallBack(String UUID) throws RemoteException {
			if (mUUIDs.containsKey(UUID)) {
				ICallBack iCallBack = mUUIDs.remove(UUID);
				iCallBack.replyMessage("当前UUID已经反注册成功");
			}

		}
	}

	public void doSomething() {
		for (Map.Entry<String, ICallBack> entry : mUUIDs.entrySet()) {

			ICallBack iCallBack = entry.getValue();

			Log.i(TAG, "doSomething...UUID:" + entry.getKey());

		}
	}

	@Override
	public boolean onUnbind(Intent intent) {
		return super.onUnbind(intent);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		mHandler.removeMessages(DO_SOMETHING);
	}
    如上所示,当client注册UUID之后,我们在doSomething中模拟任务调度,并打印出当前Server记录的所有UUID。并且我们假设Server已处于运行态,这种假设我们可以通过在Server端MainActivity中通过startService保证。
public class MainActivity extends AppCompatActivity {
	private static final String TAG = "MainActivity";


	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		startService(new Intent(getApplicationContext(), IPCService.class));
	}

}
    其次,构建Client端模拟进行注册反注册操作,并模拟注册后5s后程序被kill掉。
package com.why.ipcbinddeathclient;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.why.ipcbinddeath.ICallBack;
import com.why.ipcbinddeath.IInterface;

import java.util.UUID;


public class MainActivity extends AppCompatActivity {
	private static final String TAG = "MainActivity";
	private ServiceConnection serviceConnection;
	private IInterface iInterface;
	private String uuid = UUID.randomUUID().toString().replace("-", "");
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		findViewById(R.id.register).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				register();
			}
		});
		findViewById(R.id.unregister).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				unregister();
			}
		});
	}

	private void unregister() {
		if (serviceConnection != null) {
			try {
				if (iInterface != null) {
					iInterface.unRegisterCallBack(uuid);
				} else {
					Toast.makeText(this, "还未Bind成功...", Toast.LENGTH_SHORT).show();
				}

			} catch (RemoteException e) {
				e.printStackTrace();
			}
			unbindService(serviceConnection);
			serviceConnection = null;
		} else {
			Toast.makeText(this, "请先点击注册按钮...", Toast.LENGTH_SHORT).show();
		}
		Log.i(TAG, "unregister");
	}

	private void register() {
		Intent intent = new Intent();
		intent.setClassName("com.why.ipcbinddeath", "com.why.ipcbinddeath.server.IPCService");
		serviceConnection = new IPCServiceConnection();
		bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
		Log.i(TAG, "register");
	}

	private Handler mHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
				case 1:
					Process.killProcess(Process.myPid());
					break;
				default:
					break;
			}

		}
	};

	ICallBack mCallBack = new ICallBack.Stub() {
		@Override
		public void replyMessage(String message) throws RemoteException {

			Log.i(TAG, "replyMessage:" + message);

			mHandler.sendEmptyMessageDelayed(1,5000);
		}
	};

	private class IPCServiceConnection implements ServiceConnection {
		//绑定成功
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			iInterface = IInterface.Stub.asInterface(service);
			try {
				iInterface.registerCallBack(uuid, mCallBack);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
		//绑定失败
		@Override
		public void onServiceDisconnected(ComponentName name) {
			iInterface = null;
		}
	}

}
    通过运行client发现,当我们点击注册按钮后,收到了Server发过来的信息。
05-27 09:09:37.617 6782-6782/com.why.ipcbinddeathclient I/MainActivity: register
05-27 09:09:37.631 6782-6782/com.why.ipcbinddeathclient I/MainActivity: replyMessage:UUID:76a57dd71ab648e6a651f6734e43b866注册成功
    然而5s后client被kill时并没有实现进行unRegister操作(此情况很常见,比如用户直接点击menu键弹出的recent apps页面中进行清理)。此时我们点进server进程发现在doSomething方法中依然保持有当前client的UUID,也就是说client并没有反注册。此后Server端一直保持着UUID,认为client还存活,以后进行任务调度时会多很多冗余处理。所以当前的问题是如何在client被kill时立刻通知到Server把UUID给及时清除掉。
    其实,Google早已在源码中给出了解决办法RemoteCallbackList类。在RemoteCallbackList中使用CallBack内部类来封装了client往Server注册的callback对象,在CallBack中实现了一个关键接口类IBinder.DeathRecipient,当链接的对象所在的进程被kill时,会回调到接口的onBindDied中。不过要想onBindDied起作用,参考RemoteCallbackList中以下两个关键点。
        1.linkToDeath
 public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }
        2.unLinkToDeath
public boolean unregister(E callback) {
        synchronized (mCallbacks) {
            Callback cb = mCallbacks.remove(callback.asBinder());
            if (cb != null) {
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
                return true;
            }
            return false;
        }
    }
    基于此,那在我们的Server中也可以使用这两个方法来保证当client被kill时能立刻把消息传给Server。嗯,接下来就是对Server的Service进行改造。首先我们也可以对map中的value进行封装,封装类写在Service内部即可,如下面所示。
private class CallBack implements IBinder.DeathRecipient{
		ICallBack callBack;
		String UUID;
		public CallBack(String UUID,ICallBack callBack){
			this.UUID = UUID;
			this.callBack = callBack;
		}

		@Override
		public void binderDied() {
                        Log.i(TAG,"binderDied:"+UUID);
			mUUIDs.remove(UUID);
		}
		public ICallBack getCallBack(){
			return callBack;
		}
		public String  getUUID(){
			return UUID;
		}
	}
    对应的,service中的map集合也应该变成这样。
private Map<String, CallBack> mUUIDs = new HashMap<>();
    当我们的client调用Binder代理的register和unRegister方法时我们应该把上文所示RemoteCallbackList两个关键点补上,在注册时linkToDeath,在反注册时unLinkToDeath,这样就保证了当Client意外崩溃或被kill我们Service中的内部类CallBack中的onBindDied方法能够被调用。我们也就可以在此方法中移除掉崩溃回调所对应的UUID。以下是Service返回的重新改造的IPCBinder类
        
public class IPCBinder extends IInterface.Stub {

		@Override
		public void registerCallBack(String UUID, ICallBack callback) throws RemoteException {
			CallBack value = null;
			if (!mUUIDs.containsKey(UUID)) {

				value= new CallBack(UUID, callback);
				mUUIDs.put(UUID, value);
			} else {
				CallBack iCallBack = mUUIDs.remove(UUID);
				if (iCallBack != null&&iCallBack.getCallBack()!=null) {
					iCallBack.getCallBack().replyMessage("当前iCallBack已被替代");
				}
				value = new CallBack(UUID, callback);
				mUUIDs.put(UUID, value);

			}
			callback.asBinder().linkToDeath(value,0);
			callback.replyMessage("UUID:" + UUID + "注册成功");
		}

		@Override
		public void unRegisterCallBack(String UUID) throws RemoteException {
			if (mUUIDs.containsKey(UUID)) {
				CallBack iCallBack = mUUIDs.remove(UUID);
				iCallBack.getCallBack().replyMessage("当前UUID已经反注册成功");
				iCallBack.getCallBack().asBinder().unlinkToDeath(iCallBack,0);
			}

		}
	}
    最终运行效果,当Client端被kill时,Server立即回调到onBindDied中,得以即时删除UUID,来保证任务调度不会紊乱。
05-27 11:59:03.997 7634-7634/com.why.ipcbinddeath:server I/IPCService: doSomething...UUID:9f1044e33dac4a80b605c9a9eed72594
05-27 11:59:05.001 7634-7634/com.why.ipcbinddeath:server I/IPCService: doSomething...UUID:9f1044e33dac4a80b605c9a9eed72594
05-27 11:59:06.006 7634-7634/com.why.ipcbinddeath:server I/IPCService: doSomething...UUID:9f1044e33dac4a80b605c9a9eed72594
05-27 11:59:07.011 7634-7634/com.why.ipcbinddeath:server I/IPCService: doSomething...UUID:9f1044e33dac4a80b605c9a9eed72594
05-27 11:59:08.015 7634-7634/com.why.ipcbinddeath:server I/IPCService: doSomething...UUID:9f1044e33dac4a80b605c9a9eed72594
05-27 11:59:08.758 7634-7646/com.why.ipcbinddeath:server I/IPCService: bindDied:9f1044e33dac4a80b605c9a9eed72594
    PS:当我在直接使用Google封装好的RemoteCallbackList时,常需要直接判断和获取集合中已存在cookie对应的callback,而Google封装的类中并无直接方法可以进行判断,所以我添加了两个方法进去使得上面需求调用变得方便。
    读者可以查看 点击打开链接来获取源码及我改造的RemoteCallbackList。


猜你喜欢

转载自blog.csdn.net/qq_33859911/article/details/80470135