IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android AIDL示例-RemoteCallbackList添加移除监听 -> 正文阅读

[移动开发]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;
				}
			}
		}

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-06-25 18:16:18  更:2022-06-25 18:17:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 2:31:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码