内容简介:想当初在第一次拜读《Android艺术开发探索》时,深感真的是一本很“艺术”的书(因为当初菜的看不懂..),随着自己的成长和多次阅读,从开始的完全不懂到现在的有所理解、使用和总结,才体会到其中探索的奥妙,现在跟着安卓高级开发的学习路线,进一步学习、总结和梳理知识。多进程作为Android开发者迈向高级开发者的第一关,也使许多初级开发者望而却步,这也是每个开发者必经阶段,正好笔者在公司的开发项目中也一直使用了多进程,之前只是在使用阶段、和平时零散的知识点,而对Binder的原理和理解并不深入,本文结合最近所看
想当初在第一次拜读《Android艺术开发探索》时,深感真的是一本很“艺术”的书(因为当初菜的看不懂..),随着自己的成长和多次阅读,从开始的完全不懂到现在的有所理解、使用和总结,才体会到其中探索的奥妙,现在跟着安卓高级开发的学习路线,进一步学习、总结和梳理知识。
多进程作为Android开发者迈向高级开发者的第一关,也使许多初级开发者望而却步,这也是每个开发者必经阶段,正好笔者在公司的开发项目中也一直使用了多进程,之前只是在使用阶段、和平时零散的知识点,而对Binder的原理和理解并不深入,本文结合最近所看的文章和实际使用,从开发者的角度总结多进程和Binder的使用,即为自己梳理知识也希望帮助有需要的人。
- 定义 提到多进程就会想到多线程,这也是很多初级的面试问题,二者对比着可能更好理解:
- 线程:线程是CPU最小的调度单元,是有限的系统资源,也是处理任务的地方
- 进程:是一个执行单元,一般指设备上的一个程序或一个应用
- 理解:进程和线程是包含和被包含的关系;一个进程可以包含多个线程
- 开启方式 Android开启多进程只有一个方式:注册清单文件中,在Android四大组件中指定process属性,命名方式如下:
- 以“:”命名方式:最终的进程名为在当前的命名前面添加默认的包名
- 完整命名方式:最终的进程名就为设定的名称
android:process=":consume" android:process="com.alex.kotlin.myapplication.consume" 复制代码
- 多进程问题 因为进程开启时Application都会重新创建,所以很多数据和对象都会产生副本,因此在多进程模式下数据共享就会变得不稳定,多进程模式下会造车如下的问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharePreference可靠性下降
- Application会多次创建
进程间通信
关于进程间的通信首先想到的是Binder机制,当然开发中如果使用多进程,那Binder自当是首当其冲要了解和学习的,下文也会重点介绍Binder,在此之前来看看我们实际开发中使用的、或者可以跨进程通信的机制
- 序列化
- Serializable Serializable序列的使用很简单,只需要实现在 Java 类中实现Serializable接口,设置serialVersionUID即可
public class Book implements Serializable { private static final long serialVersionUID = 871136882801008L; String name; int age; public Book(String name, int age) { this.name = name; this.age = age; } } 复制代码
在储存数据时只需将对象序列化在磁盘中,在需要使用的地方反序列化即可获取Java实例,使用过程如下:
//序列化 val book = Book("Android",20) val file = File(cacheDir,"f.txt") val out = ObjectOutputStream(FileOutputStream(file)) out.writeObject(book) out.close() //反序列化 val file = File(cacheDir,"f.txt") val input = ObjectInputStream(FileInputStream(file)) val book: Book = input.readObject() as Book input.close() 复制代码
针对上面的serialVersionUID可能有的认为不设置也可以使用,但如果不设置serialVersionUID值,Java对象同样可以序列化,但是当Java类改变时,这时如果去反序列化的化就会报错,因为serialVersionUID是辅助序列化和反序列化的,只有两者的serialVersionUID一致才可实现反序列化,所以你不指定serialVersionUID时,系统会默认使用当前类的Hash值,当java对象改变时其Hash值也改变了,所以反序列化时就找不到对应的Java类了。
- Parcelable Parcelable也是一个接口,他是Android提供的在内存中更高效的序列化方式,使用方法是实现接口,重写其中方法即可,当然也可使用插件自动生成。
对于Parcelable和Serializable的选择使用:Serializable是Java的序列化接口,使用时开销大,需要大量的IO操作,Parcelable是Android提供的序列化接口,适合Android效率更高,对于两者的选择,如果只是在内存上序列化使用Parcelable,如果需要在磁盘上序列化使用Serializable即可。
Binder
在网上看了需对关于Binder的文章,有的深入Binder源码和底层去分析Binder的源码和实现,当然这里面的代码我是看不懂,本文主要从Android开发的角度,对Binder的通信的模型和方式做一个介绍,
- Binder模型 Binder框架定义了四个角色:Server,Client,ServiceManager(简称SMgr)以及Binder驱动。其中Server,Client,SMgr运行于用户空间,驱动运行于内核空间
- Server:服务的真正提供者,不过它会先向ServiceManager注册自己Binder表明自己可以提供服务,驱动会为这个BInder创建位于内核中的实体和ServiceManager中的引用,并将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表
- Client:服务的需求者和使用者,它向ServiceManager申请需要的服务;ServiceManager将表中的引用返回Client,Client拿到服务后即可调用服务中的方法;
- ServiceManager:Binder实体和引用的中转站,保存并分发Binder的引用;
- Binder驱动:Binder驱动默默无闻付出,却是通信的核心,驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持,借用网上的一张图片展示Binder的通信模型;
如果上面的四个功能难以理解,我们以打电话为例,将整个电话系统的程序比做Binder驱动,通讯录比作ServiceManager,你本人为Client,现在你要打电话给叫Server人求助:
- Server:Server表示你要打电话找的人,它会首先给你留一个手机号,你为了可以找到他,将号码保存到通讯录中,通讯录相当于ServiceManager(Server向ServiceManager注册服务)
- client:相当于你本人,发起打电话请求
- ServiceManager:通讯录保存电话号码,你需要的时候首先向通讯录去查找号码,它会返回Server手机号
- Binder驱动:打电话的系统,根据你输入的号码呼叫对应的人 对于Binder的通信模型如上述所述,简单的说就是Server先注册并登记表示可以提供服务功能,当有需求时向登记处查找可以提供服务的Service,登记处会给你详细的地址,然后你就可以和服务商之间合作,只是整个过程在Binder驱动作用下完成;
- Binder代理机制 通过上面的Binder通信机制的理解,相信已经了解Binder是如何跨进程通信的,可是具体的数据和对象都存在不同的进程中,那么进程间是如何相互获取的呢?比如A进程要获取B进程中的对象,它是如何实现的呢?此时就需要Binder的代理机制;
当Binder收到A进程的请求后,Binder驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可;
而对于进程A却傻傻不知道它以为拿到了B 进程中 object 对象,所以直接调用了Object的方法,当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了,所以中间的代理就只是一个面具和传输的媒介。
- Binder使用 Messenger 一种轻量级的IPC方案,它的底层实现是AIDL,Messenger通过对AIDL的封装是我们可以更简单的使用进程通信,它的构造函数如下:
public Messenger(Handler target) { mTarget = target.getIMessenger(); } public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); } 复制代码
实现一个Messenger分为两步,即服务端和客户端的实现
- 服务端 首先创建一个Service来连接客户端的请求,在Service中创建Handler实例,并使用此Handler的实例创建一个Messenger实例,并在Service的onBind()中返回Messenger实例。
//创建Handler class HandlerService : Handler() { override fun handleMessage(msg: Message?) { when (msg?.what) { MSG_WHAT -> { Log.e("MyService", "MyServer") } else -> super.handleMessage(msg) } } } //使用Handler实例创建Messenger实例 private val messenger = Messenger(HandlerService()) //服务通过 onBind() 使其返回客户端 override fun onBind(intent: Intent): IBinder { return messenger.binder } 复制代码
- 客户端 客户端在绑定Service后,会在onServiceConnected中获取IBinder的实例,客户端使用此实例创建Messenger实例,这个Messenger就可以和服务端进行通信了,发送Message信息服务端就会收到;
private var messenger: Messenger? = null private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(p0: ComponentName?, iBinder: IBinder?) { messenger = Messenger(iBinder) // 绑定service后初始化 Messenger } override fun onServiceDisconnected(p0: ComponentName?) { messenger = null } } var message = Message.obtain(null, MSG_WHAT, 100,0) // 创建Message messenger?.send(message) // 发送Message //输出结果 07-24 14:00:38.604 18962-18962/com.example.administrator.memory E/MyService:MyServer 100 复制代码
若服务端想回应客户端,那客户端就要像服务端一样创建一个接受信息的Handler和Messenger实例,在发送Message时使用msg.replyTo将Messenger实例发送给服务端,服务端就可以使用此实例回应客户端信息;
//客户端发送Messenger到Service msg.replyTo = mGetReplyMessenger; // 在Service端接收客户端的Messenger Messenger msg = msg.replyTo; 复制代码
AIDL对于进程通信来说,可能实际在项目中使用的可能更多的还是AIDL,所以作为本文的最后也是重点讲解,并结合实际的代码分析多进程的使用,Aidl支持的数据类型:
- 基本数据类型
- String和CharSequence
- List:只支持ArrayList
- Map:只支持HashMap
- Parcelable:所有实现Parcelable接口的实例
- AIDL:所有声明的AIDL文件
AIDL的使用分为三步:AIDL接口创建、服务端、客户端实现,下面实际代码分析,我们做一个简单的Demo,在主进程中输入账户密码,然后在服务进程中验证登陆,并将结果返回调用进程;
- AIDL接口创建 创建登陆ILoginBinder的AIDL接口文件,并声明登陆方法:
import com.alex.kotlin.myapplication.User; interface ILoginBinder { void login(String name ,String pasd); boolean isLogin(); User getUser(); } 复制代码
上面的Aidl文件中使用了User类,所以在Java代码中创建User类,但初次之外也要创建User.aidl文件且包名要和Java中的一样,并在ILoginBinder中导入User文件的包;
package com.alex.kotlin.myapplication; parcelable User ; 复制代码
此时点击MakePeoject系统会自动编译出AIDL文件对应的java代码 ILoginBinder类,可以在build包相应的路径下可以查看此类,代码结构如下:
public interface ILoginBinder extends android.os.IInterface{ ..... public static abstract class Stub extends android.os.Binder implements com.alex.kotlin.myapplication.binder.ILoginBinder{ ...... private static class Proxy implements com.alex.kotlin.myapplication.binder.ILoginBinder{ ..... } ...... } 复制代码
- ILoginBinder:继承android.os.IInterface的接口,并声明了AIDL文件中的方法
- Stub:编译AIdl文件后自动生成的文件,继承Binder并实现ILoginBinder接口,Stub是一个抽象类,所以它的子类要实现AIDL文件中的方法;Stub中有个重要的方法asInterface(android.os.IBinder obj),它的传入参数是Binder实例,根据判断Binder是否为当前进程,若为当前线程返回BInder的实例,若为其他进程则返回Stub.Proxy(obj)的代理类
- Proxy:它是Stub中的一个内部类,也实现了ILoginBinder接口和所有方法,不过它的方法最后的执行还是交给传入的mRemote中执行,而mRemote就是IBinder的实例,所以方法的最终调用还是在Stub的子类中
- 服务端的实现 服务端的实现也分为两步:
- 创建Stub类的子类并实现方法
class LoginBinder : ILoginBinder.Stub() { override fun login(name: String?, pasd: String?) { Log.e("======","name = $name ; pasd = $pasd") user = User(name) } override fun isLogin(): Boolean { return user != null } override fun getUser(): User? { return user } } 复制代码
- 创建Service端并在onBind方法中返回Stub的子类
class BinderMangerService : Service() { val binder = LoginBinder() override fun onBind(intent: Intent) : IBinder?{ return binder } } 复制代码
设置Service的进程
<service android:name=".binder.BinderMangerService" android:process=":service"> </service> 复制代码
- 客户端的实现 客户端的实现和服务端一样遵循者Service的使用方式,首先绑定Service服务在后的回调中获取IBinder实例,也是Stub的实现类的实例,客户端拿到此类后调用Stub中的asInterface()方法获取代理类,到此即可实现进程间的通信
runOnThread { val intent = Intent(contextWrapper, BinderMangerService::class.java) contextWrapper.bindService(intent, serviceConnect,Context.BIND_AUTO_CREATE) binderSuccessCallback?.success() } ...... override fun onServiceConnected(name: ComponentName?, service: IBinder?) { iBinderManger = IBinderManger.Stub.asInterface(service) } 复制代码
此时获取到IBinderManger的代理类后即可调用方法,下面我们调用login()方法登陆,查看输出信息:
2018-12-08 22:24:26.675 349-363/com.alex.kotlin.myapplication:service E/======: name = AAAAA ; pasd = 11111 复制代码
- AIDL的断开监听
此时在service进程中收到了默认进成发送的登陆信息,即二者之间的通信完成,但服务的连接会在某个时机因为某种原因时断开,为了获取断开的时间或保持连接的稳定性Android提供了Binder连接的死亡监听类IBinder.DeathRecipient,在绑定成功时给获取的Ibinder绑定IBinder.DeathRecipient实例,在连接断开时会收到死亡回调,我们可以断开连接后继续重连,使用如下:
//创建IBinder.DeathRecipient实例 var deathRecipient : IBinder.DeathRecipient? = null deathRecipient = IBinder.DeathRecipient { //断开连接 iBinderManger?.asBinder()?.unlinkToDeath(deathRecipient,0) iBinderManger = null //重新连接 } override fun onServiceConnected(name: ComponentName?, service: IBinder?) { iBinderManger = IBinderManger.Stub.asInterface(service) //设置死亡监听 service?.linkToDeath(deathRecipient,0) countDownLatch.countDown() } 复制代码
- AIDLBinder接口回调
但此时的通信是单向的,如果想在登陆成功或失败的时候通知默认进程,即进程间的回调,以为二者处于不同进程间,所以普通的接口回调不能满足,此时的接口也必须是跨进程的AIDl接口,所以创建ILoginCallback文件:
interface ILoginCallback { void loginSuccess(); void loginFailed(); } 复制代码
在ILoginBinder的文件中添加注册和解除监听的方法:
void registerListener(ILoginCallback iLoginCallback); void unregisterListener(ILoginCallback iLoginCallback); 复制代码
在ILoginBinder的实现类中实现这两个方法,这里需要说明的是Android为多进程中的接口注册问题提供了专门的类:RemoteCallbackList,所以在Stub的实现类中创建RemoteCallbackList,并在两个方法中添加和删除ILoginCallback的实例
private val remoteCallbackList = RemoteCallbackList<ILoginCallback>() override fun registerListener(iLoginCallback: ILoginCallback?) { remoteCallbackList.register(iLoginCallback) } override fun unregisterListener(iLoginCallback: ILoginCallback?) { remoteCallbackList.unregister(iLoginCallback) } 复制代码
对于RemoteCallbackList的遍历也有所不同,必须beginBroadcast()和finishBroadcast()的成对使用,下面在登陆成功或失败后回调接口:
f (name != null && pasd != null){ user = User(name) val number = remoteCallbackList.beginBroadcast() for (i in 0 until number){ remoteCallbackList.getBroadcastItem(i).loginSuccess() } remoteCallbackList.finishBroadcast() }else{ val number = remoteCallbackList.beginBroadcast() for (i in 0 until number){ remoteCallbackList.getBroadcastItem(i).loginFailed() } remoteCallbackList.finishBroadcast() } 复制代码
在LoginActivity中创建ILoginCallback.Stub的子类,并调用方法注册接口,
private val loginCallback = object : ILoginCallback.Stub(){ override fun loginSuccess() { Log.e("======","登陆成功") } override fun loginFailed() { } } loginBinder?.registerListener(loginCallback) 复制代码
此时再次运行结果:
2018-12-08 22:46:48.366 792-810/com.alex.kotlin.myapplication:service E/======: name = AAAAA ; pasd = 11111 2018-12-08 22:46:48.367 747-747/com.alex.kotlin.myapplication:login E/======: 登陆成功 复制代码
到这里进程间的相互通信已经完成了,现在可以在二者之间实现数据或逻辑的相互调用,是不是很happy,但是你可以调用别人也可以调用,那怎么让只有自己才能调用呢?那就用到最后的一点就是Binder的权限验证
- Binder权限验证
默认情况下远程服务任何人都可以连接,权限验证也就是阻拦那些不想让他连接的人,验证的地方有两处:
- onBind()方法中
- 服务端的onTransact()
验证的方式也有两种:
- 自定义权限验证
- 包名验证
下面分别使用两者进行服务端的验证,首先在清单文件中添加自定义权限,并默认声明此权限
<uses-permission android:name="com.alex.kotlin.myapplication.permissions.BINDER_SERVICE"/> <permission android:name="com.alex.kotlin.myapplication.permissions.BINDER_SERVICE" android:protectionLevel="normal"/> 复制代码
在onBind()中判断此权限,如果通过则返回Binder实例,否则返回null
override fun onBind(intent: Intent) : IBinder?{ val check = checkCallingOrSelfPermission("com.alex.kotlin.myapplication.permissions.BINDER_SERVICE") if (check == PackageManager.PERMISSION_DENIED){ return null } return binder } 复制代码
另一中就是在服务端的onTransact()中验证权限和包名,只有二者都通过返回true,否则返回false
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { val check = checkCallingOrSelfPermission("com.alex.kotlin.myapplication.permissions.BINDER_SERVICE") if (check == PackageManager.PERMISSION_DENIED){ return false } val packages = packageManager.getPackagesForUid(Binder.getCallingUid()) if (packages != null && !packages.isEmpty()){ val packageName = packages[0] if (!packageName.startsWith("com.alex")){ return false } } return super.onTransact(code, data, reply, flags) } 复制代码
- Binder连接池 上面过程只使用了一个Aidl文件,那如果10个呢?不可能创建和绑定10个Service,所以此时就休要使用Binder连接池,在Binder连接池中提供查询Binder功能,根据传入参数的不同获取响应Stub子类的实例,只需创建一个用于绑定和返回Binder连接池的Service即可,详细使用见文末的Demo;
到此本文的所有内容都介绍完毕了,从安卓开发和使用来说已能满足工作中的需求,文末附上一个Aidl的Demo,以商店购买商品为例,使用Binder连接池实现登陆、售货员、商店、和消费者四个进程的通信;
<activity android:name=".ConsumeActivity" android:process=":consume"> </activity> <activity android:name=".LoginActivity" android:process=":login"> </activity> <service android:name=".binder.BinderMangerService" android:process=":service"> </service> <activity android:name=".ProfuctActivity" android:process=":product"> </activity> 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Python 知识巩固:通过主进程带起多个子进程实现多进程执行逻辑
- 进程与线程:入门知识篇
- 操作系统知识之内核与进程学习总结
- 前端必须掌握的知识之Http协议基础以及发展进程
- 【深入浅出-系统架构师】(15):操作系统基础知识——进程通信与线程
- 进程:进程生命周期
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
人人都是产品经理
苏杰 / 电子工业出版社 / 2012-6 / 45.00元
本书为《人人都是产品经理》的升级版,是写给“1到3岁的产品经理”的书,适合刚入门的产品经理、产品规划师、需求分析师,以及对做产品感兴趣的学生,用户体验、市场运营、技术部门的朋友们,特别是互联网、软件行业。作为一名“4岁的产品经理”,作者讲述了过去3年的经历与体会,与前辈们的书不同,本书就像你走到作者身边,说“嗨,哥们!晚上有空吃个饭吗,随便聊聊做产品的事吧”,然后作者说“好啊”。 书名叫“......一起来看看 《人人都是产品经理》 这本书的介绍吧!