2011-09-20 32 views
2

我正在考慮從其他APK加載資源的某種方式。例如,我在SD卡上安裝了一個APK(它只是APK,它不安裝在手機內存中),其中包含我想要在我安裝的應用程序中使用的資源。是否可以從儲存在SD卡上的APK的res/x資源加載到我安裝的應用程序(例如佈局,圖像,字符串...)。從其他APK的動態資源加載

感謝

+0

看起來沒有人能夠解決它。我個人認爲這是不可能的,但你永遠不知道...... – Waypoint

+0

可能是可能的,但不會工作:反正ID會衝突。 – m0skit0

回答

0

在閱讀並調試一些AOSP代碼後,我終於設法動態加載資源。一步一步的指導:

  1. 創建一個新的Android項目,我把它叫做「動態APK加載」,指定whatever.dynamicapkloading應用ID,和左「應用」模塊名稱。

  2. 創建另一個沒有任何活動的Android應用程序模塊。我在同一個項目中完成了這項工作,但這取決於你。我調用了模塊'dynamic',並將應用程序ID設置爲whatever.dynamic

  3. 從'動態'項目中刪除所有不必要的東西。我已經將任何其他資源一起刪除了啓動畫drawable,並從build.gradle中刪除了AppCompat depencency。我的清單內容看起來很簡約,如下所示:<application />

  4. 將一些代碼添加到'dynamic'項目。例如:

    package whatever.dynamic; 
    
    import android.content.Context; 
    import android.widget.Toast; 
    
    public final class Code implements Runnable { 
    
        private final Context context; 
    
        public Code(Context context) { 
         this.context = context; 
        } 
    
        @Override 
        public void run() { 
         Toast.makeText(context, "Running dynamic code!", 
           Toast.LENGTH_SHORT).show(); 
        } 
    } 
    
  5. 將一些資源添加到'動態'項目。我創建strings.xml

    <resources> 
        <string name="someString">This is a dynamically loaded string.</string> 
        <string name="anotherString">Lol! That\'s it.</string> 
    </resources> 
    
  6. 將它添加到運行配置。將啓動選項/啓動設置爲Nothing。構建 - >構建APK!在這一步我有4.9 KB的文件叫做dynamic-debug.apk

  7. 將此文件dynamic/build/outputs/apk/dynamic-debug.apk移到我們的主項目資產中,即i。即到app/src/main/assets/

  8. 創建/打開一個類,它將加載代碼&資源。說,這將是DynamicActivity。

  9. 編寫將代碼從資產複製到私人應用程序目錄的代碼。這是無聊瑣碎&,你知道:

    File targetApk = new File(getDir("dex", Context.MODE_PRIVATE), "app.apk"); 
    // copy APK from assets to private directory 
    // remove this condition in order to keep dynamic APK fresh 
    if (!targetApk.exists() || !targetApk.isFile()) { 
        try (BufferedInputStream bis = new BufferedInputStream(
          getAssets().open("dynamic-debug.apk")); 
         OutputStream dexWriter = new BufferedOutputStream(
           new FileOutputStream(targetApk))) { 
    
         byte[] buf = new byte[4096]; 
         int len; 
         while((len = bis.read(buf, 0, 4096)) > 0) { 
          dexWriter.write(buf, 0, len); 
         } 
        } catch (IOException e) { 
         throw new RuntimeException(e); 
        } 
    } 
    
  10. 寫一段代碼,這將加載必要的類並創建實例。

    PathClassLoader loader = new PathClassLoader(
         targetApk.getAbsolutePath(), getClassLoader()); 
    Class<?> dynamicClass = loader.loadClass("whatever.dynamic.Code"); 
    Constructor<?> ctor = dynamicClass.getConstructor(Context.class); 
    dynamicInstance = (Runnable) ctor.newInstance(this); 
    
  11. 編寫將從指定APK加載資源的代碼。這是一個很難!所有的構造函數和方法都是公開的,但它們是隱藏的。我已經用反射的方式編寫了它,但爲了避免這種情況,您可以使用完整的框架jar編譯代碼或在Smali中編寫代碼的一部分。

    AssetManager assets = AssetManager.class.newInstance(); 
    Method addAssetPath = AssetManager.class 
         .getMethod("addAssetPath", String.class); 
    if (addAssetPath.invoke(assets, targetApk.getAbsolutePath()) == 
         Integer.valueOf(0)) { 
        throw new RuntimeException(); 
    } 
    
    Class<?> resourcesImpl = Class.forName("android.content.res.ResourcesImpl"); 
    Class<?> daj = Class.forName("android.view.DisplayAdjustments"); 
    Object impl = resourcesImpl 
         .getConstructor(AssetManager.class, DisplayMetrics.class, 
           Configuration.class, daj) 
         .newInstance(assets, getResources().getDisplayMetrics(), 
           getResources().getConfiguration(), daj.newInstance()); 
    
    dynamicResources = Resources.class.getConstructor(ClassLoader.class) 
         .newInstance(loader); 
    Method setImpl = Resources.class.getMethod("setImpl", 
         Class.forName("android.content.res.ResourcesImpl")); 
    setImpl.invoke(dynamicResources, impl); 
    
  12. 使用這些資源!有兩種獲取資源ID的方法。

    int someStringId = dynamicResources.getIdentifier(
         "someString", "string", "whatever.dynamic"); 
    String someString = dynamicResources.getString(someStringId); 
    
    Class<?> rString = Class.forName("whatever.dynamic.R$string", true, loader); 
    anotherStringId = rString.getField("anotherString").getInt(null); 
    String anotherString = dynamicResources.getString(anotherStringId); 
    

這就是它!這對我有效。 Full DynamicActivity.java code

在實際項目中,您必須在構建時簽名APK,並在加載時檢查其簽名。當然,不要將它存儲在SD卡上,否則您的APK可能會被欺騙!

您還應靜態緩存資源ID,但重新創建Resources並在配置發生更改時重新查詢資源值。

有用的鏈接: