Android IPC机制(进程间通信)

一、 什么是IPC?

IPC,全称Inter-Process Communication,字面意思就是进程间通信或者跨进程通信。那什么是进程呢?它和线程有什么暧昧的关系?
进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础;早期表现为一个程序,现在可以看作线程的容器。线程是CPU调度的最小单位。�一个进程可以包含一个或者多个线程,进程向系统申请资源,线程使用进程拥有的资源。
IPC不是Android所独有的,是现代操作系统都存在的机制,对于Android来说。它是一种基于Linux内核的移动OS,他的进程通信方式不仅仅包含信号量、套接字、管道等等,还包括了Android独有的、特色的Binder机制。

二、 为什么需要多进程?应用场景...

谈到IPC的使用场景就必须提到多进程,只有面对多进程这种场景下,才需要考虑多进程通信,这是很好理解的。至于一个应用使用多进程的原因,这个可能很多,辟如有些模块由于特殊原因需要运行在独立的进程;又或者为了加大一个应用可使用的内存,通过多进程的方式申请多份内存空间。还有一个情况需要使用IPC,那就是多个应用之间进行数据共享,使用ContentProvider去查询数据时也是一种进程通讯,只是对我们来说透明化了,无法感知到。

三、 Android如何开启多进程模式?

我们可以通过给四大组件在AndroidMenifest.xml指定指定 android:process属性,亦或是在JNI层fork一个新进程,别无他法,过程看似轻松简单,却暗藏杀机,如何抵消多进程带来的负面影响?

这里有个示例:

<activity
         android:name="com.ryg.chapter_2.MainActivity"
         android:configChanges="orientation|screenSize"
         android:label="@string/app_name"
         android:launchMode="standard" >
        <intent-filter>
                <action  android:name="android.intent.action.MAIN"  />
                <category  android:name="android.intent.category.LAUNCHER"  />
         </intent-filter>
     </activity
    <activity
         android:name="com.ryg.chapter_2.SecondActivity"
         android:configChanges="screenLayout"
         android:label="@string/app_name"
         android:process=":remote"  />
    <activity
         android:name="com.ryg.chapter_2.ThirdActivity"
         android:configChanges="screenLayout"
         android:label="@string/app_name"
         android:process="com.ryg.chapter_2.remote"  /> 

眼尖的人都发现了两个android:process属性的值不一样,这意味在他们会在3个不同进程内运行。通过DDMS或者命令adb shell ps 观察。进程名看起来没有什么区别,其实它们是有区别的,进程名以“:”开头的进程会属于当前应用的私有进程,其他应用组件不能通过shareUID的方式让其在同一进程中运行,相反,就是全局进程,可以被共享。
ShareUID的方式需要两个应用拥有相同的UID以及相同的签名才可以,在这种情况下,不管他们是否跑在统一进程,他们可以互相访问对方的私有数据,譬如data目录、组件信息等等,如果是的话,那么它们还能共享内存数据,看起来就是一个应用的不同部分。

四、 奇怪现象(哎呀,奇怪了。这个值怎么变了~)

有的Android开发者会选择在Application或者静态类中储存可变的属性,静态亦或是非静态的,当你开启多进程模式,你会发现你的世界不再简单如旧。

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

我们知道Android为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。既然不是同一块内存了,那就算是使用了进程锁也会失效,因为进程锁也是不同内存对象。SharedPreferences虽然最终写入XML文件,但也有利用内存机制进行加速,在不同进程下,并发写入,也会出现问题。在开启多进程模式下,一般认为Application是程序的入口而不应该变化,但可以通过打印进程名称,得知Application会启动多次,因此用于存储公共信息并不可靠。

五、 IPC基础概念

Serializable接口、Parcelable接口以及Binder,只有熟悉这三方面的内容后,我们才能更好地理解跨进程通信的各种方式。Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable。

1.如何使用Serializable来完成对象的序列化。

想让一个对象实现序列化,只需要这个类实现Serializable接口并声明一个serialVersionUID即可,实现起来非常简单,几乎所有工作都被系统自动完成了。如何进行对象的序列化和反序列化也非常简单,只需要采用ObjectOutputStream和ObjectInputStream即可轻松实现。

/**
 * @ClassName ObjectUtil
 * @Description 对可序列对象BASE64字符串化
 * @author K.W.
 * @date 2013年9月19日 上午2:10:53
 */
