我正在寻找一种在运行时以编程方式将 jar 文件中的一些类和其他文件加载到我的 Android 应用程序中的方法。
这样做的目的是,借助 jar 文件中包含的类和其他文件,可以增加应用程序的功能。我曾尝试使用 java.net.URLClassLoader,但由于 Android Dalvik VM 只能加载包含“classes.dex”文件的 jar 文件,因此这不起作用。然后,这些特殊的 jar 文件将由DexClassLoader加载,如该线程中所述。
但是,我正在寻找一种解决方案,其中可以以编程方式而不是手动方式从 jar 文件中创建 classes.dex 文件并将其添加到 jar 文件中。到目前为止,出于测试目的,我已尝试在我的操作系统(Windows10)的 cli 中手动执行此过程,但这也不起作用。这是我的 cli 的输出:
C:\Users\%USERNAME%\AppData\Local\Android\Sdk\build-tools\29.0.2>dx --dex --output=C:\Users\%USERNAME%\Downloads\classes.dex C:\Users\%USERNAME%\Downloads\Weather.jar
-Djava.ext.dirs=C:\Users\%USERNAME%\AppData\Local\Android\Sdk\build-tools\29.0.2\lib is not supported. Use -classpath instead.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
注意:“%USERNAME%”已替换为有效用户名。出于安全原因,它只是被审查了。
这是我当前的类加载解决方案的代码,它在常规 JVM (JDK/JRE 9.0.4) 上运行良好,但在 Android 应用程序中却不行:
package chrtopf.ddns.net.smarthomeapp.plugins;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericSignatureFormatError;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.MalformedParameterizedTypeException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import chrtopf.ddns.net.smarthomeapi.android.Plugin;
/**
* This class is responsible for executing the highly unstable process of getting a new instance
* of a plugins main class. There is a total number of 11 exceptions to be thrown during this
* procedure. Every exception get printed with its stack trace and a custom designed fatal error
* message. The one and only method this class makes use of is loadPlugin(). The plugins are
* loaded through the URIClassLoader. ATTENTION: If the package name of a class to be loaded
* is the same as one of the package names from the application in which this PluginLoader class
* is used a conflict between the two same named packages is created. In this conflict the same
* named package of your application is always going to be chosen first INSTEAD of the package
* from a different .jar archive file.
*
* @author ChrTopf
*
*/
public class PluginLoader {
private static final String TAG = "PluginLoader";
private PluginInterface app;
/**
* Initializes a new plugin loader object.
* @param app The plugin interface which is going to be used in the constructor of the plugins
* main class. This interface delivers access to important methods to the plugin. (PluginInterface)
*/
public PluginLoader(PluginInterface app) {
this.app = app;
}
/**
* This method loads the main class of a plugin from a specific package of a specific .jar archive file.
* @param plugin_name The name of the plugin (only for GUI and debug, but important). (String)
* @param jar_file The existing .java archive file of the plugin to be loaded. (File)
* @param main_path The package path to the plugins main class. (String)
* @return returns a new Instance of the plugins main class. (Plugin)
*/
public Plugin loadPlugin(String plugin_name, File jar_file, String main_path) {
try {
//get the url e.g. the path to the .jar file of the plugin
URL file_path = jar_file.toURI().toURL();
//try to load the .jar archive
URLClassLoader classloader = new URLClassLoader(new URL[] {file_path});
//load the main class from the archive as specified in the .properties file
Class<?> plugin = classloader.loadClass(main_path);
//get the constructor of the main plugin class
Constructor<?> plugin_const = plugin.getDeclaredConstructor(PluginInterface.class);
//prepare the constructor
plugin_const.setAccessible(true);
//get a new instance of the main plugin class using the prepared constructor
Plugin new_plugin = (Plugin) plugin_const.newInstance(this.app);
//close the classloader
classloader.close();
//return the new instance of the plugin
return new_plugin;
} catch (MalformedURLException e) {
e.printStackTrace();
Log.e(TAG, "The configuration file of the plugin with the name " + plugin_name + " has an incorrect path to the .jar archive!");
} catch (ClassNotFoundException e) {
e.printStackTrace();
Log.e(TAG, "The configuration file of the plugin with the name " + plugin_name + " has an incorrect path to the main class! Or the plugin is made for a different smarthome server version.");
} catch (TypeNotPresentException e) {
e.printStackTrace();
Log.e(TAG, "The main class of the plugin with the name " + plugin_name + " has no superclass, but it needs to be plugin.Plugin!");
} catch (MalformedParameterizedTypeException | GenericSignatureFormatError e) {
e.printStackTrace();
Log.e(TAG, "This should definitely not happen. Please contact the author of the smarthome server application or verfiy that you used java version 13.0.1 for your plugin with the name " + plugin_name);
} catch (InstantiationException e) {
e.printStackTrace();
Log.e(TAG, "A new instance of the main class could somehow not be constructed from the plugin " + plugin_name);
} catch (IllegalAccessException e) {
e.printStackTrace();
Log.e(TAG, "A new instance of the main class could not be constructed from the plugin " + plugin_name + " because it is unknown to the smarthome server.");
} catch (IllegalArgumentException e) {
e.printStackTrace();
Log.e(TAG, "A new instance of the main class could not be constructed from the plugin " + plugin_name + " due to illegal Arguments.");
} catch (InvocationTargetException e) {
e.printStackTrace();
Log.e(TAG, "A new instance of the main class could not be constructed from the plugin " + plugin_name + " due to the constructor of the main class throwing an exception.");
} catch (NoSuchMethodException e) {
e.printStackTrace();
Log.e(TAG, "A new instance of the main class could not be constructed from the plugin " + plugin_name + " because a specific method could not be found in the main plugin class.");
} catch (SecurityException e) {
e.printStackTrace();
Log.e(TAG, "A new instance of the main class could not be constructed from the plugin " + plugin_name + " due to missing file system rights.");
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "The classloader could not be closed successfully.");
}
return null;
}
}
注意: loadPlugin() 方法从 jar 文件中返回一个类,该类是 Plugin 类的接口。
由于 Dalvik VM 无法从这种类型的 jar 文件中加载类,我在 LogCat 中收到以下错误:
2020-12-05 13:36:26.648 15121-15121/chrtopf.ddns.net.smarthomeapp2 E/AndroidRuntime: FATAL EXCEPTION: main
Process: chrtopf.ddns.net.smarthomeapp2, PID: 15121
java.lang.RuntimeException: Unable to start activity ComponentInfo{chrtopf.ddns.net.smarthomeapp2/chrtopf.ddns.net.smarthomeapp.main.MainActivity}: java.lang.UnsupportedOperationException: can't load this type of class file
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3375)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3514)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2110)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7697)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:516)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
Caused by: java.lang.UnsupportedOperationException: can't load this type of class file
at java.lang.ClassLoader.defineClass(ClassLoader.java:591)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:469)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(AccessController.java:69)
at java.security.AccessController.doPrivileged(AccessController.java:94)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at chrtopf.ddns.net.smarthomeapp.plugins.PluginLoader.loadPlugin(PluginLoader.java:59)
at chrtopf.ddns.net.smarthomeapp.plugins.PluginWrap.load(PluginWrap.java:82)
at chrtopf.ddns.net.smarthomeapp.plugins.PluginManager.loadPlugin(PluginManager.java:178)
at chrtopf.ddns.net.smarthomeapp.plugins.PluginManager.startExec(PluginManager.java:131)
at chrtopf.ddns.net.smarthomeapp.main.MainActivity.onCreate(MainActivity.java:105)
at android.app.Activity.performCreate(Activity.java:7815)
at android.app.Activity.performCreate(Activity.java:7804)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1325)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3350)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3514)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2110)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7697)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:516)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
我正在使用 Android API Level 29 和 Android Studio 4.0.1
提前感谢您的支持!