美文网首页android 集结号艺术探索
Android IPC机制(进程间通信)

Android IPC机制(进程间通信)

作者: kuwork | 来源:发表于2017-06-08 10:41 被阅读861次

    一、 什么是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 网络数据交换

    相关文章

      网友评论

        本文标题:Android IPC机制(进程间通信)

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