有没有办法让Android应用程序在运行时下载和使用Java库?
以下是一个例子:
想象一下,应用程序需要根据输入值进行一些计算。应用程序请求输入这些值,然后检查所需的Classe
或Method
是否可用。
如果没有,它将连接到服务器,下载所需的库,并在运行时加载它以使用反射技术调用所需的方法。实施可能会根据各种标准(例如下载库的用户)而发生变化。
答案 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
:
用它从MyClass
提取课程"/sdcard/shlublu.jar"
并将此类存储到应用程序的"dex"
私人目录(手机的内部存储)。
然后,它创建MyClass
的实例并在创建的实例上调用doSomething()
。
它的工作原理......我在LogCat中看到MyClass
中定义的痕迹:
我已尝试使用模拟器2.1和我的物理HTC手机(运行Android 2.2且未植根)。
这意味着您可以为应用程序创建外部DEX文件以下载和执行它们。在这里它是艰难的方式(丑陋的Object
演员,Method.invoke()
丑陋的电话......),但必须有可能与Interface
s一起玩,以使事情更清洁。
哇。我是第一个感到惊讶的人。我期待SecurityException
。
有助于调查更多的一些事实:
答案 1 :(得分:14)
Shlublu的anwser非常好。虽然有些小事可以帮助初学者:
确保您拥有:
<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文件。
其他步骤与Shlublu描述的相同。
答案 2 :(得分:1)
我不确定你是否可以通过动态加载java代码来实现这一点。也许你可以尝试嵌入一个脚本引擎你的代码,如rhino,它可以执行可以动态下载和更新的java脚本。
答案 3 :(得分:1)
另外,需要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的答案是正确的,但我只想强调一些要点。
要从外部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;
}
}