[學習筆記]Android開發藝術探索:IPC機制

Android IPC簡介

IPC爲進程間通訊,兩個進程之間進行數據交換的過程。

IPC不是Android所獨有的,任何一個操作系統都有對應的IPC機制。Windows上通過剪切板、管道、油槽等進行進程間通訊。Linux上通過命名空間、共享內容、信號量等進行進程間通訊。Android中沒有完全繼承於Linux,有特色的進程間通訊方式是Binder,還支持Socket。

Android中的多進程模式

  • 同一個應用,通過給四大組件在AndroidMenifest指定android:process屬性,就可以開啓多進程模式。
  • 非常規的方式,通過JNI在Native層fork一個新的進程。
  • 查看進程信息:DDMS;adb shell ps;adb shell ps | grep [包名]。
  • 進程名以":“開頭的屬於當前應用的私有進程,其他應用的組件不可以和他跑在同一個進程裏面。而進程名不以”:"開頭的進程屬於全局進程,其他應用通過ShareUID方式可以和它跑在同一個進程中。
  • 兩個應用可以通過ShareUID跑在同一個進程並且簽名相同,他們可以共享data目錄、組件信息、共享內存數據。
  • 多進程通訊的問題
  1. 靜態成員和單例模式完全失效。

  2. 線程同步機制完全失效。

  3. SharedPreferences的可靠性下降

  4. Application會多次創建

    問題1、2原因是因爲進程不同,已經不是同一塊內存了;
    問題3是因爲SharedPreferences不支持兩個進程同事進行讀寫操作,有一定機率導致數據丟失;
    問題4是當一個組件跑在一個新的進程中,系統會爲他創建新的進程同時分配獨立的虛擬機,其實就是啓動一個應用的過程,因此相當於系統又把這個應用重新啓動了一遍,Application也重新創建。

多進程中,不同的進程組件擁有獨立的虛擬機,Application,內存。

實現跨進程通訊有很多方式,比如:通過Intent,共享文件、SharedPreferences、基於Binder的Messenger和AIDL、Socket等。

IPC基礎概念介紹

Serializable接口

Serializable是Java所提供的一個序列化接口,空接口。

serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化後的數據中的serialVersionUID要和當前類的serialVersionUID相同才能正常的序列化。

  • 靜態成員變量屬於類不屬於對象,所以不會參加序列化過程;

  • 其次用transient關鍵字標明的成員變量也不參加序列化過程。

重寫如下兩個方法可以重寫系統默認的序列化和反序列化過程

private void writeObject(java.io.ObjectOutputStream out)throws IOException{
}
private void readObject(java.io.ObjectInputStream out)throws IOException,ClassNotFoundException{
}

Parcelable接口

Android中特有的序列化方式,效率相對Serializable更高,佔用內存相對也更少,但使用起來稍微麻煩點。

public class User implements Parcelable {
    public int userId;
    public String userName;
    public boolean isMale;

    public Book book;

    public User() {
    }

    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }

    public int describeContents() {
        return 0;//返回當前對象的內容描述,含有文件描述符返回1,否則0
    }

    public void writeToParcel(Parcel out, int flags) {//將當前對象寫入序列號結構中
        out.writeInt(userId);
        out.writeString(userName);
        out.writeInt(isMale ? 1 : 0);
        out.writeParcelable(book, 0);
    }

    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        public User[] newArray(int size) {
            return new User[size];
        }
    };

    private User(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readInt() == 1;
        book = in.readParcelable(Thread.currentThread().getContextClassLoader());
    }

    @Override
    public String toString() {
        return String.format(
                "User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}",
                userId, userName, isMale, book);
    }

}

序列化功能由writeToParcel方法來完成,最終通過Parcel中的一系列write方法完成的。反序列化功能由CREATOR來完成,其內部標明瞭如何創建序列號對象和數組,並通過Parcel的一系列read方法來完成反序列化過程。內容描述功能由describeContents方法來完成,幾乎所有情況都返回0,只有當前對象存在文件描述符時,才返回1。

Serializable是Java中的序列化接口,簡單但開銷大,序列化和反序列化需要大量的IO操作。

