Android IPC機制(進程間通信)

一、 什么是IPC?

IPC,全稱Inter-Process Communication,字面意思就是進程間通信或者跨進程通信。那什么是進程呢?它和線程有什么曖昧的關系?
進程是系統(tǒng)進行資源分配和調度的基本單位,是操作系統(tǒng)結構的基礎;早期表現(xiàn)為一個程序,現(xiàn)在可以看作線程的容器。線程是CPU調度的最小單位。?一個進程可以包含一個或者多個線程,進程向系統(tǒng)申請資源,線程使用進程擁有的資源。
IPC不是Android所獨有的,是現(xiàn)代操作系統(tǒng)都存在的機制,對于Android來說。它是一種基于Linux內(nèi)核的移動OS,他的進程通信方式不僅僅包含信號量、套接字、管道等等,還包括了Android獨有的、特色的Binder機制。

二、 為什么需要多進程?應用場景...

談到IPC的使用場景就必須提到多進程,只有面對多進程這種場景下,才需要考慮多進程通信,這是很好理解的。至于一個應用使用多進程的原因,這個可能很多,辟如有些模塊由于特殊原因需要運行在獨立的進程;又或者為了加大一個應用可使用的內(nèi)存,通過多進程的方式申請多份內(nèi)存空間。還有一個情況需要使用IPC,那就是多個應用之間進行數(shù)據(jù)共享,使用ContentProvider去查詢數(shù)據(jù)時也是一種進程通訊,只是對我們來說透明化了,無法感知到。

三、 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"  /> 

眼尖的人都發(fā)現(xiàn)了兩個android:process屬性的值不一樣,這意味在他們會在3個不同進程內(nèi)運行。通過DDMS或者命令adb shell ps 觀察。進程名看起來沒有什么區(qū)別,其實它們是有區(qū)別的,進程名以“:”開頭的進程會屬于當前應用的私有進程,其他應用組件不能通過shareUID的方式讓其在同一進程中運行,相反,就是全局進程,可以被共享。
ShareUID的方式需要兩個應用擁有相同的UID以及相同的簽名才可以,在這種情況下,不管他們是否跑在統(tǒng)一進程,他們可以互相訪問對方的私有數(shù)據(jù),譬如data目錄、組件信息等等,如果是的話,那么它們還能共享內(nèi)存數(shù)據(jù),看起來就是一個應用的不同部分。

四、 奇怪現(xiàn)象(哎呀,奇怪了。這個值怎么變了~)

有的Android開發(fā)者會選擇在Application或者靜態(tài)類中儲存可變的屬性,靜態(tài)亦或是非靜態(tài)的,當你開啟多進程模式,你會發(fā)現(xiàn)你的世界不再簡單如舊。

一般來說,使用多進程會造成如下幾方面的問題:
**(1) 靜態(tài)成員和單例模式完全失效。 **
**(2) 線程同步機制完全失效。 **
**(3) SharedPreferences 的可靠性下降。 **
(4) Application會多次創(chuàng)建。

我們知道Android為每一個應用分配了一個獨立的虛擬機,或者說為每個進程都分配一個獨立的虛擬機,不同的虛擬機在內(nèi)存分配上有不同的地址空間,這就導致在不同的虛擬機中訪問同一個類的對象會產(chǎn)生多份副本。既然不是同一塊內(nèi)存了,那就算是使用了進程鎖也會失效,因為進程鎖也是不同內(nèi)存對象。SharedPreferences雖然最終寫入XML文件,但也有利用內(nèi)存機制進行加速,在不同進程下,并發(fā)寫入,也會出現(xiàn)問題。在開啟多進程模式下,一般認為Application是程序的入口而不應該變化,但可以通過打印進程名稱,得知Application會啟動多次,因此用于存儲公共信息并不可靠。

五、 IPC基礎概念

Serializable接口、Parcelable接口以及Binder,只有熟悉這三方面的內(nèi)容后,我們才能更好地理解跨進程通信的各種方式。Serializable和Parcelable接口可以完成對象的序列化過程,當我們需要通過Intent和Binder傳輸數(shù)據(jù)時就需要使用Parcelable或者Serializable。

