Android AIDL示例-RemoteCallbackList添加移除监听
Posted Jason_Lee155
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android AIDL示例-RemoteCallbackList添加移除监听相关的知识,希望对你有一定的参考价值。
前言
AIDL是一个缩写,全称是android Interface Definition Language,也就是Android接口定义语言,它是用来实现进程间通讯的,本文使用AIDL写一个小demo来实现夸进程间通讯 。
本文接着这一篇文章写,这篇文章有个留着的——就是远程AIDL服务端注册解绑Listener。
先来看看服务端和客户端大体的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是解除以前设置的死亡代理,并能够在此时做从新绑定的动作。
注意
- AIDL只提供了有限的传输数据类型,自定义的传输类型须要序列化
- RemoteCallbackList很好的解决了IPC过程当中可能出现的某一方进程Crash,引发另外一方Exception的问题
- 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