有什么方法可以使Android应用程序在运行时下载和使用Java库?
这是一个例子:
想象一下,应用程序需要根据输入值进行一些计算。应用程序要求这些输入值,然后检查所需的Classe
s或Method
s是否可用。
如果没有,它将连接到服务器,下载所需的库,然后在运行时将其加载以使用反射技术调用所需的方法。实现可能会根据各种标准(例如正在下载库的用户)而更改。
Answers:
抱歉,我来晚了,这个问题已经被接受了,但是可以,您可以下载并执行外部库。这是我的方法:
我想知道这是否可行,所以写了下面的课:
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"
专用目录(电话的内部存储)中。
然后,它创建一个实例MyClass
并doSomething()
在创建的实例上调用。
它可以正常工作...我MyClass
在LogCat中看到了定义的跟踪:
我已经在模拟器2.1和物理HTC手机(运行Android 2.2且未植根)上进行了尝试。
这意味着您可以为应用程序创建外部DEX文件,以下载并执行它们。这是很难的方法(丑陋的Object
转换,Method.invoke()
丑陋的调用...),但是必须可以与Interface
s一起使用以使某些东西更干净。
哇。我是第一个惊讶的人。我期待一个SecurityException
。
一些有助于调查的事实:
Shlublu的答案非常好。一些小事情对初学者会有帮助:
在库项目清单中,请确保您具有:(
<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所述相同。
dx --dex --keep-classes --output=newjar.jar libproject.jar
。也许有一种方法可以在Eclipse构建库时使dex自动执行。
我不确定是否可以通过动态加载Java代码来实现。也许您可以尝试将脚本引擎嵌入到您的代码(如rhino)中,该引擎可以执行可以动态下载和更新的Java脚本。
当然可以。未安装的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;
}
您的需求只是开头提到的开源项目中的一部分。
如果您将.DEX文件保存在手机的外部存储器中(例如SD卡)(不推荐使用!具有相同权限的任何应用都可以轻松覆盖您的类并执行代码注入攻击),请确保已将应用程序读取外部存储器的权限。如果是这种情况,则会引发“ ClassNotFound”异常,这很容易引起误解,请在清单中输入以下内容(有关最新版本,请咨询Google)。
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
...
</manifest>
从技术上讲应该可以,但是Google规则呢?寄件人:play.google.com/intl/zh-CN/about/developer-content-policy-print
通过Google Play分发的应用程序不得使用Google Play的更新机制以外的任何其他方式修改,替换或更新自身。同样,应用程序可能无法从Google Play以外的其他来源下载可执行代码(例如dex,JAR,.so文件)。此限制不适用于在虚拟机中运行并且对Android API(例如WebView或浏览器中的JavaScript)具有有限访问权限的代码。
我认为@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;
}
}