Dalvik classloader mystery - android

I am on Android 2.2 SDK and could not get my static block inside MultiUserChat class to execute. I have tried to force load it as
try
{
String qual = MultiUserChat.class.getName();
ClassLoader.getSystemClassLoader().loadClass(qual);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
and it always hits the catch block. 'qual' gets the valid name of the class... what can it be?

Your app includes both framework classes like ArrayList and Activity, plus application classes like FlashlightActivity. The framework classes are loaded by the system class loader (and also the bootstrap class loadeR); the application classes are loaded by the application class loader.
The system class loader can only see the system classes. It doesn't know the application class path and it can't be used to load application classes. You need to use the application class loader to do that. The easiest way to get a reference to the application class loader is via an application class:
try {
String qual = MultiUserChat.class.getName();
MyActivity.class.getClassLoader().loadClass(qual);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

Related

Getting "error: cannot find symbol method openFileOutput(String,int)" in my Android app

I'm trying to save the results of a JSON fetch to a local file. When I try to use openFileOutput, I get error: cannot find symbol method openFileOutput(String,int). Searching said that it needs Context, but I have tried every other class I have and it either keeps the same error, or I get Non-static method 'openFileOutput(java.lang.String,Int)' cannot be referenced from a static context.
All of my classes extend AppCompatActivity or ArrayAdapter, except the class I'm using this code in, which extends AsyncTask.
//SAVE JSON TO FILE
private void writeToFile(String data) {
try {
OutputStreamWriter outputStreamWriter = OutputStreamWriter(openFileOutput("BonusData.xml", 0));
outputStreamWriter.write(data);
outputStreamWriter.close();
}
catch (IOException e) {
Log.e("Exception", "File write failed: " + e.toString());
}
}
The method openFileOutput belongs to the class Context, so you cannot use it directly from inside the ArrayAdapter class.
You have multiple options:
Either pass reference of Context as an argument in your method writeToFile and use context.openFileOutput(...)
Or use the method getContext() of the class ArrayAdapter and use getContext().openFileOutput(...)
If your ArrayAdapter is an inner class of your activity, don't make it (the classs) static and then you can access all methods including openFileOutput() directly from inside the ArrayAdapter.

create an instance of Activity with Activity class object

I have got an Activity class by:
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
String activityClassName = launchIntent.getComponent().getClassName();
Class<?> activityClazz = Class.forName(activityClassName);
Is it possible to create an instance of this Activity by using the activityClazz ? If so, how?
(My code is in a independent java class. Not in activity or service. )
Technically, you can create an instance of an Activity like this. However, this instance would be useless because its underlying Context would not have been set up.
The rule is that you should never ever create instances of Android components (Activity, Service, BroadcastReceiver, Provider) yourself (using the new keyword or other means). These classes should only ever be created by the Android framework, because Android sets up the underlying Context for these objects and also manages the lifecycle.
In short, your architecture is flawed if you need to create an instance of an Activity like this.
Class.forName() needs the fully qualified name - that is, the name of the package the class is contained in, plus the simple name of the class itself.
Assuming the package containing the class is called com.your.package, the code would have to be
String className = "com.your.package.Tab3"; // Change here
Object obj= null;
try {
obj= Class.forName(className).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
yes. you can get the activity context by using below line of code
Activity activity = (Activity) getContext();

Custom Exception class to handle exceptions over entire application

I've just started to write an application.
I want to create a custom Exception class that spans over my whole application.
Want to do this so that every exception can be passed to that class file & whenever a exception is occurred, the Logs can be stored in one place.
I tired using this, but it does not seems an entirely good practice.
What is the best method to achieve this
Thank You
You could try just making a general (non-Exception) class, and pass the exception to it. Something like this should work:
Public Class ExceptionHandler {
public ExceptionHandler() {
}
public static handle(Exception e) {
// do stuff
}
}
in your code:
try {
}
catch(Exception e) {
ExceptionHandler.handle(e);
}

ClassCastException while dynamically loading a class in Android

I have a thread that loads different classes for the resources it needs depending on the specific implementation of the system. My implementation is on Android and I have a class that returns the specific classes needed by my implementation. I seem to be able to load the class fine, but when I try to assign it to the object in my main thread, it gives me a ClassCastException. Here are the snippets:
In my main thread, I do:
try {
grammarProcessor = config.loadObject(GrammarProcessor.class);
which gives me this stacktrace:
E/AndroidRuntime(6682): FATAL EXCEPTION: JVoiceXmlMain
E/AndroidRuntime(6682): java.lang.ClassCastException: org.jvoicexml.android.JVoiceXmlGrammarProcessor
E/AndroidRuntime(6682): at org.jvoicexml.JVoiceXmlMain.run(JVoiceXmlMain.java:321)
GrammarProcessor is an interface and JVoiceXmlGrammarProcessor is the class that I load and implements that interface. The loading code is as follows:
else if(baseClass == GrammarProcessor.class){
String packageName = "org.jvoicexml.android";
String className = "org.jvoicexml.android.JVoiceXmlGrammarProcessor";
String apkName = null;
Class<?> handler = null;
T b = null;
try {
PackageManager manager = callManagerContext.getPackageManager();
ApplicationInfo info= manager.getApplicationInfo(packageName, 0);
apkName= info.sourceDir;
} catch (NameNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return null;
}
PathClassLoader myClassLoader =
new dalvik.system.PathClassLoader(
apkName,
ClassLoader.getSystemClassLoader());
try {
handler = Class.forName(className, true, myClassLoader);
return (T) handler.newInstance();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
When debugging, I check what's returning from the load method and it is an object with an id number. If I click on it, it'll say org.jvoicexml.android.JVoiceXmlGrammarProcessor#40565820, and the dropdown will show the two private fields that a JVoiceXmlGrammarProcessor should have, so it looks like it's well loaded. Any ideas?
I think I understand what's happening here but I have to make an assumption that org.jvoicexml.android is not your package, i.e., you're loading from a different apk (as the bounty seems to suggest).
With that in mind, this is impossible and for a good reason.
Let's start with your own app - you have the type GrammarProcessor available from your own classes.dex and into your default ClassLoader (the PathClassLoader that you get when the zygote forks your process). Let's call this type GP1. Any class in your own application that implements GrammarProcessor actually has GP1 in their interface list.
Then, you instantiate a new classloader. If you look at the source, you'll see that PathClassLoader is just a thin wrapper around BaseDexClassLoader which in turn delegates to a DexPathList, which in turn delegates to DexFile objects which in turn do the loading in native code. Phew.
There's a subtle part of BaseDexClassLoader that's the cause of your troubles but if you haven't seen it before, you might miss it:
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
and a bit further down:
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class c = pathList.findClass(name);
if (c == null) {
...
}
return c;
}
BaseDexClassLoader does not check with its parent first!
.. and that in short is your problem.
More precisely, the DexPathList and DexFile inside it load all the classes from the other dex and never look into the classes already loaded in the VM.
So, you end up with two different loaded versions of GrammarProcessor. Then, the object you're instantiating is referring to the new GP2 class, while you're trying to cast it to GP1. Obviously impossible.
Is there a solution to this?
There's one that's been done before, but you won't like it. Facebook use it in their app to load a bunch of dex files with strong relationships between them. (It's there, before all the messing about with LinearAlloc):
we examined the Android source code and used Java reflection to directly modify some of its internal structures
I'm 90% sure they get the PathClassLoader that you're given (getSystemClassLoader()), get the DexPathList and override the dexElements private field to have an extra Element with the other dex file (apk in your case). Hacky as hell and I would advise against it.
It just occurred to me that if you don't want to use the newly loaded classes in a way that the framework sees them, you could extend from BaseDexClassLoader and implement the proper look-in-parent-before-trying-to-load behaviour. I haven't done it, so I can't promise it will work.
My advice? Just use remote services. This is what Binder is meant for. Alternatively, rethink your apk separation.

Android, how to use DexClassLoader to dynamically replace an Activity or Service

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.

Categories

Resources