有没有办法在运行时使Android应用程序下载并使用Java库?
以下是一个示例:
假设应用程序需要根据输入值进行一些计算。 应用程序请求这些输入值,然后检查所需的 Class
或 Method
是否可用。
如果不可用,它会连接到服务器,下载所需的库,并在运行时加载库,使用反射技术调用所需方法。 实现可能会随着下载库的用户等各种标准而改变。
有没有办法在运行时使Android应用程序下载并使用Java库?
以下是一个示例:
假设应用程序需要根据输入值进行一些计算。 应用程序请求这些输入值,然后检查所需的 Class
或 Method
是否可用。
如果不可用,它会连接到服务器,下载所需的库,并在运行时加载库,使用反射技术调用所需方法。 实现可能会随着下载库的用户等各种标准而改变。
抱歉,我来晚了,问题已经有一个被接受的答案了,但是是的,你可以下载并执行外部库。这是我所做的方法:
我想知道这是否可行,所以我编写了以下类:
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
用它从"/sdcard/shlublu.jar"
中提取类MyClass
并将此类存储到应用程序的"dex"
私有目录(手机的内部存储器)中。
然后,它创建了MyClass
的实例,并在创建的实例上调用doSomething()
。
这样就可以工作了...... 我在LogCat中看到了MyClass
中定义的跟踪:
我在2.1模拟器和我的物理HTC手机(运行Android 2.2,未root)上都尝试过了。
这意味着您可以为应用程序创建外部DEX文件,以便下载并执行它们。虽然这里使用了一些不太优雅的方式(丑陋的Object
强制转换,Method.invoke()
丑陋的调用...),但应该可以使用Interface
来实现更简洁的方式。
哇。我很惊讶,我以为会出现SecurityException
。
以下是一些有助于进一步调查的事实:
Shlublu的答案非常好。以下是一些小细节可以帮助初学者:
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".NotExecutable"
android:label="@string/app_name">
</activity>
</application>
(".NotExecutable"不是保留字。只是我必须在这里放点什么)
其他步骤与Shlublu所述相同。
dx --dex --keep-classes --output=newjar.jar libproject.jar
。也许有一种方法可以在Eclipse构建库时自动执行这个dex步骤。 - Harvey我不确定您是否可以通过动态加载Java代码来实现此目标。也许您可以尝试将脚本引擎嵌入到您的代码中,例如Rhino,它可以执行Java脚本,这些脚本可以被动态下载和更新。
我认为@Shlublu的答案是正确的,但我想强调一些关键点。
To load the UI from external jar we can use fragment. Create the instance of the fragment and embedded it in the Activity. But make sure fragment creates the UI dynamically as given below.
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;
}
}
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
...
</manifest>
当然,这是可能的。未安装的apk可以由主android应用程序调用。一般来说,需要解决资源和activity的生命周期问题,然后才能动态加载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;
}