android - 在应用程序内使用外部应用程序 fragment/Activity

标签 android android-fragments

是否可以使用来自外部应用程序的 fragment/Activity 并在嵌入时使用?

例如:嵌入来自 PDF 阅读器应用程序的 PDF 阅读器 fragment 。

最佳答案

可能有点晚了,但还是觉得可以加,可能对别人有帮助。

对于 Activity ,嵌入它真的没有意义,有一种方便的方式来使用其他应用程序 Activity - 用 Intent 启动它。对于在应用程序内实现某种“插件”功能的情况下的 fragment 可能有意义。

Android Blog 'Custom Class Loading in Dalvik' 中有一个官方方法可以使用来自其他应用程序的代码(或从网络加载代码) .请注意,android 与其他平台/环境没有太大区别,因此两个部分(您的应用程序和您想要加载到您的应用程序中的 fragment )都应该支持某种契约(Contract)。这意味着您不能从任何应用程序中加载任何组件,这很常见,并且有很多原因会导致这种情况。

所以,这里有一些实现的小例子。它由3部分组成:

  • 接口(interface)项目 - 该项目包含接口(interface)的定义,应由主应用程序加载以使用外部类:
    package com.example.test_interfaces;
    
    import android.app.Fragment;
    
    /**
     * Interface of Fragment holder to be obtained from external application
     */
    public interface FragmentHolder {
        Fragment getFragment();
    }
    

    对于这个例子,我们只需要一个接口(interface)来演示如何加载 fragment 。
  • 插件应用程序,其中包含您需要加载的代码——在我们的例子中,它是一个 fragment 。请注意,您的 IDE 中的这个项目应该依赖于使用“提供”类型且不导出的接口(interface)一,因为它将由主应用程序导入。

    fragment ,我们要加载 插件 fragment :
    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;
        }
    }
    

    它的布局 fragment 布局.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>
    
  • 想要从另一个应用程序加载 fragment 的主应用程序。它应该导入接口(interface)项目:

    Activity 本身 我的 Activity :
    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 的更新):
  • 如果您在调用 classLoader.loadClass 时遇到以下错误:
  • java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
    确保 fragment 模块包含在主应用程序中(作为“已编译”)
  • 如果您收到 NameNotFoundException在调用 context.getPackageManager().getApplicationInfo(packageName,0).sourceDir ,然后确保该 fragment 位于已安装的 APPLICATION 中(而不仅仅是库依赖项)。请按照以下步骤确保是这种情况:

    1) 在主应用程序的 build.gradle 中,更改 apply plugin: 'android-library'apply plugin: 'android'并确保有一个虚拟 Activity java 文件。在主应用中,移除对fragment模块的依赖(步骤3中没有指定,但是我必须在主应用中添加对fragment模块的依赖。但是fragment模块现在是一个 Activity 应用程序,你可以'不依赖这些),否则你会得到:Error:Dependency unspecified on project resolves to an APK archive which is not supported as a compilation dependency.
    2)运行 fragment 模块(你现在可以这样做,因为它是一个 Activity 应用程序)。这以 getApplicationInfo 调用可以找到它的方式安装它 Revert build.gradle 并将依赖项添加回主应用程序(作为“编译”依赖项)现在一切都应该工作了。当您对 fragment 代码进行更新时,您无需再次执行此过程。但是,如果您想在新设备上运行或添加新的 fragment 模块,您将会这样做。我希望这能够节省我试图解决上述错误的时间。

  • 安卓L

    看来,基于正常multidex support使用 Android L,则不需要上述步骤,因为类加载不同。可以使用 multidex 支持中描述的方法来代替 Android Blog 'Custom Class Loading in Dalvik' ,因为它明确指出:

    Note: The guidance provided in this document supersedes the guidance given in the Android Developers blog post Custom Class Loading in Dalvik.



    可能,android.support.multidex 的变化可能需要重用该方法。

    关于android - 在应用程序内使用外部应用程序 fragment/Activity ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7726702/

    相关文章:

    java - 单击该 fragment 内的按钮关闭 fragment

    android - 无法从非@Nullable @Provides 方法错误返回 null

    java - Fragment Recyclerview onCreateView、onViewCreated 或 onActivityCreated?

    android - 布局不维护 Android fragment 中的状态

    android - 如何在正确的 View 中从 fragment 中显示 Snackbar?

    android - ModuleVersionNotFoundException android 工作室

    android - 使用停止按钮停止音乐后播放按钮不起作用

    android - 按钮 onClick 在 vi​​ewholder 模式 ListView 中的位置错误

    android - 如何将 ListView 放入 fragment 中

    android - 为什么对话框不能与线程并行工作?