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


77

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

嵌套片段所面临的问题是,如果调用了一个嵌套片段(即,已通过的FragmentManagerreturn由添加到另一个片段的片段getChildFragmentManager()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_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>

一周前发现了相同的错误。必须在嵌套的Fragment上手动调用onActivityResult()。我个人认为这是一个错误。实际上尚未从框架中检查它是否可行,但我认为可能是可行的。他们只需要检查所有子片段中的顶级片段,然后在每个子片段上调用onActivityResult()。如果没有从框架内执行此操作的方法,那么就可以了。但是,如果是这样,我会认真考虑这是一个错误。
Bogdan Zurac

Answers:


58

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

onActivityResult的调用顺序(在Android支持库中)为

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

在第3步中,可以在FragmentManangerparent的中找到该片段Activity。因此,在您的示例中,是发现要分派的容器片段onActivityResult(),嵌套片段永远不会收到事件。

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


2
好的,问题是...这是预期的行为,还是某种错误?在我看来,至少嵌套的片段不会收到活动的结果,这是反直观的。
指节针chi 2012年

好的。您可以将其视为错误,因为android支持库和版本4.2的本机代码都无法将结果分发到嵌套片段。尝试自己解决这个问题,不是很困难...
faylon 2012年

好吧,我想那就是我要做的。我试图避免这种情况,以免在托管活动及其片段之间引入更多的耦合。
指节针

6
有一个针对它的错误:code.google.com/p/android/issues/detail?
id

似乎此错误尚未修复...?
RRTW

145

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

在容器片段中,以这种方式覆盖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
仅供参考,我在第一片段中使用带有viewpager的pagertabstrip,并在viewpager中加载许多子片段
LOG_TAG 2013年

1
Stacktrace表示NullPoinerException发生在FirstFragment.onActivityResult()方法中的某个位置。我认为,您可以在此方法中放置断点,并找出哪一行会产生此异常以及为什么会发生这种情况。
pvshnik 2013年

3
这是一个很好的答案。它不仅简单明了,而且通过嵌套的片段和活动结果解决了整个较低的16位问题。真好!
mittelmania

1
最后一行应该是第一行:-)
Martin Nuc

1
这应该是公认的答案。可以在gist.github.com/artem-zinnatullin/6916740上找到一个更强健的答案(可能过于偏执)。
swooby 2015年

8

这是我解决的方法。

活动中:

@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传递给它的片段可以实现空接口。不需要它,但我发现它很有用,因此并非所有片段都可以获取回调。
微风

6

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

2019年9月2日更新答案

Androidx会再次出现此问题,当您使用单个活动并且在中嵌套了片段时NavHostFragmentonActivityResult()不会调用的子片段NavHostFragment

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

这是使用Kotlin代码进行操作的方法。这是onActivityResult()您主持以下活动的主要活动的内容NavHostFragment

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及更高版本中修复。FragmentAndroid Support Library 23.2+中,我们不再需要做任何额外的事情。onActivityResult()现在Fragment按预期在嵌套中调用。

我刚刚使用Android支持库23.2.1对其进行了测试,并且可以正常工作。最后,我可以获得更清晰的代码!

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


您可以为此分享任何官方链接吗?
拉维

1
@RaviRupareliya我已使用链接更新了答案。
Yogesh Umesh Vaity

4

我搜索了FragmentActivity的源代码。我发现了这些事实。

  1. 当片段调用startActivityForResult()时,它实际上会调用其父FragmentActivity的startAcitivityFromFragment()。
  2. 当片段开始执行结果时,requestCode必须低于65536(16位)。
  3. 在FragmentActivity的startActivityFromFragment()中,它调用Activity.startActivityForResult(),此函数还需要一个requestCode,但此requestCode与原始requestCode不相等。
  4. 实际上,FragmentActivity中的requestCode有两个部分。较高的16位值为(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传递给其子片段。

    @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事件的方式。

当您具有fragmentActivity时,它包含fragment1,而fragment1包含fragment2。因此onActivityResult只能传递给fragment1。

并且比我找到解决这个问题的方法。首先创建一个NestedFragment扩展Fragment覆盖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扩展Fragment重写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(8bit),因为我们将requestCode分成了3部分

16位| 8位| 8位

Android已使用的较高16位,中间8位用于帮助fragment1在其fragmentManager数组中找到fragment2。最低的8位是源requestCode。


0

对于Main Activity,您可以使用Super类编写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

我遇到了同样的问题!我通过在中使用static解决ChildFragment了此问题ParentFragment,如下所示:

private static ChildFragment mChildFragment;

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

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

}

1
mChildFragment不必要是静态的。
foo64
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.