1. 程式人生 > >外掛化載入未安裝APK

外掛化載入未安裝APK

主程式專門開一個PluginActivity,在該Activity裡通過dexClassLoader動態載入apk,通過反射的方式向被載入apk的mainActivity裡傳遞PluginAvtivity這個引數,相當於在主程式的PluginActivity裡畫Apk中Activity的View。

PluginActivity中的核心方法:loadApk

DexClassLoader這個類載入器用來從.jar和.apk型別的檔案內部載入classes.dex檔案。通過這種方式可以用來執行非安裝的程式程式碼,作為程式的一部分進行執行。這個裝載類需要一個程式私有的,可寫的檔案目錄去存放優化後的classes檔案。通過Contexct.getDir(String, int)來建立這個目錄:File dexOutputDir = context.getDir("dex", 0);

public File getDir(String name, int mode):name目錄名稱、mode許可權,如果傳入的目錄不存在,系統會建立此目錄,路徑為"/data/data/程式Package Name/app_name",name就是傳入的name。

通過以下程式碼來獲得DexClassLoader。dexPath是需要裝載的APK的路徑

DexClassLoader localDexClassLoader = new DexClassLoader(dexpath, dexOutputDir.getAbsolutePath(), null, ClassLoader.getSystemClassLoader().getParent());

通過以下程式碼獲得應用資訊

 PackageInfo pkgInfo = getPackageManager().getPackageArchiveInfo(dexpath,
                PackageManager.GET_ACTIVITIES);

   if ((pkgInfo.activities != null) && (pkgInfo.activities.length > 0)) {  
                String activityname = pkgInfo.activities[0].name; //獲得應用資訊裡的第一個Activity名 
  
                localClass = localDexClassLoader.loadClass(activityname);//通過反射載入型別
                mActivityClass = localClass;  
                Constructor localConstructor = localClass.getConstructor(new Class[] {});//獲得構造方法  
                instance = localConstructor.newInstance(new Object[] {});  //建立物件例項
                mActivityInstance = instance;  
  
                  
                Method localMethodSetActivity = localClass.getDeclaredMethod("setActivity", new Class[] { Activity.class });  
                localMethodSetActivity.setAccessible(true);  
                localMethodSetActivity.invoke(instance, new Object[] { this });  //相當於呼叫MainActivity裡的setActivity方法,把PluginActivity傳遞了進去

此外,還需要獲取未安裝apk的資源——getApkResoures方法

反射呼叫android.content.res.AssetManager類,新建個例項,呼叫隱藏的方法addAssetPath(String path)將為安裝APK檔案的新增進去,然後用這個AssetManager來構建出一個Resource例項

try {
    Class<?> class_AssetManager = Class.forName("android.content.res.AssetManager");
    Object assetMag = class_AssetManager.newInstance();
    Method method_addAssetPath = class_AssetManager
            .getDeclaredMethod("addAssetPath", String.class);
    String path = 路徑;
    String fileName = 檔名;
    method_addAssetPath.invoke(assetMag, path + fileName);
    Resources res = context.getResources();
    Constructor<?> constructor_Resources = Resources.class
            .getConstructor(class_AssetManager, res.getDisplayMetrics()
            .getClass(), res.getConfiguration().getClass());
    res = (Resources) constructor_Resources.newInstance(assetMag,
            res.getDisplayMetrics(), res.getConfiguration());
} catch (Exception e) {
    e.printStackTrace();
}

在MainActivity裡設一個setResources方法,反射呼叫它,把getApkResoures方法返回的Resource例項傳遞進去。

最後反射呼叫MainActivity的onCreate方法

Method methodonCreate = localClass.getDeclaredMethod("onCreate", new Class[] { Bundle.class });  
                 methodonCreate.setAccessible(true);  
                 methodonCreate.invoke(instance, savedInstanceState); 

二,如何更換應用主題時同時同步到外掛裡去?

外掛載入資源時先判斷Activity的資源裡有沒有它,有就呼叫,沒有再呼叫自己的Resource