Android-0.IPC相关简介

源码下载

1.Android中的多进程模式

通过给四大组件指定android: process属性,我们可以轻易地开启多进程模式。

1.1.多进程模式示例

先看一个简单示例:
AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hgy.multprocess">
    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".SecondActivity"
            android:process=":remote" />

        <activity android:name=".ThirdActivity"
            android:process="com.hgy.multprocess.remote"/>
    </application>
</manifest>

分别为SecondActivityThirdActivity指定了process属性,并且它们的属性值不同,这意味着当前应用又增加了两个新进程。

—>让MainActivity拉起SecondActivity

  Intent intent = new Intent(MainActivity.this, SecondActivity.class);
   startActivity(intent);

—>SecondActivity再拉起ThirdActivity

  Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);
  startActivity(intent);

—>查看进程列表

C:\Users\Administrator>adb shell
shell@santoni:/ $ ps |grep "com.hgy"
u0_a156   15940 686   1518720 67812 SyS_epoll_ 0000000000 S com.hgy413.multprocess
u0_a156   16016 686   1514900 66384 SyS_epoll_ 0000000000 S com.hgy413.multprocess:remote
u0_a156   16037 686   1522216 61756 SyS_epoll_ 0000000000 S com.hgy.multprocess.remote

SecondActivity启动时,系统会为它创建一个单独的进程,进程名为"com.hgy.multprocess:remote"
ThirdActivity启动时,系统会为它创建一个单独的进程,进程名为"com.hgy.multprocess.remote"

它们都属于同一个应用,所以UID是相同的(u0_a156) ,SecondActivity使用了 android:process=":remote"“: ”的含义是指在冒号前面附加上当前的包名,这是一种简写的方法,你也可以写成android:process="com.hgy.multprocess:remote",它们表示的意义相同

同时进程名以“: ”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以“: ”开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。

我们知道Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。

这里要说明的是,两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以。在这种情况下,它们可以互相访问对方的私有数据,比如data目录、组件信息等,无论它们是否跑在同一个进程中。当然如果它们跑在同一个进程中,那么除了能共享data目录、组件信息,还可以共享内存数据,或者说它们看起来就像是一个应用的两个部分。

1.2.多进程模式的运行机制

我们新建了一个类,叫做UserManager,这个类中有一个public的静态成员变量,如下所示。

public class UserManager {
public static int sUserId = 1;
}

然后在MainActivityonCreate中我们把这个sUserId重新赋值为2,打印出这个静态变量的值后,再启动SecondActivity,在SecondActivity中我们再打印一下sUserId的值。

07-12 13:19:53.408 17092-17092/com.hgy413.multprocess E/hgy413: MainActivity sUserId: 2
07-12 13:19:56.074 17152-17152/com.hgy413.multprocess:remote E/hgy413: SecondActivity sUserId: 1

可以看到,多进程数据肯定是不共享的。Android为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。

拿我们这个例子来说,在进程 com.hgy413.multprocess和进程com.hgy413.multprocess:remote中都存在一个UserManager
类,并且这两个类是互不干扰的,在一个进程中修改sUserId的值只会影响当前进程,对其他进程不会造成任何影响。

一般来说,使用多进程会造成如下几方面的问题:
1.静态成员和单例模式完全失效。
2.线程同步机制完全失效。
3.SharedPreferences的可靠性下降。
4.Application会多次创建。

2.IPC基础概念

2.1.Serializable接口

Serializable是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化相当简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。

private static final long serialVersionUID = 8711368828010083044L

实际上,甚至这个serialVersionUID 也不是必需的,我们不声明这个serialVersionUID同样也可以实现序列化,但是这将
会对反序列化过程产生影响,具体什么影响后面再介绍。

User类就是一个实现了Serializable接口的类,它是可以被序列化和反序列化的,如下所示。

public class User implements Serializable {
    private static final long serialVersionUID = 519067123721295773L;
    public int userId;
    public String userName;
    public boolean isMale;
    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }
}

通过Serializable方式来实现对象的序列化,实现起来非常简单,几乎所有工作都被系统自动完成了。如何进行对象的序列化和反序列化也非常简单,只需要采用ObjectOutputStreamObjectInputStream即可轻松实现。下面举个简单的例子。
–>MainActivity中加入:(openFileOutput方法生成的文件位于/data/data/包名/files/下)

        try {
            User user = new User(0, "jake", true);
            ObjectOutputStream out = new ObjectOutputStream(openFileOutput("cache.txt", MODE_PRIVATE));
            out.writeObject(user);
            out.close();
            Log.e("hgy413", "MainActiviy write user success");
        } catch (IOException e) {
        }

–>SecondActivity中读取:

        try {
            ObjectInputStream in = new ObjectInputStream(openFileInput("cache.txt"));
            User newUser = (User)in.readObject();
            in.close();
            Log.e("hgy413", "SecondActivity read user success:" + newUser.userName);
        } catch (Exception e) {
            e.printStackTrace();
        }

–>打印结果:

07-12 14:14:42.828 19907-19907/com.hgy413.multprocess E/hgy413: MainActiviy write user success
07-12 14:14:47.374 19965-19965/com.hgy413.multprocess:remote E/hgy413: SecondActivity read user success:jake

上述代码演示了采用Serializable方式序列化对象的典型过程,很简单,只需要把实现了Serializable接口的User对象写到文件中就可以快速恢复了,恢复后的对象newUser和user的内容完全一样,当然,很明显两者并不是同一个对象

serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。

serialVersionUID的详细工作机制是这样的:

序列化的时候,系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其他中介)

当反序列化的时候,系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化。
否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变。这个时候是无法正常反序列化的,因此会报如下错误:

java.io.InvalidClassException: Main; local class incompatible: stream
classdesc serialVersionUID = 8711368828010083044,local class serialVersionUID = 8711368828010083043。

一般来说,我们应该手动指定serialVersionUID 的值,比如1L。

当我们手动指定了它以后,就可以在很大程度上避免反序列化过程的失败。比如当版本升级后,我们可能删除了某个成
员变量也可能增加了一些新的成员变量,这个时候我们的反向序列化过程仍然能够成功,程序仍然能够最大限度地恢复数据,相反,如果不指定serialVersionUID的话,程序则会挂掉。

以下两点需要特别提一下:

1.首先静态成员变量属于类不属于对象,所以不会参与序列化过程。
2.其次用transient关键字标记的成员变量不参与序列化过程。

2.2.Parcelable接口

Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过IntentBinder传递。下面的示例是一个典型的用法。

public class ParcelableUser implements Parcelable {

    public int userId;
    public String userName;
    public boolean isMale;
    
    public ParcelableUser(int userId,String userName,boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }
    
    protected ParcelableUser(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readByte() != 0;
    }

    public static final Creator<ParcelableUser> CREATOR = new Creator<ParcelableUser>() {
        @Override
        public ParcelableUser createFromParcel(Parcel in) {
            return new ParcelableUser(in);
        }

        @Override
        public ParcelableUser[] newArray(int size) {
            return new ParcelableUser[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(userId);
        dest.writeString(userName);
        dest.writeByte((byte) (isMale ? 1 : 0));
    }
}

以上代码,只要写完成员变量,alt+enter就可以全部自动生成了!,Android Studi
–>MainActivity中加入:启动SecondActivity时传入

        findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                ParcelableUser parcelableUser = new ParcelableUser(0, "parceljake", true);
                intent.putExtra("extra_parcel", parcelableUser);
                Log.e("hgy413", "MainActiviy write parcelableUser success");
                startActivity(intent);
            }
        });

–>SecondActivity中读取:

        ParcelableUser parcelableUser = getIntent().getParcelableExtra("extra_parcel");
        Log.e("hgy413", "SecondActivity read parcelableUser success:" + parcelableUser.userName);

–>打印结果:

07-12 14:55:21.654 21732-21732/com.hgy413.multprocess E/hgy413: MainActiviy write parcelableUser success
07-12 14:55:22.029 21792-21792/com.hgy413.multprocess:remote E/hgy413: SecondActivity read parcelableUser success:parceljake

这里先说一下ParcelParcel 内部包装了可序列化的数据,可以在Binder中自由传输。从上述代码中可以看出,在序列化过程中需要实现的功能有序列化反序列化内容描述

序列化writeToParcel方法来完成,最终是通过Parcel 中的一系列write方法来完成的
反序列化CREATOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel 的一系列read方法来完成反序列化过程
内容描述由descri beContents方法来完成,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1

系统已经为我们提供了许多实现了Parcelable接口的类,它们都是可以直接序列化的,比如intentBundleBitmap等,同时ListMap也可以序列化,前提是它们里面的每个元素都是可序列化的。

