Можно ли динамически загружать библиотеку во время выполнения из приложения Android?

есть ли способ сделать Android-приложение для загрузки и использования библиотеки Java во время выполнения?

вот пример:

представьте, что приложению необходимо выполнить некоторые вычисления в зависимости от входных значений. Приложение запрашивает эти входные значения, а затем проверяет, требуется ли Classeили Methods доступны.

Если нет, он подключается к серверу, загружает необходимую библиотеку и загружает ее во время выполнения для вызова требуемого методы, использующие методы отражения. Реализация может изменяться в зависимости от различных критериев, таких как пользователь, загружающий библиотеку.

7 ответов


Извините, я опоздал, и на вопрос уже принят ответ, но да, вы можете загружать и выполнять внешние библиотеки. Вот как я это сделал:

мне было интересно, возможно ли это, поэтому я написал следующий класс:

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:

enter image description here

я пробовал как на эмуляторе 2.1, так и на моем физическом телефоне HTC (который работает под управлением Android 2.2 и который не укоренен).

это означает, что вы можете создавать внешние DEX-файлов приложения, чтобы загрузить и выполнить их. Здесь был сделан трудный путь (уродливый Object слепки, Method.invoke() уродливые призывы...), но должна быть возможность играть с Interfaces, чтобы сделать что-то чище.

Вау. Я первый удивлен. Я ожидал SecurityException.

некоторые факты, чтобы помочь расследовать подробнее:

  • мой Декс шлублу.банку был подписан, но не мое приложение
  • мое приложение было выполнено из соединения Eclipse / USB. Таким образом, это беззнаковый APK, скомпилированный в режиме отладки

anwser Шлублу действительно хороший. Некоторые мелочи, хотя это помогло бы новичку:

  • для файла библиотеки "MyClass" сделайте отдельный проект приложения для Android, который имеет файл MyClass как только файл в папке src (другие вещи, такие как project.свойства, манифест, 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 (для компиляции) и найдите .APK файл из папки bin проекта.

  • скопировать .apk файл на свой телефон и переименовать его как shlublu.файл jar (APK на самом деле является специализацией jar, хотя)

другие шаги такие же, как описано в Shlublu.

  • большое спасибо Шлублу за сотрудничество.

Я не уверен, что вы можете достичь этого, динамически загружая java-код. Может быть, вы можете попробовать встроить движок скриптов в свой код, например rhino, который может выполнять Java-скрипты, которые можно динамически загружать и обновлять.


конечно, это возможно. apk, который не установлен может быть вызван приложения для Андроид.как правило,устранить ресурсов и lifecircle активности,то,может загружать jar или динамически АПК. подробно, пожалуйста, обратитесь к моим исследованиям с открытым исходным кодом на 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/en-GB/about/developer-content-policy-print

приложение, распространяемое через Google Play, не может изменять, заменять или обновлять сам, используя любой метод, кроме механизма обновления Google Play. Аналогично, приложение не может загружать исполняемый код (например, dex, JAR, .так что ... файлы) из источника, отличного от Google Play. Это ограничение не применить к коду, который выполняется в виртуальной машине и имеет ограниченный доступ к API Android (например, JavaScript в WebView или браузере).


Я думаю, что ответ @Shlublu правильный, но я просто хочу выделить некоторые ключевые моменты.

  1. мы можем загружать любые классы из внешнего jar и apk-файла.
  2. в любом случае, мы можем загрузить активность из внешнего jar, но мы не можем запустить его из-за концепции контекста.
  3. для загрузки пользовательского интерфейса из внешнего jar мы можем использовать фрагмент. Создайте экземпляр фрагмента и внедрите его в действие. Но убедитесь, что fragment создает 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;
     }
    }