Android-多进程通信(一)

一、简介

Android的多进程通信即IPC是指两个进程之间进行数据交换。进程一般指一个执行单元,在PC和移动设备中指一个程序或应用。最简单的情况下,Android应用中只有一个进程,包含一个线程,即主线程,也叫作UI线程,只能在此线程更新操作UI。普通情况下是不需要多进程的,但是当应用需要更多的内存或者某些特殊的Module或特殊的需求需要运行在多进程条件下。

在Android中多进程的方式有许多,Bundle、文件共享、Socket、Messenger、ContentProvider、AIDL。因为Android是Linux内核,所以文件的并发读写是被允许的,可写数据或将对象序列化写入文件,但是并没有提供什么便捷性,还容易出问题。Socket因为是端到端的通信,天然支持多进程,一个进程为服务端,一个进程作客户端。Messenger信使,可以在不同的进程传递Message对象,是一种轻量级的多进程方法。ContentProvider是Android提供的专门用于不同应用之间数据共享的方式,当然可用来进程间通信。系统也提供了很多内置的ContentProvider,通讯录、短信等。AIDL是Messenger的底层,实现相对复杂,但是能处理大量的并发请求及跨进程调用服务端的方法,Android将AIDL做了封装,便有了更方便上层调用的Messenger。

二、多进程模式

2.1 开启多进程

Android开启多进程的方式不多,正常情况只能在Manifest中给四大组件(Activity、Service、ContentProvider、Receiver)指定process属性。也有一个非常规的手段,通过JNI在native层去fork一个进程。以下是Activity的多进程示例,其他组件类似。:remotecom.example.package.remote表示此Activity运行在名为com.exmaple.package:remotecom.exmaple.package.remote的进程中。

<activity
    android:name=".activity.RemoteActivity2"
    android:label="Android多进程/.remote"
    android:process="com.example.package.remote" />
<activity
    android:name=".activity.RemoteActivity1"
    android:label="Android多进程/:remote"
    android:process=":remote" />

通过ADB命令查看

adb shell ps | findstr com.libo
image

可以看到有三个进程,其中以包名命名的进程是默认进程,.remote:remote是自定义的多进程,两个自定义进程之间的区别就是:是一种简写形式,实际是在:前附加上packagename;.是定义了完整的进程名,不会附加包名信息,显示com.libok.androidnote.remote是因为定义的名称如此。:开头的进程是属于当前应用的私有进程,其他应用的组件不能和它跑在同一个进程中;.进程名的是属于全局进程,其他应用可以通过ShareUID方式和它跑在同一个进程中。

