Android Binder學(xué)習(xí)筆記(二)

Android Binder學(xué)習(xí)筆記(一)接上篇添加圖書和獲取圖書列表的例子。

向服務(wù)端注冊成為觀察者

我們希望每當(dāng)服務(wù)端有新書到來的時候能主動通知客戶端,這樣就不需要客戶端去輪詢了。

首先聲明一個AIDL接口,用來注冊、通知觀察者。這里選擇使用AIDL接口是因為在AIDL中無法使用普通接口。

IOnNewBookArriveListener.aidl

// IOnNewBookArriveListener.aidl
package com.hm.aidlserver;
import com.hm.aidlserver.Book;
// Declare any non-default types here with import statements

interface IOnNewBookArriveListener {

 void onNewBookArrived(in Book newBook);

}

修改IBookManager.aidl

// IBookManager.aidl
package com.hm.aidlserver;

import com.hm.aidlserver.Book;
import com.hm.aidlserver.IOnNewBookArriveListener;

interface IBookManager {

   List<Book>getBookList();
   void addBook(in Book book);
   //注冊觀察者
   void registerListener(IOnNewBookArriveListener listener);
  //取消注冊
   void unRegisterListener(IOnNewBookArriveListener listener);
}

修改服務(wù)端的Service,實現(xiàn)registerListener和unRegisterListener方法

public class BookManagerService extends Service {

    //...
    private RemoteCallbackList<IOnNewBookArriveListener> mListenerList = new RemoteCallbackList<>();

    private Binder mBinder = new IBookManager.Stub() {

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

        @Override
        public void unRegisterListener(IOnNewBookArriveListener listener) throws RemoteException {
            mListenerList.unregister(listener);
        }
       //...
    };
    //...

    /**
     * 如果IOnNewBookArriveListener#onNewBookArrived(Book newBook)方法是耗時方法的話
     * notifyBookArrived不能運行在服務(wù)端的住線程
     * 通知觀察者有新書到來
     */
    private void notifyBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        final int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArriveListener l = mListenerList.getBroadcastItem(i);
            if (l != null) {
                l.onNewBookArrived(book);
            }
        }
        mListenerList.finishBroadcast();
    }
    //...
}

修改客戶端的實現(xiàn)

private IBookManager bookManager;

private IOnNewBookArriveListener listener = new IOnNewBookArriveListener.Stub() {
    @Override
    public void onNewBookArrived(Book newBook) throws RemoteException {
        //當(dāng)前線程是在客戶端的Binder線程池中運行的
        Log.e(TAG, "onNewBookArrived: " + Thread.currentThread().getName());
        //如果需要更新UI的話,使用handler的方式來實現(xiàn)
        mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
    }
};

private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.e(TAG, "onServiceConnected:");
        bookManager = IBookManager.Stub.asInterface(service);
        try {
            getBookList();
            //向服務(wù)端注冊成為觀察者
            bookManager.registerListener(listener);
        } catch (RemoteException e) {
            e.printStackTrace();
            Log.e(TAG, "onServiceConnected: error" + e.getMessage());
        }
    }
};

注意兩點:

  1. 如果明確知道某個遠(yuǎn)程方法是耗時的,客戶端就要避免在UI線程中去調(diào)用遠(yuǎn)程方法。

  2. 客戶端的onServiceConnected方法和onServiceDisconnected是運行在主線程的,所以也不能在這兩個方法中調(diào)用遠(yuǎn)程方法。

  3. 當(dāng)遠(yuǎn)程服務(wù)端調(diào)用客戶端的listener中的方法時,如果被調(diào)用的方法也是耗時方法,那么也不能在服務(wù)端的主線程調(diào)用。例如:如果IOnNewBookArriveListener#onNewBookArrived(Book newBook)方法是耗時方法的話,我們也不能在服務(wù)端的主線程調(diào)用。

