Is it possible to dynamically load a library at runtime from an Android application?
Asked Answered
P

7

76

Is there any way to make an Android application to download and use a Java library at runtime?

Here is an example:

Imagine that the application needs to make some calculations depending on the input values. The application asks for these input values and then checks if the required Classes or Methods are available.

If not, it connects to a server, downloads the needed library, and loads it at runtime to calls the required methods using reflection techniques. The implementation could change depending on various criteria such as the user who is downloading the library.

Practise answered 28/7, 2011 at 10:54 Comment(0)
S
98

Sorry, I'm late and the question has already an accepted answer, but yes, you can download and execute external libraries. Here is the way I did:

I was wondering whether this was feasible so I wrote the following class:

package org.shlublu.android.sandbox;

import android.util.Log;

public class MyClass {
    public MyClass() {
        Log.d(MyClass.class.getName(), "MyClass: constructor called.");
    }

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

And I packaged it in a DEX file that I saved on my device's SD card as /sdcard/shlublu.jar.

Then I wrote the "stupid program" below, after having removed MyClass from my Eclipse project and cleaned it:

public class Main extends Activity {

    @SuppressWarnings("unchecked")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        try {
            final String libPath = Environment.getExternalStorageDirectory() + "/shlublu.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);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

It basically loads the class MyClass that way:

  • create a DexClassLoader

  • use it to extract the class MyClass from "/sdcard/shlublu.jar"

  • and store this class to the application's "dex" private directory (internal storage of the phone).

Then, it creates an instance of MyClass and invokes doSomething() on the created instance.

And it works... I see the traces defined in MyClass in my LogCat:

enter image description here

I've tried on both an emulator 2.1 and on my physical HTC cellphone (which is running Android 2.2 and which is NOT rooted).

This means you can create external DEX files for the application to download and execute them. Here it was made the hard way (ugly Object casts, Method.invoke() ugly calls...), but it must be possible to play with Interfaces to make something cleaner.

Wow. I'm the first surprised. I was expecting a SecurityException.

Some facts to help investigating more:

  • My DEX shlublu.jar was signed, but not my app
  • My app was executed from Eclipse / USB connection. So this is an unsigned APK compiled in DEBUG mode
Sachiko answered 28/7, 2011 at 14:25 Comment(26)
Great! This is really what I was looking for!! As you can see here (groups.google.com/group/android-security-discuss/browse_thread/…) there isn't really a security problem because the code will execute with the permission of your app.Practise
That makes complete sense, even if surprising at first glance. Thanks for the heads-up!Sachiko
have a look at this too: android-developers.blogspot.com/2011/07/…Peh
Excellent, thank you! Yes, tis is exactly the same logic. It must be quite unusual to exceed 64k methods, so the valid use for this is definitely plugins.Sachiko
MThis must be an important question because Google answered it on its own Android developer blog! ;PPractise
I know this an old thread, but I was hoping someone here could help. I am trying to do the same thing (dynamically load a plugin). Has anyone made this work by casting to an interface?Saponaceous
This is something I am currently working on. It is a bit cumbersome for now, but I'm quite sure there is a way to make it smart.Sachiko
how you have converted classes.dex to jar?Sexagenary
@Sexagenary I simply saved my DEX as shlublu.jarSachiko
@Sachiko If your class implements the interface and supposing that this interface is a priori known (ie., no reflection), what would be the problem to cast myInstance?Gloucestershire
@okiharaherbst Playing with interfaces is the right way to go. It is just that I wasn't there yet when answering this post :)Sachiko
Hello i tried doing the same way..i used eclipse to make a demo project i.e with Myclass and renamed classes.dex to testjar.jar. But in my main code i.e Main class i always get classnotfound exception... Any help will be much appreciated..Survey
@Hardeep It might be a path issue, or a packaging issue... looking at your code would help. Why not openeing a question?Sachiko
@Shublu Thanks a ton for the reply. I saw lot many questions already opened but without any solution.. :( i can post my code.can you please chack..i am trying from last two days..Survey
@Hardeep I don't know if I'll check it personnaly or if someone else will, but if you post a clear question with it, why not referencing this question, it will certainly receive answers.Sachiko
@shublu Thanks for the reply.I have made it to work finally. Now i want to implement a interface from the dynamically loaded jar file.Can you please help me in it. ThanksSurvey
@shublu i have opened a question can you or anyone else please help me with it...#33213123Survey
can you explain more that how did you make shlublu.jar from the MyClass?Psychasthenia
@farazkhonsari It is as explained above, I just saved my resulting .dex as shlublu.jarSachiko
Shlublu I am new to android and use xamarin to develop apps. I have a question: - I want to create dll (Class Library) project then dynamically use it in my main app at run-time. Imagine my main app has a feature that has a lot of activities and when user buy it, it is downloaded to main app and then user can uses it. Can I do this with DexClassLoader?Batista
Hi All, your discussion is very helpful, but still i have doubt, we can not start an Activity from external jar, if any one has done this, please confirm.Coridon
@Coridon I didn't try actually. Should this not feasible, you external jar can provide the caller with the name of an Activity to start anyway. But remember your Activity will have to be declared in the manifest file.Sachiko
Hi, according to me we can not startActivity, even have declared it in the manifest file. But we can use fragment to load the UI but for this fragment must specify the ui dynamically(Java Code) because your fragment will not find layout file.Coridon
If i am wrong then please correct, to launch an activity we need context and .jar file do not have any context. System always look it into the default location and at there this activity does not exists.Coridon
@Coridon We are going to take long in this comments section. How about giving a try and asking a question here on StackOverflow ? ;)Sachiko
@Sachiko - Can you please have alook my question - #67668327Browse
C
16

Shlublu's anwser is really nice. Some small things though that would help a beginner:

  • for library file "MyClass" make a separate Android Application project which has the MyClass file as only file in the src folder (other stuff, like project.properties, manifest, res, etc. should also be there)
  • in library project manifest make sure you have:
   <application android:icon="@drawable/icon" 
                android:label="@string/app_name">
        <activity android:name=".NotExecutable" 
                  android:label="@string/app_name">
        </activity>
    </application>

(".NotExecutable" is not a reserved word. It is just that I had to put something here)

  • For making the .dex file, just run the library project as android application (for the compiling) and locate .apk file from the bin folder of the project.
  • Copy the .apk file to your phone and rename it as shlublu.jar file (an APK is actually a specialization of a jar, though)

Other steps are the same as described by Shlublu.

  • Big thanks to Shlublu for cooperation.
Congregationalism answered 9/9, 2014 at 15:36 Comment(4)
You are welcome Pätris, and it was a pleasure to discuss with you. (I initially wrote this comment when I +1'ed your answer but I obviously forgot to press the "Add Comment" button...)Sachiko
I appreciate the trick, but reproducing @Sachiko 's example above using your method results in a ~380kB apk whereas if I just create a library project, I get a 3kB jar. However, the library approach requires an extra command, dx --dex --keep-classes --output=newjar.jar libproject.jar. Maybe there's a way to make that dex step automatic when Eclipse builds the library.Leigha
I'm confused why we use apk file?! I am new in xamarin android. I want to create library project dll (Class Library) and then use it in my main app at run-time. How is it possible to do that?Batista
@Pätris - I tried your method (skipped 2nd step of including manifest entry) and did not change name to .jar. Instead kept the apk as default name, app-debug.apk and modified Shlublu's code (just changed file name). I am getting "ClassNotFound" Exception. I am running on Android 6.0. Am I missing anything ?Browse
D
4

Technically should work but what about Google rules? From: play.google.com/intl/en-GB/about/developer-content-policy-pr‌​int

An app distributed via Google Play may not modify, replace or update itself using any method other than Google Play’s update mechanism. Likewise, an app may not download executable code (e.g. dex, JAR, .so files) from a source other than Google Play. This restriction does not apply to code that runs in a virtual machine and has limited access to Android APIs (such as JavaScript in a WebView or browser).

Donough answered 7/12, 2017 at 10:40 Comment(2)
That's absolutely right. However, the OP asked whether this is technically feasible and may want to do such a thing for his/her own purpose without using Google Play at all. Also, it looks like a Google Play app may embed jars that would be written on the local storage at deployment time and that would be loaded later by the app without breaking this rule (regardless whether it would be a good idea). But anyway, you are right to remind developers that an app downloaded from Google Play should not download code from elsewhere. This should be a comment though.Sachiko
New URL as of 12/15/2021: support.google.com/googleplay/android-developer/answer/9888379Whomp
R
1

I am not sure if you can achieve this by dynamically loading java code. May be you can try embedding a script engine your code like rhino which can execute java scripts which can be dynamically downloaded and updated.

Rivkarivkah answered 28/7, 2011 at 11:3 Comment(6)
Thanks. I will investigate this approach (didn't know about scripting on Android before today). More information (if someone needs it) can be found here: google-opensource.blogspot.com/2009/06/…Practise
@Rivkarivkah Yes, you can also download and execute Java compiled code. See my response in this page. I wouldn't have expected that actually.Sachiko
Thanks @shlublu seems interesting :). The only problem i can think of is it relies heavily on reflections or you need to start writing wrappers i see lot of management problems, if we have to write whole code that way. That's why i thought scripts are easy and good way managing business logic.Rivkarivkah
Also how would you start generating standalone dex jar files? you would create seperate android application projects? as of now there is no concept of static libraries in android rite?Rivkarivkah
As I did here: I have exported a java project that was only containing MyClass. This generated a valid Dex file compliant with the Dalvik's format.Sachiko
Look at the the /system/framework directory of your device, it is full of jars made that way. (no root right needed)Sachiko
U
1

sure, it is possible. apk which is not installed can be invoked by host android application.generally,resolve resource and activity's lifecircle,then,can load jar or apk dynamically. detail,please refer to my open source research on github: https://github.com/singwhatiwanna/dynamic-load-apk/blob/master/README-en.md

also,DexClassLoader and reflection is needed, now look at some key code:

/**
 * Load a apk. Before start a plugin Activity, we should do this first.<br/>
 * NOTE : will only be called by host apk.
 * @param dexPath
 */
public DLPluginPackage loadApk(String dexPath) {
    // when loadApk is called by host apk, we assume that plugin is invoked by host.
    mFrom = DLConstants.FROM_EXTERNAL;

    PackageInfo packageInfo = mContext.getPackageManager().
            getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);
    if (packageInfo == null)
        return null;

    final String packageName = packageInfo.packageName;
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
    if (pluginPackage == null) {
        DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
        AssetManager assetManager = createAssetManager(dexPath);
        Resources resources = createResources(assetManager);
        pluginPackage = new DLPluginPackage(packageName, dexPath, dexClassLoader, assetManager,
                resources, packageInfo);
        mPackagesHolder.put(packageName, pluginPackage);
    }
    return pluginPackage;
}

your demands is only partly of function in the open source project mentioned at the begining.

Unapt answered 6/12, 2014 at 5:43 Comment(2)
I got some error using you project, it still has the same problem with my previous project. ClassNotFound when startActivity..Beef
I am new in android development. I don't know why we should use apk to load dynamically methods?! I want to load my dll that created by Class Library project dynamically at run-time.Batista
A
1

If you're keeping your .DEX files in external memory on the phone, such as the SD card (not recommended! Any app with the same permissions can easily overwrite your class and perform a code injection attack) make sure you've given the app permission to read external memory. The exception that gets thrown if this is the case is 'ClassNotFound' which is quite misleading, put something like the following in your manifest (consult Google for most up to date version).

<manifest ...>

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="18" />
    ...
</manifest>
Aaronaaronic answered 13/12, 2014 at 2:13 Comment(0)
C
1

I think @Shlublu answer is correct but i just want to highlight some key points.

  1. We can load any classes from external jar and apk file.
  2. In Any way, we can load Activity from external jar but we can not start it because of the context concept.
  3. To load the UI from external jar we can use fragment. Create the instance of the fragment and embedded it in the Activity. But make sure fragment creates the UI dynamically as given below.

    public class MyFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup 
      container, @Nullable Bundle savedInstanceState)
     {
      super.onCreateView(inflater, container, savedInstanceState);
    
      LinearLayout layout = new LinearLayout(getActivity());
      layout.setLayoutParams(new 
     LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.MATCH_PARENT));
    Button button = new Button(getActivity());
    button.setText("Invoke host method");
    layout.addView(button, LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT);
    
    return layout;
     }
    }
    
Coridon answered 18/9, 2018 at 11:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.