Android多程式之Binder的意外死亡及許可權校驗

xxq2dream發表於2018-08-26

Android多程式系列

通過前幾篇文章,我們對Binder的使用和工作流程有了一定的瞭解,但是還有幾個問題休要我們去解決。一個是如果服務端程式意外退出,Binder死亡,那客戶端就會請求失敗;還有一個就是許可權校驗問題,就是服務端需要校驗一下客戶端的身份許可權,不能誰都能請求服務端的服務

Binder意外死亡的處理

給Binder設定DeathRecipient監聽
  • 在繫結Service服務後的onServiceConnected回撥中給Binder註冊死亡回撥DeathRecipient
private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        Log.e(TAG, "ServiceConnection-->"+ System.currentTimeMillis());
        IBookManager bookManager = BookManagerImpl.asInterface(iBinder);
        mRemoteBookManager = bookManager;
        try {
            //註冊死亡回撥
            iBinder.linkToDeath(mDeathRecipient,0);
            ...

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

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        Log.e(TAG, "onServiceDisconnected-->binder died");
    }
};
複製程式碼
  • 在DeathRecipient中相應的處理,比如重新連線服務端
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        Log.e(TAG, "mDeathRecipient-->binderDied-->");
        if (mRemoteBookManager == null) {
            return;
        }
        mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
        mRemoteBookManager = null;
        //Binder死亡,重新繫結服務
        Log.e(TAG, "mDeathRecipient-->bindService");
        Intent intent = new Intent(MainActivity.this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }
};
複製程式碼
  • 為了測試,我們在服務端新增結束程式的程式碼
@Override
public void onCreate() {
    super.onCreate();
    Log.e(TAG, "onCreate-->"+ System.currentTimeMillis());
    new Thread(new ServiceWorker()).start();
}

private class ServiceWorker implements Runnable {
    @Override
    public void run() {
        while (!mIsServiceDestoryed.get()) {
            try {
                Thread.sleep(5000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }

            int bookId = mBookList.size() + 1;
            if (bookId == 8) {
                //結束當前程式,測試Binder死亡回撥
                android.os.Process.killProcess(android.os.Process.myPid());
                return;
            }
            ...
        }
    }
}
複製程式碼
  • 測試效果

服務端程式意外退出,客戶端收到回撥成功啟動服務端

從上面的測試我們可以看到客戶端在服務端程式意外退出後,通過重新繫結服務又把服務端程式啟動了。此外,我們還可以在ServiceConnection的onServiceDisconnected方法中處理服務端程式意外退出的情況,方法是一樣的,就不測試了。2種方法的區別就在於onServiceDisconnected方法在客戶端的UI執行緒中被回撥,而binderDied方法在客戶端的Binder執行緒池中被回撥

許可權驗證

在onBind中通過自定義許可權來驗證
  • 首先要自定義一個許可權
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xxq2dream.android_ipc">
    
    <permission
        android:name="com.xxq2dream.permission.ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal"/>
    
</manifest>
複製程式碼
  • 然後在Service中的onBind方法中校驗
@Nullable
@Override
public IBinder onBind(Intent intent) {
    Log.e(TAG, "onBind-->"+ System.currentTimeMillis());
    int check = checkCallingOrSelfPermission("com.xxq2dream.permission.ACCESS_BOOK_SERVICE");
    if (check == PackageManager.PERMISSION_DENIED) {
        Log.e(TAG, "PERMISSION_DENIED");
        return null;
    }
    Log.e(TAG, "PERMISSION_GRANTED");
    return mBinder;
}
複製程式碼

許可權校驗被拒絕

  • 從上圖中我們可以看到,由於我們的應用客戶端沒有宣告服務端校驗的許可權,所以服務端校驗不通過,我們只需要在我們的客戶端新增相應的許可權宣告即可
<uses-permission android:name="com.xxq2dream.permission.ACCESS_BOOK_SERVICE"/>

複製程式碼

許可權校驗通過

在onTransact方法中進行許可權校驗
private Binder mBinder = new BookManagerImpl(){
    @Override
    public List<Book> getBookList() throws RemoteException {
        Log.e(TAG, "getBookList-->"+ System.currentTimeMillis());
        return mBookList;
    }

    ...
    
    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        int check = checkCallingOrSelfPermission("com.xxq2dream.permission.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            Log.e(TAG, "PERMISSION_DENIED");
            return false;
        }
        
        String packageName = null;
        //獲取客戶端包名 
        String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
        if (packages != null && packages.length > 0) {
            packageName = packages[0];
        }
        //校驗包名
        if (!packageName.startsWith("com.xxq2dream")) {
            return false;
        }
        Log.e(TAG, "PERMISSION_GRANTED");
        return super.onTransact(code, data, reply,flags);
    }
};
複製程式碼
結語
  • Binder的一個很好的應用就是推送訊息和保活。比如我們可以建立一個Service執行在一個獨立的程式中,然後和我們的應用程式中的一個Service繫結。獨立程式的Service每隔一定的時間向我們的服務端請求檢視是否有新的訊息,有的話就拉取新的訊息,然後通知給應用程式的Service,執行彈出通知之類的操作。應用程式退出後,我們的Service程式還是存活的,這樣就可以一直接收訊息。當然,如果使用者一鍵清理或是直接結束應用的話,我們的Service程式仍然會被幹掉。

                                歡迎關注我的微信公眾號,期待與你一起學習,一起交流,一起成長!
複製程式碼

AntDream

相關文章