I am trying to do something similar to this stackoverflow posting. What I want to do is to read the definition of an activity or service from the SD card. To avoid manifest permission issues, I create a shell version of this activity in the .apk, but try to replace it with an activity of the same name residing on the SD card at run time. Unfortunately, I am able to load the activity class definition from the SD card using DexClassLoader, but the original class definition is the one that is executed. Is there a way to specify that the new class definition replaces the old one, or any suggestions on avoiding the manifest permission issues without actually providing the needed activity in the package? The code sample:
ClassLoader cl = new DexClassLoader("/sdcard/mypath/My.apk",
getFilesDir().getAbsolutePath(),
null,
MainActivity.class.getClassLoader());
try {
Class<?> c = cl.loadClass("com.android.my.path.to.a.loaded.activity");
Intent i = new Intent(getBaseContext(), c);
startActivity(i);
}
catch (Exception e) {
Intead of launching the com.android.my.path.to.a.loaded.activity
specified in /sdcard/mypath/My.apk
, it launches the activity statically loaded into the project.
Starting an Activity through DexClassLoader will be very tricky, because you didn't install the APK, and so there is nothing to handle your Intent.
You should have the same activity class in your platform APK and declare it in AndroidManifest.xml. Then, you should change the current ClassLoader to your desired DexClassLoader. As a result, it will start your plugin APK. (Note: "platform APK" refers to the app that is already installed in the phone, whereas "plugin APK" refers to the apk file saved in your SD card.)
The platform's application should look something like this:
public static ClassLoader ORIGINAL_LOADER;
public static ClassLoader CUSTOM_LOADER = null;
@Override
public void onCreate() {
super.onCreate();
try {
Context mBase = new Smith<Context>(this, "mBase").get();
Object mPackageInfo = new Smith<Object>(mBase, "mPackageInfo")
.get();
//get Application classLoader
Smith<ClassLoader> sClassLoader = new Smith<ClassLoader>(
mPackageInfo, "mClassLoader");
ClassLoader mClassLoader = sClassLoader.get();
ORIGINAL_LOADER = mClassLoader;
MyClassLoader cl = new MyClassLoader(mClassLoader);
//chage current classLoader to MyClassLoader
sClassLoader.set(cl);
} catch (Exception e) {
e.printStackTrace();
}
}
class MyClassLoader extends ClassLoader {
public MyClassLoader(ClassLoader parent) {
super(parent);
}
@Override
public Class<?> loadClass(String className)
throws ClassNotFoundException {
if (CUSTOM_LOADER != null) {
try {
Class<?> c = CUSTOM_LOADER.loadClass(className);
if (c != null)
return c;
} catch (ClassNotFoundException e) {
}
}
return super.loadClass(className);
}
}
For more code, you can visit https://github.com/Rookery/AndroidDynamicLoader
I'm reading the Android source code in order to find a more elegant method to implement this feature. If you have any idea, feel free to contact me.