在应用程序内部使用外部应用程序片段

luj*_*jop 5 android android-fragments

是否可以使用外部应用程序中的片段/活动并在嵌入时使用它?例如,从PDF阅读器应用程序嵌入PDF阅读器片段.

san*_*tar 33

可能会有点晚,但仍然觉得它可以添加,可能会帮助其他人.

对于活动,实际上没有必要嵌入它,有方便的方式来使用其他应用程序活动 - 有目的地启动它.对于在应用程序内部实现某种"插件"功能的情况下可能有意义的片段.

在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();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    对于这个例子,我们只需要单个接口来演示如何加载片段.

  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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    它的布局是fragment_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>
    
    Run Code Online (Sandbox Code Playgroud)
  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();
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    它的布局是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>
    
    Run Code Online (Sandbox Code Playgroud)

最后我们能够在真实设备上观察以下内容:

在此输入图像描述

可能的问题处理(感谢@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,那么请确保该片段位于已安装的APPLICATION中(而不仅仅是库依赖项).请按照以下步骤确保是这样的:

    1)在主应用程序的build.gradle中,切换apply plugin: 'android-library'到apply plugin: 'android'并确保有一个虚拟活动java文件.在主应用程序中,删除对片段模块的依赖(它未在步骤3中指定,但我必须在片段模块上添加对主应用程序的依赖.但片段模块现在是一个活动应用程序,你可以' t依赖于那些)或者你会得到这个:Error:Dependency unspecified on project resolves to an APK archive which is not supported as a compilation dependency.

    2)运行片段模块(您现在可以这样做,因为它是一个活动应用程序).这将以getApplicationInfo调用可以找到它的方式安装它Revert build.gradle并在主应用程序中添加依赖关系(作为'compile'依赖项)现在一切都应该工作.当您对片段代码进行更新时,您不需要再次执行此过程.但是,如果要在新设备上运行或添加新的片段模块,您将会这样做.我希望能够节省一些时间来解决上述错误.

Android L

看起来,基于Android L的常规 multidex支持,不需要上面的步骤,因为类加载是不同的.可以使用multidex支持中描述的方法代替Android博客'Dalvik中的自定义类加载',因为它明确指出:

注意:本文档中提供的指南取代了Android开发人员博客文章Dalvik中自定义类加载中提供的指导.

可能android.support.multidex需要对重用该方法进行更改.

  • 为什么投票?这个包含工作示例和官方文档的链接,而不是之前的答案(实际上没有'官方'证明为什么它不可能重复使用). (6认同)

Pet*_*ego 0

不,您不能“重用”其他应用程序的代码。唯一官方的方法是使用Intent来调用整个Activity。