Fragment中调用startActivityForResult的那些坑

简介:

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点

Fragment中调用startActivityForResult要注意几种情况

  1. 用getActivity方法发起调用,只有父Activity的onActivityResult会调用,Fragment中的onActivityResult不会被调用
  2. 直接发起startActivityForResult调用,当前的Fragment的onActivityResult,和父Activity的onActivityResult都会调用
  3. 用getParentFragment发起调用,则只有父Activity和父Fragment的onActivityResult会被调用,当前的Fragment的onActivityResult不会被调用。

这里2和3的前提是如果父activity中重写了onActivityResult,父Activity的onActivityResult中必须添加super.onActivityResult()

总结起来就是:从哪里发起调用,最终就会走到哪里。

源码分析

Fragment中直接调用startActivityForResult

(1)发起startActivityForResult调用

这种情况会直接调用到Fragment的startActivityForResult方法

//Fragment.class
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
    if (mHost == null) {
        throw new IllegalStateException("Fragment " + this + " not attached to Activity");
    }
    mHost.onStartActivityFromFragment(this /*fragment*/, intent, requestCode, options);
}

上面的mHost对应的就是Fragment的父FragmentActivity,所以会调用到父FragmentActivitystartActivityFromFragment方法

//FragmentActivity.class
public void startActivityFromFragment(Fragment fragment, Intent intent,
        int requestCode, @Nullable Bundle options) {
    mStartedActivityFromFragment = true;
    try {
        //一般requestCode都不会为-1,所以不会走if里面
        if (requestCode == -1) {
            ActivityCompat.startActivityForResult(this, intent, -1, options);
            return;
        }
        //这里检查requestCode是否越界了,不能超过2^16
        checkForValidRequestCode(requestCode);
        //根据这个requestIndex可以获取到对应Fragment的唯一标识mWho
        int requestIndex = allocateRequestIndex(fragment);
        //发起startActivityForResult调用,这里requestIndex和requestCode关联起来
        ActivityCompat.startActivityForResult(
                this, intent, ((requestIndex + 1) << 16) + (requestCode & 0xffff), options);
    } finally {
        mStartedActivityFromFragment = false;
    }
}

每一个Fragment在内部都有一个唯一的标识字段who,在FragmentActivity中把所有调用startActivityFromFragment方法的fragment的requestCodewho通过key-value的方式保存在mPendingFragmentActivityResults变量中