Binder死亡時,重新連接服務(wù)

  1. 使用IBinder.DeathRecipient
    /**
     * 當(dāng)Binder死亡時,我們會收到通知,這個時候,我們可以重新發(fā)起連接請求從而恢復(fù)鏈接。
     */
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.e(TAG, "binderDied: " + Thread.currentThread().getName());
            if (bookManager != null) {
                bookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
                bookManager = null;
            }
            //重新連接
            bind();
        }
    };

    //...
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "onServiceConnected:");
            bookManager = IBookManager.Stub.asInterface(service);
            try {
                //在這里設(shè)置DeathRecipient監(jiān)聽
                service.linkToDeath(mDeathRecipient, 0);
                getBookList();
                bookManager.registerListener(mOnNewBookArriveListener);
            } catch (RemoteException e) {
                e.printStackTrace();
                Log.e(TAG, "onServiceConnected: error" + e.getMessage());
            }
        }
    };

如果我們設(shè)置了DeathRecipient監(jiān)聽,當(dāng)Binder死亡的時候,我們會收到binderDied方法的回調(diào),在binderDied方法中我們可以重連遠(yuǎn)程服務(wù)。

  1. 在ServiceConnection的onServiceDisconnected方法中重新連接
private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "onServiceConnected:");
            bookManager = IBookManager.Stub.asInterface(service);
            try {
                getBookList();
                bookManager.registerListener(mOnNewBookArriveListener);
            } catch (RemoteException e) {
                e.printStackTrace();
                Log.e(TAG, "onServiceConnected: error" + e.getMessage());
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bookManager = null;
            Log.e(TAG, "onServiceDisconnected: " + Thread.currentThread().getName());
            //重新連接
            bind();
        }
    };

這兩者的區(qū)別在于ServiceConnection的onServiceDisconnected方法運行在客戶端的主線程;IBinder.DeathRecipient 的binderDied方法運行在客戶端的Binder線程池中。

跨進(jìn)程通信使用權(quán)限驗證功能

第一種驗證方式

我們可以在服務(wù)端的Service的onBind方法中驗證,驗證不通過就返回null。這樣客戶端就無法綁定服務(wù)。驗證方式有多種,比如使用permission驗證。使用這種方式,首先要在AndridManifest.xml文件中聲明綁定服務(wù)所需要權(quán)限,比如

<permission
      android:name="com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE"
      android:protectionLevel="normal"/>

BookManagerService的onBind方法

@Override
public IBinder onBind(Intent intent) {
    //在這里做權(quán)限驗證,如果驗證不通過就返回null
    int check = checkCallingOrSelfPermission("com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE");
    if (check == PackageManager.PERMISSION_DENIED) {
        Log.e(TAG, "onBind: permission denied");
        return null;
    }
    Log.e(TAG, "onBind: permission granted");
    return mBinder;
}

如果我們想要成功綁定服務(wù)端的服務(wù),就需要在客戶端的AndridManifest.xml中聲明所需要的權(quán)限

<uses-permission android:name="com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE"/>

第二種驗證方式

在服務(wù)端的Binder中的onTransact方法中驗證,如果驗證不通過,返回false。這樣服務(wù)端就不會執(zhí)行AIDL中的方法,從而達(dá)到保護(hù)服務(wù)端的效果。驗證方式有很多,我們在下面的方法中我們驗證了permission和包名。

private Binder mBinder = new IBookManager.Stub() {
    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            /**
             * 驗證permission
             */
            Log.e(TAG, "onTransact 驗證權(quán)限");
            int check = checkCallingOrSelfPermission("com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE");
            if (check == PackageManager.PERMISSION_DENIED) {
                Log.e(TAG, "onTransact: permission denied");
                return false;
            }
            /**
             * 驗證包名
             */
            String packageName;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
                if (!packageName.startsWith("com.hm")) {
                    Log.e(TAG, "onTransact: package verify failed");
                    return false;
                }
            } else {
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }
};

Binder連接池

Binder連接池的主要作用就是將客戶端的每個業(yè)務(wù)模塊的Binder請求統(tǒng)一發(fā)送到遠(yuǎn)程Service中去執(zhí)行,從而避免了重復(fù)創(chuàng)建Service的過程,不然的話,每個Binder對象就得對應(yīng)一個Service。

在這種模式下,整個工作流程是這樣的:服務(wù)端提供一個queryBinder接口,這個接口能夠根據(jù)業(yè)務(wù)模塊的標(biāo)志來返回相應(yīng)的Binder對象給客戶端,客戶端拿到所需的Binder對象后就可以進(jìn)行遠(yuǎn)程方法調(diào)用了。對服務(wù)端來說,只需要一個Service就可以了。

