Android AIDL示例-RemoteCallbackList添加移除监听

Posted Jason_Lee155

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android AIDL示例-RemoteCallbackList添加移除监听相关的知识,希望对你有一定的参考价值。

前言

AIDL是一个缩写,全称是android Interface Definition Language,也就是Android接口定义语言,它是用来实现进程间通讯的,本文使用AIDL写一个小demo来实现夸进程间通讯 。

本文接着这一篇文章写,这篇文章有个留着的——就是远程AIDL服务端注册解绑Listener。

Android AIDL示例-回调方法版

先来看看服务端和客户端大体的AIDL-生成的java文件 。

如上图所示,蓝色框内是我们定义的AIDL文件,红色框内是自动编译生成的java文件。

服务端

这里只是个简单示意,接下来会在此基础上改。下面我们看下服务端的代码:

/**
 * 音乐管理的服务类
 */
public class MusicManagerService extends Service 
 
    private static final String TAG =  MusicManagerService.class.getSimpleName();
 
    private ArrayList<Music> mMusicList = new ArrayList<>();  //生成的音乐列表
    private List<INewMusicArrivedListener> mListenerList = new ArrayList<>(); //客户端注册的接口列表
    private boolean isServiceDestroy = false; //当前服务是否结束
    private int num = 0;
 
    /**
     * 解绑服务
     * @param conn
     */
    @Override
    public void unbindService(ServiceConnection conn) 
        super.unbindService(conn);
        Log.e(TAG,"unbindService-----");
    
    
    /**
     * 服务端通过Binder实现AIDL的IMusicManager.Stub接口
     * 这个类需要实现IMusicManager相关的抽象方法
     */
    private Binder mBinder = new IMusicManager.Stub() 
        @Override
        public List<Music> getMusicList() throws RemoteException 
            
            return mMusicList;
        
 
        @Override
        public void addMusic(Music music) throws RemoteException 
            mMusicList.add(music);
        
 
        @Override
        public void registerListener(INewMusicArrivedListener listener) throws RemoteException 
            mListenerList.add(listener);
            int num = mListenerList.size();
            Log.e(TAG, "添加完成, 注册接口数: " + num);
        
 
        @Override
        public void unregisterListener(INewMusicArrivedListener listener) throws RemoteException 
            // 后文实现
        
    ;
 
 
    //新音乐到达后给客户端发送相关通知
    private void onNewMusicArrived(Music music) throws Exception 
        mMusicList.add(music);
        Log.e(TAG, "发送通知的数量: " + mMusicList.size());
        int num = mListenerList.size();
        for (int i = 0; i < num; ++i) 
            INewMusicArrivedListener listener = mListenerList.get(i);
            listener.onNewMusicArrived(music);
        
        for (Music b : mMusicList)
            Log.e(TAG,b.name+"  "+b.author);
        
    
 
    @Override public void onCreate() 
        super.onCreate();
        Log.e(TAG,"onCreate-------------");
        //首先添加两首歌曲
        mMusicList.add(new Music("《封锁我一生》", "王杰"));
        mMusicList.add(new Music("《稻香》", "周杰伦"));
    
 
    @Override public void onDestroy() 
        isServiceDestroy = true;
        super.onDestroy();
        Log.e(TAG,"onDestroy-----");
    
 
    @Nullable @Override 
    public IBinder onBind(Intent intent) 
        return mBinder;
    
 

客户端

