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

Posted

技术标签:

【中文标题】onActivityResult() 未在新的嵌套片段 API 中调用【英文标题】:onActivityResult() not called in new nested fragment API 【发布时间】:2012-11-14 19:53:10 【问题描述】:

我一直在使用 android 支持库中包含的新 nested fragment API。

我面临的嵌套片段的问题是,如果一个嵌套片段(即通过FragmentManagergetChildFragmentManager()返回的片段添加到另一个片段)调用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_
    android:layout_ >

</FrameLayout>

【问题讨论】:

一周前发现了同样的错误。必须在嵌套片段上手动调用 onActivityResult()。就个人而言,我认为这是一个错误。实际上还没有检查它是否可以从框架中执行,但我认为它可能是可行的。他们只需要检查***片段的所有子片段并在每个子片段上调用 ​​onActivityResult() 。如果在框架内没有办法做到这一点,那么好吧,就是这样。但如果是这样,我认真地认为这是一个错误。 【参考方案1】:

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

在容器片段中以这种方式覆盖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 方法的调用。

此外,正如noted Eric Brynsvold 在类似问题中一样,嵌套片段应该使用它的父片段而不是简单的 startActivityForResult() 调用来启动活动。因此,在嵌套片段开始活动中:

getParentFragment().startActivityForResult(intent, requestCode);

【讨论】:

仅供参考,我在 firstfragment 中使用带有 viewpager 的 pagertabstrip,并在 viewpager 中加载许多子片段 Stacktrace 说 NullPoinerException 发生在您的 FirstFragment.onActivityResult() 方法的某处。我认为,您可以在此方法中放置断点,并找出哪一行产生此异常以及为什么会发生这种情况。 这是一个很好的答案。它不仅简单明了,而且解决了嵌套片段和活动结果的整个低 16 位问题。不错! 最后一行应该是第一行:-) 这应该是公认的答案。可以在gist.github.com/artem-zinnatullin/6916740 找到一个稍微 [也许过于偏执] 可靠的答案。【参考方案2】:

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

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

    Activity.dispatchActivityResult()FragmentActivity.onActivityResult()Fragment.onActivityResult()

在第 3 步中,在父 ActivityFragmentMananger 中找到该片段。因此,在您的示例中,发现调度 onActivityResult() 的是容器片段,嵌套片段永远无法接收到该事件。

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

【讨论】:

好的,所以问题是......这是一种预期的行为,还是某种错误?对我来说,嵌套片段没有收到活动的结果至少是反直觉的。 Ok.. 您可以将其视为一个错误,因为 android 支持库和 4.2 版本的本机代码都无法将结果发送到嵌套片段。尝试自己解决这个问题,不是很难... 好吧,我想这就是我要做的。我试图避免这种情况,以免在托管活动及其片段之间引入更多耦合。 有一个针对它的错误:code.google.com/p/android/issues/detail?id=40537 似乎这个错误还没有被修复...?【参考方案3】:

这就是我解决它的方法。

活动中:

@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 IHandleActivityResult 只是一个空接口。想要将 handleResult 传递给它的片段可以实现空接口。它是不需要的,但我发现它很有用,因此并非所有片段都得到回调。【参考方案4】:

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

2019 年 9 月 2 日更新答案

这个问题又回到了 Androidx 中,当您使用单个活动并且您在 NavHostFragment 内有嵌套片段时,onActivityResult() 不会为 NavHostFragment 的子片段调用。

要解决此问题,您需要手动将调用从宿主活动的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 支持库 23.2+ 中使用 Fragment 做任何额外的事情。 onActivityResult() 现在按预期在嵌套的 Fragment 中调用。

我刚刚使用 Android 支持库 23.2.1 对其进行了测试,它可以正常工作。终于可以有更简洁的代码了!

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

【讨论】:

你能分享任何官方链接吗? @RaviRupareliya 我已经用链接更新了答案。【参考方案5】:

我已经搜索了 FragmentActivity 来源。我找到了这些事实。

    fragment调用startActivityForResult()的时候,其实是调用了他父FragmentActivity的startAcitivityFromFragment()。 fragment 为 result 启动活动时,requestCode 必须低于 65536(16bit)。 在FragmentActivity的startActivityFromFragment()中,调用了Activity.startActivityForResult(),这个函数也需要一个requestCode,但是这个requestCode不等于origin requestCode。 实际上FragmentActivity中的requestCode有两部分。较高的 16 位值是(片段在 FragmentManager 中的索引)+ 1。较低的 16 位等于原始请求代码。这就是 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的高16bit是否不为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 事件的方式。

当你有一个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);
        
    

就是这样! 用法:

    fragment1 扩展 MyFragment。 fragment2 扩展 NestedFragment。 没有更多不同。只需在 fragment2 上调用 startActivityForResult(),并覆盖 onActivityResult() 函数。

警告!!!!!!!!! requestCode 必须低于 512(8bit),因为我们在 3 部分中溢出了 requestCode

16 位 | 8位 | 8位

高16bit安卓已经用过了,中间8bit是为了帮助fragment1在他的fragmentManager数组中找到fragment2,低8bit是origin requestCode。

【讨论】:

【参考方案6】:

对于 Main Activity,您可以使用类似 as 的 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()的activity写意图 像

 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) 

【讨论】:

【参考方案7】:

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

private static ChildFragment mChildFragment;

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

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


【讨论】:

mChildFragment 是不必要的静态。

以上是关于onActivityResult() 未在新的嵌套片段 API 中调用的主要内容,如果未能解决你的问题,请参考以下文章

OnActivityResult未在活动的片段中调用

Zxing QR扫描仪onActivityResult未在片段中调用[重复]

onActivityResult 从未在我的嵌套片段中调用

使用角度导航时,在新选项卡中打开链接未在新选项卡中打开

Android的Fragment中onActivityResult不被调用的解决方案

SQLite 未在新创建的页面上连接