主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);
}
}
這樣每次調到接口中的函數時,都會被攔截一遍,在裏面我們可以做一些檢查,或者乾脆改變函數的執行方向。