android ipc 多个客户端,Android IPC之AIDL进阶篇
前言
在Android IPC之AIDL中我介绍了如何使用AIDL进行多进程通信,不过由于当时个人水平有限,仅仅介绍了最基础的部分,所以本篇博客主要是在Android IPC之AIDL的基础上深入介绍下AIDL的进阶的几点理解以及用法。
AIDL接口中的in out inout的含义
在Android IPC之AIDL中稍微提了下,在客户端与服务端进行复杂数据传递的时候,需要使用这三个修饰符,表示数据的流向,并没有具体说明,这里通过我的测试给出一个结果,使用in修饰,客户端的对象可以"传递给"服务端,但是服务端不能修改客户端的对象,使用out修饰,客户端的对象不可以"传递给"服务端,但是服务端可以修改客户端的对象,inout则是双向的,服务端可以拿到客户端的数据也可以修改客户端的数据。"传递"之所以有引号,表示客户端和服务端的对象不是同一个,而是序列化/反序列化的而已。
上面的传递的意思是数据类型以及值都能传递过去,举例来说,一个Person,初始化为name=a,age=10,
使用in修饰,客户端为[name=a,age=10]服务端拿到的是[name=a,age=10],但是服务端修改age=11,客户端还是[name=a,age=10]
使用out修饰,客户端为[name=a,age=10]服务端拿到的是[name=null,age=0](String的默认类型为空,int的默认类型为0),服务端修改age=11,客户端变为[name=a,age=11]
使用inout修饰符,则服务端可以拿到正确的数据,对数据的修改也会同步到客户端
oneway的用法
AIDL定义的方法是阻塞的,所以我们需要很注意不要在UI线程中调用耗时很长的AIDL方法,不然会导致ANR,如果不想客户端被阻塞可以选择开启一个线程去执行或者使用oneway修饰被调用的方法或者接口。这样当客户端调用的时候就不会被阻塞了。
//IRemote.aidl
interface IRemote{
oneway void testOneWay();
}
如果我们多次调用被oneway修饰的方法,都不会被阻塞,而且远程方法是顺序执行的,比如上面的testOneWay()方法被调用两次,只有当第一次执行完毕,第二次才会继续执行,但是对于客户端来说是感知不到的。
服务端感知客户端是否崩溃
当我们绑定一个远程服务的时候,如果远程服务崩溃了,我们可以通过ServiceConnection的onServiceDisconnected感知到,可是如果客户端崩溃了,服务端怎么感知呢?
对于这种情景,我们可以让客户端传递一个Binder对象给服务端,然后服务端使用IBinder.linkToDeath监听,当客户端终止的时候,Binder对象也会被杀死,然后服务端就可以收到客户端死亡消息了。
binder.linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
Log.i("IPC", "client died");
}
}, 0);
具体实现如下。
首先定义个AIDL接口
//IRemote.aidl
interface IRemote{
void testClientError(IBinder binder);
}
服务端实现
@Override
public void testClientError(IBinder binder) throws RemoteException {
binder.linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
Log.i("IPC", "client died");
}
}, 0);
}
客户端调用,最后一句int i = 0/0;是为了测试客户端异常退出
private IBinder mToken = new Binder();
public void testError(View v) {
if (remoteInterface == null) {
return;
}
try {
remoteInterface.testClientError(mToken);
} catch (RemoteException e) {
e.printStackTrace();
}
int i = 0 / 0;
}
扩展:既然通过Binder对象可以感知到对应的进程是否死亡,那么我们也可以换种方式在客户端获取服务端的状态,上面的例子中,我们是主动new了一个Binder对象发送给服务端,那么怎么获取到服务端的Binder对象呢?答案就在ServiceConnection的onServiceConnected中,第二个参数就可以用来监听服务端是否死亡。
服务端调用客户端的方法
假设我们有这样一个需求,一个客户连接服务端的时候,服务端需要通知其他所有的客户端,如果在同一个进程中,我们可以使用回调的方式,在多进程条件下,我们也可以使用回调,不过客户端需要实现AIDL接口才行。
具体实现如下
首先定义一个AIDL接口,当连接的时候,服务端调用所有客户端的接口,显示一个Toast
//IClientCallBack.aidl
interface IClientCallBack{
void showToastInClient(String msg);
}
然后添加注册/解注册方法
//IRemote.aidl
interface IRemote{
void register(IClientCallBack callback);
void unregister(IClientCallBack callback);
}
客户端实现AIDL接口
private IClientCallBack mCallBack = new IClientCallBack.Stub() {
@Override
public void showToastInClient(String msg) throws RemoteException {
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
};
然后接下来就是服务端保存所有的回调接口到一个List中,剩下的就和普通调用一样了。
可是重点来了,如果客户端注册了回调,但是没有解除注册就意外终止了,那么服务端是无法感知到的,这样无疑浪费了资源,而且导致程序不稳定,所以我们需要在客户端意外终止的时候移除监听,由于使用的AIDL接口,所以这时我们就可以使用上面的IBinder.linkToDeath方法了。类似如下形式。
@Override
public void register(IClientCallBack callback) throws RemoteException {
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
// TODO Auto-generated method stub
}
}, 0);
......
}
程序员都是喜欢偷懒的,能简单就简单,上面还要我们自己去实现binderDied方法,无疑是降低了开发效率,好歹谷歌给我们提供了RemoteCallbackList用来为我们保存回调列表,它会在客户端异常终止的时候自动移出,免去了我们的人工操作,流程如下。
public class RemoteService extends Service {
//定义RemoteCallbackList对象,保存类型为IClientCallBack
private RemoteCallbackList mList = new RemoteCallbackList();
@Override
public void onDestroy() {
//当RemoteService销毁的时候,清空RemoteCallbackList列表
mList.kill();
super.onDestroy();
}
private IRemote.Stub mStud = new Stub() {
@Override
public void register(IClientCallBack callback) throws RemoteException {
//保存回调到RemoteCallbackList中
mList.register(callback);
//开始通知全部客户端
int i = mList.beginBroadcast();
Log.i("IPC", "size = " + (i - 1));
//遍历客户端
while (i > 0) {
i--;
try {
mList.getBroadcastItem(i).showToastInClient("i am from Server");
} catch (RemoteException e) {
e.printStackTrace();
}
}
//结束通知客户端
mList.finishBroadcast();
}
@Override
public void unregister(IClientCallBack callback) throws RemoteException {
//将回调从RemoteCallbackList移除
mList.unregister(callback);
}
};
}
RemoteCallbackList的内部实现与我们上面提到的一样,使用的IBinder.linkToDeath方法。有兴趣的可以查看下其源码。
给服务端加入权限认证
有时候,我们并不想让我们的远程服务随便的被人绑定,这是就需要使用给我们的服务加入权限认证才行。一般来说,有两种方法。
首先我们在AndroidManifest.xml文件中声明的权限,如下
然后我们在Service的onBind方法中使用checkCallingOrSelfPermission方法验证客户端是否声明了权限,如果没有声明,则返回null,那么客户端的onServiceConnected方法将不会被回调,客户端将绑定失败。这个方法对于普通的Service同样适用。
public class MyService extends Service {
private static final String TAG = "MyService";
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
int permission = checkCallingOrSelfPermission("com.remote.service.PRI");
if (permission == PackageManager.PERMISSION_DENIED) {
Log.i(TAG, "onBind: Error");
return null;
}
Log.i(TAG, "onBind: Success");
return mBinder;
}
}
如果客户端想绑定我们的服务,那么需要在AndroidManifest.xml文件中声明这个权限才行。
对于AIDL来说,我们可以在实现AIDL接口的Stub中,覆写onTransact,在onTransact方法中进行权限验证,如下。
private IRemote.Stub mStud = new IRemote.Stub() {
/**
* 这里可以进行权限验证,true,允许绑定,false,不允许绑定
*/
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
throws RemoteException {
int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
Log.d("IPC", "onbind check=" + check);
// getCallingPid();
// getCallingUid();
// 只允许包名以org.ipc.demo开头的app 调用本服务提供的方法
// 此方法并不能阻止绑定,但是能让非法调用者无法使用本服务提供的方法
String[] packagesForUid = getPackageManager().getPackagesForUid(getCallingUid());
if (packagesForUid != null && packagesForUid.length > 0) {
if (packagesForUid[0].startsWith("org.ipc.demo")) {
return super.onTransact(code, data, reply, flags);
}
}
return false;
};
};
在onTransact方法中,我们不仅可以验证是否声明了权限,我们还可以获取到客户端的包名,对包名等信息进行限制,但是此方法会回调客户端的onServiceConnected方法,但是客户端无法调用服务端提供的方法。
参考资料:《Android开发艺术探索》第二章、
android ipc 多个客户端,Android IPC之AIDL进阶篇相关推荐
- android 分享到微博客户端,Android APP集成新浪微博分享功能
本文为大家分享了新浪微博分享功能集成,供大家参考,具体内容如下 直接导入weibosdkcore.jar:适用于只需要授权.分享.网络请求框架功能的项目. 无论使用哪一种方式,都需要先将demo中li ...
- android 启动新浪客户端,android开发新浪微博客户端
[实例简介] [实例截图] [核心代码] package weibo4andriod.examples; import java.io.*; import java.net.*; import wei ...
- android仿疯狂猜图源码,Android开发实现高仿优酷的客户端图片左右滑动切换功能实例【附源码下载】...
本文实例讲述了Android开发实现高仿优酷的客户端图片左右滑动切换功能.分享给大家供大家参考,具体如下: 本例是用ViewPager去做的实现,支持自动滑动和手动滑动,不仅优酷网,实际上有很多商城和 ...
- Android开发艺术探索--第二章IPC机制(2)之Binder
最近在拜读任主席的Android开发艺术探索,现在看了一半,再回头看前面的,感觉跟没有看一样,所以还是把知识点总结一下吧,这一节咱们来讲一下IPC中的Binder 直观来说,Binder是Androi ...
- 【Android 逆向】Android 逆向通用工具开发 ( Android 端远程命令工具 | Android 端可执行程序的 main 函数操作 | TCP 协议服务器建立 | 接收客户端数据 )
文章目录 前言 一.Android 端可执行程序的 main 函数操作 二.Android 端 TCP 协议服务器建立 三.Android 端接收 PC 端传来的数据 四.博客资源 前言 本篇博客重点 ...
- android仿知乎按钮动效,Android仿知乎客户端关注和取消关注的按钮点击特效实现思路详解...
先说明一下,项目代码已上传至github,不想看长篇大论的也可以先去下代码,对照代码,哪里不懂点哪里. 代码在这https://github.com/zgzczzw/ZHFollowButton 前几 ...
- 接口使用jwt返回token_API接口JWT方式的Token认证(下),客户端(Android)的实现
上篇文章已经介绍了 JWT 认证在 Laravel 框架服务器上的实现.这篇文章继续介绍 Android 客户端的实现.回顾下 JWT 认证的流程,客户端先提交账号密码进行登录,账号密码验证成功后,服 ...
- android新闻客户端发展趋势,基于Android平台的新闻客户端设计与实现
杨苏雯 摘 要 在Android开发平台上,并在JavaWeb开发的PC端新闻网站的基础上结合现在的需求开发了移动版的新闻客户端App,这个系统设计主要分为用户登录验证模块.新闻列表的显示功能模块以及 ...
- android获取服务器时间格式,Android 获取服务器与客户端时差的实例代码
一般我们在做商品倒计时的时候会遇到要从后台获取商品的开始时间和结束时间,还要计算商品距离开始时间的倒计时和结束时间的倒计时,但是这样只是从后台获取到开始时间,还要再和手机系统的时间相减,才能获取到开始 ...
最新文章
- oracle层次查询中prior与自上而下、自下而上查询
- cudaMemcpy2D介绍
- [leveldb] 3.put/delete操作
- js `` 手机不支持
- 一些java,spring boot图解
- vSphere 5.5 VM整合磁盘失败之—文件被锁定无法访问
- 经典:一文详解socket
- SpringBoot 统一异常处理最佳实践 -- 拓展篇
- 141.环形链表(力扣leetcode)博主可答疑该问题
- 新浪微博Emoji表情解析
- Vivado安装教程补丁
- 编译原理学习笔记之上下文无关文法
- 使用驱动精灵更新无线网卡后出现网卡错误代码56的解决办法
- onvif python3 推送音频_Python3-onvif协议之相机截图
- PHP 图片木马隐写方法及靶机演示
- css实现跳动的心形图案
- json--json2bean
- 2018年 应届毕业生 安卓开发工程师 求职准备
- 阿龙的下拉菜单demo
- 这30个CSS选择器,你必须熟记(上)
热门文章
- 关于摄像头的一些零碎知识
- b500k带开关电位器内部构造_R138带开关大功率大电流电位器 B10K B500K
- ssm 静态资源处理器
- 孙叫兽进阶之路之如何进行情绪管理
- 计算机的发展阶段及特点与未来发展,计算机的发展历史及未来
- vue 外部方法调用内部_vue函数内部调用外部函数,报错外部函数不是函数
- 《React Native 精解与实战》书籍连载「React Native 网络请求与列表绑定」
- jQuery 入口函数主要有4种写法
- SweetAlert – 替代 Alert 的漂亮的提示效果
- WeScale 技术篇 —— mpvue 与微信小程序的火花