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开发艺术探索》第二章、

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/367992.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql数据库(3)-查询

数据库设计规范 58到家数据库30条军规解读查询 创建数据库、数据表 -- 创建数据库 create database python_test_1 charsetutf8; -- 使用数据库 use python_test_1; -- students表 create table students( id int unsigned primary key auto_increment not null, name varchar(…

Spring Data Solr教程:向所有存储库添加自定义方法

如果我们在现实生活中的软件项目中使用Spring Data Solr,很可能我们迟早会遇到一个要求,该要求指出我们的应用程序必须能够与本地Solr服务器和SolrCloud进行通信 。 目前,满足此要求意味着我们必须向所有Spring Data Solr存储库添加自定义方法…

远程管理口怎么看地址_红烧羊肉怎么样做才能滋味浓郁,咸甜适口,且回味有奶香?看这里...

原汁原味红烧羊肉此菜在制作上不同于其他红烧羊肉时要放入香料去膻,但在选料上很讲究,也就是说食材的好坏决定菜的好坏。选用一年生的崇明母山羊制作,膻味很小,肉质软嫩细腻,且带有一股淡淡奶香,因此不必放…

css段落文字(中英文混杂)实现两端对齐

案例如下: 混合使用汉字和英文的段落默认如下: 两边是不对齐的(一般情况下,我们对这种情况不做处理,除非需求或者设计非要我们实现两端对齐)。 对齐之后如下: 实现思路 一般的两端对齐是使用text-align:justify&…

44集合:蒜头军学英语

转载于:https://www.cnblogs.com/passion-sky/p/8424769.html

android病毒下载地址,LINE病毒查杀

LINE病毒查杀是免费通话、免费传讯「LINE」的周边应用程序之一。它能保护智能手机上个人信息的安全,使其免于病毒或恶意程序的侵害。您只要执行几个简单的步骤就能确认手机状态或完成病毒扫描。LINE病毒查杀界面LINE病毒查杀软件功能1、智能手机上的病毒将无所遁形!…

Golang系列:打印命令行参数

记得最早在学校机房学习 Java 时,照着书上的例子,写一个最简单 main 方法,当程序运行并在屏幕上打印出 hello world 时,内心竟有种莫名的激动,相信很多人都有这种经历吧。 不管学什么编程语言,都先从命令行…

Javascript 两种 function 定义的区别

大家都知道Javascript 有两个种定义Function的方法非常常用。例如 function a(){ alert ( "a" )} var a function (){ alert ( "a" )} 虽然两个种方式定义出来的 function 调用的时候结果一样,但是中间还是有区别的。举个简单的…

android app的签名,Android APP的签名

Android APP的签名Android项目以它的包名作为唯一的标识,如果在同一部手机上安装两个包名相同的APP,后者就会覆盖前面安装的应用。为了避免Android APP被随意覆盖,Android要求对APP进行签名。下面介绍对APP进行签名的步骤1、选择builder菜单下…

5.6.50 mysql 用什么驱动_日均5亿查询量的京东订单中心,为什么舍弃MySQL用ES?

京东到家订单中心系统业务中,无论是外部商家的订单生产,或是内部上下游系统的依赖,订单查询的调用量都非常大,造成了订单数据读多写少的情况。我们把订单数据存储在MySQL中,但显然只通过DB来支撑大量的查询是不可取的。…

java常用知识

1、transient 修饰的关键字不参与序列号转载于:https://www.cnblogs.com/ng1991/p/8425694.html

可搜索的文件? 是的你可以。 选择AsciiDoc的另一个原因

Elasticsearch是一个基于Apache Lucene的灵活,功能强大的开源,分布式实时云搜索和分析引擎,可提供全文搜索功能。 它是面向文档且无架构的。 Asciidoctor是一个纯Ruby处理器,用于将AsciiDoc源文件和字符串转换为HTML 5 &#xff…

如何判断两个时间段是否有交集

给定两个左闭右开时间段 [A, B)、[X, Y)&#xff0c;如何判断它们是否有交集&#xff1f; 由于时间可以转换为时间戳&#xff0c;时间戳是一个数字&#xff0c;所以我们可以将问题转换为&#xff1a;如何判断两个左闭右开的数字区间是否有交集。 结论是如果 X < B AND A <…

Jquery 获取table当前行内容

$("a[namecheckOriginal]").click(function () { var parent $(this).parent().parent().find("td"); var moduleEnum parent.eq(7).text(); if(moduleEnum""){ } alert(moduleEnmu);}); 转载于:https://www.cnblogs.com/austi…

CSS3 iphone式开关的推荐写法

话说这个问题纠结了近一个小时&#xff0c;为什么呢&#xff1f;看看就知道了。 在公司的商旅Web移动版本项目上有这么一个交互&#xff0c;需要模仿iphone自带的开关&#xff0c;好吧&#xff0c;肯定没什么问题。 Tip&#xff1a;请使用Chrome查看以下案例 嗯&#xff0c;问…

android play gif,Play.gif image in android without using webview

问题I tried like this, which i found on the net to play gif images:private class MYGIFView extends View {Movie movie;InputStream is null;long moviestart;public MYGIFView(Context context) {super(context);// Provide your own gif animation fileis context.ge…

erp 维护费 要交吗_erp系统每年都要缴费吗

erp系统每年都要缴费吗日期&#xff1a;2020-12-29 03:32:04 浏览量&#xff1a;次企业实现企业管理系统必须选择合适的时机&#xff0c;成功实现企业管理系统的最佳时期是在企业的兴盛期及呆滞期&#xff0c;在创业期和衰退期上企业管理系统是很难成功的。在兴盛期及呆滞期&am…

监视和检测Java应用程序中的内存泄漏

因此&#xff0c;您的应用程序内存不足&#xff0c;您日夜不停地分析应用程序&#xff0c;以期捕获对象中的内存漏洞。 后续步骤将说明如何监视和检测您的内存泄漏&#xff0c;以确保您的应用程序安全。 1.怀疑内存泄漏 如果您怀疑有内存泄漏&#xff0c;可以使用一种方便的方…

表单的隔行换色

<!DOCTYPE html><html lang"en"><head> <meta charset"UTF-8"> <title>表单隔行换色</title> <script> window.οnlοadfunction () {//1.获取表格 var tbleEle document.getElementById("tb1"); //…

点a链接写邮件小技巧

无意间发现这个技巧&#xff0c;分享一下&#xff01; 当点击mailto的邮件链接的时候&#xff0c;需要填写标题和内容&#xff0c;如果你想规定一个邮件标题格式&#xff0c;那这个可以帮助你。 代码&#xff1a; <a href"mailto:haozidaqianduan.com?subject投稿&a…