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

是否可以在运行时从Android应用程序动态加载库?,android,reflection,classloader,dalvik,android-library,Android,Reflection,Classloader,Dalvik,Android Library,有没有办法让Android应用程序在运行时下载并使用Java库 以下是一个例子: 假设应用程序需要根据输入值进行一些计算。应用程序要求输入这些值,然后检查所需的Classes或方法是否可用 如果没有,它将连接到服务器,下载所需的库,并在运行时加载它,以使用反射技术调用所需的方法。实现可能会根据不同的条件而变化,例如下载库的用户。我不确定是否可以通过动态加载java代码来实现这一点。可能你可以尝试嵌入一个脚本引擎,比如rhino,它可以执行java脚本,可以动态下载和更新。对不起,我迟到了,这个问

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

以下是一个例子:

假设应用程序需要根据输入值进行一些计算。应用程序要求输入这些值,然后检查所需的
Classe
s或
方法是否可用


如果没有,它将连接到服务器,下载所需的库,并在运行时加载它,以使用反射技术调用所需的方法。实现可能会根据不同的条件而变化,例如下载库的用户。

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

对不起,我迟到了,这个问题已经有了公认的答案,但是是的,你可以下载并执行外部库。我是这样做的:

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

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();
        }
    }
}
public类主扩展活动{
@抑制警告(“未选中”)
@凌驾
创建时受保护的void(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
试一试{
最后一个字符串libPath=Environment.getExternalStorageDirectory()+“/shlublu.jar”;
最终文件tmpDir=getDir(“dex”,0);
final DexClassLoader classloader=新的DexClassLoader(libPath,tmpDir.getAbsolutePath(),null,this.getClass().getClassLoader());
最终类classToLoad=(类)classloader.loadClass(“org.shlublu.android.sandbox.MyClass”);
最终对象myInstance=classToLoad.newInstance();
最终方法doSomething=classToLoad.getMethod(“doSomething”);
调用(myInstance);
}捕获(例外e){
e、 printStackTrace();
}
}
}
它基本上是这样加载类
MyClass

  • 创建一个

  • 使用它从
    “/sdcard/shlublu.jar”

  • 并将此类存储到应用程序的
    “dex”
    专用目录(手机的内部存储)

然后,它创建一个
MyClass
的实例,并对所创建的实例调用
doSomething()

而且它是有效的。。。我在日志猫中看到了
MyClass
中定义的跟踪:

我已经在emulator 2.1和我的物理HTC手机(运行Android 2.2,没有根目录)上进行了尝试

这意味着您可以为应用程序创建外部DEX文件以下载和执行它们。在这里,这是一个艰难的过程(丑陋的
对象
强制转换,
方法.invoke()
丑陋的调用…),但必须能够使用
接口
使某些东西更干净

哇。我是第一个感到惊讶的人。我希望出现
安全异常

以下事实有助于进一步调查:

  • 我的DEX shlublu.jar已签名,但我的应用程序未签名
  • 我的应用程序是通过Eclipse/USB连接执行的。这是一个在调试模式下编译的无符号APK

Shlublu的anwser非常好。不过有些小事情对初学者会有帮助:

  • 对于库文件“MyClass”,创建一个单独的Android应用程序项目,该项目将MyClass文件作为src文件夹中的唯一文件(其他内容,如project.properties、manifest、res等也应存在)
  • 在库项目清单中,确保您具有:
    
    
    (“.NotExecutable”不是一个保留字,只是我必须在这里放一些东西)

  • 要生成.dex文件,只需将库项目作为android应用程序运行(用于编译),并从项目的bin文件夹中找到.apk文件

  • 将.apk文件复制到您的手机上,并将其重命名为shlublu.jar文件(不过,apk实际上是jar的一种特殊化)
其他步骤与Shlublu所述相同

  • 非常感谢Shlublu的合作

    • 当然,这是可能的。未安装的apk可以被宿主android应用程序调用。通常,解析资源和活动的生命周期,然后动态加载jar或apk。 详细信息,请参阅我对github的开源研究:

      此外,还需要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;
      }
      
      /**
      *加载apk。在开始插件活动之前,我们应该先这样做。
      *注意:将仅由主机apk调用。 *@param dexPath */ 公共DLPluginPackage loadApk(字符串dexPath){ //当主机apk调用loadApk时,我们假设插件由主机调用。 mFrom=DLConstants.FROM_EXTERNAL; PackageInfo PackageInfo=mContext.getPackageManager()。 getPackageGearChiveInfo(dexPath,PackageManager.GET_活动); 如果(packageInfo==null) 返回null; 最后一个字符串packageName=packageInfo.packageName; DLPluginPackage pluginPackage=mPackagesHolder.get(packageName); if(pluginPackage==null){ DexClassLoader DexClassLoader=createDexClassLoader(dexPath); AssetManager AssetManager=createAssetManager(dexPath); Resources=createResources(assetManager); pluginPackage=新的DLPluginPackage(packageName、dexPath、dexClassLoader、assetManager、, 资源,packageInfo); mPackagesHolder.put(packageName,pluginPackage); } 返回插件包; }
      你的数字高程模型
      <manifest ...>
      
          <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                       android:maxSdkVersion="18" />
          ...
      </manifest>
      
      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;
       }
      }