12

I want to add a non-native shared library to Android so every application on the device would be able to use it. I mean using packaged classes just like a core library as if they were present in the application itself.

I studied Android source code to figure out a way to add a new path to the application ClassLoader and found out that it is created during startup and there's no way to change the paths later. I can use my own ClassLoader, but all I will get after loading a class will be a reference to a Class object. This way I will be bound to work through reflection mechanism, which is slower than the native execution system.

Is there any way to organise a shared library on Android?

Update: just to be clear here, I don't need interaction between applications. I need classes which I can reuse in any application. Also static libraries don't really suit my needs because this will bloat the applications.

Malcolm
  • 41,014
  • 11
  • 68
  • 91

4 Answers4

10

There are two tricks to create dynamic load library in Android

  1. use sharedUserId in AndroidManifest for your applications and library project

  2. use dalvik.system.DexClassLoader to load library

Library Code:

It contains just java code without any Android specific entry points. AndroidManifest.xml just contains this android:sharedUserId attribute

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testlib"
    android:sharedUserId="com.example.testlib"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="15" />

    <application android:label="@string/app_name"
        android:icon="@drawable/ic_launcher"
        android:theme="@style/AppTheme">

    </application>

</manifest>

TestCore.java

package com.example.testlib;

public class TestCore implements ITestCore{
    private int count = 0;
    public String testString(String arg) {
        String res = arg + " " + count;
        count++;
        return res;
    }
}

Sample Application Code

Application that uses the library. Here is just the AndroidManifest.xml and TestApplication.java which do the trick. All other application staff is as usual.

AndroidManifest.xml

Be carefull to use same android:sharedUserId value in AndroidManifest.xml as library one

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testapp"
    android:sharedUserId="com.example.testlib"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:name=".TestApplication" >
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="android.app.Activity" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
                <category android:name="android.intent.category.DEFAULT"/>
                </intent-filter>
        </activity>
    </application>

</manifest>

ITestCore.java

Library interface has to be declared in application to avoid use reflection

package com.example.testlib;

public interface ITestCore {
    String testString(String arg);
}

TestApplication.java

In applicatiopn's onCreate handler there is real work happens

package com.example.testapp;

import com.example.testlib.ITestCore;
import dalvik.system.DexClassLoader;
import android.app.Application;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.Log;

public class TestApplication extends Application {
    ClassLoader libClassLoader;