我们看下客户端的代码:这里需要注意,回调接口在客户端注册,目的是为了让服务端拿到回调接口,通知客户端。客户端拿到的是服务端的代理,注册回调可看作服务端收到回调listener。

 private TextView music_list;
 
    private IMusicManager mRemoteMusicManager; //音乐管理类  通过aidl文件编译生成的java类
 
 
    //监听新音乐的到达的接口
    private INewMusicArrivedListener musicArrivedListener = new INewMusicArrivedListener.Stub() 
        /**
         * 服务端有新音乐生成
         * @param newMusic
         * @throws RemoteException
         */
        @Override
        public void onNewMusicArrived(Music newMusic) throws RemoteException 
            
        
    ;
 
 
    //绑定服务时的链接参数
    private ServiceConnection mConnection = new ServiceConnection() 
        @Override public void onServiceConnected(ComponentName name, IBinder service) 
            IMusicManager musicManager = IMusicManager.Stub.asInterface(service);
 
            try 
                mRemoteMusicManager = musicManager;
                Music newMusic = new Music("《客户端音乐》", "rock");
                musicManager.addMusic(newMusic);
                musicManager.registerListener(musicArrivedListener);
             catch (RemoteException e) 
                e.printStackTrace();
            
        
 
        @Override public void onServiceDisconnected(ComponentName name) 
            mRemoteMusicManager = null;
            Log.e(TAG, "绑定结束");
        
    ;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        music_list = findViewById(R.id.music_list);
    
 
    /**
     * 获取music列表
     *
     * @param view 视图
     */
    public void getMusicList(View view) 
        if (mRemoteMusicManager !=null)
            List<Music> list = null;
            try 
                list = mRemoteMusicManager.getMusicList();
            catch (Exception e)
 
            
            if (list!=null)
                String content = "";
                for (int i = 0; i < list.size(); ++i) 
                    content += list.get(i).toString() + "\\n";
                
                music_list.setText(content);
            
        
        Toast.makeText(getApplicationContext(), "正在获取中...", Toast.LENGTH_SHORT).show();
    
 
 
    /**
     * 绑定服务按钮的点击事件
     *
     * @param view 视图
     */
    public void bindService(View view) 
        Intent intent = new Intent(this, MusicManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    
 
 
    /**
     * 解绑服务
     */
    public void unbindService(View view)
        unbindService(mConnection);
    

绑定一个服务 ,然后注册一个新音乐到达的接口,当服务端有新音乐生成的时候就会触发这个接口。

跨进程通信可能遇到问题

在IPC的过程当中,可能会遇到如下状况须要考虑:

在AIDL中客户端向服务端注册一个回调方法时,服务端要考虑客户端是否意外退出(客户端由于错误应用Crash,或者被Kill掉了),服务端还不知道,去回调客户端,出现错误。

客户端和服务端进程状态处理

在进程间通讯过程当中,极可能出现一个进程死亡的状况。若是这时活着的一方不知道另外一方已经死了就会出现问题。那咱们如何在A进程中获取B进程的存活状态呢?

android确定给咱们提供了解决方式,那就是Binder的linkToDeath和unlinkToDeath方法,linkToDeath方法须要传入一个DeathRecipient对象,DeathRecipient类里面有个binderDied方法,当binder对象的所在进程死亡,binderDied方法就会被执行,咱们就能够在binderDied方法里面作一些异常处理,释放资源等操作了。

Android SDK提供一个封装好的对象:RemoteCallbackList,帮我们自动处理了Link-To-Death的问题。

这里,简单介绍一下RemoteCallbackList:

public class RemoteCallbackList
extends Object
java.lang.Object
↳ android.os.RemoteCallbackList<E extends android.os.IInterface>

负责维护远程接口列表的繁琐工作,一般用于执行从Service到其客户端的回调 。特别是:

跟踪一组已注册的IInterface回调,通过其基础唯一性IBinder进行识别:IInterface#asBinder。

附加IBinder.DeathRecipient到每一个已注册的接口,以便在其过程消失时能够将其从列表中清除。

对接口的基础列表执行锁定以处理多线程传入的调用,并以线程安全的方式遍历该列表。

要使用此类,只需与服务一块儿建立一个实例,而后在客户端注册和取消注册服务时调用其register(E)和unregister(E)方法。回调到注册客户端,使用beginBroadcast(), getBroadcastItem(int)和finishBroadcast()。

当注册的回调在过程中消失了,该类将负责自动将其从列表中删除。若是要在这种状况下做其余工作,要建立一个实现该onCallbackDied(E)方法的子类。

RemoteCallbackList帮咱们避免了IPC两个进程在调用过程当中发生意外crash,致使回调失败或者进程crash的问题。

将上面服务端-客户端代码修改如下:

/**
 * 音乐管理的服务类
 */
public class MusicManagerService extends Service 
 
    private static final String TAG =  MusicManagerService.class.getSimpleName();
 
    // 省略部分代码。。。
    
    private RemoteCallbackList<INewMusicArrivedListener> mListenerList = new RemoteCallbackList<>(); //客户端注册的接口列表
    

    //新音乐到达后给客户端发送相关通知
    private void onNewMusicArrived(Music music) throws Exception 
        mMusicList.add(music);
        
         mListenerList.beginBroadcast();
         //遍历全部注册的Listener,逐个调用它们的实现方法,也就是通知全部的注册者
         for(int i=0;i<mListenerList.getRegisteredCallbackCount();i++)
             INewMusicArrivedListener listener = mListenerList.getBroadcastItem(i);
             listener.onNewMusicArrived(music);
         
         mListenerList.finishBroadcast();

        for (Music b : mMusicList)
            Log.e(TAG,b.name+"  "+b.author);
        
    
    
    /**
     * 服务端通过Binder实现AIDL的IMusicManager.Stub接口
     * 这个类需要实现IMusicManager相关的抽象方法
     */
    private Binder mBinder = new IMusicManager.Stub() 
       
        // 省略部分代码。。。
 
        @Override
        public void registerListener(INewMusicArrivedListener listener) throws RemoteException 

            mListenerList.register(listener);
        
 
        @Override
        public void unregisterListener(INewMusicArrivedListener listener) throws RemoteException 
            
            mListenerList.unregister(listener);
        
    ;
 
 private IMusicManager mRemoteMusicManager; //音乐管理类  通过aidl文件编译生成的java类
 
 
    //监听新音乐的到达的接口
    private INewMusicArrivedListener musicArrivedListener = new INewMusicArrivedListener.Stub() 
        /**
         * 服务端有新音乐生成
         * @param newMusic
         * @throws RemoteException
         */
        @Override
        public void onNewMusicArrived(Music newMusic) throws RemoteException 
            
        
    ;

   //进程挂掉通知处理
   private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() 
        @Override
        public void binderDied() 
            if(mRemoteMusicManager != null)
                //Server端意外died
                try 
                    mRemoteMusicManager.unRegistListener(musicArrivedListener);
                 catch (RemoteException e) 
                    e.printStackTrace();
                
                mRemoteMusicManager.asBinder().unlinkToDeath(mDeathRecipient,0);
                mRemoteMusicManager = null;
                //TODO:bindService again
                bindService()
            
        
    ;
 
 
    //绑定服务时的链接参数
    private ServiceConnection mConnection = new ServiceConnection() 
        @Override public void onServiceConnected(ComponentName name, IBinder service) 
            mRemoteMusicManager = IMusicManager.Stub.asInterface(service);
 
            try 
                //设定死亡接收器,这个是针对IBinder对象的
                service.linkToDeath(mDeathRecipient,0);

                Music newMusic = new Music("《客户端音乐》", "rock");
                mRemoteMusicManager.addMusic(newMusic);
                mRemoteMusicManager.registerListener(musicArrivedListener);
             catch (RemoteException e) 
                e.printStackTrace();
            
        
 
        @Override public void onServiceDisconnected(ComponentName name) 
            mRemoteMusicManager = null;
            Log.e(TAG, "绑定结束");
        
    ;
 
 
    /**
     * 绑定服务按钮的点击事件
     *
     */
    public void bindService() 
        Intent intent = new Intent(this, MusicManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    
 
 
    /**
     * 解绑服务
     */
    public void unbindService(View view)

        try 
            if(mRemoteMusicManager != null && mRemoteMusicManager.asBinder().isBinderAlive())
                mRemoteMusicManager.unRegistListener(musicArrivedListener);
            
         catch (RemoteException e) 
            e.printStackTrace();
        
        unbindService(mConnection);
    

这里须要说明的是在客户端进程中,经过DeathRecipient对象,调用IBinder的linkToDeath和unLinkToDeath方法,实现了 万一Server进程Died以后,对Service进行再次注册

简单介绍一下linkToDeath和unlinkToDeath:

/**
     * Interface for receiving a callback when the process hosting an IBinder
     * has gone away.
     * 
     * @see #linkToDeath
     */
    public interface DeathRecipient 
        public void binderDied();
    

    /**
     * Register the recipient for a notification if this binder
     * goes away.  If this binder object unexpectedly goes away
     * (typically because its hosting process has been killed),
     * then the given @link DeathRecipient's
     * @link DeathRecipient#binderDied DeathRecipient.binderDied() method
     * will be called.
     * 
     * <p>You will only receive death notifications for remote binders,
     * as local binders by definition can't die without you dying as well.
     * 
     * @throws RemoteException if the target IBinder's
     * process has already died.
     * 
     * @see #unlinkToDeath
     */
    public void linkToDeath(@NonNull DeathRecipient recipient, int flags)
            throws RemoteException;

    /**
     * Remove a previously registered death notification.
     * The recipient will no longer be called if this object
     * dies.
     * 
     * @return @code true if the <var>recipient</var> is successfully
     * unlinked, assuring you that its
     * @link DeathRecipient#binderDied DeathRecipient.binderDied() method
     * will not be called;  @code false if the target IBinder has already
     * died, meaning the method has been (or soon will be) called.
     * 
     * @throws java.util.NoSuchElementException if the given
     * <var>recipient</var> has not been registered with the IBinder, and
     * the IBinder is still alive.  Note that if the <var>recipient</var>
     * was never registered, but the IBinder has already died, then this
     * exception will <em>not</em> be thrown, and you will receive a false
     * return value instead.
     */
    public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);

使用它比较简单,只须要实现DeathRecipient的bindDied方法。

linkToDeath和unLinkToDeath是成对出现的,参照上面客户端中MainActivity的实现,在ServiceConnection中onServiceConnected的时候link,在bindDied方法中unlink。linkToDeath是为IBinder对象设置死亡代理,unLinkToDeath是解除以前设置的死亡代理,并能够在此时做从新绑定的动作。

注意

  1. AIDL只提供了有限的传输数据类型,自定义的传输类型须要序列化
  2. RemoteCallbackList很好的解决了IPC过程当中可能出现的某一方进程Crash,引发另外一方Exception的问题
  3. DeathRecipient理解和调用(解决Server端进程意外终止,Client端得到的Binder对象丢失问题)

为什么这么麻烦,不用remoteCallList可以吗?

如果不用remoteCallList绑定和解绑listener会怎样,为什么这么麻烦呢?

可以增加几条打印,看看解绑和绑定时能不能成功:

 原因就在于我们的注册对象listener是在进程间传输的,Binder在服务端会把客户端传递过来的对象重新转换为新的对象,因而注册和解注册的根本就不是一个对象,当然不能达到解除注册的目的了

我们在服务端解除listener的代码处添加下面两行代码:

System.out.println("解绑定时候的listener:  "+listener);

System.out.println("解绑定时候的listener对应的Binder:  "+listener.asBinder());

在服务端绑定listener的代码处下面两行代码:

System.out.println("绑定时候的listener:  "+listener);

System.out.println("绑定时候的listener对应的Binder:  "+listener.asBinder());

 而后运行,查看Log输出:

 从Log输出上面很明显的可以看到绑定和解绑定的listener并不是同一个对象,那么解除绑定肯定是失败的,但是从Log输出上可以看出listener所对应的Binder对象是相同的,他们都是BinderProxy对象,原因在于:跨进程间通信时,服务端要创建Binder对象,客户端要拿到服务端Binder对象在Binder驱动中的引用,对应到客户端就是BinderProxy对象了,我们这里的情况是服务端和客户端通信,那么此时的服务端将成为客户端,所以收到的Binder就是BinderProxy类型了,为什么绑定和解绑定过程中listener对象不同,但是listener对象对应的Binder对象却的相同的呢?这个我们需要看下系统为我们的IMessageManager.aidl生成的.java文件内容了:

@Override 
public void registerLoginUser(com.hzw.messagesend.ILoginOnListener listener) throws android.os.RemoteException

android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try 
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));
mRemote.transact(Stub.TRANSACTION_registerLoginUser, _data, _reply, 0);
_reply.readException();

finally 
_reply.recycle();
_data.recycle();

其他代码就不展示了。因此,如果不用RemoteCallList而用普通的Arralist的话,也可以

public void unRegisterListener(INewMusicArrivedListener listener)
				throws RemoteException 
			for(int i = 0;i < list.size();i++)
			
				if(list.get(i).asBinder() == listener.asBinder())
				
					//解除绑定注册
					System.out.println("解绑定时候的listener:  "+listener);
					System.out.println("解绑定时候的listener对应的Binder:  "+listener.asBinder());
					list.remove(list.get(i));
					break;
				
			
		

以上是关于Android AIDL示例-RemoteCallbackList添加移除监听的主要内容,如果未能解决你的问题,请参考以下文章

如何将aidl文件添加到Android工作室(来自应用内计费示例)

Android Binder建模问题——MusicStore、IMusicStore.aidl、Music、IMusic.aidl

客户端进程(在带有aidl的android IPC中)如何知道远程服务器类?

RemoteCall_杂

AIDL简单示例

android开发aidl何时使用