Android-多进程

Android 应用内多进程实现 单APK应用多进程 Android平台支持多进程通信,也支持应用内实现多进程,Android中,默认一个APK包就对应一个进程,其进程名就为AndroidManifest.xml文件中 指定的package名。我们可以通过Activity,Service,BroadCastReceiver,ContentProvider的android:process属性来实现单APK多进程,但是需要注意进程间内存的不可见性。 阅读详情

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的多进程示例,其他组件类似。:remote和com.example.package.remote表示此Activity运行在名为com.exmaple.package:remote和com.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

可以看到有三个进程,其中以包名命名的进程是默认进程,.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。单例模式也是同样的解释,当在另一个进程中访问单例类时,在此进程中其实并没有进行初始化,所以才会失效。

  2. 线程同步机制失效

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

  3. SharedPreferences可靠性下降

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

  4. 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

  2. 在项目的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

    // 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方式时再做探讨。

  3. 有了aidl在main路径下的目录后,再创建一个Book.aidl,IBookManager.aidl中也有import Book类,但是引入的不是Book.java而是Book.aidl,之所以后建Book.aidl,是因为需要先创建IBookManager.aidl所自动创建的路径。

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

    image-20200305110258535

    /*
     * 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方法不管是否耗时都应该采用同步的方式实现,因为它已经运行在一个线程中了。
远程请求 挂起Client
写入参数
Transact
onTransact
写入结果
返回数据 唤醒Client
Client
Binder
data
Service
线程池
reply

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.IMyBookManager S t u b Stub StubProxy.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.IMyBookManager S t u b Stub StubProxy.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类就没什么关系了。

多
单
注册
binder queryLocalInterface
否
是
客户端进程
客户端进程
服务端进程, 输入code data reply
客户端进程
客户端进程
注册
客户端进程
客户端进程
客户端进程
客户端绑定Service
Service启动
创建Service Binder即Stub并实现IInterface方法
attachInterface
是否是本进程的Binder对象
返回新的内部实现接口的Proxy代理类对象
返回查询到的接口对象, 还是服务端的Stub对象
客户端执行接口方法
Proxy执行接口方法
Stub执行标识对应方法有结果则返回
Proxy返回结果
客户端接收结果
Stub对象执行接口方法

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是否死亡。

四、IPC方式

4.1 Bundle

Android四大组件中的三大组件Activity、Service、Receiver都是支持在Intent中传递Bundle数据的,Bundle也实现了Parcelable接口,所以可以方便的在不同的进程间传输。当一个进程通过Intent启动另一个进程的三大组件时,就可以在Bundle中附加所需要传输的信息通过Intent传输。显然,传输的信息必然得是能够被序列化的,不管是实现的Serializable接口还是Parcelable接口。

除启动三大组件时直接传递数据外,还有一种特别的使用场景。比如A进程启动B进程的一个组件时时需要将一个数据传递过去,但是此数据并不支持放入Bundle中,也就不能通过Intent来传输,而采用其他的IPC方式会略显复杂,此时就可以考虑通过放入数据获取的必要条件的Intent并启动B进程的一个Service组件,(比如IntentService,可在执行完毕后自行销毁)。然后让Service在后台对数据进行获取,获取完毕后再启动真正想要启动的B进程组件,因为Service也同样是运行在B进程的,数据可以直接传递,此操作也就解决了跨进程的问题。该方法的核心在于将数据获取的步骤转移到另一进程进行,只传递必要条件,如果必要条件也是不支持放入Bundle的,那么再往前找必要条件,但是再这么找下去反而是适得其反了,可以考虑下其他的IPC方式。

4.2 文件共享

文件共享也是一种不错的进程间通信方式,两个进程通过读/写,A进程把数据写入文件,B进程从文件读取数据。因为Android基于Linux,所以并发读/写是没有限制的,甚至同时两个线程对同一个文件进行写操作都是允许的,当然是很容易出问题的。下面的例子展示两个进程对文件的读/写达到传递数据的目的。

数据传递方

public void onIPCFileMode(View view) {
    SerializableBean bean = new SerializableBean();
    new Thread(new Runnable() {
        @Override
        public void run() {
            String dataSavePath = getCacheDir().getPath() + File.separator + "ipc_data_save";
            File dataFile = new File(dataSavePath);
            ObjectOutputStream objectOutputStream = null;
            try {
                objectOutputStream = new ObjectOutputStream(new FileOutputStream(dataFile));
                objectOutputStream.writeObject(bean);
                objectOutputStream.flush();
                startActivity(new Intent(RemoteActivity1.this, RemoteActivity2.class));
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (objectOutputStream != null) {
                    try {
                        objectOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }).start();
}

数据接收方

private void onIPCFileMode() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            String dataSavePath = getCacheDir().getPath() + File.separator + "ipc_data_save";
            File dataFile = new File(dataSavePath);
            if (!dataFile.exists()) {
                return;
            }
            ObjectInputStream objectInputStream = null;
            Object result = null;
            try {
                objectInputStream = new ObjectInputStream(new FileInputStream(dataFile));
                result = objectInputStream.readObject();
                if (result instanceof SerializableBean) {
                    Log.e(TAG, "onIPCFileMode run: " + result.toString());
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (objectInputStream != null) {
                    try {
                        objectInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }).start();
}

可以在接收方进程看到这样的log,表明数据传递成功。

E/RemoteActivity2: onIPCFileMode run: SerializableBean{mString=‘ABC’, mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}

通过这种方式共享数据对文件格式是没有具体要求的,可以文本文件,也可以是XML文件等,只要读/写双方约定数据格式即可。文件共享的方式也是有局限性的,比如并发读/写,如果并发读/写,那么读出的数据有可能不是祖新的,如果是并发写那就更完蛋了。尽量避免此情况发生或者使用线程同步限制多个线程的写操作。

既然任何文件格式都可以,那么SharedPreferences应该也是可行的。SharedPreferences是Android提供的轻量级的存储方案,通过键值对的方式存储数据,在底层是XML文件来存储键值对,理论上是完全支持的。但是由于系统对它的读/写有一定的缓存策略,即在内存中也会有一份SharedPreferences文件的缓存,因此在多进程模式下系统对其读/写就变得不可靠,当面对高并发的读/写访问时,SharedPreferences有很大几率丢失数据,因此不适合在进程间通信用SharedPreferences。SharedPreferences中是有多进程MODE的MODE_MULTI_PROCESS,但现在已经被弃用了,在Android-SharedPreferences中会有所阐述。

4.3 Messenger

介绍

Messenger轻量级的多进程通信方法,可以通过它在不同的进程中传递Message对象,而需要传递的数据就可以放在Message中。其底层实现是AIDL,可以在Messenger的构造方法中发现,与章节三中的AIDL方式一模一样。

public Messenger(Handler target) {
    mTarget = target.getIMessenger();
}

public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}

Messenger的使用方法也很简单,对AIDL作了封装使得更简单了。尤其是Messenger一次只能处理一个请求,不能并发请求,也不需要作线程同步的考虑。

使用

  1. 服务端

    在Service中创建一个继承Handler的内部类MessengerHandler,此类的作用就是处理从客户端传过来的消息,以Handler作参数创建一个Messenger,并在Service的onBind方法返回此Messenger的Binder供客户端构建Messenger使用。服务端和客户端的信息传递是用Message对象作载体,在MessengerHandler中的handleMessage方法进行处理。

    Message是肯定实现了Parcelable接口的,作为载体,Message能支持的对象传输类型,Messenger肯定也是能支持的,包括能被序列化的对象及基本类型。Message中有几个属性可以携带信息,what、arg1、arg2、Bundle、replyTo、object,其中what、arg1、arg2都是int类型可携带一些标识符啥的;replyTo是Messenger类型作用在完成一次客户端到服务端的信息传递后需要将信息再回传客户端,在让客户端将Message封装时附带上客户端的Messenger;Bundle是主要的信息传输,毕竟支持序列化对象及其他对象;object对象在跨进程传输时作用不是很大,因为object不能传递自定义的序列化对象,不光是Parcelable对象,传输Serializable对象也会导致java.lang.RuntimeException: Can't marshal non-Parcelable objects across processes.这样的异常。

    服务端代码如下:

    public class MessengerService extends Service {
    
        private static final String TAG = "MessengerService";
    
        public static final int OPERATION_GET_BOOK_LIST = 1;
        public static final int OPERATION_ADD_BOOK = 2;
    
        private ArrayList<Book> mBookList = new ArrayList<>();
        private Messenger mMessenger = new Messenger(new MessengerHandler(this));
    
        public MessengerService() {
            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");
            return mMessenger.getBinder();
        }
    
        private static class MessengerHandler extends Handler {
    
            private WeakReference<MessengerService> mWeakReference;
    
            MessengerHandler(MessengerService service) {
                mWeakReference = new WeakReference<>(service);
            }
    
            @Override
            public void handleMessage(Message msg) {
                Log.e(TAG, "handleMessage: " + msg.what);
                switch (msg.what) {
                    case OPERATION_GET_BOOK_LIST:
                        if (mWeakReference.get() != null) {
                            Bundle data = new Bundle();
                            data.putParcelableArrayList("result_get_list", mWeakReference.get().mBookList);
    
                            Message message = Message.obtain();
                            message.what = IPCActivity.RESULT_MESSENGER_BOOK_LIST;
                            message.setData(data);
    
                            if (msg.replyTo != null) {
                                try {
                                    msg.replyTo.send(message);
                                } catch (RemoteException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                        break;
                    case OPERATION_ADD_BOOK:
                        if (mWeakReference.get() != null) {
                            Bundle data = msg.getData();
                            data.setClassLoader(getClass().getClassLoader());
                            Parcelable parcelable = data.getParcelable("add_book");
                            if (parcelable instanceof Book) {
                                mWeakReference.get().mBookList.add((Book) parcelable);
                            }
                        }
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        }
    
    }
    
  2. 客户端

    跟服务端一样,同样创建一个Handler,并作为参数传递创建一个客户端Messenger,如果客户端传递给服务端消息后需要有回执的话就将此Messenger设置在Message的replyTo属性。其他的就是正常的绑定Service,并在onServiceConnected方法中创建一个客户端持有的服务端Messenger,用来向服务端传递Message。

    传递就很简单了,不管是示例中onGetBookListByMessenger或onAddBookToListByMessenger方法都是先获取一个Message,然后塞入需要传递的对象,有回执需求就配置上replyTo,传递完就在Handler中等服务端回传的Message。有一个非常需要注意的点是从Bundle中获取自定义Parcelable对象时需要对Bundle设置一次ClassLoader,不然会找不到自定义对象报此异常android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.libok.androidnote.aidl.Book,同样的服务端的也是如此。

    客户端代码如下:

    public class IPCActivity extends AppCompatActivity {
    
        private static final String TAG = "IPCActivity";
        
        public static final int RESULT_MESSENGER_BOOK_LIST = 9876;
    
        private Messenger mServerMessenger = null;
        private ServiceConnection mMessengerConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mServerMessenger = new Messenger(service);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                mServerMessenger = null;
            }
        };
    
        private Handler mMessengerHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case RESULT_MESSENGER_BOOK_LIST:
                        Bundle data = msg.getData();
                        Log.e(TAG, "handleMessage: get class loader:" + data.getClassLoader());
                        data.setClassLoader(getClassLoader());
                        ArrayList<Parcelable> result = data.getParcelableArrayList("result_get_list");
    
                        Log.e(TAG, "handleMessage: RESULT_MESSENGER_BOOK_LIST " + result.toString());
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };
        private Messenger mClientMessenger = new Messenger(mMessengerHandler);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_ipc);
        }
    
        @Override
        protected void onDestroy() {
            unbindService(mMessengerConnection);
            super.onDestroy();
        }
    
        /**
         * 绑定Messenger方式的Service
         */
        public void onBindMessengerService(View view) {
            Intent messengerServiceIntent = new Intent(this, MessengerService.class);
            bindService(messengerServiceIntent, mMessengerConnection, BIND_AUTO_CREATE);
        }
    
        /**
         * 通过Messenger添加书本
         */
        public void onAddBookToListByMessenger(View view) {
            if (mServerMessenger != null) {
                Book book = new Book(158, "声微饭否");
    
                Bundle data = new Bundle();
                data.putParcelable("add_book", book);
    
                Message addBookMessage = Message.obtain();
                addBookMessage.what = MessengerService.OPERATION_ADD_BOOK;
                addBookMessage.setData(data);
    
                try {
                    mServerMessenger.send(addBookMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 通过Messenger获取所有的书本
         */
        public void onGetBookListByMessenger(View view) {
            if (mServerMessenger != null) {
                Message getListMessage = Message.obtain();
                getListMessage.what = MessengerService.OPERATION_GET_BOOK_LIST;
                getListMessage.replyTo = mClientMessenger;
    
                try {
                    mServerMessenger.send(getListMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
  3. 总结

4.4 AIDL

在Binder章节中已经接触过AIDL,简单的接口,简单的调用,但是其实已经讲述了AIDL的基本用法,此节中除了再回顾一下基本操作还有些新东西。

AIDL简单介绍

依然是分为客户端和服务端两个部分:

服务端

创建一个Service来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在此文件中声明,最后在Service中实现AIDL接口。

客户端

需要先绑定服务端Service,在绑定成功后将服务端返回的Binder对象转成AIDL接口所属的类型,然后就能调用AIDL中的方法了。

具体使用上需要三个步骤,创建AIDL接口、实现远程服务端Service、实现客户端。

  1. 创建AIDL接口

    AIDL的创建在Binder中已经操作过,在此不再赘述,可以直接用以前的例子,这里着重讲一下AIDL本身的一些知识点。

    在AIDL中不是所有的数据类型都能用的,仅仅是以下的类型可以在AIDL中使用,包括:

    • 基本数据类型(int、long、char、Boolean、double等)
    • String和CharSequence
    • List只支持ArrayList,并且内部的所有元素都能被AIDL支持
    • Map只支持HashMap,并且内部的所有元素都能被AIDL支持,包括key和value
    • Parcelable,所有实现了Parcelable接口的对象
    • AIDL,所有的AIDL接口本身也可以在AIDL中使用

    以上类型就是AIDL所支持的所有类型,其中定义的Parcelable对象和AIDL对象都需要显式import引用,不管是不是和当前AIDL文件处于同一个包下,IBookManager.aidl中有所体现。

    // 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);
    }
    

    另一个需要注意的点是如果AIDL文件中用到了自定义的Parcelable对象,就必须创建一个同名的AIDL文件,并在里面声明为Parcelable类型,在Book.aidl中有所体现。

    package com.libok.androidnote.aidl;
    
    parcelable Book;
    

    第三点就是在AIDL文件中除了节本类型,其他类型的参数必须在参数前标上方向:in、out、inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。这个留作以后有时间再作讨论。

    最后,AIDL接口有一点跟传统接口不同,就是接口中只支持方法,不支持静态常量。

  2. 实现远程服务端Service

    在Binder中也有介绍,就是一个Process属性的Service,并在其中创建一个Binder,然后在onBind方法中返回此Binder。这里着重说一下CopyOnWriteArrayList,为什么采用这个List。是因为AIDL的方法是在服务端的Binder线程池中执行的,是支持并发的,当多个线程同时访问时需要在数据端作线程同步,而CopyOnWriteArrayList本身是自带同步。

    有一个疑问是,在AIDL介绍中,AIDL只支持ArrayList但是为什么能用CopyOnWriteArrayList,是因为AIDL中所支持的是抽象的List,即List接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端,所以是可行的。同样的还有ConcurrentHashMap。Binder中的示例就可以在getBookList方法中打印一下返回的List确实是ArrayList。

    E/IPCActivity: onGetBookList: [Book{mBookId=123, mBookName='Love & Peace'}, Book{mBookId=321, mBookName='Play Boy'}, Book{mBookId=523, mBookName='Own Added Book523'}] ArrayList
    
  3. 实现客户端

    客户端的实现也比较简单,仅仅是绑定下服务然后挨个调用方法测试下,Binder章节中也有IPCActivity的代码。再着重重复一点的是,在客户端中,特别要注意的是尽量不要将远程调用放在主线程,因为远程调用会将当前线程先挂起,然后执行远程方法,之后再唤醒客户端线程,要是远程方法是耗时操作的话,可能会导致ANR。

进阶使用

  1. AIDL接口

    还是上面的例子,当每次addBook后只有getBookList才能获取到最新的列表,太麻烦不说还不够优雅,其实AIDL是支持AIDL接口完成Callback的。新建如下IOnNewBookListener.aidl文件。

    // IOnNewBookListener.aidl
    package com.libok.androidnote.aidl;
    
    import com.libok.androidnote.aidl.Book;
    
    interface IOnNewBookListener {
        void onNewBookArrived(in Book book);
    }
    

    显然的在以前的IBookManager.aidl中需要加上注册接口和解绑接口的方法。

    // IBookManager.aidl
    package com.libok.androidnote.aidl;
    
    // Declare any non-default types here with import statements
    
    import com.libok.androidnote.aidl.Book;
    import com.libok.androidnote.aidl.IOnNewBookListener;
    
    interface IBookManager {
        List<Book> getBookList();
        void addBook(in Book book);
        void registerListener(IOnNewBookListener listener);
        void unregisterListener(IOnNewBookListener listener);
    }
    

    此时就不需要用手写IBookManager的对应的Java类了,都是相似的,现在直接用系统给生成的Java类。类被改变了相应的Service中也需要做些改动,Binder中实现两个方法,新开一个Thread,每5秒添加一本书。

    public class BookManagerService extends Service {
    
        private static final String TAG = "BookManagerService";
    
        private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
        private CopyOnWriteArrayList<IOnNewBookListener> mNewBookListeners = new CopyOnWriteArrayList<>();
        private AtomicBoolean mServiceDestroyed = new AtomicBoolean(false);
    
        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);
            }
    
            @Override
            public void registerListener(IOnNewBookListener listener) throws RemoteException {
                if (!mNewBookListeners.contains(listener)) {
                    mNewBookListeners.add(listener);
                }
                Log.e(TAG, "registerListener: size " + mNewBookListeners.size());
            }
    
            @Override
            public void unregisterListener(IOnNewBookListener listener) throws RemoteException {
                mNewBookListeners.remove(listener);
                Log.e(TAG, "unregisterListener: size " + mNewBookListeners.size());
            }
        };
    
        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"));
            new ServiceWorker().start();
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            // TODO: Return the communication channel to the service.
    //        throw new UnsupportedOperationException("Not yet implemented");
            return mBinder;
        }
    
        private void notifyNewBookArrived(Book newBook) throws RemoteException {
            Log.e(TAG, "notifyNewBookArrived: listener size " + mNewBookListeners.size());
            mBookList.add(newBook);
            for (IOnNewBookListener newBookListener : mNewBookListeners) {
                newBookListener.onNewBookArrived(newBook);
            }
        }
    
        @Override
        public void onDestroy() {
            mServiceDestroyed.set(true);
            super.onDestroy();
        }
    
        private class ServiceWorker extends Thread {
            @Override
            public void run() {
                super.run();
                while (!mServiceDestroyed.get()) {
                    try {
                        sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    int bookId = mBookList.size();
                    String bookName = ("BLEACH " + bookId);
                    Book newBook = new Book(bookId, bookName);
    
                    try {
                        notifyNewBookArrived(newBook);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    客户端的改动如下:

    public class IPCActivity extends AppCompatActivity {
    
        private static final String TAG = "IPCActivity";
    
        private static final int BOOK_START_ID = 522;
        public static final int RESULT_MESSENGER_BOOK_LIST = 9876;
    
        private int mBookId = BOOK_START_ID;
        private IBookManager mIBookManager = null;
        //    private IMyBookManager mOwnBookManager = null;
        private Intent mRemoteBookServiceIntent;
    
        private IOnNewBookListener mNewBookListener = new IOnNewBookListener.Stub() {
            @Override
            public void onNewBookArrived(Book book) throws RemoteException {
                Log.e(TAG, "onNewBookArrived: " + book);
            }
        };
    
        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;
                if (mIBookManager == null) {
                    return;
                }
                mIBookManager.asBinder().unlinkToDeath(this, 0);
                mIBookManager = null;
                bindService(mRemoteBookServiceIntent, mBookServiceConnection, Context.BIND_AUTO_CREATE);
                // 其他操作
            }
        };
    
        private ServiceConnection mBookServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mIBookManager = IBookManager.Stub.asInterface(service);
    //            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: ");
            }
        };
    
        private Messenger mServerMessenger = null;
        private ServiceConnection mMessengerConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mServerMessenger = new Messenger(service);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                mServerMessenger = null;
            }
        };
    
        private Handler mMessengerHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case RESULT_MESSENGER_BOOK_LIST:
                        Bundle data = msg.getData();
                        Log.e(TAG, "handleMessage: get class loader:" + data.getClassLoader());
                        data.setClassLoader(getClassLoader());
                        ArrayList<Parcelable> result = data.getParcelableArrayList("result_get_list");
    
                        Log.e(TAG, "handleMessage: RESULT_MESSENGER_BOOK_LIST " + result.toString());
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };
        private Messenger mClientMessenger = new Messenger(mMessengerHandler);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_ipc);
        }
    
        /**
         * 绑定AIDL方式的Service
         */
        public void onBindAIDLBookManagerService(View view) {
            mRemoteBookServiceIntent = new Intent(this, BookManagerService.class);
            bindService(mRemoteBookServiceIntent, 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() + " " + bookList.getClass().getSimpleName());
    //            } 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();
    //            }
    //        }
        }
    
        /**
         * 注册AIDL接口
         */
        public void onRegisterListener(View view) {
            try {
                mIBookManager.registerListener(mNewBookListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 解绑AIDL接口
         */
        public void onUnRegisterListener(View view) {
            try {
                mIBookManager.unregisterListener(mNewBookListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        protected void onDestroy() {
    //        mOwnBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
            if (mIBookManager != null && mIBookManager.asBinder().isBinderAlive()) {
                mIBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
                try {
                    mIBookManager.unregisterListener(mNewBookListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            unbindService(mBookServiceConnection);
            unbindService(mMessengerConnection);
            super.onDestroy();
        }
    
        /**
         * 绑定Messenger方式的Service
         */
        public void onBindMessengerService(View view) {
            Intent messengerServiceIntent = new Intent(this, MessengerService.class);
            bindService(messengerServiceIntent, mMessengerConnection, BIND_AUTO_CREATE);
        }
    
        /**
         * 通过Messenger添加书本
         */
        public void onAddBookToListByMessenger(View view) {
            if (mServerMessenger != null) {
                Book book = new Book(158, "声微饭否");
    
                Bundle data = new Bundle(getClassLoader());
                data.putParcelable("add_book", book);
    
                Message addBookMessage = Message.obtain();
                addBookMessage.what = MessengerService.OPERATION_ADD_BOOK;
                addBookMessage.setData(data);
    
                try {
                    mServerMessenger.send(addBookMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 通过Messenger获取所有的书本
         */
        public void onGetBookListByMessenger(View view) {
            if (mServerMessenger != null) {
                Message getListMessage = Message.obtain();
                getListMessage.what = MessengerService.OPERATION_GET_BOOK_LIST;
                getListMessage.replyTo = mClientMessenger;
    
                try {
                    mServerMessenger.send(getListMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    当绑定Service点击注册Listener完成后,会收到每5秒返回的newBook。Log如下:

    2020-03-15 17:40:07.306 28248-28293/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=2, mBookName='BLEACH 2'}
    2020-03-15 17:40:12.308 28248-28293/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=3, mBookName='BLEACH 3'}
    2020-03-15 17:40:17.311 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=4, mBookName='BLEACH 4'}
    2020-03-15 17:40:22.313 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=5, mBookName='BLEACH 5'}
    2020-03-15 17:40:27.316 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=6, mBookName='BLEACH 6'}
    2020-03-15 17:40:32.318 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=7, mBookName='BLEACH 7'}
    2020-03-15 17:40:37.319 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=8, mBookName='BLEACH 8'}
    2020-03-15 17:40:42.322 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=9, mBookName='BLEACH 9'}
    2020-03-15 17:40:47.324 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=10, mBookName='BLEACH 10'}
    

    但是当点击解绑Listener时可以发现在服务端并不会删除此Listener,点击后Log如下:

    E/BookManagerService: unregisterListener: size 1
    

    仔细想想出现这种情况也是合理的,就像是服务端Service返回的Binder,和在客户端Activity中收到的Binder并不是同一个,所以才能根据此特性在asInterface时去区分是客户端还是服务端的Binder。因此在服务端注册解绑时从客户端传过来的肯定不是同一个Listener,所以也就无法正确的解绑。要想正确的解绑就需要用到一个特殊的“List”——RemoteCallbackList。

    RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。RemoteCallbackList是一个泛型,支持管理任何的AIDL接口,从此类的声明上即可看出。

    public class RemoteCallbackList<E extends IInterface>
    

    这个类的工作原理很简单,内部有一个以IBinder作Key,以Callback作Value的ArrayMap,用来保存所有的Callback,AIDL回调最终是以Callback形式保存,在创建Callback时需要将AIDL接口传入。以Callback作Map的Value可以理解,但是为什么是IBinder作Key。盲生你发现了华点。这就涉及到了跨进程的另一个特性,虽然说每次从客户端传递过来的Listener都是新的,但是这些Listener都具有一个特点,那就是Listener的asBinder返回的都是同一个Binder。这是可以通过打印Binder验证的,但是并不是很想动手。

    RemoteCallbackList还有一个很有用的功能就是当客户端进程终止后,会自动的将此客户端注册的Listener给删除。这是因为Callback实现了Binder的死亡代理,当进程死亡Binder也死亡时会回调binderDied方法,继而会自动删除相应的AIDL接口。最后一点RemoteCallbackList内部是是实现了线程同步的,无需做额外线程同步工作。

    所以Service中mBinder又需要作以下的改动:

    @Override
    public void registerListener(IOnNewBookListener listener) throws RemoteException {
          if (!mNewBookListeners.contains(listener)) {
              mNewBookListeners.add(listener);
          }
        mRemoteCallbackList.register(listener);
        Log.e(TAG, "registerListener: size " + mRemoteCallbackList.getRegisteredCallbackCount());
    }
    
    @Override
    public void unregisterListener(IOnNewBookListener listener) throws RemoteException {
        mRemoteCallbackList.unregister(listener);
        Log.e(TAG, "unregisterListener: size " + mRemoteCallbackList.getRegisteredCallbackCount());
    }
    

    notifyNewBookArrived方法作以下改动:

    private void notifyNewBookArrived(Book newBook) throws RemoteException {
        Log.e(TAG, "notifyNewBookArrived: listener size " + mNewBookListeners.size());
        mBookList.add(newBook);
        //        for (IOnNewBookListener newBookListener : mNewBookListeners) {
        //            newBookListener.onNewBookArrived(newBook);
        //        }
        //        for (int i = 0; i < mRemoteCallbackList.getRegisteredCallbackCount(); i++) {
        //            mRemoteCallbackList.getRegisteredCallbackItem()
        //        }
        int callbackCount = mRemoteCallbackList.beginBroadcast();
        for (int i = 0; i < callbackCount; i++) {
            mRemoteCallbackList.getBroadcastItem(i).onNewBookArrived(newBook);
        }
        mRemoteCallbackList.finishBroadcast();
    }
    

    RemoteCallbackList的遍历有些别致,需要先调用beginBroadcast方法获取callback总数,然后调用getBroadcastItem方法获取回调,最后需要调用finishBroadcast方法清理内部的临时变量。其中beginBroadcast方法和finishBroadcast方法必须成对出现,否则会报异常,看内部源码就能看出所以然来,不再赘述。

    题外话:但是有一点不是很懂,不管是获取当前RemoteCallbackList中回调的数量还是获取回调都有两种方法。获取数量可以成对的调用beginBroadcast,也可以调用getRegisteredCallbackCount,同样都是获取的内部Map的数量,但是前者是将Map中的所有回调保存到一个临时数组变量mActiveBroadcast中,后者是直接返回Map的size。获取回调还是可以调用beginBroadcast,然后调用getBroadcastItem方法获取回调,也可以直接调用getRegisteredCallbackItem方法获取,但是这个方法是需要API26才能使用的,前者还是先将Map中的回调保存到mActiveBroadcast中,然后从此数组中获取,后者直接从Map中获取。所以问题来了,这两种方式有什么不同,看了一顿方法注释也没看懂。。太过愚钝,希望有懂的人给讲一讲。

    至此,Listener就可以正确的解绑了,AIDL接口也全部介绍完毕。

    但是还有两点需要注意:方法运行在哪个线程问题,注意区分主线程和Binder线程,注意主线程中尽量不要出现的远程操作及耗时操作,注意Binder线程不要访问UI。具体的可以在不明白的方法内部打印当前线程。Binder的意外死亡问题,可以为Binder设置死亡代理,前面Binder中已经说过,在死亡代理的回调中重新绑定服务。另外一种方法是在ServiceConnection中的onServiceDisconnected方法中重新绑定服务,区别就是Binder死亡代理是运行在Binder线程池中的,onServiceDisconnected方法是运行在主线程的。

  2. 权限验证

    权限验证的方法很多,在此只介绍几种。

    • 在Service的onBind方法中通过Manifest文件的permission进行验证

      首先在Manifest文件中创建一个自定义的permission如下:

      <permission
          android:name="com.libok.androidnote.aidl.IBookManager.permission.ACCESS_BIND_SERVICE"
          android:protectionLevel="normal" />
      

      并在uses-permission中声明此权限

      <uses-permission android:name="com.libok.androidnote.aidl.IBookManager.permission.ACCESS_BIND_SERVICE" />
      

      然后就可以在Service的onBind方法进行验证了。

      @Override
      public IBinder onBind(Intent intent) {
          int checkCallingOrSelfPermission = checkCallingOrSelfPermission("com.libok.androidnote.aidl.IBookManager.permission.ACCESS_BIND_SERVICE");
          Log.e(TAG, "onBind: " + checkCallingOrSelfPermission);
          if (checkCallingOrSelfPermission == PackageManager.PERMISSION_DENIED) {
              return null;
          }
          return mBinder;
      }
      

      至于权限应该配置在客户端还是服务端以及checkCallingOrSelfPermission方法的用法在此方法的注释中已经说的很清楚了。

      Determine whether the calling process of an IPC <em>or you</em> have been
      granted a particular permission.  This is the same as
      {@link #checkCallingPermission}, except it grants your own permissions
      if you are not currently processing an IPC.  Use with care!
      

      显然需要客户端配置声明权限。

    • 在服务端的onTransact方法中作权限验证

      在此方法中既可以用permission验证也可以通过getCallingUid和getCallingPid获取到客户端所属应用的UID和PID作验证。验证不成功直接返回false,表示不允许调用远程方法。以下为示例:

      private Binder mBinder = new IBookManager.Stub() {
          @Override
          public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
              int checkCallingOrSelfPermission = checkCallingOrSelfPermission("com.libok.androidnote.aidl.IBookManager.pe
              if (checkCallingOrSelfPermission == PackageManager.PERMISSION_DENIED) {
                  return false;
              }
              String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
              Log.e(TAG, "onTransact: " + Arrays.toString(packages));
              String packageName = null;
              if (packages != null && packages.length > 0) {
                  packageName = packages[0];
              }
              if (TextUtils.isEmpty(packageName) || !TextUtils.equals(packageName, "com.libok.androidnote")) {
                  return false;
              }
              return super.onTransact(code, data, reply, flags);
          }
                                                                              
          @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);
          }
                                                                              
          @Override
          public void registerListener(IOnNewBookListener listener) throws RemoteException {
                if (!mNewBookListeners.contains(listener)) {
                    mNewBookListeners.add(listener);
                }
              mRemoteCallbackList.register(listener);
              Log.e(TAG, "registerListener: size " + mRemoteCallbackList.getRegisteredCallbackCount());
          }
                                                                              
          @Override
          public void unregisterListener(IOnNewBookListener listener) throws RemoteException {
              mRemoteCallbackList.unregister(listener);
              Log.e(TAG, "unregisterListener: size " + mRemoteCallbackList.getRegisteredCallbackCount());
          }
      };
      

4.5 ContentProvider

ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,从这一点上看,天生就适合进程间通信。和Messenger一样,ContentProvider的底层实现同样也是Binder。但使用过程要比AIDL简单些,系统已经做好了封装,可以无需关注底层即可轻松实现进程间通信。

系统预置了很多ContentProvider,比如通讯录、短信、日程表等,想要访问这些信息必然是跨进程的,而且只需要通过ContentProvider的query、update、insert、delete方法即可。接下来通过创建一个ContentProvider来看看如何进行进程间通信。

新建一个ContentProvider很简单,只需要继承ContentProvider并实现六个抽象方法即可:onCreate、getType、insert、delete、update、query。onCreate方法就是ContentProvider创建时调用;getType用来返回一个Uri请求的MIME类型,详细的参考MIME 参考手册;剩下的四个方法就是CRUD操作。根据Binder的原理,六个方法都是运行在ContentProvider进程,除onCreate方法由系统回调运行在主线程外,其余五个方法都是运行在Binder线程。

ContentProvider主要是以表格的形式组织数据,并且可以包含多个表,对于每个表来说,它们都具有行和列的层次性,行往往代表一条记录,列往往对应一条记录中的一个字段,跟数据库的表类似。除了表格形式外,ContentProvider还支持文件数据,比如图片、视频等。文件数据和表数据的结构不同,因此处理这类数据时可以在ContentProvider中返回文件的句柄给外界,让外界来访问文件信心。Android系统中所提供的MediaStore就是文件类型的ContentProvider,可供参考。另外,虽然ContentProvider的底层数据看起来很像SQLite数据库,但是ContentProvider对底层的数据存储方式并没有任何要求,既可以是SQLite数据库,也可以是普通文件,json、xml等的都可以,只要是符合需求。

ContentProvider示例:

public class BookProvider extends ContentProvider {

    private static final String TAG = "BookProvider";
    
    @Override
    public boolean onCreate() {
        Log.e(TAG, "onCreate: " + Thread.currentThread().getName());
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.e(TAG, "query: " + Thread.currentThread().getName());
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        Log.e(TAG, "getType: ");
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.e(TAG, "insert: " + Thread.currentThread().getName());
        return null;
    }
    
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(TAG, "delete: " + Thread.currentThread().getName());
        return 0;
    }
    
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(TAG, "update: " + Thread.currentThread().getName());
        return 0;
    }
    
}

当然除了新建类之外还得在Manifest中声明注册这个ContentProvider。

<provider
    android:name=".provider.BookProvider"
    android:authorities="com.libok.androidnote.provider"
    android:permission="com.libok.androidnote.BOOK_PROVIDER"
    android:process=":provider" />

android:authorities是ContentProvider的唯一标识,通过这个属性外部应用就可以访问我们的BookProvider,最好在命名时加上包名以示区分。

android:permission表示要想访问ContentProvider,就必须声明com.libok.androidnote.BOOK_PROVIDER这个权限。除了permission外,ContentProvider还细分的有写权限android:writePermission以及读权限android:readPermission,只有正确声明才能正确的访问。

Activity访问示例:

public class IPCActivity extends AppCompatActivity {

    private static final String TAG = "IPCActivity";

    private Uri mProviderUri = Uri.parse("content://com.libok.androidnote.provider");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ipc);
    }

    /**
     * ContentProvider执行新增
     */
    public void onContentProviderAdd(View view) {
        getContentResolver().insert(mProviderUri, null);
    }
    
    /**
     * ContentProvider执行删除
     */
    public void onContentProviderDelete(View view) {
        getContentResolver().delete(mProviderUri, null, null);
    }
    
    /**
     * ContentProvider执行修改
     */
    public void onContentProviderUpdate(View view) {
        getContentResolver().update(mProviderUri, null, null, null);
    }
    
    /**
     * ContentProvider执行查询
     */
    public void onContentProviderQuery(View view) {
        Cursor cursor = null;
        try {
            cursor = getContentResolver().query(mProviderUri, null, null, null, null);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }
}

当然现在的insert和update操作还是会引起程序异常,但是执行delete和query方法可以从log发现onCreate方法确实是在main线程,不能做耗时操作,而其他的方法都是在Binder线程。

E/BookProvider: onCreate: main
E/BookProvider: query: Binder:1399_3
E/BookProvider: delete: Binder:1399_3

流程现在是能够跑通了,现在再为ContentProvider提供一个SQLite数据库作为底层数据存储来实现ContentProvider的跨进程通信,当然前面也说过不一定非得是数据库,其他的能存储数据的能够进行数据处理的都是可以的。

MySQLiteHelper

public class MySQLiteHelper extends SQLiteOpenHelper {

    private static final String TAG = "MySQLiteHelper";

    private static final int DB_VERSION = 1;
    private static final String DB_NAME = "test_content_provider.db";
    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TABLE_NAME = "user";

    public static final String ID = "_id";
    public static final String BOOK_ID = "book_id";
    public static final String BOOK_NAME = "name";
    public static final String USER_ID = "user_id";
    public static final String USER_NAME = "name";
    public static final String USER_SEX = "sex";

    private static final String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(" +
            ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            BOOK_ID + " INTEGER, " +
            BOOK_NAME + " TEXT" +
            ")";
    private static final String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(" +
            ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            USER_ID + " INTEGER, " +
            USER_NAME + " TEXT, " +
            USER_SEX + " INT" +
            ")";

    private static MySQLiteHelper sHelper = null;

    public static SQLiteDatabase getInstance(Context context) {
        if (sHelper == null) {
            synchronized (MySQLiteHelper.class) {
                if (sHelper == null) {
                    sHelper = new MySQLiteHelper(context, DB_NAME, null, DB_VERSION);
                }
            }
        }

        return sHelper.getWritableDatabase();
    }

    private MySQLiteHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK_TABLE);
        db.execSQL(CREATE_USER_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

数据的就用此SQLiteOpenHelper来操作。

相应的provider也要作相应更改:

  1. 用UriMatcher作Uri匹配,并在类初始化时添加匹配规则
  2. getTableName方法用作获取Uri匹配的表名

其他的就是对数据库的操作,并无特别之处,当Provider进行insert、update、delete时如果需要知晓数据发生了改变,可以针对操作的Uri作监听,即通过ContentResolver的registerContentObserver方法注册观察者,通过unregisterContentObserver来解除观察者。

public class BookProvider extends ContentProvider {

    private static final String TAG = "BookProvider";

    public static final String AUTHORITIES = "com.libok.androidnote.provider";
    public static final Uri CONTENT_BOOK_URI = Uri.parse("content://" + AUTHORITIES + "/book");
    public static final Uri CONTENT_USER_URI = Uri.parse("content://" + AUTHORITIES + "/user");

    public static final int CODE_BOOK = 0;
    public static final int CODE_USER = 1;

    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        sUriMatcher.addURI(AUTHORITIES, "book", CODE_BOOK);
        sUriMatcher.addURI(AUTHORITIES, "user", CODE_USER);
    }

    private SQLiteDatabase mDatabase;
    private Context mContext;

    @Override
    public boolean onCreate() {
        Log.e(TAG, "onCreate: " + Thread.currentThread().getName());
        mContext = getContext();
        mDatabase = MySQLiteHelper.getInstance(mContext);
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.e(TAG, "query: " + Thread.currentThread().getName());
        String tableName = getTableName(uri);
        if (!TextUtils.isEmpty(tableName)) {
            return mDatabase.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
        }
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        Log.e(TAG, "getType: ");
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.e(TAG, "insert: " + Thread.currentThread().getName());
        String tableName = getTableName(uri);
        if (!TextUtils.isEmpty(tableName)) {
            mDatabase.insert(tableName, null, values);
            mContext.getContentResolver().notifyChange(uri, null);
            return uri;
        }
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(TAG, "delete: " + Thread.currentThread().getName());
        String tableName = getTableName(uri);
        if (!TextUtils.isEmpty(tableName)) {
            int count = mDatabase.delete(tableName, selection, selectionArgs);
            if (count > 0) {
                mContext.getContentResolver().notifyChange(uri, null);
            }
            return count;
        }
        return -1;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(TAG, "update: " + Thread.currentThread().getName());
        String tableName = getTableName(uri);
        if (!TextUtils.isEmpty(tableName)) {
            int row = mDatabase.update(tableName, values, selection, selectionArgs);
            if (row > 0) {
                mContext.getContentResolver().notifyChange(uri, null);
            }
            return row;
        }
        return -1;
    }

    private String getTableName(Uri uri) {
        String tableName = null;
        switch (sUriMatcher.match(uri)) {
            case CODE_BOOK:
                tableName = MySQLiteHelper.BOOK_TABLE_NAME;
                break;
            case CODE_USER:
                tableName = MySQLiteHelper.USER_TABLE_NAME;
                break;
        }

        return tableName;
    }
}

补充:具体操作ContentProvider

/**
 * ContentProvider执行新增
 */
public void onContentProviderAdd(View view) {
    ContentValues bookValues = new ContentValues();
    bookValues.put(MySQLiteHelper.BOOK_ID, mBookId);
    bookValues.put(MySQLiteHelper.BOOK_NAME, "BLEACH " + mBookId);
    getContentResolver().insert(BookProvider.CONTENT_BOOK_URI, bookValues);
    mSavedBookIdList.add(mBookId);
    Log.e(TAG, "onContentProviderAdd: " + mSavedBookIdList);
    mBookId++;
    ContentValues userValues = new ContentValues();
    userValues.put(MySQLiteHelper.USER_ID, mBookId);
    userValues.put(MySQLiteHelper.USER_NAME, "BLEACH " + mBookId);
    userValues.put(MySQLiteHelper.USER_SEX, mBookId % 2);
    getContentResolver().insert(BookProvider.CONTENT_USER_URI, userValues);
    mBookId++;
}
/**
 * ContentProvider执行删除
 */
public void onContentProviderDelete(View view) {
    if (!mSavedBookIdList.isEmpty()) {
        int bookId = mSavedBookIdList.remove(0);
        int count = getContentResolver().delete(BookProvider.CONTENT_BOOK_URI, MySQLiteHelper.BOOK_ID + " = ?", new String[]{St
        Log.e(TAG, "onContentProviderDelete: delete count " + count);
    } else {
        Log.e(TAG, "onContentProviderDelete: 当前并没有存储Book");
    }
}
/**
 * ContentProvider执行修改
 */
public void onContentProviderUpdate(View view) {
    if (!mSavedBookIdList.isEmpty()) {
        int bookId = mSavedBookIdList.remove(0);
        ContentValues updateValues = new ContentValues();
        updateValues.put(MySQLiteHelper.BOOK_NAME, "FIRE " + bookId);
        int row = getContentResolver().update(BookProvider.CONTENT_BOOK_URI, updateValues, MySQLiteHelper.BOOK_ID + " = ?", new
        Log.e(TAG, "onContentProviderUpdate: update row " + row);
    } else {
        Log.e(TAG, "onContentProviderDelete: 当前并没有存储Book");
    }
}
/**
 * ContentProvider执行查询
 */
public void onContentProviderQuery(View view) {
    Cursor bookCursor = null;
    Cursor userCursor = null;
    StringBuilder stringBuilder = new StringBuilder();
    mSavedBookIdList.clear();
    try {
        bookCursor = getContentResolver().query(BookProvider.CONTENT_BOOK_URI, new String[]{MySQLiteHelper.BOOK_ID, MySQLiteHel
        stringBuilder.append("[\n");
        if (bookCursor != null) {
            while (bookCursor.moveToNext()) {
                int bookId = bookCursor.getInt(bookCursor.getColumnIndex(MySQLiteHelper.BOOK_ID));
                mSavedBookIdList.add(bookId);
                stringBuilder.append("\tbook id=")
                        .append(bookId)
                        .append(" name=")
                        .append(bookCursor.getString(bookCursor.getColumnIndex(MySQLiteHelper.BOOK_NAME)))
                        .append("\n");
            }
            Log.e(TAG, "onContentProviderQuery: " + mSavedBookIdList);
        }
        stringBuilder.append("]\n\n");
        userCursor = getContentResolver().query(BookProvider.CONTENT_USER_URI, new String[]{MySQLiteHelper.USER_ID, MySQLiteHel
        stringBuilder.append("[\n");
        if (userCursor != null) {
            while (userCursor.moveToNext()) {
                stringBuilder.append("\tuser id=")
                        .append(userCursor.getInt(userCursor.getColumnIndex(MySQLiteHelper.USER_ID)))
                        .append(" name=")
                        .append(userCursor.getString(userCursor.getColumnIndex(MySQLiteHelper.USER_NAME)))
                        .append(" sex=")
                        .append(userCursor.getInt(userCursor.getColumnIndex(MySQLiteHelper.USER_SEX)) == 0 ? "女" : "男")
                        .append("\n");
            }
        }
        stringBuilder.append("]\n");
    } finally {
        if (bookCursor != null) {
            bookCursor.close();
        }
        if (userCursor != null) {
            userCursor.close();
        }
    }
    mShowQueryText.setText(stringBuilder.toString());
}

4.6 Socket

Socket—套接字,是网络通信中的概念,分为流式套接字和用户数据报套接字,分别对应网络的传输控制层中的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过”三次握手“才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性;而UDP是无连接的,提供不稳定的通信功能。但在性能上UDP具有更好的效率,缺点是不能够保证正确传输,尤其是在网络拥堵的情况下。

服务端Service

public class SocketService extends Service {

    private static final String TAG = "SocketService";

    public SocketService() {
        initSocketServer();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void initSocketServer() {
        new Thread() {
            @Override
            public void run() {
                ServerSocket serverSocket = null;
                try {
                    serverSocket = new ServerSocket(7873);
                    Log.e(TAG, "initSocketServer: wait client accept...");
                    Socket client = serverSocket.accept();
                    Log.e(TAG, "initSocketServer: client accepted");
                    handleClient(client);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    private void handleClient(Socket client) throws IOException {
        PrintWriter printWriter = new PrintWriter(client.getOutputStream(), true);
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));

        String line = bufferedReader.readLine();
        Log.e(TAG, "handleClient: receive client:" + line);

        printWriter.println("Hello, this is server.");
    }
}

客户端

public class IPCActivity extends AppCompatActivity {

    private static final String TAG = "IPCActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ipc);
    }

    /**
     * 绑定socket
     *
     * @param view
     */
    public void onStartRemoteServer(View view) {
        startService(new Intent(this, SocketService.class));
    }

    /**
     * 给服务端发送消息
     *
     * @param view
     */
    public void onSendToServer(View view) {
        new Thread() {
            @Override
            public void run() {
                try {
                    Socket socket = new Socket("localhost", 7873);
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);

                    printWriter.println("Hello, this is client.");

                    String line = bufferedReader.readLine();
                    Log.e(TAG, "onBindSocket: from server:" + line);

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();

    }
}

麻雀虽小五脏俱全,简单的小栗子,客户端和服务端通一次信就拉倒,也并没有对资源进行释放,仅仅是示例。当然能传递的信息并非只有字符串,只要能进行网络传输的都能行得通。

五、Binder连接池

Binder连接池的作用是在多个业务模块下需要多个AIDL文件中体现的。按照普通的思路来说是单个AIDL对应单个Service,但是当AIDL越来越多时,这么不计数量的创建Service并不是一个好办法,而且也并不是只有一个AIDL对应一个Service的方法,而且多次创建Service是重复性质的,Service的绑定也是重复的。所以可以采用一种更简洁的方法去管理多个AIDL,将AIDL升华一下,用一个AIDL总管去管理将所有的AIDL,这样就可以将所有的AIDL放在同一个Service中,并且通过识别码的形式去按需获取AIDL。

5.1 BinderPool

新建一个BinderPool AIDL文件,当Activity绑定Service时,返回的是此AIDL的Binder,然后通过调用queryBinder方法查找另一个进程真正所需的Binder。

package com.libok.androidnote.aidl;


interface IBinderPool {
    IBinder queryBinder(in int binderCode);
}

此AIDL所对应的Service跟普通的多进程Service也是如出一辙。

public class BinderPoolService extends Service {

    private static final String TAG = "BinderPoolService";

    public static final int BINDER_CODE_SECURITY = 0;
    public static final int BINDER_CODE_COMPUTE = 1;

    private BinderPoolImpl mBinderPool = new BinderPoolImpl();

    public BinderPoolService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinderPool;
    }

    private static class BinderPoolImpl extends IBinderPool.Stub {

        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode) {
                case BINDER_CODE_SECURITY:
                    binder = new SecurityCenterImpl();
                    break;
                case BINDER_CODE_COMPUTE:
                    binder = new ComputeImpl();
                    break;
            }
            return binder;
        }
    }

}

但是接下来就大相径庭了,首先最大的不同就是本来在Activity中进行的绑定Service现在需要用一个单例去绑定管理,即BinderPool。

public static class BinderPool {

    private static final String TAG = "BinderPool";

    private static BinderPool sBinderPool = null;

    private Context mContext;
    private CountDownLatch mCountDownLatch;
    private IBinderPool mBinderPool = null;

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderPool = BinderPoolImpl.asInterface(service);
            try {
                mBinderPool.asBinder().linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Log.e(TAG, "onServiceConnected: bind success " + Thread.currentThread().getName() + " " + android.os.Process.myPid());
            mCountDownLatch.countDown();
            Log.e(TAG, "onServiceConnected: count down");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            mBinderPool.asBinder().unlinkToDeath(this, 0);
            mBinderPool = null;
            bindService();
        }
    };

    private BinderPool(Context context) {
        mContext = context.getApplicationContext();
        bindService();
    }

    public static BinderPool getInstance(Context context) {
        if (sBinderPool == null) {
            synchronized (BinderPool.class) {
                if (sBinderPool == null) {
                    sBinderPool = new BinderPool(context);
                }
            }
        }

        return sBinderPool;
    }

    private synchronized void bindService() {
        mCountDownLatch = new CountDownLatch(1);
        mContext.bindService(new Intent(mContext, BinderPoolService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
        Log.e(TAG, "bindService: start " + Thread.currentThread().getName());
        try {
            mCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.e(TAG, "bindService: finished " + Thread.currentThread().getName() + " " + android.os.Process.myPid());
    }

    public IBinder queryBinder(int bindCode) throws RemoteException {
        IBinder binder = null;

        if (mBinderPool != null) {
            binder = mBinderPool.queryBinder(bindCode);
        }

        return binder;
    }
}

首先,这个类是单例类,全局用一个类去管理,当然此类是运行在主进程的。

其次,获取单例之后再bindService,而且此bind非彼bind,是一个附加了些额外操作的套娃方法,在方法中创建一个同步辅助,并在真正的Context执行BindService方法后暂停当前线程,等待服务的成功绑定,因为绑定的过程可能会耗费很长时间。

最后,等mBinderPool变量被赋值后,即可向外部提供queryBinder服务。

实际提供服务的两个AIDL,这两个AIDL无需过多解释,与普通的AIDL服务大同小异,只是在需要时再进行懒加载。

package com.libok.androidnote.aidl;

interface ICompute {
    int add(in int a, in int b);
}
package com.libok.androidnote.aidl;


interface ISecurityCenter {
    String encrypt(in String content);
    String decrypt(in String password);
}

具体实现:

public static class ComputeImpl extends ICompute.Stub {
    @Override
    public int add(int a, int b) throws RemoteException {
        return a + b;
    }
}
public static class SecurityCenterImpl extends ISecurityCenter.Stub {

    private static final char SECRET_CODE = '^';

    @Override
    public String encrypt(String content) throws RemoteException {
        char[] chars = content.toCharArray();

        for (int i = 0; i < chars.length; i++) {
            chars[i] ^= SECRET_CODE;
        }
        return new String((chars));
    }

    @Override
    public String decrypt(String password) throws RemoteException {
        return encrypt(password);
    }
}

在queryBinder方法中按需返回远程Binder。

BinderPool类的所有方法都是运行在主进程,调用另外进程的是queryBinder方法中的binder = mBinderPool.queryBinder(bindCode);,mBinderPool变量在BinderPoolImpl绑定成功后赋值,并在之后提供另外进程向主进程返回Binder的服务。

5.2 使用

经过上面的代码逻辑,在使用上肯定会跟普通的多进程通信有点差别。下面是Activity的代码:

public class BinderPoolActivity extends AppCompatActivity {

    private static final String TAG = "BinderPoolActivity";

    private BinderPoolService.BinderPool mBinderPool;

    private String mContent = "QWER";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder_pool);
    }

    public void onBindBinderPool(View view) {
        new Thread(){
            @Override
            public void run() {
                Log.e(TAG, "run: ");
                mBinderPool = BinderPoolService.BinderPool.getInstance(BinderPoolActivity.this);
                Log.e(TAG, "run: finish");
            }
        }.start();
//        mBinderPool = BinderPoolService.BinderPool.getInstance(BinderPoolActivity.this);
    }

    public void onEncrypt(View view) {
        try {
            IBinder binder = mBinderPool.queryBinder(BinderPoolService.BINDER_CODE_SECURITY);
            ISecurityCenter securityCenter = BinderPoolService.SecurityCenterImpl.asInterface(binder);
            mContent = securityCenter.encrypt(mContent);
            Log.e(TAG, "onEncrypt: " + mContent);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void onDecrypt(View view) {
        try {
            IBinder binder = mBinderPool.queryBinder(BinderPoolService.BINDER_CODE_SECURITY);
            ISecurityCenter securityCenter = BinderPoolService.SecurityCenterImpl.asInterface(binder);
            mContent = securityCenter.decrypt(mContent);
            Log.e(TAG, "onDecrypt: " + mContent);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void onCompute(View view) {
        try {
            IBinder binder = mBinderPool.queryBinder(BinderPoolService.BINDER_CODE_COMPUTE);
            ICompute compute = BinderPoolService.ComputeImpl.asInterface(binder);
            int result = compute.add(21, 20);
            Log.e(TAG, "onCompute: " + result);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

除了onCreate方法其他的四个方法都是Button的Click方法,在绑定方法onBindBinderPool中就是5.1中所述,获取单例并绑定方法。但是有一点特别的显眼,眼尖的人已经看出来了,为什么getInstance方法需要在子线程,为什么不能在主线程。

在5.1中描述了一个BindService方法中的同步变量,此变量的作用就是保证此方法的调用线程等待Service的绑定成功,会在成功后执行countDown方法释放线程锁。如果是子线程被暂停完全没有问题,但是如果是主线程被暂停,问题可就大了。要是绑定的过程相当漫长,是会引起ANR的,这是原因一;其二也是最主要的原因,服务绑定成功的回调方法onServiceConnected是会返回到主线程的,而释放线程的代码在方法里面,这就导致了一个死循环。onServiceConnected方法需要在主线程中执行,所以主线程得是畅通的而不是被锁住的,但是要想解锁得先执行onServiceConnected方法。综上,子线程是必须的。

获取到服务的Binder之后,逻辑就简单明了了,就是将Binder转化成相应的AIDL实现上即可。

至于为什么onServiceConnected会在主线程回调就是后话了,Service的工作过程 coming soon。

六、选择合适的IPC方式

说完这么多IPC方式后,非常需要一个总结表格,更直观简单的描述优缺点及适用场景。

名称优点缺点适用场景
Bundle简单易用只能传输Bundle支持的数据类型四大组件间的进程间通信
文件共享简单易用不适合高并发的场景,并且无法做到进程间的即时通信无并发访问情形,交换简单的实时性不高的数据
AIDL功能强大,支持一对多并发通信使用复杂,需要处理好线程同步一对多且有RPC1需求
Messenger功能一般,支持一对多串行通信,支持实时通信不能很好的处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型低并发的一对多即时通信,无RPC需求,或者无需要返回结果的RPC需求
ContentProvider在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作可以理解为受约束的AIDL,主要提供数据源的CRUD操作一对多的进程间的数据共享
Socket功能强大,可通过网络传输字节流,支持一对多并发实时通信实现细节稍微有点繁琐,不支持直接的RPC网络数据交换

  1. 远程过程调用 ↩︎

Android IPC机制(一)开启多进程 1. 为何要开启多进程 为何开启android应用要开启多进程,主要有以下几点: 单进程所分配的内存不够,需要更多的内存。在早期android系统只为一个单进程的应用分配了16M的可用内存,随着手机的硬件的提升和android系统的改进,虽然可分配内存越来越多,但仍旧可以通过开启多进程来获取更多的内存来处理自己App的业务 独立运行的组件,比如个推,它的服务会另开一个进程。 2. 开启多进程 首 阅读详情

相关推荐

Unity 数字孪生笔记1 工具介绍

前言 工欲善其事必先利其器 流程思考 数字孪生本质上一种基于实际物理数据的可视化方案,通过对接实际数据,在三维界面中展示孪生结果。 那么这个流程就很简单了。 整个流程分为三块:数据来源-》数据分析-》数据展示 输入数据 首先我们需要考虑我们能拿到什么。第一,实际物体的三维数据从哪里来,第二,实际数据怎么获取到。 模型数据 工业上常常会用到CAD模型,CAD模型具有精确、统一的特点,但是CAD不是三角面片,我们不能直接导出到Unity中,而且导入之后有很多不合理的地方,例如面片杂乱、面数过高等问题。针对这个

Thissky的专栏 1万+

Android应用内多进程

Serializable使用简单但是开销很大,序列化和反序列化过程需要大量的IO操作,一般用于将对象序列化到存储设备中或者将对象序列化后通过网络传输。Parcelable使用麻烦,但效率很高。

轻口味的专栏 3577

从零开始STM32cubeide学习

使用的是正点原子的精英版,单片机是STM32F103ZET6,详细讲解完成跑马灯实验

weixin_44077208的博客 2279

Android:多进程的开启方式、注意点以及如何解决。

线程是CPU调度的最小单元。而进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程,进程和线程是包含与被包含的关系。 在很多中情况下我们需要开启多进程,最常见的比如某一个模块会占用很多的内存且比较独立,可以考虑放到单独的进程去处理。 但是多进程不仅仅是简单指定一个进程名称就好了,下面会进行详细介绍。.........

Pumpkin's Blog 5347

红外小目标检测新思路:当DETR遇上Mamba状态空间模型,速度提升3倍

本文探讨了将Mamba状态空间模型与DETR框架结合,用于解决红外小目标检测的难题。该创新方案通过线性复杂度的Mamba模块替代传统Transformer的自注意力机制,在保持高检测精度的同时,显著提升了推理速度,实现了实时性能的突破,为无人机巡检、安防监控等场景提供了高效解决方案。

hhh00的博客 664

Bootloader Design of PIC18 series MCU

注意:下列描述是在PIC单片机上启用Bootloader的一个相关知识的近似完备集。所有需要了解的,与bootloader与用户态程序交互的理论知识都已给出。包含内存组织,跳转,中断等必备功能的描述。

含光左卫的工作笔记集 1267

Android多进程(一)—— 开启多进程

Android应用要开启多进程的原因: 单进程分配的内存不够,需要更多的内存。 早期的Android系统只为一个单进程的应用分配了16MB的可用内存,随着手机硬件的提升和Android系统的改进,虽然可分配的内存越来越多,但仍然可以通过开启多进程来获取更多内存来处理自己的APP业务。 独立运行的组件,比如个推,它的服务会另开启一个进程 运行一些“不可见人”的操作,比如获取用户的隐私数据,比如防止双守护进程被用户杀掉 开启多进程 首先在Activity中启动一个服务: public class MainA

xingyu19911016的博客 5956

STM32 野火MINI 用FontCvtST.exe 生成.c文件 写汉字到文本框 按钮上

STM32 野火MINI 用FontCvtST.exe 生成.c文件 写汉字到文本框 按钮上 参考了好多大神博客,自己写的过程中发现,还有些小细节。 一、打开Notepad写需要的字 另存为 UTF-16LE 也就是Unicode。 二、打开FontCvtST转 删掉所有字 读入test.txt里的字 选择刚才的test.txt 另存为.c文件 改个英文名 复制到RTOS的例程文件夹下 三、打开GUIBuilder 画一个这样的 保存之前要注意路径配置要在当前位置,否则存不上 生成的D

weixin_41376117的博客 1935

Android——多进程

之前我们了解了 : -----------------------------------Java——多线程浅析. -----------------------------------Android——Handler详解 -----------------------------------Android——HandlerThread浅析 -----------------------------------Java——ThreadPool线程池 让我们继续看看Android多进程: 1. 概述 默认情况

Yawn 3485

Docker “docker login“ 失败? 登录阿里云镜像仓库仓库 unauthorized: authentication required

文章目录听说你 login fail?来,让我们解决一下!登录,撒花 听说你 login fail? 美滋滋的在服务器上安装完成Docker应用,开始配置阿里云镜像加速?然后被阿里云官方文档蹂躏?别急别急,让我来拯救你。 我们先来看看 阿里云官方文档 Docker镜像基本操作: 好家伙,emmm… 上来直接就Login Succeeded,这不简直so easy?我们也来照着操作操作: docker@default-online:~$ docker login registry.cn-hangzhou.a

zhuzicc的博客 1万+

revit二次开发环境搭建(2024版)(小白友好)

revit二次开发环境配置,SDK、addin manager和lookup新手友好安装教程

m0_56961251的博客 1420

Android多进程

经过模块解耦,开辟新的进程,独立的JVM,来达到数据解耦目的。解析:绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,然后就可以调用AIDL中的方法了,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端的线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,导致ANR,客户端的onServiceConnected和onServiceDisconnected方法都在UI线程中。

Dream It Possible 1095

GX WORKS2通过交换机连接PLC的方法

三菱PLC连接方法的分享

weixin_64927621的博客 3618

(十二)安卓进程与多进程

在安卓应用开发中,进程和多进程技术用于实现复杂的应用架构、提升应用性能和增强安全性。理解如何有效地使用多进程,可以帮助开发者分离不同模块的功能、实现高效的进程间通信以及提升应用的稳定性和安全性。本文将介绍进程和多进程的基础知识、在安卓平台上的实现方法、常见的使用场景以及一些实践示例,帮助你全面掌握安卓进程与多进程技术。进程是操作系统进行资源分配和调度的基本单位,是程序在一个数据集合上运行的过程。每个进程拥有独立的内存空间和系统资源。掌握进程和多进程技术是开发复杂安卓应用的关键。

小新的博客 890

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Android开发中怎样用多进程、用多进程的好处、多进程的缺陷、解决方法

1.怎样用多进程 Android多进程概念:一般情况下,一个应用程序就是一个进程,这个进程名称就是应用程序包名。我们知道进程是系统分配资源和调度的基本单位,所以每个进程都有自己独立的 资源和内存空间,别的进程是不能任意访问其他进程的内存和资源的。 如何让自己的应用拥有多个进程: 四大组件在AndroidManifest文件中注册的时候,有个属性android:process这里可以指定组件

SPENCER_HALE的博客 1万+

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

安卓多进程与进程间通信

安卓多进程与进程间通信

qq_32290639的博客 148

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Android多进程与多进程通信

Android多进程与多进程通信 Android多进程开发 如何创建使用Android多进程? 在AndroidManifest.xml里面组件声明标签android:process=":xxx"或者在jin里面使用C函数fork进程,后者没试过,建议使用前者 Android:process 为组件(Activity、Service、Provider、receiver)设置process该组件会在......

jackzhouyu的专栏 1314

Android多进程的使用

Android中多进程的使用为什么使用多进程如何使用多进程遇到的问题查找原因1查找原因2查找原因3尝试解决1尝试解决2 为什么使用多进程 增加应用程序的可用内存 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;我们应用中的 Activity、Service 等四大组件默认都位于一个进程里面,并且这个进程名称的默认值就是我们给应用定义的包名。 &nbsp;&nb...

qq_32517955的博客 338

2024年安卓最全Android多进程使用及其带来的问题(1),2024年华为Android面经

我这里整理了一份完整的学习思维以及Android开发知识大全PDF。当然实践出真知,即使有了学习线路也要注重实践,学习过的内容只有结合实操才算是真正的掌握。网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。需要这份系统化学习资料的朋友,可以戳这里获取一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

erhtre的博客 477

Android IPC | Android多进程模式

Android系统会为每个应用分配一定的内存,但是有些功能十分消耗内存,比如查看图片这种功能,这时为了防止该功能导致应用内存过多导致OOM,可以把该功能单独拎出来,在单独进程中进行,这样就可以获取双份内存,突破了单一应用的内存限制。关于Android的进程间通信(即IPC)有很多种方式,比如我们常用的AIDL、Socket等,而其中最重要而且最需要掌握的就是AIDL的使用和原理,简单来说它是通过Binder实现的。

编程进化论 1164

android多进程使用场景,Android-多进程

什么是进程按照操作系统中的描述:进程一般指一个执行单元,在 PC 和移动设备上指一个程序或者一个应用。我们知道了app是运行在虚拟机上,Android为每一个app都单独分配了一个虚拟机,也就是说每个app都有自己的进程,每个进程都有自己的内存空间,这样做的好处就是当我们当前的app出现问题的时候,系统仅仅杀死当前进程,不会导致其他的app受到牵连,回收之后释放出内存给其他app使用。进程是指一个...

weixin_34637138的博客 571

android 多进程应用,Android中应用多进程的整理总结

前言在计算机操作系统中,进程是进行资源分配和调度的基本单位。这对于基于Linux内核的Android系统也不例外。在Android的设计中,一个应用默认有一个(主)进程。但是我们通过配置可以实现一个应用对应多个进程。本文将试图对于Android中应用多进程做一些整理总结。android:process应用实现多进程需要依赖于android:process这个属性适用元素:Application,A...

weixin_39830313的博客 362

android多进程好处,android 多进程理解

如果一个进程占用内存超过了这个内存限制,就会报OOM的问题,很多涉及到大图片的频繁操作或者需要读取一大段数据在内存中使用时,很容易报OOM的问题。为了彻底地解决应用内存的问题,Android引入了多进程的概念,它允许在同一个应用内,为了分担主进程的压力,将占用内存的某些页面单独开一个进程,比如Flash、视频播放页面,频繁绘制的页面等。android应用实现多进程需要依赖于android:proc...

weixin_36330704的博客 617

【转】安卓多进程与进程间通信

1、安卓单进程的概念 一般情况下,一个应用程序就是一个进程,这个进程名称就是应用程序包名。我们知道进程是系统分配资源和调度的基本单位,所以每个进程都有自己独立的资源和内存空间,别的进程是不能任意访问其他进程的内存和资源的。 2、单进程存在的问题 每个进程所能使用的资源是有限,特别是内存,安卓系统对用户进程有严格的内存要求,超过此内存限制时,应用将OOM和崩溃。 ...

冯旭的博客 477

android 多进程关系,二、Android中的多进程模式

### 二、Android中的多进程模式#### 1、开启多进程模式正常情况下,Android中多进程是指一个应用中存在多个进程的情况。在Android中使用多进程只有一种方法:就是给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidManifest中指定android:process属性。这个属性的值就是进程名。这意味着不能在运行时指定...

weixin_28857713的博客 244
上一篇: Android四大组件之一——Activity
下一篇: Android-对象序列化
libolf
博客等级 码龄9年 0粉丝 4原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值