相对于SerializableParcelable的效率要高得多,Parcelable主要用在内存序列化上,通过Parcelable将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍显复杂,因此在这两种情况下建议大家使用Serializable

2.3 Binder

直观来说,Binder是Android中的一个类,它继承了IBinder接口。

从IPC角度来说,BinderAndroid中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder

Android Framework角度来说,BinderServiceManager连接各种Manager(ActivityManagerWindowManager,等等)和相应ManagerService的桥梁

从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

Android开发中,Binder主要用在Service中,包括AIDLMessenger,其中普通Service中的Binder不涉及进程间通信,所以较为简单,无法触及Binder的核心,而Messenger的底层其实是AIDL,所以这里选择用AIDL来分析Binder的工作机制。

新建aidl的package,然后新建三个文件Book. javaBook. aidlIBookManager. aidl ,代码如下所示。
Android Studio要先建Book. java,然后建aidl文件:
在这里插入图片描述
AIDL File填写名字的时候先随便填写(因为如果直接填Book为名字会报错,提示同名冲突,只有先创建完之后再RenameBook.aidl才不会报错)
在这里插入图片描述
为什么要有Book.aidl类,因为只有将类在aidl中声明时候,AIDL才能调用Book
Book. aidl

// Book.aidl
package com.hgy.multprocess.aidl;

parcelable Book;

要让AIDL能够传送自定义类需要 ①继承Parcelable接口 ②创建同名.aidl文件声明自己

Book. java

public class Book implements Parcelable {

    public int bookId;
    public String bookName;

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    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];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
}

IBookManager.aidl

// IBookManager.aidl
package com.hgy413.multprocess;

import com.hgy.multprocess.aidl.Book;

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

上面三个文件中
Book. java是一个表示图书信息的类,它实现了Parcelable接口。
Book.aidlBook类在AIDL中的声明。
IBookManager. aidl 是我们定义的一个接口,里面有两个方法:getBookListaddBook,其中getBookList用于从远程服务端获取图书列表,而addBook用于往图书列表中添加一本书,当然这两个方法仅做为示例。

我们可以看到,尽管Book类已经和IBookManager位于相同的包中,但是在IBookManager中仍然要导入Book类,这就是AIDL的特殊之处。
特别注意要导入import com.hgy.multprocess.aidl.Book;,Android Studio对AIDL没有自动错误提示的,不导入直接编译会报错的。

2.3.1 aidl自动生成类分析

