Skip to content

Instantly share code, notes, and snippets.

@Miha-x64
Created May 23, 2017 14:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Miha-x64/bb842d833d220c8f52c5e9f743b3ffd7 to your computer and use it in GitHub Desktop.
Save Miha-x64/bb842d833d220c8f52c5e9f743b3ffd7 to your computer and use it in GitHub Desktop.
An activity which copies an APK from assets to private directory, and loads a class and resources from it.
package whatever.dynamicapkloading;
import android.app.Activity;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.widget.TextView;
import dalvik.system.PathClassLoader;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class DynamicActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
File targetApk = new File(getDir("dex", Context.MODE_PRIVATE), "app.apk");
// copy APK from assets to private directory
if (!targetApk.exists() || !targetApk.isFile()) { // remove this condition in order to keep dynamic APK fresh
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);
}
}
Runnable dynamicInstance;
Resources dynamicResources;
int anotherStringId;
try {
// load class & instantiate it
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);
// load resources
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);
Class<?> rString = Class.forName("whatever.dynamic.R$string", true, loader);
anotherStringId = rString.getField("anotherString").getInt(null);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
// create UI
TextView textView = new TextView(this);
setContentView(textView);
// we're done, run!
dynamicInstance.run();
// grab some resources
int someStringId = dynamicResources.getIdentifier("someString", "string", "whatever.dynamic");
String someString = dynamicResources.getString(someStringId);
String anotherString = dynamicResources.getString(anotherStringId);
textView.setText(someString + " | " + anotherString);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment