使用DexClassLoader与动态加载的库进行交互

时间:2015-10-19 10:56:38

标签: android

我正在使用DexClassLoader类

在运行时从SD卡加载一个jar文件
  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);

在我的jar文件中,我正在打印几个工作正常的日志。现在我要做的是从jar文件打印Toast。执行此操作时,我得到的异常是java.lang.reflect.InvocationTargetException。我知道为什么我们得到这个异常,背后的原因是在打印吐司时使用它的上下文中的nullpointer异常。所以它不是

的副本

What could cause java.lang.reflect.InvocationTargetException?

背后的原因是

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

代码是

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.");
}}

任何人都可以帮助我实现这一点。任何帮助将不胜感激。

编辑: 我想要做的是我有一个我自己的库,我的许多客户正在使用...我想要的是从用户的SD卡加载我的库...我想要更新库什么时候我想要没有用户的知识,没有任何版本更新。图书馆包括很少的接口,碎片和活动。所以现在我能够从SD卡加载我的库并且可以调用基本函数。现在主要的挑战是从库实现接口并调用在其中使用上下文的函数。

任何涉及此类操作的样本或提示都会有所帮助。

1 个答案:

答案 0 :(得分:1)

我认为有两种解决方法,取决于你愿意做的事情:

  • 如果MyClass必须是Activity

必须使用Activity或任何变体正确启动startActivity()。它也必须在你的清单中声明。因此,只有当您的所有MyClass变体具有相同的签名时,以下内容才有效。

我假设您的启动代码位于Activity。加载classToLoad后,您可以执行以下操作:

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
}

现在以一种使用新doSomething()而不是Context的基础Activity的方式更改getApplicationContext()。然后从MyClass.onCreate()调用它,例如,看它是否有效:

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.");
    }
}

其余的 - 何时致电doSomething(),为什么等...... - 取决于你愿意做什么。

  • 如果MyClass只需显示Toast

在这种情况下无需再创建另一个ActivitydoSomething()只需收到正确的Context即可显示Toast

按如下方式更改MyClass

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.");
    }
}

并更改您的启动代码以将this传递给doSomething(),假设它是从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
}