Android进阶知识树——Android 多进程、Binder 你必须知道的一切

概述

想当初在第一次拜读《Android艺术开发探索》时,深感真的是一本很“艺术”的书(由于当初菜的看不懂..),随着本身的成长和屡次阅读,从开始的彻底不懂到如今的有所理解、使用和总结,才体会到其中探索的奥妙,如今跟着安卓高级开发的学习路线,进一步学习、总结和梳理知识。java

多进程做为Android开发者迈向高级开发者的第一关,也使许多初级开发者望而却步,这也是每一个开发者必经阶段,正好笔者在公司的开发项目中也一直使用了多进程,以前只是在使用阶段、和平时零散的知识点,而对Binder的原理和理解并不深刻,本文结合最近所看的文章和实际使用,从开发者的角度总结多进程和Binder的使用,即为本身梳理知识也但愿帮助有须要的人。android

在这里插入图片描述

  • 定义 提到多进程就会想到多线程,这也是不少初级的面试问题,两者对比着可能更好理解:
  1. 线程:线程是CPU最小的调度单元,是有限的系统资源,也是处理任务的地方
  2. 进程:是一个执行单元,通常指设备上的一个程序或一个应用
  3. 理解:进程和线程是包含和被包含的关系;一个进程能够包含多个线程
  • 开启方式 Android开启多进程只有一个方式:注册清单文件中,在Android四大组件中指定process属性,命名方式以下:
  1. 以“:”命名方式:最终的进程名为在当前的命名前面添加默认的包名
  2. 完整命名方式:最终的进程名就为设定的名称
android:process=":consume"
android:process="com.alex.kotlin.myapplication.consume"
复制代码
  • 多进程问题 由于进程开启时Application都会从新建立,因此不少数据和对象都会产生副本,所以在多进程模式下数据共享就会变得不稳定,多进程模式下会造车以下的问题:
  1. 静态成员和单例模式彻底失效
  2. 线程同步机制彻底失效
  3. SharePreference可靠性降低
  4. Application会屡次建立

进程间通讯

关于进程间的通讯首先想到的是Binder机制,固然开发中若是使用多进程,那Binder自当是首当其冲要了解和学习的,下文也会重点介绍Binder,在此以前来看看咱们实际开发中使用的、或者能够跨进程通讯的机制git

  • 序列化
  1. 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实例,使用过程以下:github

//序列化
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类了。面试

  1. Parcelable Parcelable也是一个接口,他是Android提供的在内存中更高效的序列化方式,使用方法是实现接口,重写其中方法便可,固然也可以使用插件自动生成。

对于Parcelable和Serializable的选择使用:Serializable是Java的序列化接口,使用时开销大,须要大量的IO操做,Parcelable是Android提供的序列化接口,适合Android效率更高,对于二者的选择,若是只是在内存上序列化使用Parcelable,若是须要在磁盘上序列化使用Serializable便可。bash

Binder

在网上看了需对关于Binder的文章,有的深刻Binder源码和底层去分析Binder的源码和实现,固然这里面的代码我是看不懂,本文主要从Android开发的角度,对Binder的通讯的模型和方式作一个介绍,多线程

  • Binder模型 Binder框架定义了四个角色:Server,Client,ServiceManager(简称SMgr)以及Binder驱动。其中Server,Client,SMgr运行于用户空间,驱动运行于内核空间
  1. Server:服务的真正提供者,不过它会先向ServiceManager注册本身Binder代表本身能够提供服务,驱动会为这个BInder建立位于内核中的实体和ServiceManager中的引用,并将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表
  2. Client:服务的需求者和使用者,它向ServiceManager申请须要的服务;ServiceManager将表中的引用返回Client,Client拿到服务后便可调用服务中的方法;
  3. ServiceManager:Binder实体和引用的中转站,保存并分发Binder的引用;
  4. Binder驱动:Binder驱动默默无闻付出,倒是通讯的核心,驱动负责进程之间Binder通讯的创建,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持,借用网上的一张图片展现Binder的通讯模型;

在这里插入图片描述
若是上面的四个功能难以理解,咱们以打电话为例,将整个电话系统的程序比作Binder驱动,通信录比做ServiceManager,你本人为Client,如今你要打电话给叫Server人求助:

  1. Server:Server表示你要打电话找的人,它会首先给你留一个手机号,你为了能够找到他,将号码保存到通信录中,通信录至关于ServiceManager(Server向ServiceManager注册服务)
  2. client:至关于你本人,发起打电话请求
  3. ServiceManager:通信录保存电话号码,你须要的时候首先向通信录去查找号码,它会返回Server手机号
  4. Binder驱动:打电话的系统,根据你输入的号码呼叫对应的人 对于Binder的通讯模型如上述所述,简单的说就是Server先注册并登记表示能够提供服务功能,当有需求时向登记处查找能够提供服务的Service,登记处会给你详细的地址,而后你就能够和服务商之间合做,只是整个过程在Binder驱动做用下完成;
  • Binder代理机制 经过上面的Binder通讯机制的理解,相信已经了解Binder是如何跨进程通讯的,但是具体的数据和对象都存在不一样的进程中,那么进程间是如何相互获取的呢?好比A进程要获取B进程中的对象,它是如何实现的呢?此时就须要Binder的代理机制;

当Binder收到A进程的请求后,Binder驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来如出一辙的代理对象 objectProxy,这个 objectProxy 具备和 object 一摸同样的方法,可是这些方法并无 B 进程中 object 对象那些方法的能力,这些方法只须要把把请求参数交给驱动便可;app

而对于进程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分为两步,即服务端和客户端的实现ide

  1. 服务端 首先建立一个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
}
复制代码
  1. 客户端 客户端在绑定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支持的数据类型:

  1. 基本数据类型
  2. String和CharSequence
  3. List:只支持ArrayList
  4. Map:只支持HashMap
  5. Parcelable:全部实现Parcelable接口的实例
  6. 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{
.....
}
......
}
复制代码
  1. ILoginBinder:继承android.os.IInterface的接口,并声明了AIDL文件中的方法
  2. Stub:编译AIdl文件后自动生成的文件,继承Binder并实现ILoginBinder接口,Stub是一个抽象类,因此它的子类要实现AIDL文件中的方法;Stub中有个重要的方法asInterface(android.os.IBinder obj),它的传入参数是Binder实例,根据判断Binder是否为当前进程,若为当前线程返回BInder的实例,若为其余进程则返回Stub.Proxy(obj)的代理类
  3. Proxy:它是Stub中的一个内部类,也实现了ILoginBinder接口和全部方法,不过它的方法最后的执行仍是交给传入的mRemote中执行,而mRemote就是IBinder的实例,因此方法的最终调用仍是在Stub的子类中
  • 服务端的实现 服务端的实现也分为两步:
  1. 建立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
    }
    }
复制代码
  1. 建立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权限验证

默认状况下远程服务任何人均可以链接,权限验证也就是阻拦那些不想让他链接的人,验证的地方有两处:

  1. onBind()方法中
  2. 服务端的onTransact()

验证的方式也有两种:

  1. 自定义权限验证
  2. 包名验证

下面分别使用二者进行服务端的验证,首先在清单文件中添加自定义权限,并默认声明此权限

<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>
复制代码

AIdlDemo地址

相关文章
相关标签/搜索