当绑定的客户端断开连接时,通知Android服务

10

我有一个在远程进程中的Android服务,可以从不同的客户端进行多个绑定。我的问题是,当特定绑定的客户端意外断开连接(即客户端崩溃)时,服务如何得到通知?我不能使用onUnbind(),因为它只在所有客户端都已断开连接后才会被调用。

public class MyService extends Service {

    final Messenger mServiceMessenger = new Messenger(new IncomingHandler());

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

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return Service.START_STICKY;
    }

    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
        // Handling messages logic...
        }
    }
}

基本问题:您无法从服务内部看到谁正在绑定到您(“然后系统将向任何其他绑定的客户端提供相同的IBinder”)。您发放一个绑定器,它就像描述如何通过内核功能与您的进程通信的说明。客户端可以检查绑定器是否存活,但我不知道是否可能检查另一种方式。onUnbind在Android框架中深入处理,我不确定在远程进程情况下是否保证调用它。 - zapl
2个回答

6

This can be done by the Binder.linkToDeath() mechanism - You'll have to ask each client to send new Binder() object that they initiated and then link to their (your clients') death. I'll explain how to preform this using AIDL files.

(You can choose any android IPC mechanism as long as you can pass Binder objects from your client's to your service)

Code Example -

Inside your .AIDL file - Create a method to pass the IBinder object from the client to the service

void registerProcessDeath(in IBinder clientDeathListener, String packageName);

On the client side - Initialize a new object and pass it to your service via AIDL interface.

public void onServiceConnected(ComponentName className, IBinder service) {
    mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
    //Call the registerProcessDeath method from the AIDL file and pass 
    //the new Binder object and the client's package name
    mIMyAidlInterface.registerProcessDeath(new Binder(),getPackageName());
}


On the service side -

1. Get the client's Binder and register to his linkToDeath().
2. Use helper class to handle all clients via android's IBinder.DeathRecipient class

public class MyService extends Service {
    //Helper class to handle all client's deaths.
    private volatile ClientsDeathWatcher mClientsList;

    @Override
    public IBinder onBind(Intent intent) {
        mClientsList = new ClientsDeathWatcher();
        return mStub;
    }

    private final IMyAidlInterface.Stub mStub = new IMyAidlInterface.Stub() {

        @Override
        public void registerProcessDeath(IBinder cb, String packageName){

            boolean isRegistered = mClientsList.register(cb , packageName);
        }
    };
}
//This is thread-safe helper class to handle all 
//the client death related tasks.

//All you care abut is the clientDeath() method. 
public class ClientsDeathWatcher {

    private ArrayMap<String, DeathCallBack> mCallbacks = new ArrayMap<>();

    private final class DeathCallBack implements IBinder.DeathRecipient {
        private String pn;
        private IBinder mBinder;

        DeathCallBack(String packageName,IBinder binder) {
            pn = packageName;
            mBinder = binder;
        }

        public void binderDied() {
            synchronized (mCallbacks) {
                mBinder.unlinkToDeath(this,0);
                clientDeath(pn);
            }
        }
    }

    //To be called only from thread-safe functions
    private void clientDeath(String packageName) {
        mCallbacks.remove(packageName);
        //Do your stuff here.
        //$$$$$$$$$
    }

    public boolean register(IBinder token, String packageName) {
        synchronized (mCallbacks) {
            try {
                if (!mCallbacks.containsKey(packageName)) {
                    DeathCallBack mDeathCallBack = new DeathCallBack(packageName,token);
                    mCallbacks.put(packageName, mDeathCallBack);
                    //This is where the magic happens
                    token.linkToDeath(mDeathCallBack, 0);
                }
                return true;
            } catch (RemoteException e) {
                e.printStackTrace();
                return false;
            }
        }
    }
}


在服务客户端解绑时,Binder 是否被视为已死亡?还是只有在进程死亡时才会被视为死亡? - JohnyTex

1

您可以使用您拥有的IncomingHandler处理程序,并从客户端发送一条消息,在调用unbindService(serviceConnection)之前将其解除绑定,保留Messengers(客户端)的arraylist,并在接收到消息时添加/删除。

您还可以尝试发送虚假消息,如果收到RemoteException,则表示远程客户端已死亡。

请参阅此示例http://developer.android.com/reference/android/app/Service.html

提取:

class IncomingHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_REGISTER_CLIENT:
                mClients.add(msg.replyTo);
                break;
            case MSG_UNREGISTER_CLIENT:
                mClients.remove(msg.replyTo);
                break;
            case MSG_SET_VALUE:
                mValue = msg.arg1;
                for (int i=mClients.size()-1; i>=0; i--) {
                    try {
                        mClients.get(i).send(Message.obtain(null,
                                MSG_SET_VALUE, mValue, 0));
                    } catch (RemoteException e) {
                        // The client is dead.  Remove it from the list;
                        // we are going through the list from back to front
                        // so this is safe to do inside the loop.
                        mClients.remove(i);
                    }
                }
                break;
            default:
                super.handleMessage(msg);
        }
    }
}

你是指在 IncomingHandler.handleMessage(Message msg) 中收到的 msg.replyto() 吗? - Michael Gregorov
2
基于两个答案的相似方法:每个“消息”都包含您需要回复的“Messanger”。每个“Messanger”都包含客户端的“IBinder”,以进行低级通信。我猜可以粗略地使用msg.replyTo.getBinder().linkToDeath()来获取有关客户端死亡的通知。注意:死亡!=绑定状态(也许)。 - zapl
感谢 @djodjo,我自己也考虑过使用这种方法,但是我有两个问题:1.如果我想在断开连接时得到通知,我需要定期检查所有客户端。2.我不太喜欢使用异常来定期检查绑定客户端的状态的想法。这并不是最优雅的方式,但我想如果没有其他选择,我会使用它。 - Michael Gregorov
你没有其他选择。基本上,在任何客户端/服务器场景中,有两种方法可以解决这个问题。1. 客户端通知服务器即将退出(发送消息)2. 如果客户端由于某些原因死亡并且无法发送消息,则服务器必须有一些方法来解决这个问题(远程异常处理)。因此,除非您真的需要始终更新信息,否则不会经常发生第二种情况,然后您需要一些轮询机制来发送虚拟消息。 - kalin
看起来对我来说最好的解决方案是使用msg.replyTo().send,并捕获RemoteException以检查客户端是否仍然连接,就像你建议的那样。谢谢。 - Michael Gregorov
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接