我正在考慮從其他APK加載資源的某種方式。例如,我在SD卡上安裝了一個APK(它只是APK,它不安裝在手機內存中),其中包含我想要在我安裝的應用程序中使用的資源。是否可以從儲存在SD卡上的APK的res/x資源加載到我安裝的應用程序(例如佈局,圖像,字符串...)。從其他APK的動態資源加載
感謝
我正在考慮從其他APK加載資源的某種方式。例如,我在SD卡上安裝了一個APK(它只是APK,它不安裝在手機內存中),其中包含我想要在我安裝的應用程序中使用的資源。是否可以從儲存在SD卡上的APK的res/x資源加載到我安裝的應用程序(例如佈局,圖像,字符串...)。從其他APK的動態資源加載
感謝
在閱讀並調試一些AOSP代碼後,我終於設法動態加載資源。一步一步的指導:
創建一個新的Android項目,我把它叫做「動態APK加載」,指定whatever.dynamicapkloading
應用ID,和左「應用」模塊名稱。
創建另一個沒有任何活動的Android應用程序模塊。我在同一個項目中完成了這項工作,但這取決於你。我調用了模塊'dynamic',並將應用程序ID設置爲whatever.dynamic
。
從'動態'項目中刪除所有不必要的東西。我已經將任何其他資源一起刪除了啓動畫drawable,並從build.gradle中刪除了AppCompat depencency。我的清單內容看起來很簡約,如下所示:<application />
。
將一些代碼添加到'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();
}
}
將一些資源添加到'動態'項目。我創建strings.xml
:
<resources>
<string name="someString">This is a dynamically loaded string.</string>
<string name="anotherString">Lol! That\'s it.</string>
</resources>
將它添加到運行配置。將啓動選項/啓動設置爲Nothing。構建 - >構建APK!在這一步我有4.9 KB的文件叫做dynamic-debug.apk
。
將此文件dynamic/build/outputs/apk/dynamic-debug.apk
移到我們的主項目資產中,即i。即到app/src/main/assets/
。
創建/打開一個類,它將加載代碼&資源。說,這將是DynamicActivity。
編寫將代碼從資產複製到私人應用程序目錄的代碼。這是無聊瑣碎&,你知道:
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);
}
}
寫一段代碼,這將加載必要的類並創建實例。
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);
編寫將從指定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);
使用這些資源!有兩種獲取資源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
並在配置發生更改時重新查詢資源值。
有用的鏈接:
看起來沒有人能夠解決它。我個人認爲這是不可能的,但你永遠不知道...... – Waypoint
可能是可能的,但不會工作:反正ID會衝突。 – m0skit0