OnActivityResult()未在新的嵌套片段 API 中调用

我一直在使用 Android 在支持库中包含的新 嵌套的碎片 API。

我在处理嵌套片段时遇到的问题是,如果一个嵌套片段(即通过 getChildFragmentManager()返回的 FragmentManager添加到另一个片段中的片段)调用 startActivityForResult(),那么嵌套片段的 onActivityResult()方法就不会被调用。但是,父片段的 onActivityResult()和活动的 onActivityResult()都会被调用。

我不知道我是否遗漏了关于嵌套片段的一些东西,但是我没有预料到所描述的行为。下面是重现这个问题的代码。如果有人能为我指明正确的方向,并向我解释我做错了什么,我将不胜感激:

package com.example.nestedfragmentactivityresult;


import android.media.RingtoneManager;
import android.os.Bundle;
import android.content.Intent;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;


public class MainActivity extends FragmentActivity
{
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.getSupportFragmentManager()
.beginTransaction()
.add(android.R.id.content, new ContainerFragment())
.commit();
}


public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);


// This is called
Toast.makeText(getApplication(),
"Consumed by activity",
Toast.LENGTH_SHORT).show();
}


public static class ContainerFragment extends Fragment
{
public final View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
View result = inflater.inflate(R.layout.test_nested_fragment_container,
container,
false);


return result;
}


public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
getChildFragmentManager().beginTransaction()
.add(R.id.content, new NestedFragment())
.commit();
}


public void onActivityResult(int requestCode,
int resultCode,
Intent data)
{
super.onActivityResult(requestCode, resultCode, data);


// This is called
Toast.makeText(getActivity(),
"Consumed by parent fragment",
Toast.LENGTH_SHORT).show();
}
}


public static class NestedFragment extends Fragment
{
public final View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
Button button = new Button(getActivity());
button.setText("Click me!");
button.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
startActivityForResult(intent, 0);
}
});


return button;
}


public void onActivityResult(int requestCode,
int resultCode,
Intent data)
{
super.onActivityResult(requestCode, resultCode, data);


// This is NOT called
Toast.makeText(getActivity(),
"Consumed by nested fragment",
Toast.LENGTH_SHORT).show();
}
}
}

Test _ nested _ part _ conter.xml 是:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" >


</FrameLayout>
35653 次浏览

是的,嵌套片段中的 onActivityResult()不会以这种方式调用。

OnActivityResult (在 Android 支持库中)的调用序列是

  1. Activity.dispatchActivityResult().
  2. FragmentActivity.onActivityResult().
  3. Fragment.onActivityResult().

在第三步中,在父代 ActivityFragmentMananger中找到该片段。所以在您的示例中,发现分派 onActivityResult()的是容器片段,嵌套片段永远不会接收到事件。

我认为您必须在 ContainerFragment.onActivityResult()中实现自己的分派,找到嵌套的片段并调用将结果和数据传递给它。

我是这么解决的。

活动内容:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
List<Fragment> frags = getSupportFragmentManager().getFragments();
if (frags != null) {
for (Fragment f : frags) {
if (f != null)
handleResult(f, requestCode, resultCode, data);
}
}
}


private void handleResult(Fragment frag, int requestCode, int resultCode, Intent data) {
if (frag instanceof IHandleActivityResult) { // custom interface with no signitures
frag.onActivityResult(requestCode, resultCode, data);
}
List<Fragment> frags = frag.getChildFragmentManager().getFragments();
if (frags != null) {
for (Fragment f : frags) {
if (f != null)
handleResult(f, requestCode, resultCode, data);
}
}
}

我遇到了同样的问题! 我在 ParentFragment中使用静态 ChildFragment解决了这个问题,像这样:

private static ChildFragment mChildFragment;


protected void onActivityResult(int requestCode, int resultCode, Intent data) {


mChildFragment.onActivityResult(int requestCode, int resultCode, Intent data);


}

我用以下代码解决了这个问题(使用了支持库) :

在容器片段中,以这种方式覆盖 onActivityResult:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);


List<Fragment> fragments = getChildFragmentManager().getFragments();
if (fragments != null) {
for (Fragment fragment : fragments) {
fragment.onActivityResult(requestCode, resultCode, data);
}
}
}

现在嵌套的片段将接收对 onActivityResult 方法的调用。

另外,作为 知道了 Eric Brynsvold在类似的问题中,嵌套的片段应该使用它的父片段而不是简单的 startActivityForResult ()调用来启动活动。因此,在嵌套的片段中,使用以下命令启动活动:

getParentFragment().startActivityForResult(intent, requestCode);

对于具有导航组件的 Androidx,使用 NavHostFragment

更新于2019年9月2日

这个问题又回到了 Androidx,当你使用一个单独的活动并且你在 NavHostFragment中嵌套了一个片段时,onActivityResult()并没有被调用为 NavHostFragment的子片段。

为了解决这个问题,您需要手动地从主机活动的 onActivityResult()中将子片段的调用路由到 onActivityResult()

