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

时间:2011-07-28 10:54:20

标签: android reflection classloader dalvik android-library

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

以下是一个例子:

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

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

7 个答案:

答案 0 :(得分:87)

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

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

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

  • 用它从MyClass提取课程"/sdcard/shlublu.jar"

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

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

它的工作原理......我在LogCat中看到MyClass中定义的痕迹:

enter image description here

我已尝试使用模拟器2.1和我的物理HTC手机(运行Android 2.2且未植根)。

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

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

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

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

答案 1 :(得分:14)

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

答案 2 :(得分:1)

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

答案 3 :(得分: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;
}

你的要求只是开头提到的开源项目的一部分功能。

答案 4 :(得分:1)

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

<manifest ...>

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

答案 5 :(得分:0)

技术上应该可行,但Google规则呢? 来自:play.google.com/intl/zh-CN/about/developer-content-policy-pr int

  

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

答案 6 :(得分:0)

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