我正在考虑从其他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