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

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

这里有一个例子:

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

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

50983 次浏览

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

对不起,我迟到了,这个问题已经有了一个可以接受的答案,但是 是的,你可以下载并执行外部库。我是这样做的:

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

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中定义的轨迹:

enter image description here

我试过模拟器2.1和我的物理 HTC 手机(它运行的是 Android 2.2,而且不是植根的)。

这意味着您可以创建外部 DEX 文件供应用程序下载和执行。在这里它是作出了艰难的道路(丑陋的 Object铸造,Method.invoke()丑陋的呼叫...) ,但它必须有可能发挥与 Interface使一些干净。

哇,我是第一个吃惊的,我还以为是 SecurityException呢。

一些有助于进一步调查的事实:

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

Shlublu 的回答真的很棒,虽然有些小事情会对初学者有所帮助:

  • 对于库文件“ MyClass”,创建一个单独的 Android 应用程序项目,其中 MyClass 文件作为 src 文件夹中的唯一文件(其他东西,如 project.properties、清单、 res 等也应该在那里)
  • 在图书馆项目清单中,确保你有: < application android: icon = “@draable/icon”android: label = “@string/app _ name”> < application android: icon = “@draable/icon”android: label = “@string/app _ name”> < activity android: name = “ . NotExecular” Android: label = “@string/app _ name”> (“ . NotExecute”不是一个保留词,只是我必须在这里放一些东西)

  • 谢谢你。Dex 文件,只需作为 android 应用程序运行库项目(用于编译)并定位。来自项目的 bin 文件夹的 apk 文件。

  • 收到。将 APK 文件重命名为 shlublu.jar 文件(APK 实际上是 jar 的特殊化)

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

  • 非常感谢 Shlublu 的合作。

当然,有可能。通常,解析资源和活动的生命周期,然后可以动态加载 jar 或 apk。 详细信息请参考我在 github 上的开源研究: < a href = “ https://github.com/singwhatiwanna/Dynamic-load-apk/blob/master/README-en.md”rel = “ nofollow”> 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;
}

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

如果你还留着。DEX 文件在手机的外部存储器,如 SD 卡(不推荐!任何具有相同权限的应用程序都可以轻易地覆盖您的类并执行代码注入攻击) ,请确保您已经给予应用程序读取外部内存的权限。如果是这种情况,那么抛出的异常是‘ ClassNotfound’,这很容易引起误解,在清单中添加如下内容(请参考谷歌获得最新版本)。

<manifest ...>


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

技术上应该可行,但谷歌的规则呢? Play.google.com/intl/en-gb/about/developer-content-policy-pr‌​int

通过 GooglePlay 发布的应用程序不能修改、替换或更新 使用 Google Play 的更新机制以外的任何方法。 同样地,一个应用程序可能不会下载可执行代码(例如 dex,JAR,. so) 文件)。此限制不包括 应用于运行在虚拟机中并且对 Android API (例如 WebView 或浏览器中的 JavaScript)。

我认为@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;
    }
    }