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

Posted

技术标签:

【中文标题】是否可以在运行时从 Android 应用程序动态加载库?【英文标题】:Is it possible to dynamically load a library at runtime from an Android application? 【发布时间】:2011-10-15 00:40:24 【问题描述】:

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

这是一个例子:

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

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

【问题讨论】:

【参考方案1】:

抱歉,我来晚了,问题已经有答案了,但是是的,您可以下载并执行外部库。这是我的做法:

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

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 文件,并以 /sdcard/shlublu.jar 的形式保存在设备的 SD 卡上。

然后我写了下面的“愚蠢的程序”,从我的 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() 丑陋的电话......),但必须可以与Interfaces 一起玩以使事情变得更清洁。

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

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

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

【讨论】:

太棒了!这真的是我一直在寻找的!正如您在此处 (groups.google.com/group/android-security-discuss/browse_thread/…) 所看到的,实际上并不存在安全问题,因为代码将在您的应用许可下执行。 这完全有道理,即使乍一看令人惊讶。感谢您的提醒! 也看看这个:android-developers.blogspot.com/2011/07/… 太好了,谢谢!是的,这是完全相同的逻辑。超过 64k 的方法肯定是很不寻常的,所以它的有效用途肯定是插件。 我知道这是一个旧线程,但我希望这里有人可以提供帮助。我正在尝试做同样的事情(动态加载插件)。有没有人通过转换到界面来完成这项工作?【参考方案2】:

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 的合作。

【讨论】:

欢迎您,Pätris,很高兴与您讨论。 (我最初是在对您的回答 +1 时写了这条评论,但我显然忘了按“添加评论”按钮......) 我很欣赏这个技巧,但是使用您的方法复制上面的 @Shlublu 的示例会产生 ~380kB 的 apk,而如果我只是创建一个库项目,我会得到一个 3kB 的 jar。但是,库方法需要一个额外的命令,dx --dex --keep-classes --output=newjar.jar libproject.jar。也许有一种方法可以在 Eclipse 构建库时自动执行该 dex 步骤。 我很困惑为什么我们使用 apk 文件?!我是 xamarin android 的新手。我想创建库项目 dll(类库),然后在运行时在我的主应用程序中使用它。怎么可能做到这一点? @Pätris - 我尝试了您的方法(跳过了包含清单条目的第二步)并且没有将名称更改为 .jar。而是将 apk 保留为默认名称,app-debug.apk 并修改了 Shlublu 的代码(只是更改了文件名)。我收到“ClassNotFound”异常。我在 Android 6.0 上运行。我错过了什么吗?【参考方案3】:

技术上应该可行,但 Google 规则呢? 来自: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)。

【讨论】:

完全正确。但是,OP 询问这在技术上是否可行,并且可能想要在完全不使用 Google Play 的情况下为自己的目的做这样的事情。此外,看起来 Google Play 应用程序可能会嵌入 jars,这些 jars 将在部署时写入本地存储,并且稍后将由应用程序加载而不会违反此规则(无论这是否是一个好主意)。但无论如何,你提醒开发者是对的,从 Google Play 下载的应用程序不应该从其他地方下载代码。不过,这应该是一条评论。 截至 2021 年 12 月 15 日的新网址:support.google.com/googleplay/android-developer/answer/9888379【参考方案4】:

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

【讨论】:

谢谢。我将研究这种方法(在今天之前不知道 Android 上的脚本)。更多信息(如果有人需要)可以在这里找到:google-opensource.blogspot.com/2009/06/… @Naresh 是的,您还可以下载并执行 Java 编译代码。请参阅我在此页面中的回复。实际上我没想到。 谢谢@shlublu 似乎很有趣:)。我能想到的唯一问题是它严重依赖反射,或者你需要开始编写包装器,如果我们必须以这种方式编写整个代码,我会看到很多管理问题。这就是为什么我认为脚本是管理业务逻辑的简单而好方法。 另外你将如何开始生成独立的 dex jar 文件?你会创建单独的 android 应用程序项目吗?到目前为止,android rite 中还没有静态库的概念? 查看你设备的 /system/framework 目录,里面全是这样制作的 jars。 (无需root权限)【参考方案5】:

当然,这是可能的。未安装的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;

您的需求在开头提到的开源项目中只是部分发挥作用。

【讨论】:

我在使用你的项目时遇到了一些错误,我之前的项目仍然存在同样的问题。 ClassNotFound 时 startActivity.. 我是安卓开发新手。不知道为什么要使用apk来动态加载方法?!我想在运行时动态加载由类库项目创建的 dll。【参考方案6】:

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

<manifest ...>

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

【讨论】:

【参考方案7】:

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

    我们可以从外部 jar 和 apk 文件中加载任何类。 无论如何,我们可以从外部 jar 加载 Activity,但由于上下文概念,我们无法启动它。

    要从外部 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;
     
    
    

【讨论】:

以上是关于是否可以在运行时从 Android 应用程序动态加载库?的主要内容,如果未能解决你的问题,请参考以下文章

在运行时从另一个 AppDomain 调试动态加载的 DLL

Android BLE 通知

在 Android 运行时从 xml 文件设置视图

如何在运行时从文件中加载 HTML 模板?

是否可以在运行时从 c++ 类声明中知道继承的数量?

是否可以在 Windows 上运行时从可执行文件中定位函数?