Fragment中startActivityForResult的原理分析

昨天看面经的时候看到一个问题,在Fragment中和Activity中调用StartActivityForResult有什么区别?
百度了一下没找到专门解释的,只看到一些怎么解决调用后onActivityResult()获取数据的笔记,或者是Fragment中调用startActivityForResult和getActivity().startActivityForResult区别的博文。
干脆自己追踪源码看一看好了。
如有误请读者指正![抱拳]

下面开始剖析,首先明确几个关键类:

  • Activity,对就是单纯的Activity,直接继承自ContextThemeWrapper那个。
  • Fragment。
  • FragmentHostCallback。
  • Activity的内部类HostCallbacks。
  • FragmentActivity,以及它的内部类HostCallbacks。

Fragment中startActivityForResult(...):

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

调用了mHost,这个变量就是FragmentHostCallback类,这是一个抽象类。

FragmentHostCallback mHost;

这个mHost内部持有Fragment持有Activity的引用。

Activity的内部类HostCallbacks:

class HostCallbacks extends FragmentHostCallback<Activity> {
        ......
        @Override
        public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode,
                Bundle options) {
            Activity.this.startActivityFromFragment(fragment, intent, requestCode, options);
        }
        ......
}
     * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment(
     * android.support.v4.app.Fragment,Intent,int,Bundle)}
     */
    @Deprecated
    public void startActivityFromFragment(@NonNull Fragment fragment,
            @RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) {
        startActivityForResult(fragment.mWho, intent, requestCode, options);
    }

被标记了不建议使用,建议使用FragmentActivity的方法。调用了Activity自身的startActivityForResult,和在Activity调用的区别在第一个参数,用途没找到。
而且Activity的onActivityResult方法是空方法,需要子类重写。也就是说返回的数据需要我们自己重写onActivityResult方法来手动判断、分发。

那为什么网上的博文都可以在Fragment中调用startActivityForResult并且直接在Fragment的onActivityResult中拿到返回数据?我的猜测是因为他们的并不是直接继承Activity

可能有小伙伴快猜到了,不急我们接着看FragmentActivity中的内部类HostCallbacks。

因为HostCallbacks是继承FragmentHostCallback的,自然在FragmentActivity中添加的Fragment的mHost也是用的HostCallbacks,关键点来了。

class HostCallbacks extends FragmentHostCallback<FragmentActivity> {
      ......
      public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
            FragmentActivity.this.startActivityFromFragment(fragment, intent, requestCode, options);
      }
      ......
}

是的,HostCallbacks中重写了这个关键的方法。
那我们看看FragmentActivity中的startActivityFromFragment干了啥(下面的源码稍长可以直接看结论)

FragmentActivity中的startActivityFromFragment:

public void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
        this.mStartedActivityFromFragment = true;

        try {
            if (requestCode == -1) {
                ActivityCompat.startActivityForResult(this, intent, -1, options);
                return;
            }

            checkForValidRequestCode(requestCode);
            int requestIndex = this.allocateRequestIndex(fragment);
            ActivityCompat.startActivityForResult(this, intent, (requestIndex + 1 << 16) + (requestCode & '\uffff'), options);
        } finally {
            this.mStartedActivityFromFragment = false;
        }

    }

FragmentActivity结合了调用方法的fragment和requestCode重新生成了一个requestCode(tips:从源码中可以看到我们在Fragment中startActivityForResult的requestCode不能大于0xffff,因为高16位被清0了)。
从这个操作我们也可以想到FragmentActivity中onActivityResult方法做了什么,就是把这个requestCode再拆成两部分,找到对应的Fragment并调用Fragment.onActivityResult。
(tips:我们FragmentActivity中调用startActivityForResult的requestCode不能大于0xffff,因为onActivityResult中requestCode的高16是要用来识别这个返回数据是要返回给某个Fragment还是返回给FragmentActivity自己的)

恩,流程结束了,最后是简单又关键的一点

我们平时新建一个空Activity,AS默认是帮我们继承自AppCompatActivity的,然后

public class AppCompatActivity extends FragmentActivity implements AppCompatCallback, SupportParentable, DelegateProvider

没错,Fragment可以调用startActivityForResult并接收返回的数据是因为使用的“Activity”是继承自FragmentActivity的。

顺便提一下,Fragment中调用startActivityForResult和getActivity().startActivityForResult的区别,Fragment直接调用的话FragmentActivity在收到返回数据后可以从高16位requestCode识别出需要数据的Fragment并传递过去;如果是getActivity().startActivityForResult的话就变成了是FragmentActivity自己发送的请求,最后收到数据后高16判定分发给FragmentActivity自己,Fragment就收不到了。

所以如果需要FragmentActivity自动分发给Fragment,我们要在自己重写的onActivityResult中加上super.onActivityResult。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容