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


73

有什么方法可以使Android应用程序在运行时下载和使用Java库?

这是一个例子:

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

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

Answers:


95

抱歉,我来晚了,这个问题已经被接受了,但是可以,您可以下载并执行外部库。这是我的方法:

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

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"专用目录(电话的内部存储)中。

然后,它创建一个实例MyClassdoSomething()在创建的实例上调用。

它可以正常工作...我MyClass在LogCat中看到了定义的跟踪:

在此处输入图片说明

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

这意味着您可以为应用程序创建外部DEX文件,以下载并执行它们。这是很难的方法(丑陋的Object转换,Method.invoke()丑陋的调用...),但是必须可以与Interfaces一起使用以使某些东西更干净。

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

一些有助于调查的事实:

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

4
大!这真的是我想要的!如您在此处看到的(groups.google.com/group/android-security-discuss/browse_thread/…),实际上并没有安全问题,因为代码将在您的应用程序许可下执行。
lluismontero 2011年

这是完全有道理的,即使乍看之下令人惊讶。感谢您的单挑!
Shlublu 2011年


1
非常好,谢谢!是的,tis是完全相同的逻辑。超过64k方法必须非常不寻常,因此,此方法的有效使用肯定是插件。
Shlublu 2011年

1
我知道这是一个老话题,但是我希望这里的人可以提供帮助。我正在尝试做相同的事情(动态加载插件)。有人通过投射到界面来完成这项工作吗?
cstrutton

15

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

欢迎您来到Pätris,很高兴与您讨论。(当我为您的答案+1时,我最初写了此评论,但我显然忘了按下“添加评论”按钮...)
Shlublu 2014年

我很欣赏这个技巧,但是使用您的方法重现@Shlublu的示例会导致〜380kB apk,而如果我只是创建一个库项目,则会得到一个3kB jar。但是,库方法需要一个额外的命令dx --dex --keep-classes --output=newjar.jar libproject.jar。也许有一种方法可以在Eclipse构建库时使dex自动执行。
哈维

我很困惑为什么我们使用apk文件?我是xamarin android的新手。我想创建库项目dll(类库),然后在运行时在主应用程序中使用它。如何做到这一点?
Mohammad Afrashteh

1

我不确定是否可以通过动态加载Java代码来实现。也许您可以尝试将脚本引擎嵌入到您的代码(如rhino)中,该引擎可以执行可以动态下载和更新的Java脚本。


谢谢。我将研究这种方法(今天之前还不了解Android上的脚本编写)。更多信息(如果有人需要的话)可以在这里找到:google-opensource.blogspot.com/2009/06/...
lluismontero

1
@Naresh是的,您还可以下载并执行Java编译的代码。请在此页面中查看我的回复。我本来不会期望的。
Shlublu 2011年

1
谢谢@shlublu似乎很有趣:)。我能想到的唯一问题是它在很大程度上依赖于反射,或者如果我们必须以这种方式编写整个代码,则您需要开始编写包装器,我看到了很多管理问题。这就是为什么我认为脚本是管理业务逻辑的简单而又好的方法。
Naresh

另外,您将如何开始生成独立的dex jar文件?您将创建单独的android应用程序项目?截至目前,android rite中还没有静态库的概念?
Naresh

1
查看设备的/ system / framework目录,它充满了以这种方式创建的jar。(无需root权限)
Shlublu 2011年

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;
}

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


我在使用您的项目时遇到了一些错误,但与我以前的项目仍然存在相同的问题。startActivity时显示ClassNotFound。–
KDoX

我是android开发的新手。我不知道为什么要使用apk来动态加载方法?我想在运行时动态加载由类库项目动态创建的dll。
Mohammad Afrashteh

1

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

<manifest ...>

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

1

从技术上讲应该可以,但是Google规则呢?寄件人:play.google.com/intl/zh-CN/about/developer-content-policy-pr‌int

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


1
没错 但是,OP询问这在技术上是否可行,并且可能完全出于自己的目的而不想使用Google Play来做这种事情。而且,看起来Google Play应用可能会嵌入jar,这些jar将在部署时写入本地存储中,然后在不违反该规则的情况下由应用稍后加载(无论这是一个好主意)。但是无论如何,您应该提醒开发人员,从Google Play下载的应用程序不应从其他地方下载代码。不过,这应该是一条评论。
Shlublu

1

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

  1. 我们可以从外部jar和apk文件加载任何类。
  2. 无论如何,我们都可以从外部jar加载Activity,但是由于上下文概念的原因,我们无法启动它。
  3. 要从外部jar加载UI,我们可以使用fragment。创建片段的实例并将其嵌入到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;
     }
    }
    
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.