一、 概念QA以及前置技能
二、传统方式IPC通信写法
与使用IPC框架进行RPC通信
的对比
三、Demo展示
四、框架核心思想讲解
五、 写在最后的话
正文
一、 概念QA以及前置技能
Q:什么时候会用到多进程通信?
A: 常见的多进程app
一般是大型公司的 app组,像是腾讯系的QQ 微信 QQ空间,QQ邮箱
等等,有可能 在QQ邮箱
登录时,可以直接调用QQ的登录服务
,另外,腾讯阿里都有小程序,作为一个第三方开发的小程序应用,在微信客户端运行
,如果和微信放在同一个进程运行,一旦崩溃
,微信也跟着玩完,明明是小程序开发者的锅
,硬是让腾讯给背
了,不合适。
而小型公司,emmmmm,连多进程开发都用的很少,就不要说通信了。但是,如果没有一颗进大厂的心,就学不到高阶技能,有些东西学了,总比一无所知要好。
Q:使用多进程有什么好处?
A:
1)进程隔离,子app
崩溃,不会影响其他进程。
2)系统运行期间,对每个进程的内存划分是有一个上限的,具体多少,视具体设备而定,利用多进程开发,可以提高程序的可运行内存限制。
3)如果系统运行期间内存吃紧,可以杀死子进程,减少系统压力。杀死进程的方式,往往比优化单个app的内存更加直接有效
Q:什么叫
RPC
?
A:从客户端上通过参数传递的方式调用服务器上的一个函数并得到返回的结果,隐藏底层的通讯细节。在使用形式上像调用本地函数
一样去调用远程函数
。
Q:我们自己定义一个
RPC
进程间通信框架,有什么实际用处?
A:定义框架的作用,都是 把脏活,累活,别人不愿意重复干的活,都放到框架里面去,让使用者用最干净的方式使用业务接口。定义一个RPC
进程间通信框架,可以把C/S两端那些恶心人的AIDL
编码都集中放到框架module
中,这是最直观的好处,另外,客户端原本还需要手动去bindService
,定义ServiceConnection
,取得Binder
,再去通信,使用RPC
框架,这些内容都可以放到框架module
中. 而C/S两端的代码,就只剩下了S端
的服务注册,C端
的RPC
接口调用,代码外观上非常简洁(可能这里文字描述不够直观,后面有图)
前置技能
要理解本文的核心代码,还是需要一些基础的,大致如下:
四大组件之一Service
使用方法,
androidAIDL
通信机制,
java注解,java反射,java 泛型
二、传统方式IPC通信写法
与 使用IPC框架进行RPC通信
的对比
见github : https://github.com/18598925736/MyRpcFramework , 运行 aidl_client
和 aidl_service
先展示原本效果
图中的查找用户
,是从服务端
读取的数据,观察一下核心代码:这是我优化之后的
IPC
项目结构(如果不优化,那么客户端 服务端都需要编写一样的AIDL代码,还要有一个包括包名在内神马都要一模一样的JavaBean,实在是丑陋
):
服务端
核心代码:
public class ServiceActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService(new Intent(this, MyService.class));//服务端,app启动之后,自动启动服务
}
}
public class MyService extends Service {
ConcurrentMap<String, UserInfoBean> map;
@Nullable
@Override
public IBinder onBind(Intent intent) {
map = new ConcurrentHashMap<>();
for (int i = 0; i < 100; i++) {
map.put(“name” + i, new UserInfoBean(“name” + i, “accountNo” + i, i));
}
return new IUserInfo.Stub() {//数据接收器 Stub
@Override
public UserInfoBean getInfo(String name) {
return map.get(name);
}
};
}
@Override
public void onCreate() {
super.onCreate();
Log.e(“MyService”, “onCreate: success”);
}
}
客户端
核心代码 :
public class ClientActivity extends AppCompatActivity {
private TextView resultView;
private String TAG = “clientLog”;
private int i = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
resultView = findViewById(R.id.resultView);
findViewById(R.id.connect).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bindService();
}
});
findViewById(R.id.disconnect).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
unbindService(connection);
resultView.setText(“尝试释放”);
} catch (IllegalArgumentException e) {
resultView.setText(“已经释放了”);
}
}
});
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (iUserInfo != null) {
try {
((Button) v).setText(“查找name为:name” + ((i++) + 1) + “的UserInfoBean”);
UserInfoBean bean = iUserInfo.getInfo(“name” + i);
if (bean != null)
resultView.setText(bean.toString());
else
resultView.setText(“没找到呀”);
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
resultView.setText(“没有连接上service”);
}
}
});
}
//作为IPC的客户端,我们需要 建立起和Service的连接
private IUserInfo iUserInfo;//操作句柄,可以通过它向service发送数据
private void bindService() {
if (iUserInfo == null) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(
“study.hank.com.aidl_service”,
“study.hank.com.aidl_service.MyService”));
bindService(intent, connection, Context.BIND_AUTO_CREATE);
resultView.setText(“尝试连接”);
} else {
resultView.setText(“已经连接上service” + iUserInfo);
}
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iUserInfo = IUserInfo.Stub.asInterface(service);
resultView.setText(“连接成功”);
Log.d(TAG, “connection:” + “连接成功”);
}
@Override
public void onServiceDisconnected(ComponentName name) {
iUserInfo = null;
resultView.setText(“连接 已经断开”);
Log.d(TAG, “connection:” + “已经断开”);
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
}
很容易发现,服务端的代码量尚可,不是很复杂,但是客户端这边,要处理 connection
,要手动去绑定以及解绑Service
,所有参与通信的javabean
还必须实现序列化接口parcelable
Demo
中只有一个客户端,还不是很明显,但是如果有N
个客户端Activity
都需要与service
发生通信,意味着每一个Activity
都必须写类似的代码. 不但累赘
,而且丑陋
.
前方高能
不使用RPC框架时,CS两端的代码的结构,已经有了大致的印象,下面是 使用IPC框架时
客户端、服务端 的核心代码
客户端
之前的bindService
呢?没了。客户端使用此框架来进行 进程通信,不用去关心AIDL
怎么写了,不用关注bindService,ServiceConnection
,省了很多事。
服务端
对比 使用框架前后,我们的核心代码的变化
有什么变化?显而易见,极大缩减了
客户端
的编码量,而且,一劳永逸,除非需求大改,不然这个框架,一次编写,终身使用。除此之外,使用框架还可以极大地节省客户端
代码量,减少人为编码时产生的可能疏漏(比如忘记释放连接造成泄漏等). 试想一下,如果你是一个团队leader
,团队成员的水平很有可能参差不齐
,那么如何保证项目开发中出错概率最小
-------使用框架
, 用框架来简化团队成员的编码量
和编码难度
,让他们傻瓜式
地写代码.
三、Demo展示
github地址:https://github.com/18598925736/MyRpc
以上Demo,模拟的场景是:
服务端:开启一个登录服务
,启动服务之后,保存一个可以登录
的用户名和密码
客户端1:RPC
调用登录服务
,用户名和密码 和服务端的一样
,可以登录成功
客户端2:RPC
调用登录服务
,用户名和密码 和服务端的不一样
,登录失败
Demo
工程代码结构图
注:客户端和服务端必须同时依赖框架层module implementation project(":ipc")
四、框架核心思想讲解
我们不使用IPC框架
时,有两件事
非常恶心:
1. 随着业务的扩展,我们需要频繁(
因为要新增业务接口
)改动AIDL
文件,而且AIDL
修改起来没有任何代码提示,只有到了编译之后,编译器才会告诉我哪里错了,而且 直接引用到的JavaBean
还必须手动再声明一次。实在是不想在这个上面浪费时间。
2. 所有客户端Activity
,只要想进行进程间binder
通信,就不可避免要去手动bindService
,随后去处理Binder
连接,重写ServiceConnection
,还要在适当的时候释放连接
,这种业务不相关而且重复性很大的代码,要尽量少写。
IPC框架将会着重解决这两个问题。下面开始讲解核心设计思想
注:
1.搭建框架牵涉的知识面会很广,我不能每个细节都讲得很细致,一些基础部分一笔带过的,如有疑问,希望能留言讨论。
2.设计思路都是环环相扣的,阅读时最好是从上往下依次理解.
框架思想四部曲:
1)业务注册
上文说到,直接使用
AIDL
通信,当业务扩展时,我们需要对AIDL
文件进行改动,而改起来比较费劲,且容易出错。怎么办?利用业务注册
的方式,将业务类
的class
对象,保存到服务端 内存中。
进入Demo代码Registry.java
:
public class Ipc {
/**
- @param business
*/
public static void register(Class<?> business) {
//注册是一个单独过程,所以单独提取出来,放在一个类里面去
Registry.getInstance().register(business);//注册机是一个单例,启动服务端,
// 就会存在一个注册机对象,唯一,不会随着服务的绑定解绑而受影响
}
…省略无关代码
}
/**
- 业务注册机
*/
public class Registry {
…省略不关键代码
/**
- 业务表
/
private ConcurrentHashMap<String, Class<?>> mBusinessMap
= new ConcurrentHashMap<>();
/* - 业务方法表, 二维map,key为serviceId字符串值,value为 一个方法map - key,方法名;value
*/
private ConcurrentHashMap<String, ConcurrentHashMap<String, Method>> mMethodMap
= new ConcurrentHashMap<>();
/**
- 业务类的实例,要反射执行方法,如果不是静态方法的话,还是需要一个实例的,所以在这里把实例也保存起来
*/
private ConcurrentHashMap<String, Object> mObjectMap = new ConcurrentHashMap<>();
/**
- 业务注册
- 将业务class的class和method对象都保存起来,以便后面反射执行需要的method
*/
public void register(Class<?> business) {
//这里有个设计,使用注解,标记所使用的业务类是属于哪一个业务ID,在本类中,ID唯一
ServiceId serviceId = business.getAnnotation(ServiceId.class);//获取那个类头上的注解
if (serviceId == null) {
throw new RuntimeException(“业务类必须使用ServiceId注解”);
}
String value = serviceId.value();
mBusinessMap.put(value, business);//把业务类的class对象用 value作为key,保存到map中
//然后要保存这个business类的所有method对象
ConcurrentHashMap<String, Method> tempMethodMap = mMethodMap.get(value);//先看看方法表中是否已经存在整个业务对应的方法表
if (tempMethodMap == null) {
tempMethodMap = new ConcurrentHashMap<>();//不存在,则new
mMethodMap.put(value, tempMethodMap);// 并且将它存进去
}
for (Method method : business.getMethods()) {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
String methodMapKey = getMethodMapKeyWithClzArr(methodName, parameterTypes);
tempMethodMap.put(methodMapKey, method);
}
…省略不关键代码
}
…省略不关键代码
/**
- 如何寻找到一个Method?
- 参照上面的构建过程,
- @param serviceId
- @param methodName
- @param paras
- @return
*/
public Method findMethod(String serviceId, String methodName, Object[] paras) {
ConcurrentHashMap<String, Method> map = mMethodMap.get(serviceId);
String methodMapKey = getMethodMapKeyWithObjArr(methodName, paras); //同样的方式,构建一个StringBuilder
return map.get(methodMapKey);
}
/**
- 放入一个实例
- @param serviceId
- @param object
*/
public void putObject(String serviceId, Object object) {
mObjectMap.put(serviceId, object);
}
/**
- 取出一个实例
- @param serviceId
*/
public Object getObject(String serviceId) {
return mObjectMap.get(serviceId);
}
}
/**
- 自定义注解,用于注册业务类的
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceId {
String value();
}
我利用一个单例的
Registry
类,将当前这个业务class
对象,拆解出每一个Method
,保存到map
集合中。
而,保存这些Clas
s,Method
,则是为了 反射执行指定的业务Method
做准备。
此处有几个精妙设计:
1, 利用自定义注解@ServiceId
对业务接口和实现类,都形成约束,这样业务实现类就有了进行唯一性约束,因为在Registry
类中,一个ServiceId
只针对一种业务,如果用Registry
类注册一个没有@ServiceId
注解的业务类,就会抛出异常。
2, 利用注解
@ServiceId
的value
作为key
,保存所有的业务实现类的Class
, 以及该Class
的所有public
的Method
到map
集合中,通过日志打印,很容易看出当前服务端有哪些 业务类,业务类有哪些可供外界调用的方法。(·这里需要注意,保存方法时,必须连同方法的参数类型一起作为key
,因为存在同名方法重载的情况·)
当你运行Demo
,启动服务端的时候,过滤一下日志,就能看到:
3 ,如果再发生 业务扩展的情况,我们只需要直接改动加了@ServiceId
注解的业务类即可,并没有其他多余的动作。
如果我在IUserBusiness
接口中,增加一个logout
方法,并且在实现类中去实现它。那么,再次启动服务端
app,上图的日志中就会多出一个logout
方法.
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
总结:
各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
-
BAT大厂面试题、独家面试工具包,
-
资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。
-
BAT大厂面试题、独家面试工具包,
-
资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,
[外链图片转存中…(img-BeKKmHLU-1712735091498)]
[外链图片转存中…(img-wGN3u4R9-1712735091499)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-DYk00Cda-1712735091499)]