public class ObjectUtil {
    public static String getBASE64String(Object obj) {
        if(obj==null){
            return null;
        }
        ByteArrayOutputStream toByte = new ByteArrayOutputStream();
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(toByte);
            oos.writeObject(obj);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 对byte[]进行Base64编码
        return Base64.encode(toByte.toByteArray());
    }
    
    public static Object getObject(String base64String) {
        byte[] base64Bytes;
        try {
            base64Bytes = Base64.decodeToByteArray(base64String);
            ByteArrayInputStream bais = new ByteArrayInputStream(base64Bytes);
            ObjectInputStream ois = new ObjectInputStream(bais);
            return ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

2.Android中如何使用Parcelable进行序列化操作

Parcelable 方法说明

Implements Parcelable的时候需要实现内部的方法:

1).writeToParcel 将对象数据序列化成一个Parcel对象(序列化之后成为Parcel对象.以便Parcel容器取出数据)

2).重写describeContents方法,默认值为0

3).Public static final Parcelable.Creator<T>CREATOR (将Parcel容器中的数据转换成对象数据) 同时需要实现两个方法:
  3.1 CreateFromParcel(从Parcel容器中取出数据并进行转换.)
  3.2 newArray(int size)返回对象数据的大小

public class Book implements Parcelable{
 private String bookName;
 private String author;
 private int publishDate;
  
 public Book(){
   
 }
  
 public String getBookName(){
  return bookName;
 }
  
 public void setBookName(String bookName){
  this.bookName = bookName;
 }
  
 public String getAuthor(){
  return author;
 }
  
 public void setAuthor(String author){
  this.author = author;
 }
  
 public int getPublishDate(){
  return publishDate;
 }
  
 public void setPublishDate(int publishDate){
  this.publishDate = publishDate;
 }
  
 @Override
 public int describeContents(){
  return 0;
 }
  
 @Override
 public void writeToParcel(Parcel out, int flags){
  out.writeString(bookName);
  out.writeString(author);
  out.writeInt(publishDate);
 }
  
 public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>(){
   
     @Override
  public Book[] newArray(int size){
   return new Book[size];
  }
   
  @Override
  public Book createFromParcel(Parcel in){
   return new Book(in);
  }
 };
  
 public Book(Parcel in){
  //如果元素数据是list类型的时候需要: lits = new ArrayList<?> in.readList(list); 
  //否则会出现空指针异常.并且读出和写入的数据类型必须相同.如果不想对部分关键字进行序列化,可以使用transient关键字来修饰以及static修饰.
  bookName = in.readString();
  author = in.readString();
  publishDate = in.readInt();
 }
}

3.Parcelable与Serializable的性能比较

首先Parcelable的性能要强于Serializable的原因我需要简单的阐述一下

1). 在内存的使用中,前者在性能方面要强于后者

2). 后者在序列化操作的时候会产生大量的临时变量,(原因是使用了反射机制)从而导致GC的频繁调用,因此在性能上会稍微逊色

3). Parcelable是以Ibinder作为信息载体的.在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable,既然是内存方面比价有优势,那么自然就要优先选择.

4). 在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上.

但是:虽然Parcelable的性能要强于Serializable,但是仍然有特殊的情况需要使用Serializable,而不去使用Parcelable,因为Parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化.(原因是在不同的Android版本当中,Parcelable可能会不同,因此数据的持久化方面仍然是使用Serializable)

六、 Binder (AIDL)

Binder 是一个可以很深入的话题,这里不打算深入探究Binder的内部。Android 开发中,Binder主要使用在Service中,包括AIDL和Messager。
我们来新建一个AIDL,体验下这个过程,还是以上面的BOOK为例子,首先建两个aidl文件:Book.aidl、IBookManager.aidl。

// Book.aidl
package com.ajb.kotlintest;
parcelable Book ;

// IBookManager.aidl
package com.ajb.kotlintest;

// Declare any non-default types here with import statements
import com.ajb.kotlintest.Book;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

build一下就有了java类文件。