Parceable是Android中的序列化方式,使用起來麻煩,但是效率高。

Binder

Binder簡介

直觀來說,Binder是Android中一個類,實現了IBinder接口;

從IPC的角度來說,Binder是Android的一種跨進程的通訊方式;Binder還可以理解爲一種虛擬的物理設備,設備驅動是/dev/binder;

從Android Framework角度來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager、等等)和相應ManagerService的橋樑;

從Android應用層來說,Binder是客戶端與服務端通訊的媒介。在Android開發中,Binder主要用於Service中,包括AIDL和Messenger,其中普通的Service的Binder不涉及進程間通訊;而Messenger的底層其實就是AIDL。

AIDL:

Book.aidl:
package com.zza.stardust.app.ui.androidart;

parcelable Book;

IBookManager.aidl
package com.zza.stardust.app.ui.androidart;

import com.zza.stardust.app.ui.androidart.Book;

//  /build/generated/aidl_source_output_dir目錄下的com.zza.stardust.app.ui.androidart包中
interface IBookManager {
     List<Book> getBookList();
     void addBook(in Book book);
}

生成的Java類:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /app/src/main/aidl/com/zza/stardust/app/ui/androidart/IBookManager.aidl
 */
package com.zza.stardust.app.ui.androidart;
//gen目錄下的com.zza.stardust.app.ui.androidart.aidl包中

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

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

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

            @Override
            public void addBook(com.zza.stardust.app.ui.androidart.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.zza.stardust.app.ui.androidart.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.zza.stardust.app.ui.androidart.Book book) throws android.os.RemoteException;

}

系統會根據AIDL文件生成同名的.java類;首先聲明與AIDL文件相對應的幾個接口方法,還申明每個接口方法相對應的int id來做爲標識,這幾個id在transact過程中標識客戶端請求的到底是什麼方法。接着會聲明一個內部類Stub,這個Stub就是一個Binder類,當客戶端和服務端處於同一個進程的時候,方法調用不會走transact過程,處於不同進程時,方法調用會走transact過程,這個邏輯由Stub的內部代理類Proxy來完成。所以核心實現在於它的內部類Stub和Stub的內部代理類Proxy,下面分析其中的方法:

DESCRIPTOR:Binder的唯一標識,一般用當前Binder的類名錶示。

asInterface(android.os.IBinder obj):將服務端的Binder對象轉換成客戶端所需要的AIDL接口類型的對象;如果客戶端和服務端位於相同進程,那麼此方法返回的就是服務端Stub對象本身,否則返回系統封裝後的Stub.proxy對象。

asBinder:用於返回當前的Binder對象

onTransact:運行在服務端的Binder線程池中,當客戶端發起跨進程通訊時,遠程請求會通過系統底層封裝交由此方法處理。

 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException 
  • 服務端通過code確定客戶端請求的目標方法是什麼
  • 接着從data取出目標方法所需要的參數,然後執行目標方法
  • 執行後向reply寫入返回值
  • 如果返回false,服務端請求會失敗,可以做權限驗證

Proxy#getBookList和Proxy#addBook

  • 運行在客戶端,首先該方法所需要的輸入型對象Parcel _data對象,輸出對象Parcel _reply對象和返回值對象List
  • 然後把該方法的參數信息寫入Parcel _data對象。
  • 接着調研transact方法發起RPC,同時當前線程掛起
  • 然後服務端的onTransact方法會被調用,直到RPC過程返回後,當前線程繼續執行,並從_reply取出RPC的返回結果,最後返回 _reply中的數據

Binder的兩個重要方法linkToDeathunlinkToDeath。通過linkToDeath可以給Binder設置一個死亡代理,當Binder死亡時,我們就會收到通知,然後就可以重新發起連接請求。聲明一個DeathRecipient對象,DeathRecipient是一個接口,其內部只有一個方法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 = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);

Android中的IPC方式

使用Bundle

由於Binder實現了Parcelable接口,所以可以方便的在不同進程中傳輸;Activity、Service和Receiver都支持在Intent中傳遞Bundle數據。

使用文件共享