下面是如何使用 Kotlin 代码进行操作。这将进入主要活动 NavHostFragmentonActivityResult()中:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.your_nav_host_fragment)
val childFragments = navHostFragment?.childFragmentManager?.fragments
childFragments?.forEach { it.onActivityResult(requestCode, resultCode, data) }
}

这将确保子片段的 onActivityResult()将被正常调用。

Android 支援库

旧答案

这个问题已经在 Android 支援库23.2中解决了。 我们不需要在 Android 支持库23.2 + 中使用 Fragment做任何额外的事情。现在,正如预期的那样,在嵌套的 Fragment中调用 onActivityResult()

我刚刚使用 Android 支持库23.2.1对它进行了测试,它运行良好,我终于可以拥有更清晰的代码了!

因此,解决方案是开始使用最新的 Android 支持库。

对于 Main Activity,使用如下超类编写 OnActivityForResult

  @Override
public void onActivityResult(int requestCode, int resultCode, Intent result) {
super.onActivityResult(requestCode, resultCode, result);
if (requestCode == Crop.REQUEST_PICK && resultCode == RESULT_OK) {
beginCrop(result.getData());
} }

为活动编写没有 getActivity ()的意图 就像

 Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts"));
pickContactIntent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE); // Show user only contacts w/ phone numbers
startActivityForResult(pickContactIntent, 103);

片段的 OnActivityResult

   @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 101 && resultCode == getActivity().RESULT_OK && null != data) {

}}

我已经搜索了碎片活动的来源,我找到了这些事实。

  1. 当片段调用 startActivityForResult ()时,它实际上调用了它的父片段活动的 startActivityFromFragment ()。
  2. 当结果的片段启动活动时,requestCode 必须小于65536(16位)。
  3. 在 FragmentActivity 的 startActivityFromFragment ()中,它调用 Activity.startActivityForResult () ,这个函数也需要一个 requestCode,但是这个 requestCode 不等于原始的 requestCode。
  4. 实际上,片段活动中的 requestCode 有两部分。较高的16位值是(片段在 FragmentManager 中的索引) + 1。较低的16位等于原始 request 代码。这就是为什么请求代码必须低于65536。

观察片段活动中的代码。

public void startActivityFromFragment(Fragment fragment, Intent intent,
int requestCode) {
if (requestCode == -1) {
super.startActivityForResult(intent, -1);
return;
}
if ((requestCode&0xffff0000) != 0) {
throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
}
super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff));
}

结果出来后。片段活动覆盖 onActivityResult 函数,并检查 requestCode 的较高16位是否不是0,然后将 onActivityResult 传递给其子代片段。

    @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mFragments.noteStateNotSaved();
int index = requestCode>>16;
if (index != 0) {
index--;
final int activeFragmentsCount = mFragments.getActiveFragmentsCount();
if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) {
Log.w(TAG, "Activity result fragment index out of range: 0x"
+ Integer.toHexString(requestCode));
return;
}
final List<Fragment> activeFragments =
mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount));
Fragment frag = activeFragments.get(index);
if (frag == null) {
Log.w(TAG, "Activity result no fragment exists for index: 0x"
+ Integer.toHexString(requestCode));
} else {
frag.onActivityResult(requestCode&0xffff, resultCode, data);
}
return;
}


super.onActivityResult(requestCode, resultCode, data);
}

这就是片段活动调度 ActivityResult 事件的方式。

当你有一个片段活动时,它包含片段1,片段1包含片段2。 所以 onActivityResult 只能传递给 Fragment1。

然后我找到了解决这个问题的方法。 首先创建一个 NestedFragment 扩展片段覆盖 startActivityForResult 函数。

public abstract class NestedFragment extends Fragment {


@Override
public void startActivityForResult(Intent intent, int requestCode) {


List<Fragment> fragments = getFragmentManager().getFragments();
int index = fragments.indexOf(this);
if (index >= 0) {
if (getParentFragment() != null) {
requestCode = ((index + 1) << 8) + requestCode;
getParentFragment().startActivityForResult(intent, requestCode);
} else {
super.startActivityForResult(intent, requestCode);
}


}
}

}

而不是创建 MyFragment 扩展片段覆盖 onActivityResult 函数。

public abstract class MyFragment extends Fragment {


@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
int index = requestCode >> 8;
if (index > 0) {
//from nested fragment
index--;


List<Fragment> fragments = getChildFragmentManager().getFragments();
if (fragments != null && fragments.size() > index) {
fragments.get(index).onActivityResult(requestCode & 0x00ff, resultCode, data);
}
}
}

}

仅此而已! 用法:

  1. 扩展我的片段。
  2. 片段2扩展嵌套片段。
  3. 不再有什么不同。只需要在 fragment2上调用 startActivityForResult () ,并覆盖 onActivityResult ()函数。

警惕! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! RequestCode 必须低于512(8位) ,因为我们将 requestCode 分为3部分

16位 | 8位 | 8位

Android 已经使用了较高的16位,中间的8位是为了帮助片段1在他的片段管理器数组中找到片段2。最低的8位是原始请求代码。