2.2 多进程带来的问题

  1. 静态变量失效

    在一个Activity中新建一个静态变量TEST_STATIC,并在RemoteActivity1中的onStartOtherRemoteActivity方法中自增,之后启动RemoteActivity2,并在2中打印TEST_STATIC的值。

    public static int TEST_STATIC = 21;
    
    public void onStartOtherRemoteActivity(View view) {
        TEST_STATIC++;
        Log.e(TAG, "onStartOtherRemoteActivity: " + TEST_STATIC);
        startActivity(new Intent(this, RemoteActivity2.class));
    }
    

    结果:

    // RemoteActivity1 log
    E/RemoteActivity1: onStartOtherRemoteActivity: 22
        
    // RemoteActivity2 log
    E/RemoteActivity2: onCreate: 21
    

    并不相同的数值说明在多进程中静态变量是失效的,同样的因为静态变量带来的问题是单例模式的失效。原因就是多进程时Android为其他进程分配了一个新的虚拟机,导致不同的虚拟机在内存上有不同的内存地址, 当在新的进程访问变量时,访问的其实是这个类在新的虚拟机中的副本,也就是相当于在:remote和.remote中各有一个RemoteActivity1类,而.remote访问的那个副本中的TEST_STATIC是没有进行自增操作的,所以还是会打印出21的初始数值,而在:remote中是自增过的22。单例模式也是同样的解释,当在另一个进程中访问单例类时,在此进程中其实并没有进行初始化,所以才会失效。

  1. 线程同步机制失效

    本质上跟静态变量类似,在一个进程锁住的是副本的对象,而在另一个副本中,内存都不同,所以肯定是无效的。

  2. SharedPreferences可靠性下降

    因为SharedPreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失。SharedPreferences的底层是通过读写XML文件实现的,并发写很可能导致问题,并发读写都不能保证不会出问题。

  3. Application会被创建多次

    当一个组件跑在一个新的进程中时,系统给新的进程分配一个新的虚拟机,就相当于应用又一次的重新启动,Application作为应用基础肯定也会被重新创建。

    新建Application类,继承自Application,并在onCreate方法中输出当前进程的PID:

    public class LApplication extends Application {
    
        private static final String TAG = "LApplication";
        
        @Override
        public void onCreate() {
            super.onCreate();
            Log.e(TAG, "onCreate: " + android.os.Process.myPid());
        }
    }
    

    当依次开启进程后输出如下:

    // Main
    E/LApplication: onCreate: 16031
    // RemoteActivity1
    E/LApplication: onCreate: 16127
    // RemoteActivity2
    E/LApplication: onCreate: 16202
    

    Application被创建多次带来的问题是,有些时候会需要在Application中初始化些依赖,但是多进程就会随着Application的创建而重复初始化,可以在Application中设置一些条件跳过重复初始化部分。

    // 根据pid获取进程名
    private String getAppName(int pid) {
        String processName = null;
        ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> list = am.getRunningAppProcesses();
        for (ActivityManager.RunningAppProcessInfo info : list) {
            try {
                if (info.pid == pid) {
                    processName = info.processName;
                    return processName;
                }
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        return null;
    }
    

    通过PID获取进程名,与包名做对比,只有跟包名一致时才做一些初始化工作。

三、Binder

Binder是一个实现了IBinder接口的类。从IPC的角度来说,是一种跨进程通信方式,还可以理解为一种在Linux中没有的虚拟物理设备,设备驱动是/dev/binder;从Android Framework角度说,Binder是ServiceManager连接各种Manager和相应的ManagerService的桥梁;从Android应用层来说,Binder是服务端和客户端进行通信的媒介,当bindService时,服务端会返回一个包含服务端调用的Binder对象,通过这个对象,客户端就可以获取服务端提供的服务或信息数据,这里的服务包括普通服务和基于AIDL的服务。

3.1 初识Binder

Binder主要用在Service中,包括AIDL和Messenger,普通的Service中的Binder不涉及进程间的通信,新建一个AIDL的示例如下。

  1. 在项目目录下,新建aidl文件夹,并新建实现Parcelable接口的Book类。

    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];
            }
        };
    
        private int mBookId;
        private String mBookName;
    
        public Book(int bookId, String bookName) {
            mBookId = bookId;
            mBookName = bookName;
        }
    
        private Book(Parcel in) {
            mBookId = in.readInt();
            mBookName = in.readString();
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(mBookId);
            dest.writeString(mBookName);
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        public int getBookId() {
            return mBookId;
        }
    
        public void setBookId(int bookId) {
            mBookId = bookId;
        }
    
        public String getBookName() {
            return mBookName;
        }
    
        public void setBookName(String bookName) {
            mBookName = bookName;
        }
    
        @Override
        public String toString() {
            return "Book{" +
                    "mBookId=" + mBookId +
                    ", mBookName='" + mBookName + '\'' +
                    '}';
        }
    }
    
    image-20200305102234931
  1. 在项目的main路径下,新建aidl文件夹及子目录com.example.name.aidl,并在其中创建IBookManager.aidl,如果是在AndroidStudio中,就省去了创建目录的操作,直接在main->java->aidl下右键new->new AIDL File创建IBookManager.aidl就会自动创建到main->aidl->com.example.name.aidl目录下。
    image-20200305102541442
