Использовать внешний фрагмент приложения / активность внутри приложения - PullRequest
5 голосов
/ 11 октября 2011

Можно ли использовать фрагмент / действие из внешнего приложения и использовать его как встроенный?Например, вставьте фрагмент для чтения PDF из приложения для чтения PDF.

Ответы [ 2 ]

32 голосов
/ 09 января 2014

Может быть немного поздно, но все же чувствую, что это может быть добавлено и может помочь другим.

Что касается активности, то в действительности нет смысла встраивать ее, есть удобный способ использовать другие действия в приложениях - начните с намерения. Для фрагментов может иметь смысл в случае реализации какой-либо функциональности «плагин» внутри приложения.

Существует официальный способ использования кода из других приложений (или загрузки кода из сети) в Блог блога Android «Загрузка пользовательских классов в Dalvik» . Обратите внимание, что Android не сильно отличается от других платформ / сред, поэтому обе части (ваше приложение и фрагмент, который вы хотите загрузить в свое приложение) должны поддерживать какой-то контракт. Это означает, что вы не можете загрузить какой-либо компонент из какого-либо приложения, что является довольно распространенным явлением, и для этого есть ряд причин.

Итак, вот небольшой пример реализации. Он состоит из 3 частей:

  1. Проект интерфейсов - этот проект содержит определения интерфейсов, которые должны загружаться основным приложением для использования внешних классов:

    package com.example.test_interfaces;
    
    import android.app.Fragment;
    
    /**
     * Interface of Fragment holder to be obtained from external application
     */
    public interface FragmentHolder {
        Fragment getFragment();
    }
    

    Для этого примера нам нужен только один интерфейс, чтобы продемонстрировать, как загрузить фрагмент.

  2. Плагин-приложение, содержащее код, который необходимо загрузить - в нашем случае это фрагмент. Обратите внимание, что этот проект в вашей среде IDE должен зависеть от интерфейса, использующего тип «предоставлен» и без экспорта, поскольку он будет импортирован основным приложением.

    Фрагмент, мы собираемся загрузить PlugInFragment :

    package com.sandrstar.plugin;
    
    import com.example.test_interfaces.FragmentHolder;
    
    public class PlugInFragment extends Fragment implements FragmentHolder {
    
        @Override
        public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
    
            // Note that loading of resources is not the same as usual, because it loaded actually from another apk
            final XmlResourceParser parser = container.getContext().getPackageManager().getXml("com.sandrstar.plugin", R.layout.fragment_layout, null);
    
            return inflater.inflate(parser, container, false);
        }
    
        @Override
        public Fragment getFragment() {
            return this;
        }
    }
    

    И это макет frag_layout.xml :

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/black">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="This is from fragment"
            android:textColor="@android:color/white"/>
    </LinearLayout>
    
  3. Основное приложение, которое хочет загрузить фрагмент из другого приложения. Должен быть импортирован интерфейсный проект:

    Сама деятельность MyActivity :

    public class MyActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.main);
    
            try {
                Class<?> requiredClass = null;
                final String apkPath = getPackageManager().getApplicationInfo("com.sandrstar.plugin",0).sourceDir;
                final File dexTemp = getDir("temp_folder", 0);
                final String fullName = "com.sandrstar.plugin.PlugInFragment";
                boolean isLoaded = true;
    
                // Check if class loaded
                try {
                    requiredClass = Class.forName(fullName);
                } catch(ClassNotFoundException e) {
                    isLoaded = false;
                }
    
                if (!isLoaded) {
                    final DexClassLoader classLoader = new DexClassLoader(apkPath,
                            dexTemp.getAbsolutePath(),
                            null,
                            getApplicationContext().getClassLoader());
    
                    requiredClass = classLoader.loadClass(fullName);
                }
    
                if (null != requiredClass) {
                    // Try to cast to required interface to ensure that it's can be cast
                    final FragmentHolder holder = FragmentHolder.class.cast(requiredClass.newInstance());
    
                    if (null != holder) {
                        final Fragment fragment = holder.getFragment();
    
                        if (null != fragment) {
                            final FragmentTransaction trans = getFragmentManager().beginTransaction();
    
                            trans.add(R.id.fragmentPlace, fragment, "MyFragment").commit();
                        }
                    }
                }
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
    

    И это макет main.xml :

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:clipChildren="false"
        android:id="@+id/root">
    
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/down_image" />
    
        <FrameLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/fragmentPlace"
            android:layout_centerInParent="true" />
    </RelativeLayout>
    

И, наконец, мы можем наблюдать следующее на реальном устройстве:

enter image description here

Обработка возможных проблем (спасибо @ MikeMiller за обновление):

  1. Если при вызове classLoader.loadClass появляется следующая ошибка:

java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation

Убедитесь, что фрагментированные модули включены в основное приложение (как «скомпилированные»)

  1. Если вы получаете NameNotFoundException при вызове context.getPackageManager().getApplicationInfo(packageName,0).sourceDir, то убедитесь, что фрагмент находится в установленном ПРИЛОЖЕНИИ (а не только в зависимости от библиотеки). Чтобы убедиться, что это так, выполните следующие действия:

    1) В build.gradle основного приложения измените apply plugin: 'android-library' на apply plugin: 'android' и убедитесь, что в нем есть java-файл фиктивной активности. В основном приложении удалите зависимость от модуля фрагмента (это не указано в шаге 3, но мне пришлось добавить зависимость от модуля фрагмента в основное приложение. Но модуль фрагмента теперь является приложением действия, и вы можете ' у них нет зависимостей) или вы получите это: Error:Dependency unspecified on project resolves to an APK archive which is not supported as a compilation dependency.

    2) Запустите модуль фрагмента (что вы можете сделать сейчас, потому что это приложение активности). Это устанавливает его так, чтобы вызов getApplicationInfo мог его найти. Восстановить build.gradle и добавить зависимость обратно в основное приложение (как зависимость 'compile'). Теперь все должно работать. Когда вы обновляете фрагмент кода, вам больше не нужно проходить этот процесс. Тем не менее, если вы хотите запустить на новом устройстве или добавить новый модуль фрагмента. Я надеюсь, что это поможет кому-то сэкономить время, которое я потратил на устранение вышеуказанных ошибок.

Android L

Кажется, на основе normal multidex support с Android L, описанные выше шаги не нужны, поскольку загрузка классов отличается. Подход, описанный в поддержке multidex, можно использовать вместо Android-блога «Загрузка пользовательских классов в Dalvik» , поскольку в нем четко указано, что:

Примечание. Указания, представленные в этом документе, заменяют собой указанияприведено в блоге разработчиков Android. Загрузка пользовательских классов в Dalvik.

Возможно, для повторного использования этого подхода могут потребоваться изменения в android.support.multidex.

1 голос
/ 11 октября 2011

Нет, вы не можете «повторно» использовать код из других приложений.Единственный официальный способ - использовать Intent для вызова всей активности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...