舉個例子:客戶端中的一個業(yè)務(wù)模塊需要遠(yuǎn)程調(diào)用加解密字符串的功能,另一個業(yè)務(wù)模塊需要使用計算加法的功能。

聲明兩個AIDL文件

ISecurityCenter.aidl

// ISecurityCenter.aidl
package com.hm.aidlserver;

// Declare any non-default types here with import statements

interface ISecurityCenter {

   String encrypt(String content);
   String decrypt(String password);
}

ICompute.aidl

// ICompute.aidl
package com.hm.aidlserver;

// Declare any non-default types here with import statements

interface ICompute {

   int add(int a,int b);
}

兩個接口的實現(xiàn)

SecurityCenterImpl

public class SecurityCenterImpl extends ISecurityCenter.Stub {

    private static final String TAG = "SecurityCenterImpl";
    private static final char SECRET_CODE = '^';

    @Override
    public String encrypt(String content) throws RemoteException {
        char[] chars = content.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            chars[i] ^= SECRET_CODE;
        }
        return new String(chars);
    }

    @Override
    public String decrypt(String password) throws RemoteException {
        return encrypt(password);
    }
}

IComputeImpl

public class IComputeImpl extends ICompute.Stub {

    private static final String TAG = "IComputeImpl";

    @Override
    public int add(int a, int b) throws RemoteException {
        return a + b;
    }
}

現(xiàn)在不同業(yè)務(wù)模塊需要的AIDL接口和實現(xiàn)已經(jīng)完成了,注意這里并沒有為每個模塊的AIDL單獨創(chuàng)建Service,接下來就是服務(wù)端和Binder連接池的工作了。

首先為Binder連接池創(chuàng)建AIDL接口IBinderPool.aidl

IBinderPool.aidl

// IBinderPool.aidl
package com.hm.aidlserver;

// Declare any non-default types here with import statements

interface IBinderPool {
  //根據(jù)不同的查詢碼返回相應(yīng)的Binder對象
  IBinder queryBinder(int binderCode);
}

接下來在服務(wù)端定義一個Service用來返回IBinderPool

import com.hm.aidlserver.impl.IComputeImpl;
import com.hm.aidlserver.impl.SecurityCenterImpl;

public class BinderPoolService extends Service {

    private static final String TAG = "BinderPoolService";

    private Binder mBinderPool = new BinderPool();

    public BinderPoolService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind: ");
        return mBinderPool;
    }
    //聲明BinderPool類實現(xiàn)IBinderPool接口
    private static class BinderPool extends IBinderPool.Stub {

        private static final String TAG = "BinderPool";
        private static final int BINDERT_COMPUTE = 0;
        private static final int BINDERT_NOSECURITY_CENTER = 1;

        private BinderPool() {
            Log.e(TAG, "調(diào)用構(gòu)造函數(shù)");
        }

        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode) {
                case BINDERT_NOSECURITY_CENTER:
                    binder = new SecurityCenterImpl();
                    break;
                case BINDERT_COMPUTE:
                    binder = new IComputeImpl();
                    break;
            }
            return binder;
        }
    }
}

接下來在客戶端實現(xiàn)具體的Binder連接池,在它的內(nèi)部首先要去綁定遠(yuǎn)程服務(wù),綁定成功后,客戶端就可以通過它的queryBinder方法去獲取各自對應(yīng)的Binder,拿到所需的Binder以后,不同的業(yè)務(wù)模塊就可以進(jìn)行各自的操作了。

/**
 * Created by dumingwei on 2017/5/7.
 * Binder連接池的具體實現(xiàn)
 */
public class BinderPoolHelper {

    private static final String TAG = "BinderPoolHelper";
    public static final int BINDERT_COMPUTE = 0;
    public static final int BINDERT_NOSECURITY_CENTER = 1;