package com.ajb.kotlintest;

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

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

        /**
         * Cast an IBinder object into an com.ajb.kotlintest.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.ajb.kotlintest.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.ajb.kotlintest.IBookManager))) {
                return ((com.ajb.kotlintest.IBookManager) iin);
            }
            return new com.ajb.kotlintest.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 {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.ajb.kotlintest.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.ajb.kotlintest.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.ajb.kotlintest.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.ajb.kotlintest.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.ajb.kotlintest.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.ajb.kotlintest.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.ajb.kotlintest.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

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

    public void addBook(com.ajb.kotlintest.Book book) throws android.os.RemoteException;
}

上述代码是系统生成的,它继承了IInterface这个接口,同时它自己也还是个接口,所有可以在Binder中传输的接口都需要继承IInterface接口。首先,它声明了两个方法getBookList和addBook,显然这就是我们在IBookManager.aidl中所声明的方法,同时它还声明了两个整型的id分别用于标识这两个方法,这两个id用于标识在transact过程中客户端所请求的到底是哪个方法。接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。这么来看,IBookManager这个接口的确很简单,但是我们也应该认识到,这个接口的核心实现就是它的内部类Stub和Stub的内部代理类Proxy,下面详细介绍针对这两个类的每个方法的含义。

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

asBinder
此方法用于返回当前Binder对象。

onTransact
这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。该方法的原型为 public Booleanon Transact(intcode,android.os.Parcel
data,android.os.Parcel reply,int flags)。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值的话),onTransact方法的执行过程就是这样的。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。

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

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

在服务端需要创建一个 BookManagerImpl 的对象并在 Service 的 onBind 方法中返回即可。以下就是一个服务端的典型实现。

//服务器端
public class CustomService extends Service {
    private List<Book> mBookList;

    public CustomService() {

    }

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

    private class BookManagerImpl extends 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);
                }
            }
        }

        @Override
        public void linkToDeath(DeathRecipient recipient, int flags) {
            super.linkToDeath(recipient, flags);
        }

        @Override
        public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
            return super.unlinkToDeath(recipient, flags);
        }
    }
}

Binder有两个很重要的方法linkToDeath和unlinkToDeath。

Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败。更为关键的是,如果我们不知道Binder连接已经断裂,那么客户端的功能就会受到影响。

为了解决这个问题,Binder中提供了两个配对的方法linkToDeath和unlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。那么到底如何给Binder设置死亡代理呢?也很简单。

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

//客户端(Activity)内
        private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {  
  
        @Override  
        public void binderDied() {  
            // TODO Auto-generated method stub  
            if (mBookManager == null)  
                return;  
            mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);  
            mBookManager = null;  
            // TODO:重新绑定远程服务
       bindService(new Intent("com.ajb.kotlintest.CustomService").
          setPackage("com.ajb.kotlintest"), conn, BIND_AUTO_CREATE);  


        }  
    };  
      
    private ServiceConnection conn = new ServiceConnection() {  
  
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
  
        }  
  
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            mBookManager = IBookManager.Stub.asInterface(service);  
            try {  
                service.linkToDeath(mDeathRecipient, 0);  
                Toast.makeText(getApplicationContext(), mBookManager.getName(),  
                        Toast.LENGTH_LONG).show();  
            } catch (RemoteException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }  
    }; 
//然后在onCreate先bindService一次,由上面处理重绑。

这样我们就能简单的传递信息了,但是这其中还有一些坑我们没遇到过,通过IBinder在服务端与客户端之间传递自定义对象,自定义对象需要实现Parcelable接口,就是说他们是在序列化和反序列化之间运作,服务端与客户端之间传递的对象并不是同一个对象。

5.Listener 在Binder中使用

虽然说多次,跨进程传输客户端的对象会在服务端生成不同的对象,但是生成的对象有一个共同点,就是它们底层的Binder对象是同一个,RemoteCallbackList利用这一特性,在解除注册Listener时,找到和注册时一样Binder对象的listener进行删除。RemoteCallbackList 当客户端死亡,它自动解除客户端持有的 listener,而且由于其线程同步的特性,不需要额外的线程同步工作。

添加注册和解注册的办法。

// IBookManager.aidl
package com.ajb.kotlintest;

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

在Service中,添加相关实现代码

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

@Override 
public void registerListener( IOnNewBookArrivedListener listener) 
                                           throws RemoteException {
    mListenerList. register( listener);
 }

@Override 
public void unregisterListener( IOnNewBookArrivedListener listener) 
                                           throws RemoteException { 
    mListenerList. unregister( listener); 
};

利用这些就可以在新书到来之时,通知需要知道的监听者,不需要时就解除注册即可。

七、 ContentProvider

创建一个自定义的ContentProvider很简单,只需要继承ContentProvider类并实现六个抽象方法即可:onCreate、query、update、insert、delete和getType。

onCreate
代表ContentProvider的创建,一般来说我们需要做一些初始化工作;

getType
用来返回一个Uri请求所对应的MIME类型(媒体类型),比如图片、视频等,这个媒体类型还是有点复杂的,如果我们的应用不关注这个选项,可以直接在这个方法中返回null或者** “ */* ” **;Android系统所提供的MediaStore功能就是文件类型的ContentProvider,详细实现可以参考MediaStore。