1.如何使用Serializable來完成對象的序列化。

想讓一個對象實現(xiàn)序列化,只需要這個類實現(xiàn)Serializable接口并聲明一個serialVersionUID即可,實現(xiàn)起來非常簡單,幾乎所有工作都被系統(tǒng)自動完成了。如何進行對象的序列化和反序列化也非常簡單,只需要采用ObjectOutputStream和ObjectInputStream即可輕松實現(xiàn)。

/**
 * @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的時候需要實現(xiàn)內(nèi)部的方法:

1).writeToParcel 將對象數(shù)據(jù)序列化成一個Parcel對象(序列化之后成為Parcel對象.以便Parcel容器取出數(shù)據(jù))

2).重寫describeContents方法,默認值為0

3).Public static final Parcelable.Creator<T>CREATOR (將Parcel容器中的數(shù)據(jù)轉換成對象數(shù)據(jù)) 同時需要實現(xiàn)兩個方法:
  3.1 CreateFromParcel(從Parcel容器中取出數(shù)據(jù)并進行轉換.)
  3.2 newArray(int size)返回對象數(shù)據(jù)的大小

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){
  //如果元素數(shù)據(jù)是list類型的時候需要: lits = new ArrayList<?> in.readList(list); 
  //否則會出現(xiàn)空指針異常.并且讀出和寫入的數(shù)據(jù)類型必須相同.如果不想對部分關鍵字進行序列化,可以使用transient關鍵字來修飾以及static修飾.
  bookName = in.readString();
  author = in.readString();
  publishDate = in.readInt();
 }
}

3.Parcelable與Serializable的性能比較

首先Parcelable的性能要強于Serializable的原因我需要簡單的闡述一下

1). 在內(nèi)存的使用中,前者在性能方面要強于后者

2). 后者在序列化操作的時候會產(chǎn)生大量的臨時變量,(原因是使用了反射機制)從而導致GC的頻繁調用,因此在性能上會稍微遜色

3). Parcelable是以Ibinder作為信息載體的.在內(nèi)存上的開銷比較小,因此在內(nèi)存之間進行數(shù)據(jù)傳遞的時候,Android推薦使用Parcelable,既然是內(nèi)存方面比價有優(yōu)勢,那么自然就要優(yōu)先選擇.

4). 在讀寫數(shù)據(jù)的時候,Parcelable是在內(nèi)存中直接進行讀寫,而Serializable是通過使用IO流的形式將數(shù)據(jù)讀寫入在硬盤上.

但是:雖然Parcelable的性能要強于Serializable,但是仍然有特殊的情況需要使用Serializable,而不去使用Parcelable,因為Parcelable無法將數(shù)據(jù)進行持久化,因此在將數(shù)據(jù)保存在磁盤的時候,仍然需要使用后者,因為前者無法很好的將數(shù)據(jù)進行持久化.(原因是在不同的Android版本當中,Parcelable可能會不同,因此數(shù)據(jù)的持久化方面仍然是使用Serializable)

六、 Binder (AIDL)

Binder 是一個可以很深入的話題,這里不打算深入探究Binder的內(nèi)部。Android 開發(fā)中,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;
}

上述代碼是系統(tǒng)生成的,它繼承了IInterface這個接口,同時它自己也還是個接口,所有可以在Binder中傳輸?shù)慕涌诙夹枰^承IInterface接口。首先,它聲明了兩個方法getBookList和addBook,顯然這就是我們在IBookManager.aidl中所聲明的方法,同時它還聲明了兩個整型的id分別用于標識這兩個方法,這兩個id用于標識在transact過程中客戶端所請求的到底是哪個方法。接著,它聲明了一個內(nèi)部類Stub,這個Stub就是一個Binder類,當客戶端和服務端都位于同一個進程時,方法調用不會走跨進程的transact過程,而當兩者位于不同進程時,方法調用需要走transact過程,這個邏輯由Stub的內(nèi)部代理類Proxy來完成。這么來看,IBookManager這個接口的確很簡單,但是我們也應該認識到,這個接口的核心實現(xiàn)就是它的內(nèi)部類Stub和Stub的內(nèi)部代理類Proxy,下面詳細介紹針對這兩個類的每個方法的含義。

asInterface(android.os.IBinderobj)
用于將服務端的Binder對象轉換成客戶端所需的AIDL接口類型的對象,這種轉換過程是區(qū)分進程的,如果客戶端和服務端位于同一進程,那么此方法返回的就是服務端的Stub對象本身,否則返回的是系統(tǒng)封裝后的Stub.proxy對象。

asBinder
此方法用于返回當前Binder對象。

onTransact
這個方法運行在服務端中的Binder線程池中,當客戶端發(fā)起跨進程請求時,遠程請求會通過系統(tǒng)底層封裝后交由此方法來處理。該方法的原型為 public Booleanon Transact(intcode,android.os.Parcel
data,android.os.Parcel reply,int flags)。服務端通過code可以確定客戶端所請求的目標方法是什么,接著從data中取出目標方法所需的參數(shù)(如果目標方法有參數(shù)的話),然后執(zhí)行目標方法。當目標方法執(zhí)行完畢后,就向reply中寫入返回值(如果目標方法有返回值的話),onTransact方法的執(zhí)行過程就是這樣的。需要注意的是,如果此方法返回false,那么客戶端的請求會失敗,因此我們可以利用這個特性來做權限驗證,畢竟我們也不希望隨便一個進程都能遠程調用我們的服務。

Proxy#getBookList
這個方法運行在客戶端,當客戶端遠程調用此方法時,它的內(nèi)部實現(xiàn)是這樣的:首先創(chuàng)建該方法所需要的輸入型Parcel對象_data、輸出型Parcel對象_reply和返回值對象List;然后把該方法的參數(shù)信息寫入_data中(如果有參數(shù)的話);接著調用transact方法來發(fā)起RPC(遠程過程調用)請求,同時當前線程掛起;然后服務端的onTransact方法會被調用,直到RPC過程返回后,當前線程繼續(xù)執(zhí)行,并從_reply中取出RPC過程的返回結果;最后返回_reply中的數(shù)據(jù)。

Proxy#addBook
這個方法運行在客戶端,它的執(zhí)行過程和getBookList是一樣的,addBook沒有返回值,所以它不需要從_reply中取出返回值。

在服務端需要創(chuàng)建一個 BookManagerImpl 的對象并在 Service 的 onBind 方法中返回即可。以下就是一個服務端的典型實現(xiàn)。

//服務器端
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連接已經(jīng)斷裂,那么客戶端的功能就會受到影響。

為了解決這個問題,Binder中提供了兩個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以給Binder設置一個死亡代理,當Binder死亡時,我們就會收到通知,這個時候我們就可以重新發(fā)起連接請求從而恢復連接。那么到底如何給Binder設置死亡代理呢?也很簡單。

首先,聲明一個DeathRecipient對象。DeathRecipient是一個接口,其內(nèi)部只有一個方法binderDied,我們需要實現(xiàn)這個方法,當Binder死亡的時候,系統(tǒng)就會回調binderDied方法,然后我們就可以移出之前綁定的binder代理并重新綁定遠程服務。

//客戶端(Activity)內(nèi)
        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在服務端與客戶端之間傳遞自定義對象,自定義對象需要實現(xiàn)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中,添加相關實現(xiàn)代碼

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

利用這些就可以在新書到來之時,通知需要知道的監(jiān)聽者,不需要時就解除注冊即可。

七、 ContentProvider

創(chuàng)建一個自定義的ContentProvider很簡單,只需要繼承ContentProvider類并實現(xiàn)六個抽象方法即可:onCreate、query、update、insert、delete和getType。

onCreate
代表ContentProvider的創(chuàng)建,一般來說我們需要做一些初始化工作;

getType
用來返回一個Uri請求所對應的MIME類型(媒體類型),比如圖片、視頻等,這個媒體類型還是有點復雜的,如果我們的應用不關注這個選項,可以直接在這個方法中返回null或者** “ */* ” **;Android系統(tǒng)所提供的MediaStore功能就是文件類型的ContentProvider,詳細實現(xiàn)可以參考MediaStore。