    private Context mContext;
    private IBinderPool mBinderPool;
    private static volatile BinderPoolHelper sInstance;
    private CountDownLatch mConnectBinderPoolCountDownLatch;

    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mConnectBinderPoolCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.e(TAG, "binderDied: ");
            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
            mBinderPool = null;
            connectBinderPoolService();
        }
    };

    private BinderPoolHelper(Context context) {
        mContext = context.getApplicationContext();
        connectBinderPoolService();

    }

    public static BinderPoolHelper getsInstance(Context context) {
        if (sInstance == null) {
            synchronized (BinderPoolHelper.class) {
                if (sInstance == null) {
                    sInstance = new BinderPoolHelper(context);
                }
            }

        }
        return sInstance;
    }

    private synchronized void connectBinderPoolService() {
        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.hm.aidlserver", "com.hm.aidlserver.BinderPoolService"));
        mContext.bindService(intent, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
        try {
            mConnectBinderPoolCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public IBinder queryBinder(int binderCode) {
        IBinder binder = null;
        if (mBinderPool != null) {
            try {
                binder = mBinderPool.queryBinder(binderCode);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        return binder;
    }
}

使用

private ISecurityCenter mSecurityCenter;
private ICompute mComputer;
private BinderPoolHelper binderPoolHelper;

public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.btn_ISecurityCenter:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        binderPoolHelper = BinderPoolHelper.getsInstance(BinderPoolActivity.this);
                        IBinder securityBinder = binderPoolHelper.queryBinder(BinderPoolHelper.BINDERT_NOSECURITY_CENTER);
                        mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder);
                        Log.e(TAG, "doWork: visit ISecurityCenter");
                        String msg = "helloworld-安卓";
                        String password;
                        try {
                            password = mSecurityCenter.encrypt(msg);
                            Log.e(TAG, "doWork: encrypt" + password);
                            String content = mSecurityCenter.decrypt(password);
                            Log.e(TAG, "doWork: decrypt" + content);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
                break;
            case R.id.btn_ICompute:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        binderPoolHelper = BinderPoolHelper.getsInstance(BinderPoolActivity.this);
                        IBinder computeBinder = binderPoolHelper.queryBinder(BinderPoolHelper.BINDERT_COMPUTE);
                        mComputer = IComputeImpl.asInterface(computeBinder);
                        Log.e(TAG, "doWork: visit ICompute");
                        try {
                            Log.e(TAG, "doWork: " + mComputer.add(7, 14));
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
                break;
            default:
                break;
        }
    }

Binder中定向tag in out inout的結(jié)論

Binder中的定向 tag 表示了在跨進(jìn)程通信中數(shù)據(jù)的流向,其中 in 表示數(shù)據(jù)只能由客戶端流向服務(wù)端, out 表示數(shù)據(jù)只能由服務(wù)端流向客戶端,而 inout 則表示數(shù)據(jù)可在服務(wù)端與客戶端之間雙向流通。

  • in 為定向 tag 的話表現(xiàn)為服務(wù)端將會接收到一個那個對象的完整數(shù)據(jù),但是客戶端的那個對象不會因為服務(wù)端對傳參的修改而發(fā)生變動;
  • out 的話表現(xiàn)為服務(wù)端將會接收到那個對象,但是對象的各個字段都是默認(rèn)值了。在服務(wù)端對接收到的對象有任何修改之后客戶端將會同步變動;
  • inout 為定向 tag 的情況下,服務(wù)端將會接收到客戶端傳來對象的完整信息,并且在服務(wù)端對接收到的對象有任何修改之后客戶端將會同步變動;

Binder的優(yōu)勢

Binder的優(yōu)點.png

Android 進(jìn)程間通信的其他方式

  1. 使用Bundle。

  2. 使用文件共享,兩個進(jìn)程通過讀/寫同一個文件來交換數(shù)據(jù)。(并發(fā)讀寫不好處理同步問題)。

  3. 使用Messenger:Messenger可以譯為信使。通過它可以在不同的進(jìn)程中傳遞Message對象。在Message中放入我們需要傳遞的數(shù)據(jù),就可以輕松地實現(xiàn)數(shù)據(jù)的進(jìn)程間傳遞了。Messenger是一種輕量級的IPC方案,它的底層實現(xiàn)是AIDL。同時Messenger一次處理一個請求,在服務(wù)端不用考慮線程同步的問題。Messenger的主要作用是為了傳遞消息,很多時候我們可能需要跨進(jìn)程調(diào)用服務(wù)端的方法。這種情況下Messenger就無法做到了。

  4. 使用ContentProvider。

  5. 使用Socket。

各種方式適用場景對比。

IPC方式的優(yōu)缺點和適用場景.png

參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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