Interacting with dynamically loaded library using DexClassLoader
Asked Answered
T

1

1

I am loading a jar file at runtime from SD card using DexClassLoader class

  final String libPath = Environment.getExternalStorageDirectory() +     "/test.jar";
        final File tmpDir = getDir("dex", 0);

        final DexClassLoader classloader = new DexClassLoader(libPath, tmpDir.getAbsolutePath(), null, this.getClass().getClassLoader());
        final Class<Object> classToLoad = (Class<Object>) classloader.loadClass("org.shlublu.android.sandbox.MyClass");

        final Object myInstance  = classToLoad.newInstance();
        final Method doSomething = classToLoad.getMethod("doSomething");



        doSomething.invoke(myInstance);

In my jar file i am printing few logs which is working fine.Now what i am trying to do is print a Toast from jar file. While doing this the exception i am getting is java.lang.reflect.InvocationTargetException. I know why we get this exception and reason behind it is nullpointer exception at context using it while printing toast. So its not a duplicate of

What could cause java.lang.reflect.InvocationTargetException?

cause behind it is

  java.lang.NullPointerException: Attempt to invoke virtual method      'android.content.Context android.content.Context.getApplicationContext()' on a null object reference 

code in jar file is

public class MyClass extends Activity {


@Override
protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
}


public void doSomething() {

    Toast.makeText(getApplicationContext(), "MyClass: doSomething() called.", Toast.LENGTH_LONG).show();

    Log.e(MyClass.class.getName(), "MyClass: doSomething() called.");
}}

Can anyone help me to achieve this.Any help will be appreciated.

Edit: what i am trying to do is I have a library of my own which many of my clients are using...what i want is that to load my library from user's SD card..and i want to update the library when ever i want without user's knowledge and without any version updates.Library includes few interfaces,Fragments and Activities. so now i am able to load my library from sd card and can invoke basic functions.Now the main challenge is to implement interfaces from library and invoking functions having usage of context in them.

Any samples or hint involving such operations will be much helpful.

Tumor answered 19/10, 2015 at 10:56 Comment(14)
Possible duplicate of What could cause java.lang.reflect.InvocationTargetException?Potence
@Boss exception is not my issue here.i know its cause.I need assistance to achieve the required stuff..so its not a duplicate for that java.lang.reflect.InvocationTargetException questionTumor
... yes, it is a duplicate ... you need to unwrap this exception to get know what is causing it ...Olivero
@selvin Please go through my edited question i already know the cause.I just asked what will be the workaround. ThanksTumor
Ok ... error is obvious ... Your class is prolly extending Activity or Service ... and you create instance of it by your own ... then in doSomething you call getApplicationContext() ...Olivero
@selvin ..Yes it extends activity then how can i achieve it then..please check my edited question..Tumor
there is no way to do this ... if you would know android's basics it would be obvious for you that the only way to create valid Activity instance is to call Contenxt.startIntent ... but such activity had to be declared in manifest ... solution is to use Fragments insteadOlivero
@selvin But that's my requirement...i have a android library that can change anytime remotely..so you know a better way to do this...?? It would be great if you can help... :)Tumor
@selvin with fragment you mean i should use fragment in jar file in place of activity..?? but don't you think using even fragment would involve a activity...Tumor
oh come on ... then you would have one generic activity which as intent parameters takes a path to the jar file and the fragment class name ... creating the fragment is up to you(i'm not sure about configuration changes or other places where OS is creating the instance of fragment by itself) ... if you don't know android framework well, this task is beyond your possibilities ... seriously you need to know android's components lifecycles very wellOlivero
@selvin Yes i am sure you are really good at android framework.. i can sniff it in your comments..Can you please help me doing it...Tumor
@Olivero you can't say this to OP "if you don't know android framework well, this task is beyond your possibilities ... seriously you need to know android's components lifecycles very well" . we all here to help each other instead of behaving like this.If you find this task quite easy for you why don't you post the working code or hint at-least.Satyr
@TGMCians i can give him a wroking code(ready to copy&paste) ... but, based on his exception from the question, he will not able to use it properly ... gist.github.com/SelvinPL/eefa88add4fa33f41ca8 ... jar loaded from this code contains dex with only 1 class Fragment1 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { TextView textView = new TextView(getActivity()); textView.setText("This is fragment loaded dynamically"); return textView; }Olivero
@ Selvin please see my edited question..Tumor
P
1

I see two ways of solving this, depending on what you are willing to do:

  • If MyClass has to be an Activity

An Activity has to be started properly, using startActivity() or any variant. It also has to be declared in your manifest. So the following works only if all your MyClass variants have the same signature.

I'm assuming your startup code sits in an Activity. Right after having loaded classToLoad, you could do the following:

final File tmpDir = getDir("dex", 0);
final String libPath = Environment.getExternalStorageDirectory() + "/test.jar";
final DexClassLoader classloader = new DexClassLoader(libPath, tmpDir.getAbsolutePath(), null, this.getClass().getClassLoader());

try {
    final Class<Object> classToLoad = (Class<Object>) classloader.loadClass("org.shlublu.android.sandbox.MyClass");

    // CHANGED: THIS STARTS PROPERLY YOUR ACTIVITY ONCE THE CLASS LOADED
    final Intent intent = new Intent(this, classToLoad);
    startActivity(intent); 

} catch (ClassNotFoundException e) {
    // handle that Exception properly here
}

Now change doSomething() in a way it uses the underlying Context of your new Activity instead of getApplicationContext(). Then call it from MyClass.onCreate() for example, and see that it works:

public class MyClass extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        doSomething(); // CHANGED: just as an example
    }

    private void doSomething() {
        // CHANGED: now the following line uses 'this' instead of `getApplicationContext()`
        Toast.makeText(this, "MyClass: doSomething() called.", Toast.LENGTH_LONG).show(); 

        Log.d(MyClass.class.getName(), "MyClass: doSomething() called.");
    }
}

