談動態代理在Android插件中的一些用法

主APP爲插件提供了一系列接口,我們需要考慮以下幾個問題:

一、權限控制,檢查調用者權限

  • 如果接口都封裝到service中,則可以在Manifest文件中對暴露的service增加signature的保護級別

  • 使用Binder的靜態方法getCallingPid或者getCallingUid來驗證IPC調用者的身份,在獲得調用者uid以後,可進一步使用PackageManager.getPackagesForUid(int uid)來獲得調用者的包名,然後使用PackageManager.getPackageInfo(String Packagename, int flag)檢查是否具有相應的權限(使用PackageManager.GET_PERMISSIONS flag)

  • 在Service的OnBind方法中調用Context.checkCallingPermission(String permission)或者checkCallingPermissionOrSelf (String permission) 方法,驗證IPC調用者是否擁有指定的權限,同樣適用於Messenger;

  • 使用Context.enforceCallingPermission(String permission, String message),如果調用者不具備權限,自動拋出SecurityException

二、接口異常的統一捕獲,防禦各種崩潰,並上報崩潰日誌

三、接口有效性檢查,典型的爲防禦NoSuchMethodError

現有的對接口的兼容性檢查是採用api level的方式,插件中定義一個minLevel,主app中提供某個level的api,主app會根據兩個level來選擇具體加載哪個插件。與此類似的是Android SDK,每個APP都會提供minSdkVersion,如果在低版本手機上調用高版本系統api就會報找不到類或者函數,結果是崩潰。可以對這種情況進行防禦,解決的辦法就是對所有調用進行有效性檢查,檢查接口的實現類和函數是否存在,如果不存在就返回失敗。

四、接口的調用上報,後臺能查看接口調用情況,包括頻次,系統環境,所帶的數據等

五、接口的轉向,比如對於某個接口的調用,我們將其替換成執行另一個動作,這些對於接口實現本身來說是透明的

考慮到這些問題都與具體接口無關,所以我們可以統一進行處理,而最有效的辦法就是利用動態代理攔截掉所有的接口調用,然後在統一的入口去執行這些檢查。

接下來用代碼來舉例說明,先給出當前接口的形式,如下:

public abstract class AbsBluetoothManager {

    static AbsBluetoothManager instance;

    public static AbsBluetoothManager getInstance() {
        return instance;
    }

    abstract void connect(String mac);
}

這個AbsBluetoothManager是開放給插件的類,是個抽象類,具體的實現在主APP中,如下:

public class BluetoothManager extends AbsBluetoothManager {

    public static void init() {
        instance = new BluetoothManager();
    }

    @Override
    void connect(String mac) {
        // TODO Auto-generated method stub

    }
}

這樣對插件來說只能看到一個instance,具體實現對插件是不可見的。

現在爲了攔截所有的接口調用,我們需要做一點改動,將抽象類中的函數分離出接口IBluetoothManager:

public abstract class AbsBluetoothManager implements IBluetoothManager {

    static IBluetoothManager instance;

    public static IBluetoothManager getInstance() {
        return instance;
    }
}
public interface IBluetoothManager {
    void connect(String mac);
}

在主APP的實現類中用動態代理給instance包一層:

public class BluetoothManager extends AbsBluetoothManager {

    public static void init() {
        if (instance == null) {
            BluetoothManager manager = new BluetoothManager();
            instance = (IBluetoothManager) Proxy.newProxyInstance(
                    BluetoothManager.class.getClassLoader(),
                    new Class<?>[] {IBluetoothManager.class},
                    new ProxyHandler(manager));
        }
    }

    private static class ProxyHandler implements InvocationHandler {

        private Object subject;

        ProxyHandler(Object subject) {
            this.subject = subject;
        }

        @Override
        public Object invoke(Object object, Method method, Object[] args)
                throws Throwable {
            // TODO Auto-generated method stub
            Object result = null;

            // 檢查調用權限
            checkPermission();

            try {
                result = method.invoke(subject, args);

                // 接口調用上報
                addCallRecord();
            } catch (Exception e) {
                e.printStackTrace();
            }

            return result;
        }
    };

    @Override
    public void connect(String mac) {
        // TODO Auto-generated method stub
        System.out.println(mac);
    }
}

這樣每次調到接口中的函數時,都會被攔截一遍,在裏面我們可以做一些檢查,或者乾脆改變函數的執行方向。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章