兩個進程通過讀/寫一個文件來交換數據

適合對數據同步要求性不高的場景,並要避免併發寫這種場景或者處理好線程同步問題。

SharedPreferences是個特例,雖然也是文件的一種,但系統在內存中有一份SharedPreferences文件的緩存,因此在多線程模式下,系統的讀/寫就變得不可靠,高併發讀寫SharedPreferences有一定機率會丟失數據,因此不建議在多進程通信中使用SharedPreferences。

使用Messenger

可以在不同的進程之間傳遞Message對象。Messenger是輕量級的IPC方案,底層實現是AIDL,對AIDL進行了封裝。Messenger 服務端是以串行的方式來處理客戶端的請求的,不存在併發執行的情形。

服務端:

  • 創建一個Service來處理客戶端的連接請求
  • 創建一個Handler並通過它來創建一個Messager對象
  • 在Service的onBind中返回這個Messager對象底層的Binder即可
public class MessengerService extends Service {
    public MessengerService() {
    }

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    ToastUtil.show("receive msg from Client:" + msg.getData().getString("msg"));
                    Messenger client = msg.replyTo;
                    Message relpyMessage = Message.obtain(null, 1);
                    Bundle bundle = new Bundle();
                    bundle.putString("reply", "嗯,你的消息我已經收到,稍後會回覆你。");
                    relpyMessage.setData(bundle);
                    try {
                        client.send(relpyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

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

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

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
}

客戶端

  • 綁定這個服務端的Server
  • 用服務端返回的IBinder對象創建一個Messager對象
  • 若需要回應客戶端,需要創建一個Handler並創建一個新的Messager,並通過Messa的replyTo參數傳遞給服務端,服務端通過這個replyTo參數就可以迴應客戶端
public class MessengerActivity extends Activity {

    private Messenger mService;
    private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());

    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    LogUtil.i("receive msg from Service:" + msg.getData().getString("reply"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            mService = new Messenger(service);
            LogUtil.d("bind service");
            Message msg = Message.obtain(null, 1);
            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();
            }
        }

        public void onServiceDisconnected(ComponentName className) {
        }
    };

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

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        findViewById(R.id.bt_start_service).setOnClickListener(v -> {
            Intent intent = new Intent();
            intent.setAction("com.zza.MessengerService.launch");
            intent.setPackage(this.getPackageName());//需要添加包名
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        });
    }
}

使用AIDL

Messenger不適合處理大併發請求。Messenger主要作用傳遞消息,跨進程調用服務端方法,無法做到。

服務端首先創建一個Service用來監聽客戶端的連接請求,然後創建一個AIDL文件,將暴露給客戶端的接口在AIDL文件中聲明,最後在Service中實現這個AIDL接口即可。

客戶端首先綁定服務端的Service,綁定成功後,將服務端返回的Binder對象轉化成AIDL接口所屬的類型,調用相對應的AIDL中的方法。

AIDL支持的數據類型:

  • 基本數據類型;

  • String、CharSequence;

  • List: 只支持ArrayList,裏面的元素必須都能被AIDL所支持;

  • Map: 只支持HashMap,裏面的元素(key和value)必須都能被AIDL所支持;

  • Parcelable: 所有實現了Parcelable接口的對象;

  • AIDL: 所有AIDL接口本身也可以在AIDL文件中使用。

自定義的Parcelable對象和AIDL對象必須顯示的import進來(即使在同一個包)。

如果ALDL文件中用到了自定義的Parcelable類型,必須新建一個和它同名ALDL文件,並在其中聲明它爲Parcelable類型。

AIDL中除了基本數據類型,其他類型參數必須標上方向:in、out或inout。

AIDL接口中只支持方法,不支持聲明靜態常量。

爲了方便AIDL開發,建議把所有和AIDL相關的類和文件都放在同一個包中,好處在於,當客戶端是另一個應用的時候,我們可以直接把整個包複製到客戶端工程中去。

AIDL的包結構在服務端和客戶端要保持一致,否則會運行出錯。

