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.

Community
  • 1
  • 1
Harry Sharma
  • 2,190
  • 2
  • 15
  • 41
  • 2
    Possible duplicate of [What could cause java.lang.reflect.InvocationTargetException?](http://stackoverflow.com/questions/6020719/what-could-cause-java-lang-reflect-invocationtargetexception) – Emil Oct 19 '15 at 11:00
  • @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 question – Harry Sharma Oct 19 '15 at 11:03
  • ... yes, it is a duplicate ... you need to unwrap this exception to get know what is causing it ... – Selvin Oct 19 '15 at 11:07
  • @selvin Please go through my edited question i already know the cause.I just asked what will be the workaround. Thanks – Harry Sharma Oct 19 '15 at 11:13
  • 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()` ... – Selvin Oct 19 '15 at 11:15
  • @selvin ..Yes it extends activity then how can i achieve it then..please check my edited question.. – Harry Sharma Oct 19 '15 at 11:16
  • 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 instead – Selvin Oct 19 '15 at 11:18
  • @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... :) – Harry Sharma Oct 19 '15 at 11:21
  • @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... – Harry Sharma Oct 19 '15 at 11:31
  • 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 well – Selvin Oct 19 '15 at 11:35
  • @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... – Harry Sharma Oct 19 '15 at 11:54
  • @Selvin 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. – Ajay S Oct 19 '15 at 13:08
  • @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 ... https://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; }` – Selvin Oct 19 '15 at 13:54
  • @ Selvin please see my edited question.. – Harry Sharma Oct 20 '15 at 06:31

1 Answers1

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
}
Shlublu
  • 10,917
  • 4
  • 51
  • 70
  • @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"); – Harry Sharma Oct 19 '15 at 12:55
  • As i thought 1st approach is throwing ActivityNotFoundException... :( – Harry Sharma Oct 19 '15 at 12:59
  • `Method.invoke()` can receive all the parameters you need: http://developer.android.com/reference/java/lang/reflect/Method.html#invoke(java.lang.Object, java.lang.Object...) I updated my answer accordingly. – Shlublu Oct 19 '15 at 13:00
  • obviously (as i wrote in the comment) it will not work as activity is not registered in the manifest ... – Selvin Oct 19 '15 at 13:00
  • @Selvin 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. – Shlublu Oct 19 '15 at 13:02
  • @Selvin yes we know it won't work...can you please provide something that can work...Thanks – Harry Sharma Oct 19 '15 at 13:02
  • *// 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 toasts – Selvin Oct 19 '15 at 13:03
  • @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.... – Harry Sharma Oct 19 '15 at 13:05
  • @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. – Shlublu Oct 19 '15 at 13:09
  • 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... – Harry Sharma Oct 19 '15 at 13:15
  • 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"); – Harry Sharma Oct 20 '15 at 06:11
  • @Hardeep getMethod() should receive the signature of your method: number of args and types. http://developer.android.com/reference/java/lang/Class.html#getMethod(java.lang.String, java.lang.Class>...) – Shlublu Oct 20 '15 at 06:34
  • @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... – Harry Sharma Oct 20 '15 at 06:38
  • @shublu I changed the code the way you said but still getting the same error...java.lang.NoSuchMethodException: doSomething [class android.content.Context] ... – Harry Sharma Oct 20 '15 at 06:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/92827/discussion-between-shlublu-and-hardeep). – Shlublu Oct 20 '15 at 08:38
  • 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 ... – Selvin Oct 20 '15 at 08:44
  • @Selvin I didn't because this is not what the OP asked. His question is a follow-up to to this other question actually: http://stackoverflow.com/questions/6857807/is-it-possible-to-dynamically-load-a-library-at-runtime-from-an-android-applicat/6860579#6860579 – Shlublu Oct 20 '15 at 09:16
  • @Shlublu 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) :) – Selvin Oct 20 '15 at 09:18
  • @selvin what you mean by your comment... "you can make library with contract only and use it in both main app and "plugin"" ?? – Harry Sharma Oct 21 '15 at 06:33
  • @Selvin 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 http://stackoverflow.com/questions/6857807/is-it-possible-to- 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 offence – Harry Sharma Oct 21 '15 at 06:41
  • @shublu i am still getting that error though i have updated my function with context argument..java.lang.NoSuchMethodException: doSomething [class android.content.Context] .. – Harry Sharma Oct 21 '15 at 08:07
  • @Hardeep Sounds strange. Available for a chat? – Shlublu Oct 21 '15 at 08:29
  • 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... – Harry Sharma Nov 27 '15 at 05:33
  • Hello @shlublu,i have made few advancements in project..can you please help me in implementing a interface from dynamically loaded jar file. Thanks – Harry Sharma Dec 29 '15 at 06:14
  • @Shlublu 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 project – KDoX Sep 13 '17 at 09:02
  • @KDoX 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 https://stackoverflow.com/questions/6857807/is-it-possible-to-dynamically-load-a-library-at-runtime-from-an-android-applicat/6860579#6860579 ? – Shlublu Sep 13 '17 at 12:00
  • @Shlublu are you referring to singhatiwana answer? Im currently testing his project https://github.com/singwhatiwanna/dynamic-load-apk/blob/master/README-en.md – KDoX Sep 14 '17 at 02:38
  • @KDoX Yes, to this and to this Q/A in general. Ok! – Shlublu Sep 14 '17 at 06:05
  • 1
    @Shlublu thanx for your confirmation. #Salute – KDoX Sep 14 '17 at 06:07