private int allocateRequestIndex(Fragment fragment) {
    ...

    int requestIndex = mNextCandidateRequestIndex;
    
    //将requestIndex和fragment的mWho保存起来
    mPendingFragmentActivityResults.put(requestIndex, fragment.mWho);
    mNextCandidateRequestIndex =
            (mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;

    return requestIndex;
}

这里allocateRequestIndex方法就把requestIndex和Fragment的mWho变量关联起来了

在上面的startActivityFromFragment方法中调用ActivityCompatstartActivityForResult方法发起启动Activity的时候又把requestIndexrequestCode关联起来了

这样后面回调onActivityResult方法时就可以根据requestCode获取对应的Fragment,以便调用Fragment的onActivityResult方法

最后看一下ActivityCompatstartActivityForResult方法

public static void startActivityForResult(@NonNull Activity activity, @NonNull Intent intent,
        int requestCode, @Nullable Bundle options) {
    if (Build.VERSION.SDK_INT >= 16) {
        activity.startActivityForResult(intent, requestCode, options);
    } else {
        activity.startActivityForResult(intent, requestCode);
    }
}
(2)onActivityResult方法回调

通过断点调试的方法,我们会发现最先被回调的就是父Activity的onActivityResult,也就是我们的FragmentActivity的onActivityResult

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    mFragments.noteStateNotSaved();
    int requestIndex = requestCode>>16;
    //requestIndex = 0就表示没有Fragment发起过startActivityForResult调用
    if (requestIndex != 0) {
        requestIndex--;
        
        //根据requestIndex获取Fragment的who变量
        String who = mPendingFragmentActivityResults.get(requestIndex);
        mPendingFragmentActivityResults.remove(requestIndex);
        if (who == null) {
            Log.w(TAG, "Activity result delivered for unknown Fragment.");
            return;
        }
        
        //然后根据who变量获取目标Fragment
        Fragment targetFragment = mFragments.findFragmentByWho(who);
        if (targetFragment == null) {
            Log.w(TAG, "Activity result no fragment exists for who: " + who);
        } else {
            //最后调用Fragment的onActivityResult
            targetFragment.onActivityResult(requestCode & 0xffff, resultCode, data);
        }
        return;
    }

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

从上面的方法中可以看出FragmentActivity中的onActivityResult方法中对于Fragment的startActivityForResult调用已经做了处理。

这里就有一个问题需要注意了,我们一般都会覆写父Activity中的onActivityResult方法,这个时候我们必须在onActivityResult方法加上super.onActivityResult(),否则Fragment中的onActivityResult方法就没有办法回调到了。

这就是文章开头中提到的2、3两点需要注意的原因

getParentFragment发起调用

这种情况一般发生在嵌套多层Fragment的时候

getParentFragment发起调用的过程和上面的类似,只不过发起调用的是当前Fragment的父Fragment,所以最后回调的也是父Activity的onActivityResult方法和父Fragment的onActivityResult方法。

所以如果想在子Fragment中监听到onActivityResult方法的回调,就不要用这种方式

getActivity方法发起调用

这个就更简单了,直接调用的是父Activity的onActivityResult方法

//FragmentActivity.class
@Override
    public void startActivityForResult(Intent intent, int requestCode) {
        // If this was started from a Fragment we've already checked the upper 16 bits were not in
        // use, and then repurposed them for the Fragment's index.
        if (!mStartedActivityFromFragment) {
            if (requestCode != -1) {
                checkForValidRequestCode(requestCode);
            }
        }
        super.startActivityForResult(intent, requestCode);
    }

所以从源码也可以看出,这种方式最后不会回调Fragment的onActivityResult方法

总结

在Fragment中调用startActivityForResult以及监听onActivityResult是很常见的一种应用方式,但是稍不注意就会掉到坑里,比如因为ActivityonActivityResult方法没有调用super.onActivityResult()方法而导致Fragment中死活接收不到onActivityResult的回调。

最后总结一下几种场景的应用步骤:

(1)一个Activity嵌套一层Fragment,Fragment中需要监听onActivityResult返回结果
  • 直接在Fragment中调用startActivityForResult方法
  • 如果父Activity中覆写了onActivityResult,则需要确保调用了super.onActivityResult()方法
  • Fragment中实现onActivityResult方法即可监听回调结果
(2)一个Activity嵌套多层Fragment,Fragment中需要监听onActivityResult返回结果
  • 这种情况和上面的是一样的,从上面的源码中我们可以看到,在哪个Fragment发起的startActivityForResult调用,只要父Activity的onActivityResult方法调用了super.onActivityResult()方法,Fragment中的onActivityResult方法就会回调

其实,说白了就是在Fragment中直接调用startActivityForResult方法就行,不要用getActivity().startActivityForResult(),也不要用getParentFragment().startActivityForResult(),除非你知道为什么非要用这2种方式!


欢迎关注我的微信公众号,和我一起每天进步一点点!
AntDream

目录
相关文章
|
10月前
|
缓存
DevEco Studio 5.0.1 Beta3安装及配置
本文介绍了如何下载、安装和配置华为DevEco Studio 5.0.1 Beta3,并详细说明了安装HarmonyOS模拟器及创建虚拟机的步骤。包括下载安装包、卸载旧版本、选择安装目录、创建快捷方式、启动DevEco Studio、配置环境、进入设备管理器、下载虚拟机镜像、创建和启动虚拟机等操作。
1604 1
|
API Android开发 开发者
Android经典实战之使用ViewCompat来处理View兼容性问题
本文介绍Android中的`ViewCompat`工具类,它是AndroidX库核心部分的重要兼容性组件,确保在不同Android版本间处理视图的一致性。文章列举了设置透明度、旋转、缩放、平移等功能,并提供了背景色、动画及用户交互等实用示例。通过`ViewCompat`,开发者可轻松实现跨版本视图操作,增强应用兼容性。
355 5
|
API Android开发 容器
36. 【Android教程】侧滑菜单:DrawerLayout
36. 【Android教程】侧滑菜单:DrawerLayout
302 1
|
设计模式 存储 缓存
单例模式与工厂方法模式之间的差异
【8月更文挑战第24天】
172 0
|
XML Android开发 UED
|
Java API 开发工具
解决 Android 依赖冲突
解决 Android 依赖冲突
435 0
|
XML Java API
Android原生TabLayout使用全解析,看这篇就够了
Android原生TabLayout使用全解析,看这篇就够了
2295 0
Android原生TabLayout使用全解析,看这篇就够了
|
Android开发 开发者
Android启动优化之精确测量启动各个阶段的耗时
Android启动优化之精确测量启动各个阶段的耗时
Android启动优化之精确测量启动各个阶段的耗时
解决Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 62问题~
解决Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 62问题~
576 0
|
缓存 IDE 编译器
Git文件.gitignore和.gitkeep用法解析
Git文件.gitignore和.gitkeep用法解析
892 0