![image-20200305102917196](https://upload-images.jianshu.io/upload_images/8533197-5e7adadf18ab8921.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

```java
// IBookManager.aidl
package com.libok.androidnote.aidl;

// Declare any non-default types here with import statements

import com.libok.androidnote.aidl.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}
```

其中getBookList方法是获取所有的Book,addBook方法是添加一个Book。addBook的参数中有个关键字in,表示数据是从客户端传递到服务端,相应的还会有out以及inout,这个等后面的IPC方式时再做探讨。
  1. 有了aidl在main路径下的目录后,再创建一个Book.aidl,IBookManager.aidl中也有import Book类,但是引入的不是Book.java而是Book.aidl,之所以后建Book.aidl,是因为需要先创建IBookManager.aidl所自动创建的路径。

    package com.libok.androidnote.aidl;
    
    parcelable Book;
    
  1. 等Java类、类对应AIDL、业务数据处理AIDL三个文件(根据自身需要做调整)建好后执行AndroidStudio->Build->Make Project即可自动生成IBookManager.java。AndroidStudio3.6.1+gradle3.6.1目录如下:

    image-20200305110258535
```java
/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.libok.androidnote.aidl;

public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.libok.androidnote.aidl.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.libok.androidnote.aidl.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.libok.androidnote.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.libok.androidnote.aidl.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.libok.androidnote.aidl.IBookManager))) {
                return ((com.libok.androidnote.aidl.IBookManager) iin);
            }
            return new com.libok.androidnote.aidl.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(descriptor);
                    java.util.List<com.libok.androidnote.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(descriptor);
                    com.libok.androidnote.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.libok.androidnote.aidl.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.libok.androidnote.aidl.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.util.List<com.libok.androidnote.aidl.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.libok.androidnote.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.libok.androidnote.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.libok.androidnote.aidl.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public java.util.List<com.libok.androidnote.aidl.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.libok.androidnote.aidl.Book book) throws android.os.RemoteException;
}
```

至此Binder就已经创建完毕,接下来各方面解析一下自动生成的IBookManager.java。

3.2 再识Binder

IBookManager类乍看有点乱,其实还是比较清晰的。

image-20200305111355036

IBookManager接口继承自android.os.IInterface,并在内部实现一个静态抽象内部类Stub,除此之外还声明了两个IBookManager.aidl中定义的getBookList()方法和addBook(com.libok.androidnote.aidl.Book book)方法。重点关注的应该是其内部类Stub。

Stub内部解析

image-20200305121201924
  • Stub类

    1.Stub继承了android.os.Binder并实现了IBookManager接口,继承Binder重写了onTransact方法,使其能处理自定的业务和数据。

    2.实现了IBookManager接口的继承方法asBinder。

  • DESCRIPTOR

    Binder唯一标识,通常用当前Binder的类名表示,防止冲突。

  • TRANSACTION_getBookList和TRANSACTION_addBook

    标识声明的方法,在方法onTransact中区分远程调用的方法是什么。范围在IBinder.FIRST_CALL_TRANSACTION到IBinder.LAST_CALL_TRANSACTION之间。

  • asInterface方法

    用于将服务端的Binder对象转换成客户端所需要的AIDL接口类型的对象,如果客户端和服务端在同一进程,则返回的是服务端的Stub对象本身,不在同一进程,返回的就是系统封装后的Stub.Proxy对象。

  • asBinder方法

    返回当前Binder对象。

  • onTransact方法

    此方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程会通过系统底层封装后交由此方法处理。

    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    

    服务端通过code区分被调用的方法,从data中取出方法所需的参数,如果有参数的话,然后执行方法。当方法执行完毕,通过reply写入返回值,如果有返回值的话。返回值表示客户端是否请求成功,如果返回false,那么客户端的请求会失败,可用来作权限验证,是否是准许的客户端调用而不是其他的任意客户端。

  • Proxy类

    代理类,当客户端和服务端不在同一进程时会被从asInterface方法中创建,客户端通过调用Proxy的方法实现调用服务端的方法。

Proxy内部解析

image-20200305122125185
  • getBookList方法

    方法运行在客户端,当客户端远程调用此方法时,内部过程是这样的:首先创建输入型Parcel对象_data、输出型Parcel对象_reply以及返回值对象List;接着把方法的参数写进_data中(如果有参数的话),调用transact方法发起RPC(远程过程调用)请求,同时将当前线程挂起;然后服务端的onTransact方法会被调用,将远程方法结果写入_reply中(如果有结果的话),并返回请求结果true或false,直到RPC过程返回后,当前线程才会继续执行,并从_reply中获取RPC过程的方法结果;最后返回_reply中的数据。

  • addBook方法

    调用过程与上一个方法一样,只是多了参数,少了返回值。

总结

  1. 因为在客户端发起请求后,所在的线程会被挂起直到服务端进程返回结果,所以如果一个远程方法是耗时的,那么不能在UI线程中发起请求,会导致不必要的ANR。
  2. 由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式实现,因为它已经运行在一个线程中了。
graph LR
A[Client]-->|远程请求 挂起Client|B[Binder]
B-->|写入参数|C(data)
C-->|Transact|D[Service]
D-->|onTransact|E(线程池)
E-->|写入结果|F(reply)
F-->B
B-->|返回数据 唤醒Client|A

3.3 手写Binder

public interface IMyBookManager extends IInterface {

    public static final String TAG = "IMyBookManager";

    public static abstract class Stub extends Binder implements IMyBookManager {
        private static final String DESCRIPTOR = "com.libok.androidnote.core.IMyBookManager";
        private static final int FUNCTION_GET_BOOK_LIST = FIRST_CALL_TRANSACTION + 1;
        private static final int FUNCTION_ADD_BOOK = FIRST_CALL_TRANSACTION + 2;

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static IMyBookManager asInterface(IBinder binder) {
            if (binder == null) {
                return null;
            }
            IInterface iInterface = binder.queryLocalInterface(DESCRIPTOR);
            if (iInterface instanceof IMyBookManager) {
                Log.e(TAG, "asInterface: local interface");
                return (IMyBookManager) iInterface;
            }
            Log.e(TAG, "asInterface: proxy interface");
            return new Proxy(binder);
        }

        @Override
        public IBinder asBinder() {
            return this;
        }

        @Override
        protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION:
                    if (reply != null) {
                        reply.writeString(DESCRIPTOR);
                    }
                    return true;
                case FUNCTION_GET_BOOK_LIST:
                    data.enforceInterface(DESCRIPTOR);
                    List<Book> bookList = this.getBookList();
                    if (reply != null) {
                        reply.writeNoException();
                       // reply.writeException(new NullPointerException("Test Proxy reply.readException"));
                        reply.writeTypedList(bookList);
                    }
                    return true;
                case FUNCTION_ADD_BOOK:
                    data.enforceInterface(DESCRIPTOR);
                    Book book = null;
                    if (data.readInt() != 0) {
                        book = Book.CREATOR.createFromParcel(data);
                    }
                    this.addBook(book);
                    if (reply != null) {
                        reply.writeNoException();
                    }
                    return true;
                default:
                    return super.onTransact(code, data, reply, flags);
            }
        }

        public static class Proxy implements IMyBookManager {

            private IBinder mBinder;

            public Proxy(IBinder binder) {
                mBinder = binder;
            }

            @Override
            public IBinder asBinder() {
                return mBinder;
            }

            public String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public List<Book> getBookList() throws RemoteException {
                Parcel data = Parcel.obtain();
                Parcel reply = Parcel.obtain();
                List<Book> result = null;

                try {
                    data.writeInterfaceToken(DESCRIPTOR);
                    // data.writeInterfaceToken(DESCRIPTOR + "1");
                    mBinder.transact(FUNCTION_GET_BOOK_LIST, data, reply, 0);
                    reply.readException();
                    result = reply.createTypedArrayList(Book.CREATOR);
                } finally {
                    data.recycle();
                    reply.recycle();
                }
                return result;
            }

            @Override
            public void addBook(Book book) throws RemoteException {
                Parcel data = Parcel.obtain();
                Parcel reply = Parcel.obtain();

                try {
                    data.writeInterfaceToken(DESCRIPTOR);
                    if (book != null) {
                        data.writeInt(1);
                        book.writeToParcel(data, 0);
                    } else {
                        data.writeInt(0);
                    }
                    mBinder.transact(FUNCTION_ADD_BOOK, data, reply, 0);
                } finally {
                    data.recycle();
                    reply.recycle();
                }
            }
        }

    }

    public List<Book> getBookList() throws RemoteException;

    public void addBook(Book book) throws RemoteException;
}

