在新的嵌套片段API中,onActivityResult()未被调用

80

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

我遇到的问题是,如果一个嵌套的Fragment(即通过getChildFragmentManager()返回的FragmentManager将其添加到另一个Fragment中)调用了startActivityForResult(),则嵌套的Fragment的onActivityResult()方法将不会被调用。但是,父Fragment的onActivityResult()和Activity的onActivityResult()都会被调用。

我不知道是否有关于嵌套Fragment的什么事情我没有考虑到,但我没有预料到会出现上述行为。以下是重现此问题的代码。如果有人能指点我正确方向并解释我做错了什么,我将不胜感激:

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_fragment_container.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>

一周前发现了同样的错误。必须在嵌套片段上手动调用onActivityResult()。个人认为这是一个错误。实际上还没有检查是否可以从框架中完成,但我认为可能是可以的。他们只需要检查顶层片段的所有子片段,并在每个片段上调用onActivityResult()。如果没有办法从框架内部完成这个操作,那么好吧,就这样吧。但如果可以的话,我认为这是一个错误。 - Bogdan Zurac
7个回答

145

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

在容器片段中,以如下方式覆盖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);

2
FYI,我在第一个Fragment中使用PagerTabStrip和ViewPager,并在ViewPager中加载许多子Fragment。 - LOG_TAG
1
堆栈跟踪显示,在您的FirstFragment.onActivityResult()方法中发生了NullPoinerException异常。我认为,您可以在此方法中设置断点,找出哪一行产生了此异常以及为什么会发生这种情况。 - pvshnik
3
这是一个很棒的答案。它不仅简单易懂,而且解决了嵌套片段和活动结果的整个低16位问题。不错! - mittelmania
1
那最后一行应该是第一行 :-) - Martin Nuc
1
这应该是被接受的答案。稍微更加[可能过度谨慎]健壮的答案可以在https://gist.github.com/artem-zinnatullin/6916740找到。 - swooby
显示剩余4条评论

58

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

在Android支持库中,onActivityResult()的调用顺序如下:

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

在第三步中,片段在父ActivityFragmentMananger中找到。因此在您的示例中,被发现以分派onActivityResult()的是容器片段,嵌套片段无法接收该事件。

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


2
好的,问题是...这是一种预期行为,还是某种错误?对我来说,嵌套片段没有接收到活动结果至少是不直观的。 - Knuckles the Echidna
6
有一个针对此问题的错误报告:http://code.google.com/p/android/issues/detail?id=40537 - Markus Junginger
这个 bug 似乎还没有被修复吗? - RRTW
请查看此帖子,其中包含问题描述和常见解决方法:http://shomeser.blogspot.com/2014/01/nested-fragments-for-result.html - Oleksii K.
似乎在sdk 22中仍未修复此错误。我的一个嵌套片段接收了onActivityResult(),但第二个没有... - E-Kami
显示剩余3条评论

8
以下是我的解决方案。
在Activity中:
@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);
        }
    }
}

谢谢你的技巧!但是你能否通过编辑你的答案来解释一下代码中的_IHandleActivityResult_接口是什么?+1 - LOG_TAG
1
IHandleActivityResult 只是一个空接口。想要 handleResult 传递给它的片段可以实现这个空接口。这是不必要的,但我发现这很有用,因为不是所有的片段都需要获得回调。 - whizzle

7

对于使用NavHostFragment的Androidx和导航组件

2019年9月2日更新的答案

当您使用单个活动并且在NavHostFragment内嵌套片段时,此问题会重新出现。对于NavHostFragment的子片段,onActivityResult()不会被调用。

为解决此问题,您需要从主活动的onActivityResult()手动将调用路由到子片段的onActivityResult()

以下是使用Kotlin代码执行此操作的方法。这些代码应该放在托管NavHostFragment的主要活动的onActivityResult()中:

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 Support Library 23.2及以后的版本中得到解决。 我们不再需要在Android Support Library 23.2+中使用Fragment进行任何额外的操作。onActivityResult()现在按预期在嵌套的Fragment中调用。 我刚刚测试了Android Support Library 23.2.1,它可以正常工作。最终我可以拥有更干净的代码!
因此,解决方案是开始使用最新的Android支持库。

请问您能否分享任何官方链接? - Ravi
1
@RaviRupareliya 我已经更新了答案并附上了链接。 - Yogesh Umesh Vaity

4

我查看了FragmentActivity的源代码,发现以下事实:

  1. 当fragment调用startActivityForResult()时,实际上是调用其父FragmentActivity的startAcitivityFromFragment()。
  2. 当fragment启动带有返回值的activity时,requestCode必须小于65536(16位)。
  3. 在FragmentActivity的startActivityFromFragment()函数中,它调用了Activity.startActivityForResult()函数,这个函数也需要一个requestCode,但这个requestCode并不等于原来的requestCode。
  4. 实际上,FragmentActivity中的requestCode有两部分。高16位的值是(fragment在FragmentManager中的索引+1),低16位等于原来的requestCode。这就是为什么requestCode必须小于65536的原因。

请查看FragmentActivity中的代码。

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));
}

当结果返回时,FragmentActivity覆盖onActivityResult函数,并检查requestCode的高16位是否不为0,然后将onActivityResult传递给其子fragment。

    @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);
}

这就是FragmentActivity分发onActivityResult事件的方式。

当你有一个包含fragment1的fragmentActivity,并且fragment1包含fragment2时,onActivityResult只能传递到fragment1。

然后我找到了解决这个问题的方法。首先创建一个继承自Fragment的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);
        }

    }
}

创建一个继承自Fragment的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. fragment1继承MyFragment。
  2. fragment2继承NestedFragment。
  3. 没有更多的区别了。只需在fragment2上调用startActivityForResult()并重写onActivityResult()函数即可。

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

16位 | 8位 | 8位

高16位Android已经使用,中间8位是帮助fragment1在其fragmentManager数组中找到fragment2的代码。最低的8位是原始requestCode。


0

对于主活动,您可以编写带有超类的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) {

}}


-2

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

private static ChildFragment mChildFragment;

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

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

}

1
mChildFragment 不必要地是静态的。 - foo64

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接