java—以编程方式/动态地将classes.dex包含到jar文件中

r6hnlfcb  于 2021-07-03  发布在  Java
关注(0)|答案(0)|浏览(198)

我正在寻找一种方法,在运行时以编程方式将jar文件中的一些类和其他文件加载到android应用程序中。
这样做的目的是,在jar文件中包含的类和其他文件的帮助下,可以增加应用程序的功能。我曾尝试使用java.net.urlclassloader,但由于android dalvik vm只能加载包含“classes.dex”文件的jar文件,所以没有成功。这些特殊的jar文件将由dexclassloader加载,如本线程所述。
但是,我正在寻找一种解决方案,可以通过编程方式而不是手动创建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) 

我使用的是AndroidAPI level 29和AndroidStudio 4.0.1
提前感谢您的支持!

暂无答案!

目前还没有任何答案,快来回答吧!

相关问题