【问题标题】:Interacting with dynamically loaded library using DexClassLoader使用 DexClassLoader 与动态加载的库进行交互
【发布时间】:2016-01-17 16:39:45
【问题描述】:

我在运行时使用 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。我知道为什么我们会得到这个异常,它背后的原因是在打印 toast 时使用它的上下文中的空指针异常。所以它不是重复的

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 卡加载我的库并可以调用基本函数。现在主要的挑战是从库中实现接口并调用其中使用上下文的函数。

任何涉及此类操作的示例或提示都会很有帮助。

【问题讨论】:

  • @Boss 异常不是我的问题。我知道它的原因。我需要帮助来实现所需的东西..所以它不是那个 java.lang.reflect.InvocationTargetException 问题的重复
  • ...是的,它是重复的...您需要解开此异常以了解导致它的原因...
  • @selvin 请仔细阅读我编辑的问题,我已经知道原因。我只是问了解决方法。谢谢
  • 好的...错误很明显...您的类正在扩展活动或服务...您自己创建它的实例...然后在doSomething中调用@987654327 @ ...

标签: android


【解决方案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(),使其使用新Activity 的底层Context,而不是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
}

【讨论】:

  • @shublu 非常感谢您的回复..第一种方法不起作用,因为如果我们使用 Intent,它将在当前项目中找到该活动...您能告诉我们如何通过这以 doSomething() 作为参数..我的意思是在 classToLoad.getMethod("doSomething");
  • 我认为第一种方法是抛出 ActivityNotFoundException ... :(
  • Method.invoke() 可以接收你需要的所有参数:developer.android.com/reference/java/lang/reflect/…, java.lang.Object...) 我相应地更新了我的答案。
  • 显然(正如我在评论中所写)它不会工作,因为活动没有在清单中注册......
  • @Shlublu 感谢您的确认。 #敬礼
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-10
  • 1970-01-01
  • 2011-03-13
相关资源
最近更新 更多