Можно ли динамически загружать библиотеку во время выполнения из приложения Android? - PullRequest
66 голосов
/ 28 июля 2011

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

Вот пример:

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

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

Ответы [ 7 ]

87 голосов
/ 28 июля 2011

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

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

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() некрасивые звонки ...), но должна быть возможность играть с Interface s, чтобы сделать что-то чище.

Ничего себе. Я первый удивлен. Я ожидал SecurityException.

Некоторые факты, которые помогут расследовать больше:

  • Мой DEX shlublu.jar был подписан, но не мое приложение
  • Мое приложение было выполнено из соединения Eclipse / USB. Так что это неподписанный APK, скомпилированный в режиме отладки
14 голосов
/ 09 сентября 2014

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

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

Другие шаги такие же, как описано Шлублу.

  • Большое спасибо Шлублу за сотрудничество.
1 голос
/ 13 декабря 2014

Если вы храните файлы .DEX во внешней памяти телефона, например, на SD-карте (не рекомендуется! Любое приложение с такими же разрешениями может легко перезаписать ваш класс и выполнить атаку с помощью внедрения кода), убедитесь, что вы:дали приложению разрешение на чтение внешней памяти.Исключением, которое выдается, если это так, является ClassNotFound, что вводит в заблуждение, добавьте в манифест что-то вроде следующего (проконсультируйтесь с Google для получения самой последней версии).

<manifest ...>

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="18" />
    ...
</manifest>
1 голос
/ 06 декабря 2014

конечно, это возможно.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;
}

ваши требования только частичнофункции в проекте с открытым исходным кодом, упомянутым в начале.

1 голос
/ 28 июля 2011

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

0 голосов
/ 18 сентября 2018

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

  1. Мы можем загрузить любые классы из внешнего файла jar и apk.
  2. В любом случае, мы можем загрузить Activity из внешнего jar-файла, но не можем запустить его из-за концепции контекста.
  3. Для загрузки пользовательского интерфейса с внешнего фляги мы можем использовать фрагмент. Создайте экземпляр фрагмента и внедрите его в Activity. Но убедитесь, что фрагмент создает пользовательский интерфейс динамически как указано ниже.

    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;
     }
    }
    
0 голосов
/ 07 декабря 2017

Технически должно работать, но как насчет правил Google?От: play.google.com/intl/en-GB/about/developer-content-policy-pr‌ int

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

...