query、update、insert、delete
剩下的四個方法對應于CRUD操作,即實現(xiàn)對數(shù)據(jù)表的增刪改查功能。

根據(jù)Binder的工作原理,我們知道這六個方法均運行在ContentProvider的進程中,除了onCreate由系統(tǒng)回調并運行在主線程里,其他五個方法均由外界回調并運行在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 關聯(lián)到一起
        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創(chuàng)建時,初始化數(shù)據(jù)庫。注意:這里僅僅是為了演示,實際使用中不推薦在主線程中進行耗時的數(shù)據(jù)庫操作
        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 來區(qū)分外界要訪問的的數(shù)據(jù)集合,為了知道外界要訪問的是哪個表,我們需要為它們定義單獨的 Uri 和 Uri_Code ,并將 Uri 和對應的 Uri_Code 相關聯(lián),我們可以使用 UriMatcher 的 addURI 方法將 Uri 和 Uri_Code 關聯(lián)到一起。這樣,當外界請求訪問 BookProvider 時,我們就可以根據(jù)請求的 Uri 來得到 Uri_Code ,有了 Uri_Code 我們就可以知道外界想要訪問哪個表,然后就可以進行相應的數(shù)據(jù)操作了,

接著我們需要注冊這個 BookProvider,否則它無法向外界提供有效的數(shù)據(jù)。 如下所示。