纸上得来终觉浅,绝知此事要躬行。

不手动写一遍还体会不到这个流程到底是怎样的,不知道Stub中onTransact方法执行时首先需要data参数执行一个enforceInterface方法,需要检验一下client和service执行的是不是同一个方法,没写到Proxy类时,还在纳闷这个方法到底是什么作用,源码中也没有作什么解释,在Stack Overflow上搜加写完Proxy类后才知道是一个检验方法,在Proxy类中执行接口的方法第一步就是先在data执行writeInterfaceToken,这个方法有注释是这样写的:

/**
 * Store or read an IBinder interface token in the parcel at the current
 * {@link #dataPosition}.  This is used to validate that the marshalled
 * transaction is intended for the target interface.
 */

enforceInterface是一对函数,这才恍然大悟,这也就是自己手动写一遍的好处。

在写的时候慢慢的有了更深的认识,当然再深都只是应用层的东西,想要更了解可以去看底层源码。

Stub中的DESCRIPTOR、FUNCTION_GET_BOOK_LIST以及FUNCTION_ADD_BOOK没什么好说的了。

  • Stub要继承Binder,这是必然的,如果不继承那么就没多进程通信什么事了。
  • 还要实现IMyBookManager接口,当然这也是必然的,不然到了服务端都没有业务方法可执行。
  • Stub的构造函数,只有一句this.attachInterface(this, DESCRIPTOR);但是却关系着服务端运行在与客户端相同或不同的进程时asInterface方法返回的Binder,也就是绑定Service时返回的Binder。只有在Binder中绑定Interface才会在Binder的queryLocalInterface方法查询到,才能在不同的环境下返回正确的Binder。
  • asInterface静态函数,返回相应的继承了相应接口的对象,在客户端和服务端相同进程时返回Stub构造函数中注册的本身,在不同进程时返回代理。其中首先参数binder执行queryLocalInterface方法,查询是否存在跟调用端也就是客户端同一进程的接口对象。参数binder从何而来,在bindService时需要的ServiceConnection中的onServiceConnected方法参数,在此方法的Binder肯定不会查询到接口对象,所以才会创建一个Proxy代理对象。Proxy同样也是运行在客户端进程的。
  • asBinder方法没啥可讲的,不管是同进程还是不同进程,都是返回服务端的Binder。
  • 先说Proxy类再说Stub的onTransact方法,Proxy类是要实现IMyBookManager接口的,这也是必然,既然叫Proxy就得有代理的地方,也就是实现接口供客户端调用方法,继而在方法中完成跨进程的方法调用。
  • Proxy类的构造函数接收一个Binder对象,此Binder对象是Stub的asInterface方法中接收的Binder对象,同样也是onServiceConnected的方法参数。
  • Proxy实现的接口方法中创建Parcel对象data和reply,需要注意的是Parcel对象的获得及回收,因为Parcel类是有一个静态的Parcel数组作Parcel池,要正确的维护此ParcelPool。其中data存放的是需要执行的方法参数,reply存放的是方法的执行结果。在执行远程方法执行请求之前要先在data中写入一个token,以便在onTransact方法中作检验,之后执行远程方法的执行请求——Binder的transact方法,此方法需要的参数是执行的远程方法的标识、data、reply,及一个flags,方法注释中描述常规的远程调用默认是0,如果是单向调用那么flags需要填写FLAG_ONEWAY,此标记的意思是Proxy发送远程方法的调用请求后立即返回,不再等远程方法的执行结果,看情况选择哪一个flags。

    /**
     * Flag to {@link #transact}: this is a one-way call, meaning that the
     * caller returns immediately, without waiting for a result from the
     * callee. Applies only if the caller and callee are in different
     * processes.
     */
    

    之后一步是执行reply的readException方法读取一下方法执行是否有异常,异常的写入同样是在远程的onTransact方法中,如果有异常会在此步抛出异常。如果没有异常那么就可以从reply中读取正确的返回值,之后将Parcel对象data和reply回收,最后将返回值返回到调用方。

  • asBinder方法没啥可讲的,返回服务端的Binder。
  • Stub的onTransact方法,服务端具体执行接口方法的地方

    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException
    

    方法参数code标明Proxy想要调用的是哪个方法,data保存方法参数,reply保存方法执行结果。执行自定义方法时先执行data的enforceInterface,正常来说是没问题的,但可以通过人为的操作让其不正常,见手写示例的Proxy类中的注释语句,如果检验不成功的话是会报以下异常。

    Caused by: java.lang.SecurityException: Binder invocation to an incorrect interface
    at android.os.Parcel.createException(Parcel.java:2071)
    at android.os.Parcel.readException(Parcel.java:2039)
    at android.os.Parcel.readException(Parcel.java:1987)
    at com.libok.androidnote.core.IMyBookManagerStubProxy.getBookList(IMyBookManager.java:111)
    at com.libok.androidnote.activity.IPCActivity.onGetBookList(IPCActivity.java:65)

    之后就是调用远程方法,远程方法的实现在Service中创建Binder时实现的方法,此时就已经是真正自己的业务逻辑位置。如果方法执行没问题就可以在reply中写入NoException,要是有异常可以执行reply的writeException方法,将异常写入reply,需要记得的是writeException方法和readException方法得成对出现。故意在其中写入一个NullPointerException,见手写示例的Stub类中的注释语句,会在Proxy中执行readException时抛出以下异常。

    Caused by: java.lang.NullPointerException: Test Proxy reply.readException
    at android.os.Parcel.createException(Parcel.java:2077)
    at android.os.Parcel.readException(Parcel.java:2039)
    at android.os.Parcel.readException(Parcel.java:1987)
    at com.libok.androidnote.core.IMyBookManagerStubProxy.getBookList(IMyBookManager.java:111)
    at com.libok.androidnote.activity.IPCActivity.onGetBookList(IPCActivity.java:65)

    之后再将方法执行结果写入reply中。onTransact方法是具有返回值的,返回true表示远程方法调用成功,false则失败,可以用此返回值做一些其他的检验,比如数据校验等,不正常可直接返回false终止调用。