    @Override
    public void onCreate() {
        PackageManager pm = getPackageManager();
        String libSrcPath = null;
        for (ApplicationInfo app : pm.getInstalledApplications(0)) {
            if (app.packageName.equals("com.rhomobile.testlibrary")) {
                libSrcPath = app.sourceDir;
                Log.d("TestApplication", ">>>>>>>>>>>>>>> package: " + app.packageName + ", sourceDir: " + app.sourceDir);
            }
        }
        try {
            libClassLoader = new DexClassLoader(libSrcPath, getDir("dex", 0).getAbsolutePath(), null, getClassLoader());
            Class<?> clazz = libClassLoader.loadClass("com.rhomobile.testlib.TestCore");            
            try {
                ITestCore core = (ITestCore)clazz.newInstance();
                String str = core.testString("TestApplication 1:");
                Log.i("TestApplication", ">>>>>>>>>>>>>>> output: " + str);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            Log.e("TestApplication", libClassLoader.toString());
            e.printStackTrace();
        }
    }
}
khr055
  • 28,690
  • 16
  • 36
  • 48
Alexey
  • 146
  • 1
  • 4
  • I am getting "java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation" using it this way.Any ideas? – EvZ Mar 11 '13 at 09:40
  • So do I. I am getting "Class ref in pre-verified class resolved to unexpected implementation" error? Have any solution? – N3CAT1 Jul 24 '13 at 10:10
  • I got the same error. I had to put the common interface in its own library and include that library in my application and in the dynamic library that implements that interface. – Harvey Jan 23 '15 at 20:09
3

Recent post on the Android blog explains exactly what I needed:

The Dalvik VM provides facilities for developers to perform custom class loading. Instead of loading Dalvik executable (“dex”) files from the default location, an application can load them from alternative locations such as internal storage or over the network.

The key is to use DexClassLoader to load the needed library. After that the code can be accessed either through interfaces, or by using reflection.

Malcolm
  • 41,014
  • 11
  • 68
  • 91
  • In xamarin android I want to load a dll (Class Library) and create and run its activities at runtime. Is DexClassLoader a proper solution for that? – Mohammad Afrashteh Jan 14 '18 at 17:46
  • @MohammadAfrashteh I doubt it. You better create a new question with regard to how to do this. – Malcolm Jan 14 '18 at 20:31
2

For static libraries of reusable classes you can use library projects. For dynamically interacting with other applications securely you can bind to a service.

adamp
  • 28,862
  • 9
  • 81
  • 69
  • If I use static libraries, this means that I'll have to shove the same code into every single application, which is exactly what I'm trying to avoid. And I'm definitely not interested in interaction betweeen applications. – Malcolm Jun 13 '10 at 09:31
  • What problem are you trying to solve? Are you concerned about storage space for multiple apps using common code? (And if so, is this a premature optimization?) How many apps do you expect will use it? How do you plan to deploy this shared library? Do you want to make it available to 3rd parties? Do you want centralized updating of common components? What if an update to the library breaks older apps? What about security? – adamp Jun 13 '10 at 18:41
  • I'm planning to write a tool to convert Java ME applications to APK, but to do that I need all the libraries present in the platform, in my case - JSRs defined by MSA and probably even more. So if I just insert all the code into the applications, this will make them much bigger than they should be. I could offset this by killing dead code, but this would complicate my app. And how many apps do I expect to create this way - well, as many as my users will want to. So the less excessive code I add the better. – Malcolm Jun 13 '10 at 19:11
  • I see. Eliminating dead code as part of your build process using a tool like proguard and including the necessary libraries statically is probably going to be your best bet. (What do you mean by "complicate your app?") Android does not offer first-class support for what you want at this time. – adamp Jun 13 '10 at 20:15
  • Yeah, I also think that I'll have to port ProGuard to Android and use it as a part of my app in that case. And this is what I meant by complicating the app. Actually Android really diappoints me with the lack of dynamic libraries support, even MIDP 3.0 can do that, not to mention other platforms. And they could implement this very easily by allowing to adjust classpath just like it is possible in Java. Okay, I mark your answer as an accepted answer, thanks for the comments. ;) Though if someone still figures out some hackish way to do it (say, like with Java SE), he's welcome. :) – Malcolm Jun 13 '10 at 20:43
  • You don't have to port proguard to Android, it runs on the host machine after compiling but before dexing. – adamp Jun 13 '10 at 21:18
  • Maybe I didn't explain this clearly, I'm writing my app for mobile devices running Android, not for PCs, that's why I'm so concerned about keeping my app lighter. So I will have to get ProGuard running on Android. – Malcolm Jun 13 '10 at 22:31
  • By the way, if I were writing for PC, then Java ME apps would have to be prepared for running on Android beforehand. And I want to have something like a normal JVM on Android, which supports at least MSA, probably even CDC or MIDP 3.0 on top of it. That would enable crossplatform programming, which is quite useful when there's a handful of variegated platforms. – Malcolm Jun 13 '10 at 22:46
  • Proguard does not run on the target Android device just like ant, javac, dx, or aapt does not run on the target device. Proguard runs at build time between compiling and dexing on your development machine, not at run time on the target Android device. By the time you have an apk ready to put on an Android device, proguard has already done its job and is out of the picture. As long as proguard can run on your development machine you're set. – adamp Jun 13 '10 at 23:20
  • I'm well aware that ProGuard is meant to run on JARs before converting them to APKs. But it will process JARs prepared by my app and only then they will be converted to APKs. Yes, my app for Android will include the dx tool, this was my intention from the beginning. – Malcolm Jun 14 '10 at 10:28
0

Even if you get a Class object, the security model (binder) on Android will prohibit applications from reading into another application's directory.

The best you can do on a non-root phone is to create libraries via Intents.

Yann Ramin
  • 32,895
  • 3
  • 59
  • 82
  • Maybe I didn't explain properly, but I need to create classes from the library. Intents have nothing to do with what I want because I don't need interaction between applications. I need reusable classes. – Malcolm Jun 12 '10 at 21:31