是否有可能在运行时从Android应用程序dynamic加载库?
有没有什么办法让Android应用程序在运行时下载和使用Java库?
这里是一个例子:
想象一下,应用程序需要根据input值进行一些计算。 应用程序要求input这些值,然后检查所需的Classe
或Method
是否可用。
如果没有,则连接到服务器,下载所需的库,并在运行时加载它以使用reflection技术调用所需的方法。 实现可能会根据各种标准(例如正在下载库的用户)而改变。
对不起,我迟到了,问题已经被接受了,但是,你可以下载并执行外部库。 我这样做了:
我想知道这是否可行,所以我写了下面的课:
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
:
-
创build一个
DexClassLoader
-
使用它从
"/sdcard/shlublu.jar"
提取类MyClass
-
并将这个类存储到应用程序的
"dex"
私有目录(手机的内部存储)中。
然后,它创build一个MyClass
的实例,并在创build的实例上调用doSomething()
。
它的工作原理…我看到在我的LogCat MyClass
中定义的痕迹:
我已经尝试了一个模拟器2.1和我的物理macros达手机(这是运行Android 2.2,并没有根)。
这意味着您可以为应用程序创build外部DEX文件来下载并执行它们。 这是困难的方式(丑陋的Object
转换, Method.invoke()
丑陋的调用…),但它必须有可能与Interface
s玩,使更清洁。
哇。 我第一个惊讶。 我期待SecurityException
。
一些事实来帮助调查更多:
- 我的DEX shlublu.jar签名,但不是我的应用程序
- 我的应用程序是从Eclipse / USB连接执行的。 所以这是一个以DEBUG模式编译的未签名的APK
Shlublu的anwser真的很好。 一些小的东西,虽然这将有助于初学者:
- 对于库文件“MyClass”创build一个单独的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文件夹中find.apk文件。
- 将.apk文件复制到您的手机,并将其重命名为shlublu.jar文件(APK实际上是一个jar的专业化)
其他步骤与Shlublu所描述的相同。
- 非常感谢Shlublu的合作。
我不知道你是否可以通过dynamic加载java代码来实现这一点。 也许你可以尝试embedded一个脚本引擎你的代码,如犀牛,它可以执行Java脚本,可以dynamic下载和更新。
当然,这是可能的。 未安装的apk可以被主机android应用调用,通常parsing资源和活动的生命周期,然后可以dynamic加载jar或apk。 详细信息,请参阅我在github上的开源研究: https : //github.com/singwhatiwanna/dynamic-load-apk/blob/master/README-en.md
另外,需要DexClassLoader和reflection,现在看一些关键代码:
/** * 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; }
你的要求只是开头提到的开源项目的一部分function。
如果您将.DEX文件保存在手机的外部存储器中,如SD卡(不推荐!任何具有相同权限的应用程序都可以轻松地覆盖您的课程并执行代码注入攻击),请确保已经提供了应用程序权限来读取外部内存。 如果出现这种情况,则抛出exception是'ClassNotFound',这很容易让人误解,请在清单中join类似如下内容(请向Google咨询最新版本)。
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" /> ... </manifest>