-
-
Save marshall/839003 to your computer and use it in GitHub Desktop.
// | |
// !!WARNING: Not recommended for production code!! | |
// | |
public class ClassLoaderActivity extends Activity | |
{ | |
public void onCreate(Bundle savedInstanceState) | |
{ | |
// file.jar has a dex'd "classes.dex" entry that you can generate with "dx" from any number of JARs or class files | |
ClassLoader dexLoader = new DexClassLoader("/path/to/file.jar", getCacheDir().getAbsolutePath(), null, getClassLoader()); | |
setAPKClassLoader(dexLoader); | |
try { | |
Class<?> activityClass = dexLoader.loadClass("com.company.MyActivity"); | |
Intent intent = new Intent(this, activityClass); | |
startActivity(intent); | |
} catch (ClassNotFoundException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
finish(); | |
} | |
private void setAPKClassLoader(ClassLoader classLoader) | |
{ | |
try { | |
Field mMainThread = getField(Activity.class, "mMainThread"); | |
Object mainThread = mMainThread.get(this); | |
Class threadClass = mainThread.getClass(); | |
Field mPackages = getField(threadClass, "mPackages"); | |
HashMap<String,?> map = (HashMap<String,?>) mPackages.get(mainThread); | |
WeakReference<?> ref = (WeakReference<?>) map.get(getPackageName()); | |
Object apk = ref.get(); | |
Class apkClass = apk.getClass(); | |
Field mClassLoader = getField(apkClass, "mClassLoader"); | |
mClassLoader.set(apk, classLoader); | |
} catch (IllegalArgumentException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} catch (IllegalAccessException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
} | |
private Field getField(Class<?> cls, String name) | |
{ | |
for (Field field: cls.getDeclaredFields()) | |
{ | |
if (!field.isAccessible()) { | |
field.setAccessible(true); | |
} | |
if (field.getName().equals(name)) { | |
return field; | |
} | |
} | |
return null; | |
} | |
} |
Hi @marshall, it appears that Android switched HashMap on line 31 to an ArrayMap. Please consider this revision to work with both versions of the Android OS: https://gist.github.com/ndahlquist/19867c60ca4a6e7c1ca1/revisions
To use this code, list the activity that does not exist in the Android Manifest of the app.
it's also possible to do so directly in Application
class. we can invoke android.app.ActivityThread.currentActivityThread() to get mainThread
. here is the code:
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
@SuppressLint("PrivateApi")
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method getCurrentActivityThread = activityThreadClass.getMethod("currentActivityThread");
getCurrentActivityThread.setAccessible(true);
Object mainThread = getCurrentActivityThread.invoke(null);
// the rest is the same
} catch (Exception e) {
}
}
Please note that if your are loading a dex file that calls a native function, you should specify the path to the shared library containing that function as the third parameter of DexClassLoader
constructor, otherwise the runtime will report UnsatisfiedLinkError
. Besides, you need to load the shared library after your dex file is loaded.
Hey marshall, this is exactly what I am trying to do.
I tried running your code but I always get the exception: class resolved by unexpected dex.
Do you have any Idea why this is? Did this code work for an external apk signed by a different developer?
I would really appreciate some feedback. Thanks