query、update、insert、delete
剩下的四个方法对应于CRUD操作,即实现对数据表的增删改查功能。

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

// DbOpenHelper. java 
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_TALBE_NAME = "user";
    private static final int DB_VERSION = 1; // 图书和用户信息表
    private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";
    private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS " + USER_TALBE_NAME + "(_id INTEGER 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
    }
}

// ContentProvider. java
public class BookProvider extends ContentProvider {
    private static final String TAG = "BookProvider";
    public static final String AUTHORITY = "com.ajb.kotlintest.provider";
    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 {
        //使用 UriMatcher 的 addURI 方法将 Uri 和 Uri_Code 关联到一起
        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();
        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_TALBE_NAME);
//ContentProvider创建时,初始化数据库。注意:这里仅仅是为了演示,实际使用中不推荐在主线程中进行耗时的数据库操作
        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");
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.d(TAG, " insert");
        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");
        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");
        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_TALBE_NAME;
                break;
            default:
                break;
        }
        return tableName;
    }
}

ContentProvider 通过 Uri 来区分外界要访问的的数据集合,为了知道外界要访问的是哪个表,我们需要为它们定义单独的 Uri 和 Uri_Code ,并将 Uri 和对应的 Uri_Code 相关联,我们可以使用 UriMatcher 的 addURI 方法将 Uri 和 Uri_Code 关联到一起。这样,当外界请求访问 BookProvider 时,我们就可以根据请求的 Uri 来得到 Uri_Code ,有了 Uri_Code 我们就可以知道外界想要访问哪个表,然后就可以进行相应的数据操作了,

接着我们需要注册这个 BookProvider,否则它无法向外界提供有效的数据。 如下所示。

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

注册了ContentProvider以后,我们就可以在外部应用中访问它了。

注意点:android:authorities 必须是唯一的,这里建议加上包名前缀。android:process让BookProvider运行在独立的进程中并给它添加了权限,这样外界应用如果想访问BookProvider,就必须声明“com.ajb.kotlin.PROVIDER”这个权限。ContentProvider的权限还可以细分为读权限和写权限,分别对应android:readPermission和android:writePermission属性,如果分别声明了读权限和写权限,那么外界应用也必须依次声明相应的权限才可以进行读/写操作,否则外界应用会异常终止。

八、Messenger

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

public class MessengerService extends Service {
    private static final String TAG = "MessengerService";

    private static class MessengerHandler extends Handler {
        private static final int MSG_FROM_CLIENT = 1000;

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_FROM_CLIENT:
                    Log.i(TAG, " receive msg from Client:" + msg.getData().getString(" msg"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

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

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

关于 Messenger的介绍就这么多,更详细的资料请查看相关网络资料。

九、 Socket

我们也可以通过 Socket 来实现进程间的通信。 Socket 也称为“套接字”,它分为流式套接字( TCP 协议)和用户数据报套接字( UDP 协议)两种。

TCP 协议是面向连接的协议,提供稳定的双向通信功能, TCP 连接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性;

而 UDP 是无连接的,提供不稳定的单向通信功能,当然 UDP 也可以实现双向通信功能。在性能上, UDP 具有更好的效率,其缺点是不保证数据一定能够正确传输,尤其是在网络拥塞的情况下。关于 TCP 和 UDP 的介绍就这么多,更详细的资料请查看相关网络资料。

至此,Android IPC机制的介绍暂告一段落了。

名称 优点 缺点 适用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件间的进程间通信
文件共享 简单易用 不适合高并发场景,并且无法做到进程间即时通信 无并发访问情形,交换简单的数据实时性不高的场景
AIDL 功能强大,支持一对多并发通信,支持实时通信 使用稍复杂,需要处理好线程同步 一对多通信且有RPC需求
Messenger 功能一般,支持一对多串行通信,支持实时通信 不能很处理高并发清醒,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 低并发的一对多即时通信,无RPC需求,或者无需返回结果的RPC需求
ContentProvider 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 一对多的进程间数据共享
Socket 功能请打,可以通过网络传输字节流,支持一对多并发实时通信 实现细节稍微有点烦琐,不支持直接的RPC 网络数据交换
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容