<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 做了封裝,使得我們可以更簡便地進行進程間通信。同時,由于它一次處理一個請求,因此在服務端我們不用考慮線程同步的問題,這是因為服務端中不存在并發(fā)執(zhí)行的情形。

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的介紹就這么多,更詳細的資料請查看相關網(wǎng)絡資料。

九、 Socket

我們也可以通過 Socket 來實現(xiàn)進程間的通信。 Socket 也稱為“套接字”,它分為流式套接字( TCP 協(xié)議)和用戶數(shù)據(jù)報套接字( UDP 協(xié)議)兩種。

TCP 協(xié)議是面向連接的協(xié)議,提供穩(wěn)定的雙向通信功能, TCP 連接的建立需要經(jīng)過“三次握手”才能完成,為了提供穩(wěn)定的數(shù)據(jù)傳輸功能,其本身提供了超時重傳機制,因此具有很高的穩(wěn)定性;

而 UDP 是無連接的,提供不穩(wěn)定的單向通信功能,當然 UDP 也可以實現(xiàn)雙向通信功能。在性能上, UDP 具有更好的效率,其缺點是不保證數(shù)據(jù)一定能夠正確傳輸,尤其是在網(wǎng)絡擁塞的情況下。關于 TCP 和 UDP 的介紹就這么多,更詳細的資料請查看相關網(wǎng)絡資料。

至此,Android IPC機制的介紹暫告一段落了。

名稱 優(yōu)點 缺點 適用場景
Bundle 簡單易用 只能傳輸Bundle支持的數(shù)據(jù)類型 四大組件間的進程間通信
文件共享 簡單易用 不適合高并發(fā)場景,并且無法做到進程間即時通信 無并發(fā)訪問情形,交換簡單的數(shù)據(jù)實時性不高的場景
AIDL 功能強大,支持一對多并發(fā)通信,支持實時通信 使用稍復雜,需要處理好線程同步 一對多通信且有RPC需求
Messenger 功能一般,支持一對多串行通信,支持實時通信 不能很處理高并發(fā)清醒,不支持RPC,數(shù)據(jù)通過Message進行傳輸,因此只能傳輸Bundle支持的數(shù)據(jù)類型 低并發(fā)的一對多即時通信,無RPC需求,或者無需返回結果的RPC需求
ContentProvider 在數(shù)據(jù)源訪問方面功能強大,支持一對多并發(fā)數(shù)據(jù)共享,可通過Call方法擴展其他操作 可以理解為受約束的AIDL,主要提供數(shù)據(jù)源的CRUD操作 一對多的進程間數(shù)據(jù)共享
Socket 功能請打,可以通過網(wǎng)絡傳輸字節(jié)流,支持一對多并發(fā)實時通信 實現(xiàn)細節(jié)稍微有點煩瑣,不支持直接的RPC 網(wǎng)絡數(shù)據(jù)交換
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容