动态图片演示,源码直析:手把手讲解IPC框架(1)

751 阅读7分钟

作者:享学课堂终身VIP周周

转载请声明出处!

前言

手把手讲解系列文章,是我写给各位看官,也是写给我自己的。文章可能过分详细,但是这是为了帮助到尽量多的人,毕竟工作5,6年,不能老吸血,也到了回馈开源的时候. 这个系列的文章:

1、用通俗易懂的讲解方式,讲解一门技术的实用价值

2、详细书写源码的追踪,源码截图,绘制类的结构图,尽量详细地解释原理的探索过程

3、提供Github 的 可运行的Demo工程,但是我所提供代码,更多是提供思路,抛砖引玉,请酌情cv

4、集合整理原理探索过程中的一些坑,或者demo的运行过程中的注意事项

5、用gif图,最直观地展示demo运行效果

如果觉得细节太细,直接跳过看结论即可。本人能力有限,如若发现描述不当之处,欢迎留言批评指正。

学到老活到老,路漫漫其修远兮。与众君共勉 !

正文大纲

一、 概念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使用方法, android AIDL通信机制, java注解,java反射,java 泛型

二、传统方式IPC通信写法使用IPC框架进行RPC通信 的对比

见github : github.com/18598925736… , 运行 aidl_clientaidl_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地址github.com/18598925736…

以上Demo,模拟的场景是:

服务端:开启一个 登录服务,启动服务之后,保存一个 可以登录的用户名和密码

客户端1RPC调用 登录服务,用户名和密码 和服务端的 一样,可以登录成功

客户端2RPC调用 登录服务,用户名和密码 和服务端的 不一样,登录失败

Demo工程代码结构图

客户端和服务端必须同时依赖框架层module implementation project(":ipc")

(未完待续......)

关注我,还有更多技术干货分享~