luj*_*jop 5 android android-fragments
是否可以使用外部应用程序中的片段/活动并在嵌入时使用它?例如,从PDF阅读器应用程序嵌入PDF阅读器片段.
san*_*tar 33
可能会有点晚,但仍然觉得它可以添加,可能会帮助其他人.
对于活动,实际上没有必要嵌入它,有方便的方式来使用其他应用程序活动 - 有目的地启动它.对于在应用程序内部实现某种"插件"功能的情况下可能有意义的片段.
在Android博客"Dalvik中的自定义类加载"中,有一种使用其他应用程序代码(或从网络加载代码)的官方方法.请注意,android与其他平台/环境没有太大区别,因此这两个部分(您的应用程序和片段,您希望加载到您的应用程序中)应该支持某种合同.这意味着您无法从任何应用程序加载任何组件,这是很常见的,并且有很多原因可以这样.
所以,这是一个小实现的例子.它由3部分组成:
接口项目 - 此项目包含接口的定义,应由主应用加载以使用外部类:
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)
对于这个例子,我们只需要单个接口来演示如何加载片段.
插件应用程序,其中包含您需要加载的代码 - 在我们的示例中,它是一个片段.请注意,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)想要从另一个应用程序加载片段的主要应用程序.它应该导入接口项目:
活动本身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的更新):
classLoader.loadClass:java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
确保片段模块包含在主应用程序中(作为"已编译")
如果您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需要对重用该方法进行更改.