美文网首页
Android艺术探索学习笔记:第2章 IPC机制

Android艺术探索学习笔记:第2章 IPC机制

作者: Android绝世小菜鸟 | 来源:发表于2017-06-04 23:39 被阅读0次

    1.Android IPC简介

    • IPC为进程间通讯,两个进程之间进行数据交换的过程。
    • IPC不是Android所独有的,任何一个操作系统都有对应的IPC机制。Windows上通过剪切板、管道、油槽等进行进程间通讯。Linux上通过命名空间、共享内容、信号量等进行进程间通讯。Android中没有完全继承于Linux,有特色的进程间通讯方式是Binder(基于BInder的Messenger和AIDL),还支持Socket。
    • 使用场景:由于某些原因应用自身需要采用多进程模式来实现,或者为了加大一个应用可使用的内存,因为Android对当个应用可使用的最大内存做了限制。

    2.Android中的多进程模式

    • 同一个应用,通过给四大组件指定android:process属性,就可以开启多进程模式。
    • 进程名以":"开头的属于当前应用的私有进程,其他应用的组件不可以和他跑在同一个进程里面。而进程名不以":"开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。两个应用可以通过ShareUID跑在同一个进程并且签名相同,他们可以共享data目录、组件信息、共享内存数据。
    • 多进程通讯的问题:
      1.静态成员和单例模式完全失效。
      2.线程同步机制完全失效。
      3.SharedPreferences的可靠性下降
      4.Application会多次创建

    问题1、2原因是因为进程不同,已经不是同一块内存了;
    问题3 是因为SharedPreferences不支持两个进程同时进行读写操作,有一定几率导致数据丢失;
    问题4 是当一个组件跑在一个新的进程中,系统会为他创建新的进程同时分配独立的虚拟机,所有这个过程其实就是启动一个应用的过程,,因此相当于系统又把这个应用重新启动了一遍,Application也是新建了。
    实现跨进程通讯有很多方式共享文件、SharedPreferences、基于Binder的Messenger和AIDL、Socket等。

    3. IPC基础概念介绍

    1.Serializable

    • serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID要和当前类的serialVersionUID相同才能正常的序列化,(当类结构改变不能反序列化)。
    • 静态成员变量属于类不属于对象,所以不会参加序列化过程;其次用transient关键字标明的成员变量也不参加序列化过程。
    • 重写如下两个方法可以重写系统默认的序列化和反序列化过程
    private void writeObject(java.io.ObjectOutputStream out)throws IOException{
    }
    private void readObject(java.io.ObjectInputStream out)throws IOException,ClassNotFoundException{
    }
    

    2.Parcable

    Serializable是Java中的序列化接口,需要大量的I/O操作,开销大,Parcable是Android的序列化方式,更适合Android,效率高,(如果涉及到持久化保存,建议使用Serializable,Parcable版本不同,可能会发生变化)。

    public class Book implements Parcelable {
     public static final Creator<Book> CREATOR = new Creator<Book>() {
         @Override
         public Book createFromParcel(Parcel in) {
             return new Book(in);
         }
         @Override
         public Book[] newArray(int size) {
             return new Book[size];
         }
     };
     public int code;
     public String name;
     public Book(int code, String name) {
         this.code = code;
         this.name = name;
     }
     private Book(Parcel in) {
         code = in.readInt();
         name = in.readString();
     }
     public int describeContents() {
         return 0;
     }
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(code);
         dest.writeString(name);
     }
    }
          序列化功能由writeToParcel方法来完成,最终通过Parcel中的一系列write方法完成的。  
          反序列化功能由CREATEOR来完成,其内部标明了如何创建序列号对象和诉诸,  
          并通过Parcel的一系列read方法来完成反序列化过程。内容描述功能由describeContents方法来完成  
        ,几乎所有情况都返回0,只有当前对象存在文件描述符时,才返回1。
    

    3.Binder(例子

    AIDL的本质是系统为我们提供了一个实现Binder的工具,仅此而已。
    源码分析见书

    1.从IPC的角度来说,Binder是Android的一种跨进程的通讯方式;Binder也可以理解为是一种虚拟额物理设备,他的设备驱动是/dev/binder;
    2.从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager、等等)和ManagerService的桥梁;
    3.从Android应用层来说,Binder是客户端与服务端通讯的媒介。在Android开发中,Binder主要用于Service中,包括AIDL和Messenger,其中普通的Service的Binder不涉及进程间通讯;而Messenger的底层其实就是AIDL。

    Binder死亡时重连服务

    1.设置死亡代理判断Binder是否死亡

    Binder的两个重要方法linkToDeath和unlinkToDeath。通过linkToDeath可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,然后就可以重新发起连接请求。声明一个DeathRecipient对象,DeathRecipient是一个接口,其内部只有一个方法binderDied,实现这个方法后就可以在Binder死亡的时候收到通知了。

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
    @Override
    public void binderDied(){
        if(mBookManager == null){
            return;
        }
        mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
        mBookManager = null;
        // TODO:接下来重新绑定远程Service
    }
    }
    

    在客户端绑定远程服务成功后,给Binder设置死亡代理

    mService = IBookManager.Stub.asInterface(binder);
    binder.linkToDeath(mDeathRecipient,0);
    

    2.在onServiceDisconnected重连远程服务

    区别:binderDied是在客户端的Binder线程中回调,不能访问UI,而onServiceDisconnected可以访问UI。

    源码示例
    方法介绍:

    1.DESCRIPTOR: Binder的唯一标识,一般用当前Binder的类名表示。
    2.asInterface(android.os.IBinder obj): 将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象;  
      如果客户端和服务端位于相同进程,那么此方法返回的就是服务端Stub对象本身,否则返回系统封装后的Stub.proxy对象。
    3.asBinder :用于返回当前的Binder对象
    4.onTransact 运行在服务端的Binder线程池中,当客户端发起跨进程通讯时,远程请求会通过系统底层封装交由此方法处理。
        public Boolean onTransact(int code,Parcelable data,Parcelable reply,int flags)
    服务端通过code确认客户端请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果有),然后执行目标方法。  
    当目标方法执行完后,向reply中写入返回值(如果有)。如果方法返回值为false,那么服务端的请求会失败,利用这个特性我们可以来做权限验证。
    

    4.Android中的IPC方式

    1.使用Bundle:

    由于Bundle实现了Parcelable接口,所以可以方便的在不同进程中传输;Activity、Service和Receiver都支持在Intent中传递Bundle数据。

    2.使用文件共享:

    两个进程通过读/写一个文件来交换数据;适合对数据同步要求性不高的场景;并要避免并发写这种场景或者处理好线程同步问题。SharedPreferences是个特例,虽然也是文件的一种,但系统在内存中有一份SharedPreferences文件的缓存,因此在多线程模式下,系统对他的读/写就变得不可靠,高并发读写SharedPreferences有一定几率会丢失数据,因此不建议在多进程通讯时采用SharedPreferences。

    3.使用Messenger代码示例

    Messenger是轻量级的IPC方案,底层实现是AIDL,他对AIDL进行了封装,Messenger 服务端是以串行的方式来处理客户端的请求的,不存在并发执行的情形。
    缺憾:无法并发,只适合传递消息,无法跨进程调用方法

    4.使用AIDL[代码示例]
    1.AIDL注意

    Messenger也是AIDL,系统做了封装。

    服务端首先创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在AIDL文件中声明,最后在Service中实现这个AIDL接口即可。客户端首先绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转化成AIDL接口所属的类型,调用相对应的AIDL中的方法。

    注意:

    1.AIDL支持的数据类型:
         - 基本数据类型;
         -  String、CharSequence;
         - List 只支持ArrayList,里面的元素必须都能被AIDL所支持;
         - Map 只支持HashMap,里面的元素(key和value)必须都能被AIDL所支持;
         -  Parcelable 所有实现了Parcelable接口的对象;
         - AIDL 所有AIDL接口本身也可以在AIDL文件中使用。
    2.自定义的Parcelable对象和AIDL对象必须显示的import进来(即使在同一个包)。
    3.除了基本数据类型,需要用inout表示输入输出型参数。
    4.为了方便AIDL开发,建议把所有和AIDL相关的类和文件都放在同一个包中,好处在于,当客户端是另一个应用的时候,我们可以直接把整个包复制到客户端工程中去。
    5.RemoteCallbackList是系统专门提供用于删除跨进程listener的接口,RemoteCallbackList是泛型,支持管理任意的AIDL接口,因为所有AIDL接口都继承自android.os.IInterface接口。
    6.需注意AIDL客户端发起RPC过程的时候,客户端的线程会挂起,如果是UI线程发起的RPC过程,如果服务端处理事件过长,就会导致ANR。
    

    2.设置远程服务权限

    方法1:在onBind中验证

    ①.在Manifest声明权限 
      <permission
            android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"
            android:protectionLevel="normal" />
    ②.在Service中的onBind中验证
        @Override
        public IBinder onBind(Intent intent) {
            int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
            Log.d(TAG, "onbind check=" + check);
            if (check == PackageManager.PERMISSION_DENIED) {
                return null;
            }
            return mBinder;
        }
    ③.在Manifest注册权限
    <uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" />
    

    方法2:在服务端的onTransact方法中进行权限验证

    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                    throws RemoteException {
                int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
                Log.d(TAG, "check=" + check);
                if (check == PackageManager.PERMISSION_DENIED) {
                    return false;
                }
    
                String packageName = null;
                String[] packages = getPackageManager().getPackagesForUid(
                        getCallingUid());
                if (packages != null && packages.length > 0) {
                    packageName = packages[0];
                }
                Log.d(TAG, "onTransact: " + packageName);
                if (!packageName.startsWith("com.ryg")) {
                    return false;
                }
    
                return super.onTransact(code, data, reply, flags);
            }
    
    3.AIDl接口实现观察者模式

    BookManagerService
    BookManagerActivity

    4.RemoteCallbackList删除跨进程listener

    ①.使用RemoteCallbackList代替CopyOnWriteArrayList

    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList =  
        new RemoteCallbackList<IOnNewBookArrivedListener>();
    

    ②.获取监听器对象

     final int N = mListenerList.beginBroadcast();
     mListenerList.finishBroadcast();//需要配套使用,哪怕是只获取一个
    
    5.注意:

    服务端本身可以做异步操作,客户端当访问耗时操作时,需要开启子线程
    同理,当服务端调用客户端的listener中的方法时,被调用的方法也运行在Binder线程池中,同样,需开启子线程

    5.使用ContentProvider

    ContentProvider底层实现也是Binder,专门用于不同应用间的进行数据共享的方式,ContentProvider主要是表格的形式来组织数据,并且可以包含多个表,同时还支持文件数据,图片,视频等。

    6.使用Socket

    网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket,

    7.使用Binder连接池

    当我们使用AIDL的使用,有很多业务模块需要用到AIDL,会创建很多服务,这个时候就需要用到BInder连接池,来管理这些AIDL,这个时候就只需要使用一个Service,根据不同的AIDL的code,去创建不同的AIDL即可。

    相关文章

      网友评论

          本文标题:Android艺术探索学习笔记:第2章 IPC机制

          本文链接:https://www.haomeiwen.com/subject/dlvlxxtx.html