是否可以在运行时从 Android 应用程序动态加载库?

发布于 2024-11-26 16:03:01 字数 226 浏览 0 评论 0原文

有没有办法让Android应用程序在运行时下载并使用Java库?

下面是一个示例:

假设应用程序需要根据输入值进行一些计算。应用程序请求这些输入值,然后检查所需的ClasseMethod是否可用。

如果没有,它会连接到服务器,下载所需的库,并在运行时加载它以使用反射技术调用所需的方法。实现可能会根据各种标准(例如下载库的用户)而改变。

Is there any way to make an Android application to download and use a Java library at runtime?

Here is an example:

Imagine that the application needs to make some calculations depending on the input values. The application asks for these input values and then checks if the required Classes or Methods are available.

If not, it connects to a server, downloads the needed library, and loads it at runtime to calls the required methods using reflection techniques. The implementation could change depending on various criteria such as the user who is downloading the library.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

还给你自由 2024-12-03 16:03:01

抱歉,我迟到了,问题已经得到了接受的答案,但是是的,您可以下载并执行外部库。我是这样做的:

我想知道这是否可行,所以我编写了以下类:

package org.shlublu.android.sandbox;

import android.util.Log;

public class MyClass {
    public MyClass() {
        Log.d(MyClass.class.getName(), "MyClass: constructor called.");
    }

    public void doSomething() {
        Log.d(MyClass.class.getName(), "MyClass: doSomething() called.");
    }
}

并将其打包到 DEX 文件中,并将其保存在设备的 SD 卡上,名称为 /sdcard/shlublu.jar

然后,在从 Eclipse 项目中删除 MyClass 并清理它之后,我编写了下面的“愚蠢的程序”:

public class Main extends Activity {