客戶端的listener和服務端的listener不是同一個對象,RemoteCallbackList是系統專門提供用於刪除跨進程listener的接口,RemoteCallbackList是泛型,支持管理任意的AIDL接口,因爲所有AIDL接口都繼承自android.os.IInterface接口。

需注意AIDL客戶端發起RPC過程的時候,客戶端的線程會掛起,如果是UI線程發起的RPC過程,如果服務端處理事件過長,就會導致ANR。

AIDL:

package com.zza.stardust.app.ui.androidart.aidl;

parcelable Book;



package com.zza.stardust.app.ui.androidart.aidl;

import com.zza.stardust.app.ui.androidart.aidl.Book;
import com.zza.stardust.app.ui.androidart.aidl.IOnNewBookArrivedListener;

//  /build/generated/aidl_source_output_dir目錄下的com.zza.stardust.app.ui.androidart包中
interface IBookManager {
     List<Book> getBookList();
     void addBook(in Book book);
     void registerListener(IOnNewBookArrivedListener listener);
     void unregisterListener(IOnNewBookArrivedListener listener);
}



package com.zza.stardust.app.ui.androidart.aidl;

import com.zza.stardust.app.ui.androidart.aidl.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}

服務端:

public class BookManagerService extends Service {

    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);

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

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

    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            //測試
            //SystemClock.sleep(5000);
            return mBookList;
        }

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

        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                throws RemoteException {
            int check = checkCallingOrSelfPermission("com.zza.permission.ACCESS_BOOK_SERVICE");
            LogUtil.d("check=" + check);
            if (check == PackageManager.PERMISSION_DENIED) {
                return false;
            }

            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(
                    getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            LogUtil.d("onTransact: " + packageName);
            if (!packageName.startsWith("com.zza")) {
                return false;
            }

            return super.onTransact(code, data, reply, flags);
        }

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

            final int N = mListenerList.beginBroadcast();
            mListenerList.finishBroadcast();
            LogUtil.d("registerListener, current size:" + N);
        }

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

            if (success) {
                LogUtil.d("unregister success.");
            } else {
                LogUtil.d("not found, can not unregister.");
            }
            final int N = mListenerList.beginBroadcast();
            mListenerList.finishBroadcast();
            LogUtil.d("unregisterListener, current size:" + N);
        }

        ;

    };

    @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) {
        int check = checkCallingOrSelfPermission("com.zza.permission.ACCESS_BOOK_SERVICE");
        LogUtil.d("onbind check=" + check);
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mBinder;
    }

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

    private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        final int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
            if (l != null) {
                try {
                    l.onNewBookArrived(book);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mListenerList.finishBroadcast();
    }

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

客戶端:

public class BookManagerActivity extends MActivity {

    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;

    private IBookManager mRemoteBookManager;

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    LogUtil.d("receive new book :" + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            LogUtil.d("binder died. tname:" + Thread.currentThread().getName());
            if (mRemoteBookManager == null)
                return;
            mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mRemoteBookManager = null;
            // TODO:這裏重新綁定遠程Service
        }
    };

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            mRemoteBookManager = bookManager;
            try {
                mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
                List<Book> list = bookManager.getBookList();
                LogUtil.d("query book list, list type:"
                        + list.getClass().getCanonicalName());
                LogUtil.d("query book list:" + list.toString());
                Book newBook = new Book(3, "Android進階");
                bookManager.addBook(newBook);
                LogUtil.d("add book:" + newBook);
                List<Book> newList = bookManager.getBookList();
                LogUtil.d("query book list:" + newList.toString());
                bookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            mRemoteBookManager = null;
            LogUtil.d("onServiceDisconnected. tname:" + Thread.currentThread().getName());
        }
    };

    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 onInit(Bundle savedInstanceState) {
        super.onInit(savedInstanceState);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    public void onButton1Click(View view) {
        Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show();
        new Thread(new Runnable() {

            @Override
            public void run() {
                if (mRemoteBookManager != null) {
                    try {
                        List<Book> newList = mRemoteBookManager.getBookList();
                        runOnUiThread(() -> ToastUtil.show("size:" + newList.size()));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

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

    @Override
    protected BasePresenter createPresenter() {
        return null;
    }

    @Override
    protected int provideContentViewId() {
        return R.layout.activity_book_manager;
    }
}

使用ContentProvider

ContentProvider是Android專門用於不同應用之間進行數據共享的方式,適合跨進程通信,底層採用Binder實現。

ContentProvider主要以表格的形式來組織數據,可以包含多個表;ContentProvider支持普通文件,甚至可以採用內存中得一個對象來進行數據存儲。

通過ContentProvider的notifyChange方法來通知外界當前ContentProvider中的數據已經發生改變。

query、update、insert、delete四大方法是存在多線程併發訪問的,內部需要做好線程同步。

使用Socket

Socket也被稱爲“套接字”,是網絡通訊中得概念,分爲流式套接字和用戶數據報套接字兩種,分別對應網絡的傳輸控制層中得TCP和UDP協議。

Service:

public class TCPServerService extends Service {
    private boolean mIsServiceDestoryed = false;
    private String[] mDefinedMessages = new String[] {
            "你好啊,哈哈",
            "請問你叫什麼名字呀?",
            "今天北京天氣不錯啊,shy",
            "你知道嗎?我可是可以和多個人同時聊天的哦",
            "給你講個笑話吧:據說愛笑的人運氣不會太差,不知道真假。"
    };

    @Override
    public void onCreate() {
        new Thread(new TcpServer()).start();
        super.onCreate();
    }

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

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

    private class TcpServer implements Runnable {

        @SuppressWarnings("resource")
        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(8688);
            } catch (IOException e) {
                System.err.println("establish tcp server failed, port:8688");
                e.printStackTrace();
                return;
            }

            while (!mIsServiceDestoryed) {
                try {
                    // 接受客戶端請求
                    final Socket client = serverSocket.accept();
                    System.out.println("accept");
                    new Thread() {
                        @Override
                        public void run() {
                            try {
                                responseClient(client);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        };
                    }.start();

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

    private void responseClient(Socket client) throws IOException {
        // 用於接收客戶端消息
        BufferedReader in = new BufferedReader(new InputStreamReader(
                client.getInputStream()));
        // 用於向客戶端發送消息
        PrintWriter out = new PrintWriter(new BufferedWriter(
                new OutputStreamWriter(client.getOutputStream())), true);
        out.println("歡迎來到聊天室!");
        while (!mIsServiceDestoryed) {
            String str = in.readLine();
            System.out.println("msg from client:" + str);
            if (str == null) {
                break;
            }
            int i = new Random().nextInt(mDefinedMessages.length);
            String msg = mDefinedMessages[i];
            out.println(msg);
            System.out.println("send :" + msg);
        }
        System.out.println("client quit.");
        // 關閉流
        out.close();
        in.close();
        client.close();
    }
}

Client:

public class TCPClientActivity extends MActivity implements OnClickListener {

    private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
    private static final int MESSAGE_SOCKET_CONNECTED = 2;

    private Button mSendButton;
    private TextView mMessageTextView;
    private EditText mMessageEditText;

    private PrintWriter mPrintWriter;
    private Socket mClientSocket;

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_RECEIVE_NEW_MSG: {
                    mMessageTextView.setText(mMessageTextView.getText()
                            + (String) msg.obj);
                    break;
                }
                case MESSAGE_SOCKET_CONNECTED: {
                    mSendButton.setEnabled(true);
                    break;
                }
                default:
                    break;
            }
        }
    };

    @Override
    protected void onInit(Bundle savedInstanceState) {
        super.onInit(savedInstanceState);
        mMessageTextView = (TextView) findViewById(R.id.msg_container);
        mSendButton = (Button) findViewById(R.id.send);
        mSendButton.setOnClickListener(this);
        mMessageEditText = (EditText) findViewById(R.id.msg);
        Intent service = new Intent(this, TCPServerService.class);
        startService(service);
        new Thread() {
            @Override
            public void run() {
                connectTCPServer();
            }
        }.start();
    }

    @Override
    protected void onDestroy() {
        if (mClientSocket != null) {
            try {
                mClientSocket.shutdownInput();
                mClientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        super.onDestroy();
    }

    @Override
    protected BasePresenter createPresenter() {
        return null;
    }

    @Override
    protected int provideContentViewId() {
        return R.layout.activity_tcpclient;
    }

    @Override
    public void onClick(View v) {
        if (v == mSendButton) {
            final String msg = mMessageEditText.getText().toString();
            if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
                //不能主線程   android.os.NetworkOnMainThreadException
                //不應該這要做,這裏爲方便不做修改
                new Thread(() -> mPrintWriter.println(msg)).start();
                mMessageEditText.setText("");
                String time = formatDateTime(System.currentTimeMillis());
                final String showedMsg = "self " + time + ":" + msg + "\n";
                mMessageTextView.setText(mMessageTextView.getText() + showedMsg);
            }
        }
    }

    @SuppressLint("SimpleDateFormat")
    private String formatDateTime(long time) {
        return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
    }

    private void connectTCPServer() {
        Socket socket = null;
        while (socket == null) {
            try {
                socket = new Socket("localhost", 8688);
                mClientSocket = socket;
                mPrintWriter = new PrintWriter(new BufferedWriter(
                        new OutputStreamWriter(socket.getOutputStream())), true);
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
                System.out.println("connect server success");
            } catch (IOException e) {
                SystemClock.sleep(1000);
                System.out.println("connect tcp server failed, retry...");
            }
        }

        try {
            // 接收服務器端的消息
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    socket.getInputStream()));
            while (!TCPClientActivity.this.isFinishing()) {
                String msg = br.readLine();
                System.out.println("receive :" + msg);
                if (msg != null) {
                    String time = formatDateTime(System.currentTimeMillis());
                    final String showedMsg = "server " + time + ":" + msg
                            + "\n";
                    mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg)
                            .sendToTarget();
                }
            }
            System.out.println("quit...");
            mPrintWriter.close();
            br.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Binder連接池

當項目越來越龐大後,需要使用到的AIDL接口文件也越來越多,但我們不能有多少個AIDL就添加多少個Service,Service是四大組件之一,是一種系統資源,太多的Service會讓我們的App看起來很重量級;我們應該把所有AIDL放在一個Service中去管理。

這時候不同業務模塊之間是不能有耦合的,所有實現細節需要單獨來開,然後向服務端提供一個queryBInder接口,這個接口根據業務模塊的特徵來返回Binder對象給它們,不同的業務模塊拿到所需的Binder對象給它們,不同的業務模塊拿到所需的Binder對象後就可以進行遠程方法調用了;由此可見Binder連接池的主要作用就是將每個業務模塊的Binder請求統一轉發到遠程Service中去執行。

當新業務模塊加入新的AIDL,那麼在它實現自己的AIDL接口後,只需要修改BinderPoolImpl中的queryBinder方法,給自己添加一個新的binderCode並返回相對應的Binder對象即可,不需要添加新的Service。建議在AIDL開發過程中引入BinderPool機制。

選用合適的IPC方式

名稱 優點 缺點 適用場景
Bundle 簡單易用 只能傳輸 Bundle 支持的數據類型 四大組件的進程間通信
文件共享 簡單易用 不適合高併發場景,並且無法做到進程間的即時通信 無開發訪問情形,交換簡單的數據實時性不高的場景。
AIDL 功能強大,支持一對多併發通信,支持實時通信。 使用複雜,需要處理好線程同步 一對多通信,且有 RPC 需求。
Message 功能一般,支持一對多串行通信,支持實時通信。 不能很好地處理高併發情形,不支持 RPC,數據通過 Message 進行傳輸,因此只能傳輸 Bundle 支持的數據類型 低併發的一對多即時通信,無RPC 需求,或者無需要返回結果的 RPC 請求。
ContentProvider 在數據源訪問方面數據強大,支持一對多併發數據共享,可通過 Call 方法擴展其他操作。 可以理解爲受約束的 AIDL,主要提供數據的 CRUD 操作。 一對多的進程間數據共享。
Socket 功能強大,可以通過網絡傳輸字節流,支持一對多併發實時通信。 實現細節稍微有點繁瑣,不支持直接的 RPC. 網絡數據傳輸。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章