generatedJava目录下的 com.hgy.multprocess.aidl包中自动生成了IBookManager. java的类:

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

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

        /**
         * Cast an IBinder object into an com.hgy.multprocess.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.hgy.multprocess.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.hgy.multprocess.aidl.IBookManager))) {
                return ((com.hgy.multprocess.aidl.IBookManager) iin);
            }
            return new com.hgy.multprocess.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.hgy.multprocess.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(descriptor);
                    com.hgy.multprocess.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.hgy.multprocess.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.hgy.multprocess.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.hgy.multprocess.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.hgy.multprocess.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.hgy.multprocess.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.hgy.multprocess.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.hgy.multprocess.aidl.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.hgy.multprocess.aidl.Book book) throws android.os.RemoteException;
}

它继承了android.os.IInterface这个接口,同时它自己也还是个接口,所有可以在Binder中传输的接口都需要继承IInterface接口

这个类的结构其实很简单,首先,它声明了两个方法接口getBookListaddBook,显然这就是我们在IBookManag er. aidl 中所声明的方法。

public java.util.List<com.hgy.multprocess.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.hgy.multprocess.aidl.Book book) throws android.os.RemoteException;

所以我们可以在服务端来重载Stub,实现真正的功能,后面会多处提到这个重载Stub的真正实现类

private final IBookManager.Stub mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
synchronized (mBookList) {
return mBookList;
}
}
@Override
public void addBook(Book book) throws RemoteException {
synchronized (mBookList) {
if (!mBookList.contains(book)) {
mBookList.add(book);
}
}
}
}

IInterface接口的好处是,在Service端通过子类化IBookManager.Stub实现真正的函数功能,
IBookManager asInterface(IBinder obj)又提供了将IBinder转换成IBookManager 对象,从而在Client端调用addBook这些接口函数。

同时它还声明了两个整型的id分别用于标识这两个方法,这两个id用于标识在transact过程中客户端所请求的到底是哪个方法。

public static abstract class Stub extends android.os.Binder implements com.hgy.multprocess.aidl.IBookManager {
....
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
	  case TRANSACTION_getBookList: {
	  		....
	  }
	  case TRANSACTION_addBook: {
	  		....
	  }
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。

这个接口的核心实现就是它的内部类StubStub的内部代理类Proxy,下面详细介绍针对这两个类的每个方法的含义。

private static final java.lang.String DESCRIPTOR = "com.hgy.multprocess.aidl.IBookManager";

DESCRIPTORBinder的唯一标识,一般用当前Binder的类名表示

        public static com.hgy.multprocess.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.hgy.multprocess.aidl.IBookManager))) {
                return ((com.hgy.multprocess.aidl.IBookManager) iin); // 本进程返回sub本身
            }
            return new com.hgy.multprocess.aidl.IBookManager.Stub.Proxy(obj);// 跨进程返回Proxy
        }

asInterface方向用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象

这两个类都是implements IBookManager的实现体, StubService端重载真正实现相关接口,参看前面mBinder,而Stub.proxy会跨进程转发到StubonTransact方法。

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

此方法用于返回当前Binder对象, 其实就是Service端对 Stub的真正实现类,参看前面mBinder

        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.hgy.multprocess.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(descriptor);
                    com.hgy.multprocess.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.hgy.multprocess.aidl.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

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

方法原型为 public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException

服务端通过code可以确定客户端所请求的目标方法是什么(如TRANSACTION_addBook
接着从data中取出目标方法所需的参数(如 _arg0 = aidl.Book.CREATOR.createFromParcel(data);
然后执行目标方法,当目标方法执行完毕后,就向reply中写入返回值(如this.addBook(_arg0);

onTransact方法的执行过程就是这样的。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。

以下是Stub.proxy的远程调用函数

 public java.util.List<com.hgy.multprocess.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.hgy.multprocess.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.hgy.multprocess.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:

首先创建该方法所需要的输入型Parcel 对象_data、输出型Parcel 对象_reply和返回值对象List
然后把该方法的参数信息写入_data中(如果有参数的话),接着调用transact方法来发起RPC(远程过程调用)请求, 同时当前线程挂起
然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_reply中的数据。

            public void addBook(com.hgy.multprocess.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();
                }
            }

这个方法运行在客户端,它的执行过程和getBookList是一样的,addBook没有返回值,所以它不需要从_reply中取出返回值。

有两点还是需要额外说明一下:
首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;
其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。

为了更好地说明Binder,下面给出一个Binder的工作机制图
在这里插入图片描述

这两个函数其实也给了我们怎么远程调用的示例封装,virtualapp就用了这种方式

从上述分析过程来看,我们完全可以不提供AIDL文件即可实现Binder,之所以提供AIDL文件,是为了方便系统为我们生成代码。

系统根据AIDL文件生成Java文件的格式是固定的,我们可以抛开AIDL文件直接写一个Binder出来,接下来我们就介绍如何手动写一个Binder

2.3.2 aidl手动生成类

参考上面系统自动生成的IBookManager. java这个类的代码,可以发现这个类是相当有规律的,根据它的特点,我们完全可以自己写一个和它一模一样的类出来,然后这个不借助AIDL文件的Binder就完成了。

但是我们发现系统生成的类看起来结构不清晰,可以试着对它进行结构上的调整,手动实现一个Binder可以通过如下步骤来完成:
(1)声明一个AIDL性质的接口,只需要继承IInterface接口即可,IInterface接口中只有一个asBinder方法。这个接口的实现如下:

public interface IBookManager extends IInterface {

    static final java.lang.String DESCRIPTOR = "com.hgy.multprocess.aidl.IBookManager";

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

    public List<Book> getBookList() throws RemoteException;
    public void addBook(Book book) throws RemoteException;
}

可以看到,在接口中声明了一个Binder描述符和另外两个id,这两个id分别表示的是getBookListaddBook方法,这段代码原本也是系统生成的,我们仿照系统生成的规则去手动书写这部分代码。

(2)实现Stub类和Stub类中的Proxy代理类,这段代码我们可以自己写,但是写出来后会发现和系统自动生成的代码是一样的,因此这个Stub类我们只需要参考系统生成的代码即可,只是结构上需要做一下调整,调整后的代码如下所示。

public class BookManagerImpl extends Binder implements IBookManager{

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

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

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

    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_getBookList: {
                data.enforceInterface(DESCRIPTOR);
                List<Book> result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(result);
                return true;
            }
            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);
                Book arg0;
                if ((0 != data.readInt())) {
                    arg0 = Book.CREATOR.createFromParcel(data);
                } else {
                    arg0 = null;
                }
                this.addBook(arg0);
                reply.writeNoException();
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    @Override
    public List<Book> getBookList() throws RemoteException {
        // TODO 待实现
        return null;
    }

    @Override
    public void addBook(Book book) throws RemoteException {
        // TODO 待实现
    }

    private static class Proxy implements IBookManager {

        private IBinder mRemote;

        Proxy(IBinder remote) {
            mRemote = remote;
        }

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

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

        @Override
        public List<Book> getBookList() throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            List<Book> result;
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(TRANSACTION_getBookList, data, reply, 0);
                reply.readException();
                result = reply.createTypedArrayList(Book.CREATOR);
            } finally {
                reply.recycle();
                data.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);
                }
                mRemote.transact(TRANSACTION_addBook, data, reply, 0);
                reply.readException();
            } finally {
                reply.recycle();
                data.recycle();
            }
        }
    }
}

通过将上述代码和系统生成的代码对比,可以发现简直是一模一样的。也许有人会问:既然和系统生成的一模一样,那我们为什么要手动去写呢?我们在实际开发中完全可以通过AIDL文件让系统去自动生成,手动去写的意义在于可以让我们更加理解Binder的工作原理,同时也提供了一种不通过AIDL文件来实现Binder的新方式。也就是说,AIDL文件并不是实现Binder的必需品。

如果是我们手写的Binder,那么在服务端只需要创建一个BookManagerImpl 的对象并在ServiceonBind方法中返回即可。

最后,是否手动实现Binder没有本质区别,二者的工作原理完全一样,AIDL文件的本质是系统为我们提供了一种快速实现
Binder的工具,仅此而已。

2.3.3 异常断开处理

我们知道,Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断
裂(称之为Binder死亡),会导致我们的远程调用失败。Binder中提供了两个配对的方法linkToDeathunlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。

首先,声明一个DeathRecipient对象。DeathRecipient是一个接口,其内部只有一个方法binderDied,我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied方法,然后我们就可以移出之前绑定的binder代理并重新绑定远程服务:

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

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

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

其中linkToDeath的第二个参数是个标记位,我们直接设为0即可。经过上面两个步骤,就给我们的Binder设置了死亡代理,当Binder死亡的时候我们就可以收到通知了。另外,通过Binder的方法isBinderAlive也可以判断Binder是否死亡。

3.IPC通用方式

3.1 使用Bundle

四大组件中的三大组件(ActivityServiceReceiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地在不同的进程间传输。

基于这一点,当我们在一个进程中启动了另一个进程的ActivityServiceReceiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。

当然,我们传输的数据必须能够被序列化,比如基本类型、实现了Parcellable接口的对象、实现了Serializable接口的对象以及一些Android支持的特殊对象,具体内容可以看Bundle这个类,就可以看到所有它支持的类型。Bundle不支持的类型我们无法通过它在进程间传递数据。

3.2 使用文件共享

参考前面的User对象读写,很明显,不支持并发读写这种多线程操作。

3.3 使用Messenger

Messenger可以翻译为信使,顾名思义,通过它可以在不同进程中传递Messenger对象,在Messenger中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,为什么这么说呢,我们大致看一下Messenger这个类的构造方法就明白了。下面是Messenger的两个构造方法,从构造方法的实现上我们可以明显看出AIDL的痕迹,不管是IMessenger还是Stub. asInterface,这种使用方法都表明它的底层是AIDL

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

Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以更简便地进行进程间通信。同时,由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。实现一个Messenger有如下几个步骤,分为服务端和客户端。

1. 服务端进程

首先,我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在ServiceonBind中返回这个Messenger对象底层的Binder即可。

2. 客户端进程

客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为Messenger对象。

如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过MessagereplyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。

示例1.服务端无法回应客户端
首先看服务端的代码,这是服务端的典型代码,可以看到MessengerHandler用来处理客户端发送的消息,并从消息中取出客户端发来的文本信息。

mMessenger是一个Messenger对象,它和MessengerHandler相关联,并在onBind方法中返回它里面的Binder对象,可以看出,这里Messenger的作用是将客户端发送的消息传递给MessengerHandler处理。

public class MessengerService extends Service {

    private static class MessageHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MyConstants.MSG_FROM_CLIENT:
                    Log.i("hgy413","receive msg from Client:" + msg.getData().getString("msg"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    private final Messenger mMessenger = new Messenger(new MessageHandler());

    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

然后,注册service,让其运行在单独的进程中:

        <service
            android:name=".messenger.MessengerService"
            android:process=":remote_messenger"/>

接下来再看看客户端的实现,客户端的实现也比较简单,首先需要绑定远程进程的MessengerService,绑定成功后,根据服务端返回的binder对象创建Messenger对象并使用此对象向服务端发送消息。下面的代码在Bundle中向服务端发送了一句话,在上面的服务端代码中会打印出这句话。

public class MessengerActivity extends AppCompatActivity {

    private Messenger mService;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
            Bundle data = new Bundle();
            data.putString("msg","hello,this is client.");
            msg.setData(data);
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

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

–>打印结果:

com.hgy.multprocess.multprocess:remote_messenger E/hgy413: receive msg from Client:hello,this is client.

通过上面的例子可以看出,在Messenger中进行数据传递必须将数据放入Message中,而MessengerMessage都实现了Parcelable接口,因此可以跨进程传输。

简单来说,Message中所支持的数据类型就是Messenger所支持的传输类型。实际上,通过Messenger来传输Messag e,Message中能使用的载体只有whatarg1arg2Bundle以及replyTo, object字段仅支持系统提供的实现了Parcelable接口的对象(不支持自定义的Parcelable接口的对象)。

示例1.服务端回应客户端
还是采用上面的例子,但是稍微做一下修改,每当客户端发来一条消息,服务端就会自动回复一条嗯,你的消息我已经收到,稍后会回复你。

服务端只需要修改MessengerHandler,当收到消息后,会立即回复一条消息给客户端。

	
                    Log.e(BuildConfig.TAG,"receive msg from Client:" + msg.getData().getString("msg"));
                    Messenger client = msg.replyTo;
                    Message relpyMessage = Message.obtain(null, MyConstants.MSG_FROM_SERVICE);
                    Bundle bundle = new Bundle();
                    bundle.putString("reply", "嗯,你的消息我已经收到,稍后会回复你。");
                    relpyMessage.setData(bundle);
                    try {
                        client.send(relpyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
       

接着再看客户端的修改,为了接收服务端的回复,客户端也需要准备一个接收消息的MessengerHandler,如下所示。

    private Messenger mGetReplyMessenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MyConstants.MSG_FROM_SERVICE:
                    Log.e(BuildConfig.TAG, "receive msg from Service:" + msg.getData().getString("reply"));
                    break;
                default:
                    break;
            }
        }
    });

很明显,我们还要把mGetReplyMessenger 赋值到replyTo(服务端从replyTo取得并回复):

mService = new Messenger(service);
Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg","hello,this is client.");
msg.setData(data);
//注意下面这句
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}

–>打印结果:

com.hgy.multprocess.multprocess:remote_messenger E/hgy413: receive msg from Client:hello,this is client.
com.hgy.multprocess.multprocess E/hgy413: receive msg from Service:嗯,你的消息我已经收到,稍后会回复你。

下面给出一张Messenger的工作原理图以方便更好地理解Messenger
在这里插入图片描述

3.4 使用AIDL

Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如
果有大量的并发请求,那么用Messenger就不太合适了。

同时,Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。

前面我们介绍了Binder的概念,大家对Binder也有了一定的了解,在Binder的基础上我们可以更加容易地理解AIDL。这里先介绍使用 AIDL来进行进程间通信的流程,分为服务端客户端两个方面。

3.4.1 AIDL服务端

服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可(参看上面的Binder示例,服务端利用IBookManager.aidl, 重载了IBookManager.Stub相关接口)。

3.4.2 AIDL客户端

客户端所要做事情就稍微简单一些,首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

3.4.3 AIDL接口的创建

首先看AIDL接口的创建,如下所示,我们创建了一个后缀为AIDL的文件,在里面声明了一个接口和两个接口方法。

import com.hgy.multprocess.aidl.Book;

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

AIDL文件中,并不是所有的数据类型都是可以使用的,那么到底AIDL文件支持哪些数据类型呢?如下所示。

  1. 基本数据类型(intlongcharbooleandouble等)
  2. StringCharSequence
  3. List:只支持ArrayList,里面每个元素都必须能够被AIDL支持
  4. Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括keyvalue
  5. Parcelable:所有实现了 Parcelable接口的对象
  6. AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

以上6种数据类型就是AIDL所支持的所有类型,其中自定义的 Parcelable对象和AIDL对象必须要显式import进来,不管它们是否和当前的AIDL文件位于同一个包内,不然就会报错。

另外一个需要注意的地方是,如果AIDL文件中用到了自定义的 Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为 Parcelable类型。在上面的IBookManager. aidl 中,我们用到了Book这个类,所以,我们必须要创建Book. aidl ,然后在里面添加如下内容:

// Book.aidl
package com.hgy.multprocess.aidl;

parcelable Book;

Book.java

public class Book implements Parcelable {

    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    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];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }
    @Override
    public String toString() {
        return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
    }
}

我们需要注意,AIDL中每个实现了 Parcelable接口的类都需要按照上面那种方式去创建相应的AIDL文件并声明那个类为Parcelable

在Android Studio中,Book.aidlBook.java不但同名,而且要同包名,Book.java位于com.hgy.multprocess.aidl下,
那么Book.aidl就位于aidl.com.hgy.multprocess.aidl下(最上层多了一层aidl目录)。不然编译就会报错

除此之外,AIDL中除了基本数据类型,其他类型的参数必须标上方向:inout或者inout输入型参数输出型参数或者输入输出型参数),我们要根据实际需要去指定参数类型,不能一概使用out或者inout,因为这在底层实现是有开销的。

最后,AIDL接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口。

AIDL的包结构在服务端和客户端要保持一致,否则运行会出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化,程序也就无法正常运行。

3.4.4 AIDL远程服务端Service的实现

上面讲述了如何定义AIDL接口,接下来我们就需要实现这个接口了。我们先创建一个Service,称为BookManagerService,代码如下:

public class BookManagerService extends Service {
    private static final String TAG = "BMS";
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

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

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "Ios"));
    }

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

上面是一个服务端Service的典型实现,首先在onCreate中初始化添加了两本图书的信息,然后创建了一个Binder对象并在onBind中返回它,这个对象继承自IBookManager.Stub并实现了它内部的AIDL方法

注意这里采用了CopyOnWriteArrayList,这个CopyOnWriteArrayList支持并发读/写。在前面我们提到,AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,而我们这里直接使用CopyOnWriteArrayList来进行自动的线程同步。

前面我们提到,AIDL中能够使用的List只有ArrayList,但是我们这里却使用了CopyOnWriteArrayList(注意它不是继承自ArrayList),为什么能够正常工作呢?这是因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端。所以,我们在服务端采用CopyOnWriteArrayList是完全可以的。和此类似的还有ConcurrentHashMap

然后我们需要在XML中注册这个Service,让它运行在独立进程中

        <service
            android:name=".aidl.BookManagerService"
            android:process=":remote_bookmanager"/>
3.4.5 AIDL客户端的实现

客户端的实现就比较简单了,首先要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了

public class BookManagerActivity extends Activity {

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> list = bookManager.getBookList();
                Log.e(BuildConfig.TAG,"query book list,list type:" + list.getClass().getCanonicalName());
                Log.e(BuildConfig.TAG,"query book list:" + list.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

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

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

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

绑定成功以后,会通过bookManager去调用getBookList方法,然后打印出所获取的图书信息。需要注意的是,服务端的方法有可能需要很久才能执行完毕,这个时候下面的代码就会导致ANR,这一点是需要注意的,后面会再介绍这种情况,之所以先这么写是为了让大家更好地了解AIDL的实现步骤。

运行程序,log 如下所示:

E/hgy413: query book list,list type:java.util.ArrayList
E/hgy413: query book list:[[bookId:1,bookName:Android],[bookId:2,bookName:Ios]]

第一行表明虽然我们在服务端返回的是CopyOnWriteArrayList类型,但是客户端收到的仍然是ArrayList类型,这也证实了我们在前面所做的分析。第二行表明客户端成功地得到了服务端的图书列表信息。

3.4.6 AIDL难点
3.4.6.1 绑定监听回调

我们接着再调用一下另外一个接口addBook,我们在客户端给服务端添加一本书,然后再获取一次,看程序是否能够正常工作。还是上面的代码,客户端在服务连接后,在onServiceConnected中做如下改动:

                List<Book> list = bookManager.getBookList();
                Book newBook = new Book(3, "andriod开发");
                bookManager.addBook(newBook);
                List<Book> newlist = bookManager.getBookList(); // 重新获取
                Log.e(BuildConfig.TAG,"query book list:" + newlist.toString());

很明显,我们成功的向服务端添加了一本书

E/hgy413: query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios], [bookId:3, bookName:andriod开发]]

现在我们考虑一种情况,假设有一种需求:用户不想时不时地去查询图书列表了,太累了,于是,他去问图书馆,“当有新书时能不能把书的信息告诉我呢?”。大家应该明白了,这就是一种典型的观察者模式,每个感兴趣的用户都观察新书,当新书到的时候,图书馆就通知每一个对这本书感兴趣的用户,这种模式在实际开发中用得很多,下面我们就来模拟这种情形。

首先,我们需要提供一个AIDL接口,每个用户都需要实现这个接口并且向图书馆申请新书的提醒功能,当然用户也可以随时取消这种提醒。之所以选择AIDL接口而不是普通接口,是因为AIDL中无法使用普通接口。

这里我们创建一个IOnNewBookArrivedListener. aidl 文件,我们所期望的情况是:当服务端有新书到来时,就会通知每一个已经申请提醒功能的用户。从程序上来说就是调用所有IOnNewBookArrivedListener对象中的onNewBookArrived方法,并把新书的对象通过参数传递给客户端,内容如下所示。

// IOnNewBookArrivedListener.aidl
package com.hgy.multprocess.aidl;
import com.hgy.multprocess.aidl.Book;
interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}

除了要新加一个AIDL接口,还需要在原有的接口中添加两个新方法用于注册和反注册,代码如下所示。

package com.hgy.multprocess.aidl;

import com.hgy.multprocess.aidl.Book;
import com.hgy.multprocess.aidl.IOnNewBookArrivedListener;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}

接着,服务端中Service的实现也要针对这两个方法给出实现,同时,在BookManagerService中还开启了一个线程,每隔5s就向书库中增加一本新书并通知所有感兴趣的用户,整个代码如下所示。

public class BookManagerService extends Service {
    private static final String TAG = "BMS";
    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
    private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<IOnNewBookArrivedListener>();
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

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

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            if (!mListenerList.contains(listener)) {
                mListenerList.add(listener);
            }
            Log.e(BuildConfig.TAG,"registerListener,size:" + mListenerList.size());
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            if (mListenerList.contains(listener)) {
                mListenerList.remove(listener);
               Log.e(BuildConfig.TAG,"unregister listener succeed.");
            } else {
                Log.e(BuildConfig.TAG,"not found,can not unregister.");
            }
            Log.e(BuildConfig.TAG,"unregisterListener,size:" + mListenerList.size());
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "Ios"));
        new Thread(new ServiceWorker()).start();
    }

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

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }

    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            // do background processing here....
            while (!mIsServiceDestoryed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                int bookId = mBookList.size() + 1;
                Book newBook = new Book(bookId,"new book#" + bookId);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        private void onNewBookArrived(Book book) throws RemoteException{
            mBookList.add(book);
            for (int i=0; i<mListenerList.size(); i++) {
                IOnNewBookArrivedListener listener = mListenerList.get(i);
                listener.onNewBookArrived(book);
            }
        }
    }
}

最后,我们还需要修改一下客户端的代码,主要有两方面:
首先客户端要注册IOnNewBookArrivedListener 到远程服务端,这样当有新书时服务端才能通知当前客户端,同时我们要在Activity退出时解除这个注册

另一方面,当有新书时,服务端会回调客户端的IOnNewBookArrivedListener对象中的onNewBookArrived方法,但是这个方法是在客户端的Binder线程池中执行的,因此,为了便于进行UI操作,我们需要有一个Handler可以将其切换到客户端的主线程中去执行,客户端的代码修改如下:

public class BookManagerActivity extends Activity {

    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
    private IBookManager mRemoteBookManager;
    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.e(BuildConfig.TAG,"receive new book :" + msg.obj);
                    break;
                default:
                    break;
            }
        }
    };

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                mRemoteBookManager = bookManager;
                List<Book> list = bookManager.getBookList();
                Book newBook = new Book(3, "andriod开发");
                bookManager.addBook(newBook);
                List<Book> newlist = bookManager.getBookList();
                Log.e(BuildConfig.TAG,"query book list:" + newlist.toString());
                bookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

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

    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
        }
    };

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

    @Override
    protected void onDestroy() {
        if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) {
            try {
                mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
        super.onDestroy();
    }
}

打印结果如下:

E/hgy413: query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios], [bookId:3, bookName:andriod开发]]
E/hgy413: registerListener,size:1
E/hgy413: onNewBookArrived,notify listener:com.hgy.multprocess.aidl.IOnNewBookArrivedListener$Stub$Proxy@928ecf2
E/hgy413: receive new book :[bookId:4, bookName:new book#4]
.....

可以看出,客户端的确收到了服务端每5s一次的新书推送.

3.4.6.2 RemoteCallbackList解除绑定监听回调

从上面的代码可以看出,当BookManagerActivity 关闭时,我们会在onDestroy中去解除已经注册到服务端的listener,这就相当于我们不想再接收图书馆的新书提醒了,按back键退出BookManagerActivity ,下面是打印出的log:

E/hgy413: not found,can not unregister.

从上面的log 可以看出,程序没有像我们所预期的那样执行。在解注册的过程中,服务端竟然无法找到我们之前注册的那个listener,在客户端我们注册和解注册时明明传递的是同一个listener啊!

其实,这是必然的,这种解注册的处理方式在日常开发过程中时常使用到,但是放到多进程中却无法奏效,因为Binder会把客户端传递过来的对象重新转化并生成一个新的对象。虽然我们在注册和解注册过程中使用的是同一个客户端对象,但是通
Binder传递到服务端后,却会产生两个全新的对象。别忘了对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么AIDL中的自定义对象都必须要实现Parcelable接口的原因。

那么到底我们该怎么做才能实现解注册功能呢?答案是使用RemoteCallbackList ,RemoteCallbackList系统专门提供的用于删除跨进程listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口, 这点从它的声明就可以看出,因为所有的AIDL接口都继承自IInterface接口

public class RemoteCallbackList<E extends IInterface>

它的工作原理很简单,在它的内部有一个Map结构专门用来保存所有的AIDL回调,这个MapkeyIBinder类型,valueCallback类型,如下所示。

ArrayMap<IBinder,Callback> mCallbacks = new ArrayMap<IBinder,Callback>();

其中Callback中封装了真正的远程listener。当客户端注册listener的时候,它会把这个listener的信息存入mCallbacks中,其中keyvalue分别通过下面的方式获得:

IBinder key= listener.asBinder()
Callback value = new Callback(listener,cookie)

虽然说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一个共同点,那就是它们底层的Binder对象是同一个,也就是key相同,利用这个特性,就可以实现上面我们无法实现的功能。

当客户端解注册的时候,我们只要遍历服务端所有的listener,找出那个和解注册listener具有相同Binder对象的服务端listener并把它删掉即可,这就是RemoteCallbackList为我们做的事情。

同时RemoteCallbackList还有一个很有用的功能,那就是当客户端进程终止后,它能够自动移除客户端所注册的listener。另外,RemoteCallbackList内部自动实现了线程同步的功能,所以我们使用它来注册和解注册时,不需要做额外的线程同步工作。

RemoteCallbackList使用起来很简单,我们要对BookManagerService做一些修改,首先要创建一个RemoteCallbackList对象来替代之前的CopyOnWriteArrayList,如下所示。

   private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();

然后修改registerListenerunregisterListener这两个接口的实现:
特别注意beginBroadcastfinishBroadcast要成对出现

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
            Log.e(BuildConfig.TAG,"registerListener,size:" + mListenerList.beginBroadcast());
            mListenerList.finishBroadcast();
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
            Log.e(BuildConfig.TAG,"unregisterListener,size:" + mListenerList.beginBroadcast());
            mListenerList.finishBroadcast();
        }

接着要修改onNewBookArrived方法,当有新书时,我们就要通知所有已注册的listener,如下所示。

        private void onNewBookArrived(Book book) throws RemoteException{
            mBookList.add(book);
            int count = mListenerList.beginBroadcast();
            for (int i=0; i<count; i++) {
                IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
                Log.e(BuildConfig.TAG,"onNewBookArrived,notify listener:" + listener);
                listener.onNewBookArrived(book);
            }
        }

输出结果:

com.hgy.multprocess.multprocess:remote_bookmanager E/hgy413: registerListener,size:1
com.hgy.multprocess.multprocess:remote_bookmanager E/hgy413: unregisterListener,size:0
3.4.6.3 避免ANR耗时操作

我们知道,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间地阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR

因此,如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去访问远程方法

由于客户端的onServiceConnectedonServiceDisconnected方法都运行在UI线程中,所以也不可以在它们里面直接调用服务端的耗时方法,这点要尤其注意
由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作

下面我们稍微改造一下服务端的getBookList方法,我们假定这个方法是耗时的,那么服务端可以这么实现:

        public List<Book> getBookList() throws RemoteException {
            SystemClock.sleep(5000);
            return mBookList;
        }

然后在客户端中放一个按钮,单击它的时候就会调用服务端的getBookList方法

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView1"
        android:onClick="onButton1Click"
        android:text="Button" />
</LinearLayout>
    public void onButton1Click(View view) {
        try {
            List<Book> newList = mRemoteBookManager.getBookList();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

–>可以预知,连续单击几次,客户端就ANR
在这里插入图片描述
我们只需要把调用放在非UI线程即可避免这种ANR

    public void onButton1Click(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    List<Book> newList = mRemoteBookManager.getBookList();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

同理,当远程服务端需要调用客户端的listener中的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池,所以,我们同样不可以在服务端中调用客户端的耗时方法。比如针对BookManagerServiceonNewBookArrived方法,如下所示。在它内部调用了客户端的IOnNewBookArrivedListener中的onNewBookArrived方法,如果客户端的这个onNewBookArrived方法
比较耗时的话,那么请确保BookManagerService中的onNewBookArrived运行在非UI线程中,否则将导致服务端无法响应。

另外,由于客户端的IOnNewBookArrivedListener中的onNewBookArrived方法运行在客户端的Binder线程池中,所以要借助Handler切换到UI线程

3.4.6.4 Bindler重连

第一种方法是给Binder设置DeathRecipient监听,当Binder死亡时,我们会收到binderDied方法的回调,在binderDied方法中我们可以重连远程服务


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

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                mRemoteBookManager = bookManager;
                mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);

另一种方法是在onServiceDisconnected中重连远程服务。

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteBookManager = null;
            // TODO:这里重新绑定远程Service
        }

它们的区别在于:onServicesconnected在客户端的UI线程中被回调,而binderDied在客户端的Binder线程池中
被回调。也就是说,在binderDied方法中我们不能访问UI。

3.4.6.5 权限问题

默认情况下,我们的远程服务任何人都可以连接,但这应该不是我们愿意看到的,所以我们必须给服务加入权限验证功能,权限验证失败则无法调用服务中的方法。

第一种方法,我们可以在onBind中进行验证,验证不通过就直接返回null ,这样验证失败的客户端直接无法绑定服务。
至于验证方式可以有多种,比如使用permission验证。使用这种验证方式,我们要先在AndroidMenifest中声明所需的权限,比如:

    <permission
        android:name="com.hgy.multprocess.permission.ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal" />

定义了权限以后,就可以在BookManagerServiceonBind方法中做权限验证了,如下所示:

    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.hgy.multprocess.permission.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mBinder;
    }

如果我们自己内部的应用想绑定到我们的服务中,只需要在它的AndroidMenifest中申请所需的权限:

    <uses-permission android:name="com.hgy.multprocess.permission.ACCESS_BOOK_SERVICE" />

第二种方法,我们可以在服务端的onTransact方法中进行权限验证,如果验证失败就直接返回false,至于具体的验证方式
有很多,可以采用permission验证,具体实现方式和第一种方法一样。还可以采用UidPid来做验证,通过getCallingUidgetCallingPid可以拿到客户端所属应用的UidPid,通过这两个参数我们可以做一些验证工作,比如验证包名。

    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            int check = checkCallingOrSelfPermission("com.hgy.multprocess.permission.ACCESS_BOOK_SERVICE");
            if (check == PackageManager.PERMISSION_DENIED) {
                return false;
            }

            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            if (!packageName.startsWith("com.hgy")) {
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }

3.5 使用ContentProvider

3.5.1 使用ContentProvider方式

ContentProviderAndroid中提供的专门用于不同应用间进行数据共享的方式,从这一点来看,它天生就适合进程间通信。和Messenger一样,ContentProvider的底层实现同样也是Binder

系统预置了许多ContentProvider,比如通讯录信息、日程表信息等,要跨进程访问这些信息,只需要通过ContentResolverqueryupdateinsertdelete方法即可。

我们来实现一个自定义的ContentProvider,并演示如何在其他应用中获取ContentProvider中的数据从而实现进程间通信这一目的。

首先,我们创建一个ContentProvider,名字就叫BookProvider。实现六个抽象方法即可:onCreatequeryupdateinsertdeletegetType

onCreate代表ContentProvider的创建,一般来说我们需要做一些初始化工作.
getType用来返回一个Uri 请求所对应的MIME类型, 比如图片、视频等.
剩下的四个方法对应于CRUD操作,即实现对数据表的增删改查功能.

根据Binder的工作原理,我们知道这六个方法均运行在ContentProvider的进程中,除了onCreate由系统回调并运行在主线程里,其他五个方法均由外界回调并运行在Binder线程池

ContentProvider主要以表格的形式来组织数据,并且可以包含多个表,对于每个表格来说,它们都具有行和列的层次性,行往往对应一条记录,而列对应一条记录中的一个字段,这点和数据库很类似。

除了表格的形式,ContentProvider还支持文件数据,比如图片、视频等。文件数据和表格数据的结构不同,因此处理这类数据时可以在ContentProvider中返回文件的句柄给外界从而让文件来访问ContentProviderr中的文件信息。

虽然ContentProvider的底层数据看起来很像一个SQLite数据库,但是ContentProvider对底层的数据存储方式没有任何要求,我们既可以使用SQLite数据库,也可以使用普通的文件,甚至可以采用内存中的一个对象来进行数据的存储。

下面看一个最简单的示例,它演示了ContentProvider的工作流程:
在下面的代码中,我们什么都没干,尽管如此,这个BookProvider也是可以工作的,只是它无法向外界提供有效的数据而已。

public class BookProvider extends ContentProvider {
    private static final String TAG = "BookProvider";

    @Override
    public boolean onCreate() {
        Log.d(TAG, "onCreate, current thread:" + Thread.currentThread().getName());
        return false;
    }

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

    @Override
    public String getType( Uri uri) {
        Log.d(TAG, "getType, current thread:" + Thread.currentThread().getName());
        return null;
    }

    @Override
    public Uri insert( Uri uri, ContentValues values) {
        Log.d(TAG, "insert, current thread:" + Thread.currentThread().getName());
        return null;
    }

    @Override
    public int delete( Uri uri, String selection, String[] selectionArgs) {
        Log.d(TAG, "delete, current thread:" + Thread.currentThread().getName());
        return 0;
    }

    @Override
    public int update( Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        Log.d(TAG, "update, current thread:" + Thread.currentThread().getName());
        return 0;
    }
}

接着我们需要注册这个BookProvider,如下所示

        <provider
            android:name=".provider.BookProvider"
            android:authorities="com.hgy.multprocess.provider.BookProvider"
            android:permission="com.hgy.multprocess.PROVIDER"
            android:process=":provider"/>

android:name指定类所在路径。
android:authoritiesContentProvider的唯一标识,通过这个属性外部应用就可以访问我们的BookProvider,所以一定要是唯一的,一般建议是命名前加上包名前缀。
android:permission指定了一个权限,这样外界应用如果想访问BookProvider,就必须声明com.hgy.multprocess.PROVIDER这个权限。

注册了ContentProvider以后,我们就可以在外部应用中访问它了。注意要声明权限:

<uses-permission android:name="com.hgy.multprocess.PROVIDER" />
public class ProviderActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);
        Uri uri = Uri.parse("content://com.hgy.multprocess.provider.BookProvider");
        getContentResolver().query(uri, null,null, null, null);
        getContentResolver().query(uri, null,null, null, null);
        getContentResolver().query(uri, null,null, null, null);
    }
}

在上面的代码中,我们通过ContentResolver对象的query方法去查询BookProvider中的数据,们运行后看一下log:

multprocess:provider D/BookProvider: onCreate, current thread:main
multprocess.multprocess:provider D/BookProvider: query, current thread:Binder:26610_2
multprocess:provider D/BookProvider: query, current thread:Binder:26610_1
multprocess:provider D/BookProvider: query, current thread:Binder:26610_2

可以看出,BookProvider中的onCreate在主线程main中调用,query方法被调用了三次,并且这三次调用不在同一个线程中,可以看出,它们运行在一个Binder线程池中,前面提到updateinsertdelete方法同样也运行在Binder线程池中。另
外,onCreate在主线程main中调用,也就是UI线程,所以我们不能在onCreate中做耗时操作。

接下来,在上面的基础上,我们继续完善BookProvider,从而使其能够对外部应用提供数据。现在我们要提供一个BookProvider,外部应用可以通过BookProvider来访问图书信息,为了更好地演示ContentProvider的使用,用户还可以通过BookProvider访问到用户信息。

为了完成上述功能,我们需要一个数据库来管理图书和用户信息,代码如下:

public class DbOpenHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "book_provider.db";
    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TABLE_NAME = "user";

    private static final int DB_VERSION = 3;

    // _id integer primary key 表示设置字段_id,类型为int整形,为主键
    // name为string
    private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "
            + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";

    // _id integer primary key 表示设置字段_id,类型为int整形,为主键
    // name为string, sex为int
    private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "
            + USER_TABLE_NAME + "(_id INTERGER PRIMARY KEY," + "name TEXT," +"sex INT)";

    public DbOpenHelper(Context context) {
        super(context, DB_NAME, null, DB_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) {
        // TODO ignored
    }
}

注意DB_NAME的命名是book_provider.db,如果你写成book_provider_db,可能就挂了。
我们借助SQLiteOpenHelper来管理数据库的创建、升级和降级。下面我们就要通过BookProvider向外界提供上述数据库中的信息了。我们知道,ContentProvider通过Uri 来区分外界要访问的的数据集合,在本例中支持外界对BookProvider中的book表和user表进行访问

为了知道外界要访问的是哪个表,我们需要为它们定义单独的UriUri_Code,并将Uri 和对应的Uri_Code相关联,我们可以使用UriMatcheraddURI方法将UriUri_Code关联到一起。这样,当外界请求访问BookProvider时,我们就可以根据请求的Uri 来得到Uri_Code,有了Uri_Code我们就可以知道外界想要访问哪个表,然后就可以进行相应的数据操作了,具体代码如下所示。

public class BookProvider extends ContentProvider {
    private static final String TAG = "BookProvider";
    // 和xml中的android:authorities一样
    public static final String AUTHORITY = "com.hgy.multprocess.provider.BookProvider";
    public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
    public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
    public static final int BOOK_URI_CODE = 0;
    public static final int USER_URI_CODE = 1;
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
        sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
    }

从上面代码可以看出,我们分别为book表和user表指定了Uri ,分别为content://com.hgy.multprocess.provider.BookProvider/bookcontent://com.hgy.multprocess.provider.BookProvider/user, 这两个Uri 所关联的Uri_Code分别为0和1:

sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);

根据Uri先取出Uri_Code,根据Uri_Code再得到数据表的名称,知道了外界要访问的表,接下来就可以响应外界的增删改查请求了。

    private String getTableName(Uri uri) {
        String tableName = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_URI_CODE:
                tableName = DbOpenHelper.BOOK_TABLE_NAME;
                break;
            case USER_URI_CODE:
                tableName = DbOpenHelper.USER_TABLE_NAME;
                break;
            default:
                break;
        }
        return tableName;
    }

接着,我们就可以实现queryupdateinsertdelete方法了。如下是query方法的实现,首先我们要从Uri 中取出外界要访问的表的名称,然后根据外界传递的查询参数就可以进行数据库的查询操作了,这个过程比较简单。

   public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Log.d(TAG, "query, current thread:" + Thread.currentThread().getName());
        String table =getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI" + uri);
        }
        return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
    }

另外三个方法的实现思想和query是类似的,只有一点不同,那就是updateinsertdelete方法会引起数据源的改变,这个时候我们需要通过ContentResolvernotifyChange方法来通知外界当前ContentProvider中的数据已经发生改变。

要观察一个ContentProvider中的数据改变情况,可以通过ContentResolverregisterContentObserver方法来注册观察者,通过unregisterContentObserver方法来解除观察者。BookProvider的完整代码如下:

public class BookProvider extends ContentProvider {
    private static final String TAG = "BookProvider";
    // 和xml中的android:authorities一样
    public static final String AUTHORITY = "com.hgy.multprocess.provider.BookProvider";
    public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
    public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
    public static final int BOOK_URI_CODE = 0;
    public static final int USER_URI_CODE = 1;
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
        sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
    }

    private Context mContext;
    private SQLiteDatabase mDb;

    @Override
    public boolean onCreate() {
        Log.d(TAG, "onCreate, current thread:" + Thread.currentThread().getName());
        mContext = getContext();
        //ContentProvider创建时,初始化数据库。注意:这里仅仅是为了演示,实际使用中不推荐在主线程
        initProviderData();
        return true;
    }

    private void initProviderData() {
        mDb = new DbOpenHelper(mContext).getWritableDatabase();
        mDb.execSQL("delete from " + DbOpenHelper.BOOK_TABLE_NAME);
        mDb.execSQL("delete from " + DbOpenHelper.USER_TABLE_NAME);
        mDb.execSQL("insert into book values(3, 'Android');");
        mDb.execSQL("insert into book values(4, 'Ios');");
        mDb.execSQL("insert into book values(5, 'Html5');");
        mDb.execSQL("insert into user values(1, 'jake', 1);");
        mDb.execSQL("insert into user values(2, 'jasmine', 0);");
    }

    @Override
    public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Log.d(TAG, "query, current thread:" + Thread.currentThread().getName());
        String table =getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI" + uri);
        }
        return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
    }

    @Override
    public String getType( Uri uri) {
        Log.d(TAG, "getType, current thread:" + Thread.currentThread().getName());
        return null;
    }

    @Override
    public Uri insert( Uri uri, ContentValues values) {
        Log.d(TAG, "insert, current thread:" + Thread.currentThread().getName());
        String table =getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI" + uri);
        }
        mDb.insert(table, null, values);
        mContext.getContentResolver().notifyChange(uri, null);
        return uri;
    }

    @Override
    public int delete( Uri uri, String selection, String[] selectionArgs) {
        Log.d(TAG, "delete, current thread:" + Thread.currentThread().getName());
        String table =getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI" + uri);
        }
        int count = mDb.delete(table, selection, selectionArgs);
        if (count > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return count;
    }

    @Override
    public int update( Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        Log.d(TAG, "update, current thread:" + Thread.currentThread().getName());
        String table =getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI" + uri);
        }
        int row = mDb.update(table, values, selection, selectionArgs);
        if (row > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return row;
    }

    private String getTableName(Uri uri) {
        String tableName = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_URI_CODE:
                tableName = DbOpenHelper.BOOK_TABLE_NAME;
                break;
            case USER_URI_CODE:
                tableName = DbOpenHelper.USER_TABLE_NAME;
                break;
            default:
                break;
        }
        return tableName;
    }
}

需要注意的是,queryupdateinsertdelete四大方法是存在多线程并发访问的,因此方法内部要做好线程同步。在本例中,由于采用的是SQLite并且只有一个SQLiteDatabase的连接,所以可以正确应对多线程的情况。具体原因是SQLiteDatabase内部对数据库的操作是有同步处理的,接着我们在外部访问一下它,看看是否能够正常工作。

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);
        Uri bookUri = Uri.parse("content://com.hgy.multprocess.provider.BookProvider/book");
        ContentValues values = new ContentValues();
        values.put("_id", 6);
        values.put("name", "程序的艺术");
        getContentResolver().insert(bookUri, values);
        Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
        while (bookCursor.moveToNext()) {
            Log.d(TAG, "query book:" + bookCursor.getInt(0) + ":" + bookCursor.getString(1));
        }
        bookCursor.close();

        Uri userUri = Uri.parse("content://com.hgy.multprocess.provider.BookProvider/user");
        Cursor userCursor = getContentResolver().query(userUri, new String[]{"_id", "name","sex"}, null, null, null);
        while (userCursor.moveToNext()) {
            Log.d(TAG, "query user:" + userCursor.getInt(0)
                    + ":" + userCursor.getString(1)
                    + ":" + userCursor.getInt(2));
        }
        userCursor.close();
    }

Cursor 可以理解为游标,简单的说,它就是得到table表中满足条件的所有行,AbstractCursor.java为它的具体实现体,它的遍历操作就是移动内部的mPos,默认mPos为-1

    public AbstractCursor() {
        mPos = -1;
    }
    public final boolean moveToFirst() {
        return moveToPosition(0);
    }
    public final boolean moveToLast() {
        return moveToPosition(getCount() - 1);
    }
    public final boolean moveToNext() { // 开始如果调用moveToNext,就是-1 + 1 刚好是moveToFirst
        return moveToPosition(mPos + 1);
    }
    public final boolean moveToPosition(int position) {
        // Make sure position isn't past the end of the cursor
        final int count = getCount();
        if (position >= count) {
            mPos = count;
            return false;
        }

        // Make sure position isn't before the beginning of the cursor
        if (position < 0) {
            mPos = -1;
            return false;
        }

ContentProvider除了支持对数据源的增删改查这四个操作,还支持自定义调用,这个过程是通过ContentResolveCall 方法和ContentProviderCall 方法来完成的。

3.5.2 ContentProvider源码分析
3.5.2.1 新ContentProvider进程启动流程

当一个应用启动时,入口方法为ActivityThreadmain方法

下面分析单实例的ContentProvider的启动过程。统一使用API23做调试分析:

 Cursor bookCursor = getContentResolver().query(

–>

    public ContentResolver getContentResolver() {
        return mBase.getContentResolver();
    }

mBaseandroid.app.ContextImpl
–>

    public ContentResolver getContentResolver() {
        return mContentResolver;
    }

在这里插入图片描述
getContentResolver方法获取的实际上是ApplicationContentResolver对象,ApplicationContentResolver类继承了ContentResolver并实现了ContentResolver中的抽象方法。
搜索可以知道,在mBase初始化时mContentResolver也跟着初始化
–>

   private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
            Display display, Configuration overrideConfiguration, int createDisplayWithId) {
              ....
              mContentResolver = new ApplicationContentResolver(this, mainThread, user);
            }

之后进入query方法, 这里会调用到acquireUnstableProvider,最终会调用到ActivityThread::acquireProvider,来获取ContentProvider

   public final @Nullable Cursor query(final @NonNull Uri uri, @Nullable String[] projection,
            @Nullable String selection, @Nullable String[] selectionArgs,
            @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
        Preconditions.checkNotNull(uri, "uri");
        IContentProvider unstableProvider = acquireUnstableProvider(uri);
        --protected IContentProvider acquireUnstableProvider(Context c, String auth) {
            return mMainThread.acquireProvider(c,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), false);
        }

在这里插入图片描述
ActivityThreadacquireProvider方法的源码如下所示:

 public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
        final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
        if (provider != null) {
            return provider;
        }

        // There is a possible race here.  Another thread may try to acquire
        // the same provider at the same time.  When this happens, we want to ensure
        // that the first one wins.
        // Note that we cannot hold the lock while acquiring and installing the
        // provider since it might take a long time to run and it could also potentially
        // be re-entrant in the case where the provider is in the same process.
        IActivityManager.ContentProviderHolder holder = null;
        try {
            holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), auth, userId, stable);
        } catch (RemoteException ex) {
        }
        if (holder == null) {
            Slog.e(TAG, "Failed to find provider info for " + auth);
            return null;
        }

        // Install provider will increment the reference count for us, and break
        // any ties in the race.
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }

上面的代码首先会从ActivityThread中查找是否已经存在目标ContentProvider了,如果存在就直接返回,并增加引用计数, ActivityThread中通过mProviderMap来存储已经启动的ContentProvider对象。

final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
    public final IContentProvider acquireExistingProvider(
            Context c, String auth, int userId, boolean stable) {
        synchronized (mProviderMap) {
            final ProviderKey key = new ProviderKey(auth, userId);
            final ProviderClientRecord pr = mProviderMap.get(key);
            if (pr == null) {
                return null;
            }

            IContentProvider provider = pr.mProvider;
            IBinder jBinder = provider.asBinder();
            if (!jBinder.isBinderAlive()) {
                // The hosting process of the provider has died; we can't
                // use this one.
                Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
                        + ": existing object's process dead");
                handleUnstableProviderDiedLocked(jBinder, true);
                return null;
            }

            // Only increment the ref count if we have one.  If we don't then the
            // provider is not reference counted and never needs to be released.
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc != null) {
                incProviderRefLocked(prc, stable);
            }
            return provider;
        }
    }

如果目前ContentProvider没有启动,acquireExistingProvider 返回null, 那么就发送一个进程间请求给AMS让其启动目标ContentProvider

        try {
            holder = ActivityManagerNative.getDefault().getContentProvider(
                    getApplicationThread(), auth, userId, stable);
        } catch (RemoteException ex) {
        }

–>
在这里插入图片描述
这时调试器要附加到system_process进程,并切换,用于观察AMS,mRemote.transact会调到ActivityManagerServiceonTransact中。
在这里插入图片描述
ActivityManagerServiceonTransact会调回它所在进程的ActivityManagerNativeonTransact

 case GET_CONTENT_PROVIDER_TRANSACTION: {
           .....
          ContentProviderHolder cph = getContentProvider(app, name, userId, stable);

getContentProvider对应IActivityManager的接口,也就是ActivityManagerService的接口

  public final ContentProviderHolder getContentProvider(
            IApplicationThread caller, String name, int userId, boolean stable) {
        ....
        return getContentProviderImpl(caller, name, null, stable, userId);
    }

–>

 private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, boolean stable, int userId) {
             .....
              // If the provider is not already being launched, then get it
                // started.
                if (i >= N) {
   					....
                            proc = startProcessLocked(cpi.processName,
                                    cpr.appInfo, false, 0, "content provider",
                                    new ComponentName(cpi.applicationInfo.packageName,
                                            cpi.name), false, false, false);
            }

那么AMS是如何启动ContentProvider的呢?我们知道,ContentProvider被启动时会伴随着进程的启动,在AMS中,首先会启动ContentProvider所在的进程,然后再启动ContentProvider。由上可知启动进程是由AMSstartProcessLocked方法来完成的。其内部主要是通过Processstart方法来完成一个新进程的启动,
新进程启动后其入口方法为ActivityThreadmain方法

 public static void main(String[] args) {
   ....
 	    ActivityThread thread = new ActivityThread();
        thread.attach(false);
          ...
        Looper.loop();
 }

可以看到,ActivityThreadmain方法是一个静态方法,在它内部首先会创建ActivityThread 的实例并调用attach方法来进行一系列初始化,接着就开始进行消息循环了。

ActivityThreadattach方法会将ApplicationThread对象通过AMSattachApplication方法跨进程传递给AMS

final ApplicationThread mAppThread = new ApplicationThread();
private void attach(boolean system) {
...
    mgr.attachApplication(mAppThread);
    ...
}

所以我们在AMSattachApplication方法下个断点
在这里插入图片描述
AMSattachApplication方法调用了attachApplicationLocked方法

 private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid) {
             ....
             final String processName = app.processName;
             ...
              thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
            }

调试到这里,我们可以看到新进程的名字了:
在这里插入图片描述
attachApplicationLocked方法又调用了ApplicationThreadbindApplication,注意这个过程也是进程间调用
ApplicationThreadbindApplication会发送一个BIND_APPLICATION类型的消息给mH,mH是一个Handler,它收到消息后会调用ActivityThreadhandleBindApplication方法
ActivityThreadhandleBindApplication方法则完成了Application的创建以及ContentProvider的创建,所以要调入新进程,我们只需新建一个我们自定义的Application,在构造函数中waitForDebugger, 我们就可以附加到新ContentProvider进程了,如下:
在这里插入图片描述
调用方来自ActivityThread,它创建Application对象

  Application app = data.info.makeApplication(data.restrictedBackupMode, null);
   mInitialApplication = app;
 // don't bring up providers in restricted mode; they may depend on the
 // app's custom Application class
 if (!data.restrictedBackupMode) {
     List<ProviderInfo> providers = data.providers;
     if (providers != null) {
       installContentProviders(app, providers);

在它的后面设置一个断点,就可以继续调试新ContentProvider进程启动流程了,之后调用到installContentProviders

    private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<IActivityManager.ContentProviderHolder> results =
            new ArrayList<IActivityManager.ContentProviderHolder>();

        for (ProviderInfo cpi : providers) {
            if (DEBUG_PROVIDER) {
                StringBuilder buf = new StringBuilder(128);
                buf.append("Pub ");
                buf.append(cpi.authority);
                buf.append(": ");
                buf.append(cpi.name);
                Log.i(TAG, buf.toString());
            }
            IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }

        try {
            ActivityManagerNative.getDefault().publishContentProviders(
                getApplicationThread(), results);
        } catch (RemoteException ex) {
        }
    }

installContentProviders完成了ContentProvider的启动工作,它的实现如上所示。首先会遍历当前进程的ProviderInfo的列表并一一调用install Provider方法来启动它们,接着将已经启动的ContentProvider发布到AMS中,AMS会把它们存储在ProviderMap中,这样一来外部调用者就可以直接从AMS中获取ContentProvider了。

下面看一下ContentProvider对象的创建过程,在install Provider方法中有下面一段代码,其通过类加载器完成了ContentProvider对象的创建:

  final java.lang.ClassLoader cl = c.getClassLoader();
                localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
                provider = localProvider.getIContentProvider();
                if (provider == null) {
                    Slog.e(TAG, "Failed to instantiate class " +
                          info.name + " from sourceDir " +
                          info.applicationInfo.sourceDir);
                    return null;
                }
                if (DEBUG_PROVIDER) Slog.v(
                    TAG, "Instantiating local provider " + info.name);
                // XXX Need to create the correct context for this provider.
                localProvider.attachInfo(c, info);

在上述代码中,除了完成ContentProvider对象的创建,还会通过ContentProviderattachInfo方法来调用它的onCreate方法,如下所示。

    private void attachInfo(Context context, ProviderInfo info, boolean testing) {
        mNoPerms = testing;

        /*
         * Only allow it to be set once, so after the content service gives
         * this to us clients can't change it.
         */
        if (mContext == null) {
            mContext = context;
            if (context != null) {
                mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
                        Context.APP_OPS_SERVICE);
            }
            mMyUid = Process.myUid();
            if (info != null) {
                setReadPermission(info.readPermission);
                setWritePermission(info.writePermission);
                setPathPermissions(info.pathPermissions);
                mExported = info.exported;
                mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
                setAuthorities(info.authority);
            }
            ContentProvider.this.onCreate();
        }
    }