    @SuppressWarnings("unchecked")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        try {
            final String libPath = Environment.getExternalStorageDirectory() + "/shlublu.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);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

它基本上以这种方式加载类 MyClass

  • 创建一个DexClassLoader

  • 用它来提取类"/sdcard/shlublu.jar"

    中的 MyClass

  • 并将此类存储到应用程序的“dex”私有目录(手机的内部存储)。

然后,它创建一个 MyClass 实例并在创建的实例上调用 doSomething()

它有效...我在 LogCat 中看到 MyClass 中定义的跟踪:

在此处输入图像描述

我已经在模拟器 2.1 和我的实体 HTC 手机(运行 Android 2.2 并且未 root)上进行了尝试。

这意味着您可以创建外部 DEX 文件供应用程序下载和执行它们。这里它是用困难的方式制作的(丑陋的 Object 转换,Method.invoke() 丑陋的调用......),但它必须可以使用 Interface 是为了让东西更干净。

哇。我是第一个惊讶的。我期待着一个SecurityException

一些有助于调查更多信息的事实:

  • 我的 DEX shlublu.jar 已签名,但我的应用程序未签名
  • 我的应用程序是通过 Eclipse/USB 连接执行的。所以这是一个以DEBUG模式编译的未签名APK

Sorry, I'm late and the question has already an accepted answer, but yes, you can download and execute external libraries. Here is the way I did:

I was wondering whether this was feasible so I wrote the following class:

package org.shlublu.android.sandbox;

import android.util.Log;

public class MyClass {
    public MyClass() {
        Log.d(MyClass.class.getName(), "MyClass: constructor called.");
    }

    public void doSomething() {
        Log.d(MyClass.class.getName(), "MyClass: doSomething() called.");
    }
}

And I packaged it in a DEX file that I saved on my device's SD card as /sdcard/shlublu.jar.

Then I wrote the "stupid program" below, after having removed MyClass from my Eclipse project and cleaned it:

public class Main extends Activity {

    @SuppressWarnings("unchecked")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        try {
            final String libPath = Environment.getExternalStorageDirectory() + "/shlublu.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);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

It basically loads the class MyClass that way:

  • create a DexClassLoader

  • use it to extract the class MyClass from "/sdcard/shlublu.jar"

  • and store this class to the application's "dex" private directory (internal storage of the phone).

Then, it creates an instance of MyClass and invokes doSomething() on the created instance.

And it works... I see the traces defined in MyClass in my LogCat:

enter image description here

I've tried on both an emulator 2.1 and on my physical HTC cellphone (which is running Android 2.2 and which is NOT rooted).

This means you can create external DEX files for the application to download and execute them. Here it was made the hard way (ugly Object casts, Method.invoke() ugly calls...), but it must be possible to play with Interfaces to make something cleaner.

Wow. I'm the first surprised. I was expecting a SecurityException.

Some facts to help investigating more:

  • My DEX shlublu.jar was signed, but not my app
  • My app was executed from Eclipse / USB connection. So this is an unsigned APK compiled in DEBUG mode
输什么也不输骨气 2024-12-03 16:03:01

Shlublu 的 anwser 真的很好。不过,一些小事情会对初学者有所帮助:

  • 对于库文件“MyClass”,创建一个单独的 Android 应用程序项目,其中 MyClass 文件作为 src 文件夹中的唯一文件(其他内容,如 project.properties、manifest、res 等应该也在那里)
  • 在库项目清单中确保你有:(
   <application android:icon="@drawable/icon" 
                android:label="@string/app_name">
        <activity android:name=".NotExecutable" 
                  android:label="@string/app_name">
        </activity>
    </application>

“.NotExecutable”不是保留字。只是我必须在这里放一些东西)

  • 为了制作.dex文件,只需将库项目作为android应用程序运行(用于编译)并从项目的 bin 文件夹中找到 .apk 文件。
  • 将 .apk 文件复制到您的手机并将其重命名为 shlublu.jar 文件(不过,APK 实际上是 jar 的特化)

其他步骤与 Shlublu 中描述的相同。

  • 非常感谢 Shlublu 的合作。

Shlublu's anwser is really nice. Some small things though that would help a beginner:

  • for library file "MyClass" make a separate Android Application project which has the MyClass file as only file in the src folder (other stuff, like project.properties, manifest, res, etc. should also be there)
  • in library project manifest make sure you have:
   <application android:icon="@drawable/icon" 
                android:label="@string/app_name">
        <activity android:name=".NotExecutable" 
                  android:label="@string/app_name">
        </activity>
    </application>

(".NotExecutable" is not a reserved word. It is just that I had to put something here)

  • For making the .dex file, just run the library project as android application (for the compiling) and locate .apk file from the bin folder of the project.
  • Copy the .apk file to your phone and rename it as shlublu.jar file (an APK is actually a specialization of a jar, though)

Other steps are the same as described by Shlublu.

  • Big thanks to Shlublu for cooperation.
久光 2024-12-03 16:03:01

从技术上讲应该可行,但是谷歌的规则呢?
来自:play.google.com/intl/en-GB/about/developer-content-policy-pr‌ int

通过 Google Play 分发的应用程序不得修改、替换或更新
本身使用 Google Play 更新机制以外的任何方法。
同样,应用程序可能无法下载可执行代码(例如 dex、JAR、.so
文件)来自 Google Play 以外的来源。此限制不
适用于在虚拟机中运行且访问权限有限的代码
Android API(例如 WebView 或浏览器中的 JavaScript)。

Technically should work but what about Google rules?
From: play.google.com/intl/en-GB/about/developer-content-policy-pr‌​int

An app distributed via Google Play may not modify, replace or update
itself using any method other than Google Play’s update mechanism.
Likewise, an app may not download executable code (e.g. dex, JAR, .so
files) from a source other than Google Play. This restriction does not
apply to code that runs in a virtual machine and has limited access to
Android APIs (such as JavaScript in a WebView or browser).

苍景流年 2024-12-03 16:03:01

我不确定是否可以通过动态加载java代码来实现这一点。也许你可以尝试在你的代码中嵌入一个脚本引擎,比如 rhino,它可以执行可以动态下载和更新的 java 脚本。

I am not sure if you can achieve this by dynamically loading java code. May be you can try embedding a script engine your code like rhino which can execute java scripts which can be dynamically downloaded and updated.

年华零落成诗 2024-12-03 16:03:01

当然,这是可能的。未安装的apk可以被宿主android应用程序调用。一般来说,解析资源和activity的生命周期,然后,可以动态加载jar或apk。
详细请参考我在github上的开源研究: https ://github.com/singwhatiwanna/dynamic-load-apk/blob/master/README-en.md

另外,还需要DexClassLoader和反射,现在看一些关键代码:

/**
 * Load a apk. Before start a plugin Activity, we should do this first.<br/>
 * NOTE : will only be called by host apk.
 * @param dexPath
 */
public DLPluginPackage loadApk(String dexPath) {
    // when loadApk is called by host apk, we assume that plugin is invoked by host.
    mFrom = DLConstants.FROM_EXTERNAL;

    PackageInfo packageInfo = mContext.getPackageManager().
            getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);
    if (packageInfo == null)
        return null;

    final String packageName = packageInfo.packageName;
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
    if (pluginPackage == null) {
        DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
        AssetManager assetManager = createAssetManager(dexPath);
        Resources resources = createResources(assetManager);
        pluginPackage = new DLPluginPackage(packageName, dexPath, dexClassLoader, assetManager,
                resources, packageInfo);
        mPackagesHolder.put(packageName, pluginPackage);
    }
    return pluginPackage;
}

你的需求只是其中的一部分开头提到的开源项目中的函数。

sure, it is possible. apk which is not installed can be invoked by host android application.generally,resolve resource and activity's lifecircle,then,can load jar or apk dynamically.
detail,please refer to my open source research on github: https://github.com/singwhatiwanna/dynamic-load-apk/blob/master/README-en.md

also,DexClassLoader and reflection is needed, now look at some key code:

/**
 * Load a apk. Before start a plugin Activity, we should do this first.<br/>
 * NOTE : will only be called by host apk.
 * @param dexPath
 */
public DLPluginPackage loadApk(String dexPath) {
    // when loadApk is called by host apk, we assume that plugin is invoked by host.
    mFrom = DLConstants.FROM_EXTERNAL;

    PackageInfo packageInfo = mContext.getPackageManager().
            getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);
    if (packageInfo == null)
        return null;

    final String packageName = packageInfo.packageName;
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
    if (pluginPackage == null) {
        DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
        AssetManager assetManager = createAssetManager(dexPath);
        Resources resources = createResources(assetManager);
        pluginPackage = new DLPluginPackage(packageName, dexPath, dexClassLoader, assetManager,
                resources, packageInfo);
        mPackagesHolder.put(packageName, pluginPackage);
    }
    return pluginPackage;
}

your demands is only partly of function in the open source project mentioned at the begining.

倒带 2024-12-03 16:03:01

如果您将 .DEX 文件保存在手机的外部存储器中,例如 SD 卡(不推荐!任何具有相同权限的应用程序都可以轻松覆盖您的类并执行代码注入攻击),请确保您已给出应用程序读取外部存储器的权限。如果是这种情况,抛出的异常是“ClassNotFound”,这是相当具有误导性的,请在清单中添加类似以下内容(请咨询 Google 以获取最新版本)。

<manifest ...>

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="18" />
    ...
</manifest>

If you're keeping your .DEX files in external memory on the phone, such as the SD card (not recommended! Any app with the same permissions can easily overwrite your class and perform a code injection attack) make sure you've given the app permission to read external memory. The exception that gets thrown if this is the case is 'ClassNotFound' which is quite misleading, put something like the following in your manifest (consult Google for most up to date version).

<manifest ...>

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="18" />
    ...
</manifest>
2024-12-03 16:03:01

我认为@Shlublu 的答案是正确的,但我只是想强调一些关键点。

  1. 我们可以从外部 jar 和 apk 文件加载任何类。
  2. 无论如何,我们可以从外部 jar 加载 Activity,但由于上下文概念,我们无法启动它。
  3. 要从外部 jar 加载 UI,我们可以使用fragment。创建片段的实例并将其嵌入到活动中。但请确保片段动态创建 UI
    如下所示。

    public class MyFragment 扩展 Fragment {
    @可为空
    @覆盖
    公共视图 onCreateView(LayoutInflater inflater, @Nullable ViewGroup 
      容器,@Nullable Bundle savingInstanceState)
     {
      super.onCreateView(充气机,容器,savedInstanceState);
    
      LinearLayout 布局 = new LinearLayout(getActivity());
      布局.setLayoutParams(new 
     LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.MATCH_PARENT));
    按钮按钮 = new Button(getActivity());
    button.setText("调用宿主方法");
    布局.addView(按钮,LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT);
    
    返回布局;
     }
    }
    

I think @Shlublu answer is correct but i just want to highlight some key points.

  1. We can load any classes from external jar and apk file.
  2. In Any way, we can load Activity from external jar but we can not start it because of the context concept.
  3. To load the UI from external jar we can use fragment. Create the instance of the fragment and embedded it in the Activity. But make sure fragment creates the UI dynamically
    as given below.

    public class MyFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup 
      container, @Nullable Bundle savedInstanceState)
     {
      super.onCreateView(inflater, container, savedInstanceState);
    
      LinearLayout layout = new LinearLayout(getActivity());
      layout.setLayoutParams(new 
     LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.MATCH_PARENT));
    Button button = new Button(getActivity());
    button.setText("Invoke host method");
    layout.addView(button, LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT);
    
    return layout;
     }
    }
    
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文