1. 程式人生 > >談動態代理在Android外掛中的一些用法

談動態代理在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);
    }
}

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