75

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

这是一个例子:

想象一下,应用程序需要根据输入值进行一些计算。应用程序请求这些输入值,然后检查所需Classe的 s 或Methods 是否可用。

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

4

7 回答 7

97

抱歉,我迟到了,这个问题已经被接受了,但是的,你可以下载并执行外部库。这是我的做法:

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

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.

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

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

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

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

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

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

在此处输入图像描述

我已经在模拟器 2.1 和我的物理 HTC 手机(运行 Android 2.2 且未植根)上进行了尝试。

这意味着您可以为应用程序创建外部 DEX 文件以下载和执行它们。在这里,它是艰难的(丑陋的Object演员,Method.invoke()丑陋的电话......),但必须可以与Interfaces 一起玩以使事情变得更干净。

哇。我是第一个吃惊的。我期待一个SecurityException.

一些有助于调查的事实:

  • 我的 DEX shlublu.jar 已签名,但不是我的应用程序
  • 我的应用程序是从 Eclipse / USB 连接执行的。所以这是一个在 DEBUG 模式下编译的未签名 APK
于 2011-07-28T14:25:20.903 回答
16

Shlublu 的回答真的很好。一些小事情虽然会帮助初学者:

  • 对于库文件“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 的合作。
于 2014-09-09T15:36:42.710 回答
4

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

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

于 2017-12-07T10:40:44.263 回答
1

当然,这是可能的。未安装的apk可以被宿主android应用调用。一般来说,解决资源和活动的生命周期,然后,可以动态加载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;
}

您的需求在开头提到的开源项目中只是部分功能。

于 2014-12-06T05:43:07.723 回答
1

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

<manifest ...>

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="18" />
    ...
</manifest>
于 2014-12-13T02:13:32.677 回答
1

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

于 2011-07-28T11:03:34.703 回答
1

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

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

    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;
     }
    }
    
于 2018-09-18T11:46:32.463 回答