The rest - when to call doSomething(), why, etc... - depends all on what you are willing to do.

  • If MyClass just has to display a Toast

No need to create another Activity in that case. doSomething() just needs to receive the proper Context to display the Toast.

Change MyClass as follows:

public class MyClass {
    private void doSomething(Context ctx) {
        Toast.makeText(ctx, "MyClass: doSomething() called.", Toast.LENGTH_LONG).show();

        Log.d(MyClass.class.getName(), "MyClass: doSomething() called.");
    }
}

And change your startup code to pass this to doSomething(), assuming it is run from an Activity:

final File tmpDir = getDir("dex", 0);
final String libPath = Environment.getExternalStorageDirectory() + "/test.jar";
final DexClassLoader classloader = new DexClassLoader(libPath, tmpDir.getAbsolutePath(), null, this.getClass().getClassLoader());

try {
    final Class<Object> classToLoad = (Class<Object>) classloader.loadClass("org.shlublu.android.sandbox.MyClass");

    // CHANGED: LOADS THE METHOD doSomething(Context). EXECUTES IT WITH this AS AN ARGUMENT
    final Class[] args = new Class[1];
    args[0] = Context.class;
    final Method doSomething = classToLoad.getMethod("doSomething", args);

    final Object myInstance  = classToLoad.newInstance();

    doSomething.invoke(myInstance, this);    
} catch (ClassNotFoundException e) {
    // handle that Exception properly here
}
Provisional answered 19/10, 2015 at 12:33 Comment(29)
@shublu Thanks a lot for the reply..The first approach won't work because it will find that activity in current project if we are using Intent... Can you please tell how we will pass this to doSomething() as a argument..i mean how it is possible in classToLoad.getMethod("doSomething");Tumor
As i thought 1st approach is throwing ActivityNotFoundException... :(Tumor
Method.invoke() can receive all the parameters you need: developer.android.com/reference/java/lang/reflect/…, java.lang.Object...) I updated my answer accordingly.Provisional
obviously (as i wrote in the comment) it will not work as activity is not registered in the manifest ...Olivero
@Olivero Yes that's true! I added it to my manifest without saying that in my answer! In my test I'm always loading a plugin that has the same class declaration.Provisional
@Olivero yes we know it won't work...can you please provide something that can work...ThanksTumor
// CHANGED: now the following line uses 'this' instead of getApplicationContext() <= this is really uneccesery ... looks like a programming by permutation for me ... app context is pretty valid for toastsOlivero
@shublu i got your point ..but here situation is different and i cant declare it in my current project's manifest...and thanks a lot for answering....Tumor
@selvin 'app context is pretty valid for toasts': Thanks for the tip. Feel free to change the answer accordingly. I tend to use getApplicationContext() as less as possible, which might be a bad habit.Provisional
Yes using getApplicationContext() everywhere is a bad habit...why use a complete application's reference for small tasks like Toast...using this is much better...Tumor
If i use your second approach i.e passing context as argument i get exception java.lang.NoSuchMethodException: doSomething [] at final Method doSomething = classToLoad.getMethod("doSomething");Tumor
@Hardeep getMethod() should receive the signature of your method: number of args and types. developer.android.com/reference/java/lang/…, java.lang.Class<?>...)Provisional
@shublu Ok i will go through this link...please see my edited question...you were wondring what i want to do in my MyClass..i mentioned it there... :) Thanks for the reply and help...Tumor
@shublu I changed the code the way you said but still getting the same error...java.lang.NoSuchMethodException: doSomething [class android.content.Context] ...Tumor
Let us continue this discussion in chat.Provisional
why don't you use interface for this? ... in my example i just cast the class from ddownloaded jar to Fragment ... you can make library with contract only and use it in both main app and "plugin" ... in "plugin" library you can implement interface from this library and in main app just create the instance of the class cast it to the interface and use it ...Olivero
@Olivero I didn't because this is not what the OP asked. His question is a follow-up to to this other question actually: #6858307Provisional
@Provisional it wasnt for you ... :) you are trying best to help him ... but it seems like you will fail(of course not your fault, you example is ok) :)Olivero
@selvin what you mean by your comment... "you can make library with contract only and use it in both main app and "plugin"" ??Tumor
@Olivero yes he will fail..you have hardly added any helpful comment here...but you are constantly discouraging and criticizing things.. see the quality of answer that shublu gave https://mcmap.net/q/245591/-is-it-possible-to-dynamically-load-a-library-at-runtime-from-an-android-application dynamically-load-a-library-at-runtime-from-an-android-applicat/6860579#6860579 every thing is so well explained...his work speaks louder than your comments... someone inteligent can write a code that even others can understand ##No offenceTumor
@shublu i am still getting that error though i have updated my function with context argument..java.lang.NoSuchMethodException: doSomething [class android.content.Context] ..Tumor
@Hardeep Sounds strange. Available for a chat?Provisional
hello shlublu.. hope u r fine....soory this project was on hold for few days...m back on this..m so sorry i left conversation in between...can we discuss it over again... Thanks in advance...Tumor
Hello @shlublu,i have made few advancements in project..can you please help me in implementing a interface from dynamically loaded jar file. ThanksTumor
@Provisional do you have example code that call Activity class in the dynamic library? (point 1), when I use intent, it got error ClassNotFound, cause its not in the main projectSmelser
@Smelser Well, I probably did but I didn't dig anymore into that since October 2015. But if you have a ClassNotFoundError it means that the class has not been loaded. If the issue is on downloadable dynamic lib side, you might have to use a mockup class or some reflection to have it compiled properly. Also, did you have a look to #6858307 ?Provisional
@Provisional are you referring to singhatiwana answer? Im currently testing his project github.com/singwhatiwanna/dynamic-load-apk/blob/master/…Smelser
@Smelser Yes, to this and to this Q/A in general. Ok!Provisional
@Provisional thanx for your confirmation. #SaluteSmelser

© 2022 - 2024 — McMap. All rights reserved.