下面附上测试代码及一点说明。

IPCActivity作为本地客户端,布局就不列出了,只有两个Button,其中注释的代码是用的通过AIDL来自动创建的IBookManager.java。

public class IPCActivity extends AppCompatActivity {

    private static final String TAG = "IPCActivity";

    private static final int BOOK_START_ID = 7873;

    private int mBookId = BOOK_START_ID;
//    private IBookManager mIBookManager = null;
    private IMyBookManager mOwnBookManager = null;

    private ServiceConnection mBookServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
//            mIBookManager = IBookManager.Stub.asInterface(service);
            mOwnBookManager = IMyBookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ipc);
        Intent remoteBookServiceIntent = new Intent(this, BookManagerService.class);
        bindService(remoteBookServiceIntent, mBookServiceConnection, Context.BIND_AUTO_CREATE);
    }

    public void onGetBookList(View view) {
//        if (mIBookManager != null) {
//            try {
//                List<Book> bookList = mIBookManager.getBookList();
//                Log.e(TAG, "onGetBookList: " + bookList.toString());
//            } catch (RemoteException e) {
//                e.printStackTrace();
//            }
//        }
        if (mOwnBookManager != null) {
            try {
                List<Book> bookList = mOwnBookManager.getBookList();
                Log.e(TAG, "onGetBookList: " + bookList.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    public void onAddBookToList(View view) {
//        if (mIBookManager != null) {
//            try {
//                mBookId++;
//                mIBookManager.addBook(new Book(mBookId, "Added Book" + mBookId));
//            } catch (RemoteException e) {
//                e.printStackTrace();
//            }
//        }
        if (mOwnBookManager != null) {
            try {
                mBookId++;
                mOwnBookManager.addBook(new Book(mBookId, "Own Added Book" + mBookId));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mBookServiceConnection);
    }
}

BookManagerService作服务端,并在Manifest中设置Service的Process属性让其运行在另一个进程。

public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() {
            Log.e(TAG, "getBookList: " + mBookList.size());
            return mBookList;
        }

        @Override
        public void addBook(Book book) {
            Log.e(TAG, "addBook: " + book.toString());
            mBookList.add(book);
        }
    };

    private Binder mOwnBinder = new IMyBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    public BookManagerService() {
        mBookList.add(new Book(123, "Love & Peace"));
        mBookList.add(new Book(321, "Play Boy"));
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
//        throw new UnsupportedOperationException("Not yet implemented");
        Log.e(TAG, "onBind: " + (mOwnBinder.queryLocalInterface("com.libok.androidnote.core.IMyBookManager") == null) + mOwnBinder.toString());
        return mOwnBinder;
    }
}

在服务端创建IMyBookManager的内部类Stub的对象mBinder,mBinder不仅是个Binder对象还是个IMyBookManager对象,并需要实现IMyBookManager接口的所有方法,即服务端业务逻辑处理。在Service被绑定时返回mBinder对象供客户端中的服务会话ServiceConnection调用。ServiceConnection中onServiceConnected方法的参数IBinder service与mBinder是有不同点的,在Stub中能否通过DESCRIPTOR找到对应的接口对象,只有通过创建的Binder才会被注册。

当服务端不是另一个进程时,流程就简单了,只是继承的Binder类跑到了IMyBookManager中,在绑定Service时onBind方法返回的和ServiceConnection中onServiceConnected方法参数是同一个Binder,而获取接口对象的asInterface方法会直接返回找到的IInterface对象,即在构造函数中绑定的Binder this,然后跟Proxy类就没什么关系了。

graph TB
A[客户端绑定Service]-->|多|B[Service启动]
A-->|单|B
B-->C[创建Service Binder即Stub并实现IInterface方法]
C-->|注册|D[attachInterface]
D-->|binder queryLocalInterface|E{是否是本进程的Binder对象}
E-->|否|F[返回新的内部实现接口的Proxy代理类对象]
E-->|是|G[返回查询到的接口对象, 还是服务端的Stub对象]
F-->|客户端进程|H[客户端执行接口方法]
H-->|客户端进程|I[Proxy执行接口方法]
I-->|服务端进程, 输入code data reply|J[Stub执行标识对应方法有结果则返回]
J-->|客户端进程|K[Proxy返回结果]
K-->|客户端进程|L[客户端接收结果]

C-->|注册|D
G-->|客户端进程|H
H-->|客户端进程|M[Stub对象执行接口方法]
M-->|客户端进程|L

3.4 Binder死亡代理

Service不管是运行在另一个进程还是跟客户端同进程,都可能会碰到被杀死的情况,此时Binder不能正常工作,导致调用失败,当然Binder在设计是也是充分考虑到了这种情况,所以Binder中有一对很重要方法linkToDeath和unlinkToDeath可以做一些针对性的工作。

首先得声明一个DeathRecipient对象,实现其binderDied方法,当代理的Binder死亡是会调用此方法,我们可以在此方法中作重新绑定Service等操作。

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        Log.e(TAG, "binderDied: ");
        if (mOwnBookManager == null) {
            return;
        }
        mOwnBookManager.asBinder().unlinkToDeath(this, 0);
        mOwnBookManager = null;
        bindService(mRemoteBookServiceIntent, mBookServiceConnection, Context.BIND_AUTO_CREATE);
        // 其他操作
    }
};

在ServiceConnection中的onServiceConnected方法绑定死亡代理,即客户端绑定远程服务成功后,等Binder死亡时就可以收到通知并会执行既定方法。

private ServiceConnection mBookServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
          mIBookManager = IBookManager.Stub.asInterface(service);
        Log.e(TAG, "onServiceConnected: " + service.toString());
        mOwnBookManager = IMyBookManager.Stub.asInterface(service);
        try {
            service.linkToDeath(mDeathRecipient, 0);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.e(TAG, "onServiceDisconnected: ");
    }
};

除此之外还可以通过Binder的isBinderAlive方法判断Binder是否死亡。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352