是否可以使用外部应用程序中的片段/活动并在嵌入时使用? 例如,从PDF阅读器应用程序中嵌入PDF阅读器片段。
答案 0 :(得分:31)
可能有点晚了,但仍然觉得它可以添加并可能有助于其他人。
对于活动,实际上没有必要嵌入它,有方便的方式来使用其他应用程序活动 - 有目的地启动它。对于在应用程序内部实现某种“插件”功能的情况下可能有意义的片段。
在Android Blog 'Custom Class Loading in 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();
}
对于这个例子,我们只需要单个接口来演示如何加载片段。
插件应用程序,其中包含您需要加载的代码 - 在我们的示例中,它是一个片段。请注意,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;
}
}
它的布局是 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>
想要从其他应用程序加载片段的主应用程序。它应该导入接口项目:
活动本身 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>
最后我们能够在真实设备上观察以下内容:
处理可能的问题(感谢 @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 support,不需要上面的步骤,因为类加载是不同的。可以使用multidex支持中描述的方法代替Android Blog 'Custom Class Loading in Dalvik',因为它明确指出:
注意:本文档中提供的指南取代了指南 在Android开发者博客文章中给出自定义类加载 的Dalvik。
可能需要对android.support.multidex
进行更改才能重复使用该方法。
答案 1 :(得分:1)
不,您无法“重用”其他应用程序中的代码。唯一的官方方法是使用Intent
来调用整个Activity。
答案 2 :(得分:0)
我对sandrstart使用了非常相似的方法。也许它不太安全。 我使用从使用插件包名称创建的packagecontext派生的普通Classloader。所有插件的软件包名称都将与其他配置一起从config网站加载和保存。
Context ctx = createPackageContext(packetName, Context.CONTEXT_INCLUDE_CODE |
Context.CONTEXT_IGNORE_SECURITY);
ClassLoader cl = ctx.getClassLoader();
Class<?> c = cl.loadClass(className);
Fragment fragObj = (Fragment)c.newInstance();
但是我想强调一下我的方法,我认为sandrstar的方法仅适用于android内部android.app.Fragment类。
我试图(再次)作为采用android 9的一部分,从android.app.Fragment(已描述)切换到android.support.v4.Fragment,但无法正常工作。
原因是:
apk1:framework.class.A == apk2.framework.class.A但
apk1:someJarOrAar.class.B!= aps2.someJarOrAar.class.B
即使在两个项目中使用了完全相同的jar / aar。
因此,我将始终在(Fragment)c.newInstance();
上收到ClassCastException。
我试图通过插件的classloader进行强制转换,并通过主包的classloader进行强制转换,但无济于事。我认为没有办法解决,因为即使罐子/ aar中具有相同的名称和相同的类名,也无法保证罐子/ aar确实是相同的,因此即使它们相同也将它们视为不同是一个安全问题
我当然希望有一些解决方法(除了像我现在要做的那样,即使在android 9下也继续使用android.app.Fragment),但是我也很感谢提供注释,说明为什么共享类绝对不能使用它(例如support.v4.Fragment)。