到此为止,ContentProvider已经被创建并且其onCreate方法也已经被调用,这意味着ContentProvider已经启动完成了。

之后我们回到ActivityThreadhandleBindApplication方法,

 if (!data.restrictedBackupMode) {
                List<ProviderInfo> providers = data.providers;
                if (providers != null) {
                    installContentProviders(app, providers); // `ContentProvider`已经被创建并且其`onCreate`方法也已经被调用
            
                }
            }
		   ......

            try {
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {

mInstrumentation.callApplicationOnCreate(app);调用ApplicationonCreate方法,

可以看出 ContentProvider类的onCreate方法要先于ApplicationonCreate方法启动

这时,ContentProvider已经成功启动,并且其所在进程的Application也已经启动,这意味着ContentProvider所在的进程已经完成了整个的启动过程,然后其他应用就可以通过AMS来访问这个ContentProvider了。

拿到了ContentProvider以后,就可以通过它所提供的接口方法来访问它了。需要注意的是,这里的ContentProvider并不是原始的ContentProvider,而是ContentProvider的Binder类型的对象IContentProviderIContentProvider的具体实现是ContentProviderNativeContentProvider.Transport,其中ContentProvider.Transport继承了ContentProviderNative

因此其他应用调用IContentProviderquery方法时最终会以进程间通信的方式调用到ContentProvider.Transportquery方法,它的实现如下所示。

  public Cursor query(String callingPkg, Uri uri, String[] projection,
                String selection, String[] selectionArgs, String sortOrder,
                ICancellationSignal cancellationSignal) {
            validateIncomingUri(uri);
            uri = getUriWithoutUserId(uri);
            if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
                // The caller has no access to the data, so return an empty cursor with
                // the columns in the requested order. The caller may ask for an invalid
                // column and we would not catch that but this is not a problem in practice.
                // We do not call ContentProvider#query with a modified where clause since
                // the implementation is not guaranteed to be backed by a SQL database, hence
                // it may not handle properly the tautology where clause we would have created.
                if (projection != null) {
                    return new MatrixCursor(projection, 0);
                }

                // Null projection means all columns but we have no idea which they are.
                // However, the caller may be expecting to access them my index. Hence,
                // we have to execute the query as if allowed to get a cursor with the
                // columns. We then use the column names to return an empty cursor.
                Cursor cursor = ContentProvider.this.query(uri, projection, selection,
                        selectionArgs, sortOrder, CancellationSignal.fromTransport(
                                cancellationSignal));
                if (cursor == null) {
                    return null;
                }

                // Return an empty cursor for all columns.
                return new MatrixCursor(cursor.getColumnNames(), 0);
            }

很显然,ContentProvider.Transportquery方法调用了ContentProviderquery方法,query方法的执行结果再通过Binder返回给调用者,这样一来整个调用过程就完成了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值