【源码解析】Activity之setResult工作原理

引言

假设现在有一个 Activity A,从 Activity A 通过 startActivityForResult 方法启动了 Activity B,在 Activity B 销毁前,Activity B 想将一些数据回传给 Activity A,那么可以主动调用 setResult 方法,这样在 Activity B 销毁后,Activity A 重新展示时,在 Activity AonActivityResult 方法就能够获取 Activity B 回传过来的数据。

上面是 Activity 回传数据的一个流程,我们也非常的熟悉了,整体流程如下图所示:

一个例子

setResult方法的使用还是比较简单的,但是使用不当那很有可能会踩到坑。我们来看下面一个例子。

public class MainActivity extends Activity {
    public static final String TAG = "MyApplication";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.test).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivityForResult(intent, 0);
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == 100) {
            Log.d(TAG, "MainActivity onActivityResult");
        }

    }
}

MainActivity 作为启动页,点击触发 startActivityForResult 跳转到 SecondActivity,并在 onActivityResult 方法监听 SecondActivity 传递回来的数据。

public class SecondActivity extends Activity {
    public static final String TAG = "MyApplication";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_layout);
        //setResult();
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "SecondActivity onResume");
        //setResult();
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "SecondActivity onPause");
        setResult();
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "SecondActivity onStop");
        //setResult();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "SecondActivity onDestroy");
        //setResult();
    }

    @Override
    public void finish() {
        super.finish();
        Log.d(TAG, "SecondActivity finish");
    }

    private void setResult() {
        setResult(100);
        Log.d(TAG, "SecondActivity setResult");
    }
}

SecondActivity在其各个生命周期中分别调用setResult给MainActivity返回数据,当用户在SecondActivity点击返回键返回时,根据Log信息跟踪方法的调用路径。例如上面的例子中在SecondActivity的onPause方法调用setResult,得到的Log如下图所示:

从上图可以看到当在 onPause 方法执行 setResult 方法时,在执行 onPause 之前 SecondActivity 就已经执行了 finish 方法了,因此即使 SecondActivity 成功执行了 setResult 方法,但 MainActivityonActvivityResult 方法还是没有收到 SecondActivity 的返回信息。同理在 onStoponDestroy 中执行 setResult 也是不可以的,但是在 onPause 方法前执行 setResult 就能顺利的将 SecondActivity 的信息传递给 MainActivity 。如下图所示就是在 onResume 方法中执行 setResult 的Log信息。

源码解析

setResult 方法由 Activity 类提供,不管是调用 setResult(int resultCode) 或者 setResult(int resultCode, Intent data),最终都是赋值操作,将 resultCode 赋值给 mResultCode,将 data 赋值给 mResultData

public final void setResult(int resultCode, Intent data) {
    synchronized (this) {
        mResultCode = resultCode;
        mResultData = data;
    }
}

从上面的例子中可以知道,setResult 是否发送成功与 finish 方法有很大的关系。我们就此分析一下 finish 方法。finish 方法又调用了 finish(int finishTask) 方法。

private void finish(int finishTask) {
    if (mParent == null) {
        int resultCode;
        Intent resultData;
        synchronized (this) {
            resultCode = mResultCode;
            resultData = mResultData;
        }
        try {
            if (resultData != null) {
                resultData.prepareToLeaveProcess(this);
            }
            if (ActivityManager.getService()
                    .finishActivity(mToken, resultCode, resultData, finishTask)) {
                mFinished = true;
            }
        } 
    }
    ...
}

finish 方法中引用了 mResultCodemResultData,然后调用了 AMSfinishActivity 方法。

public final boolean finishActivity(IBinder token, int resultCode, Intent resultData,
        int finishTask) {
    ...
    try {
        ...
        } else {
            res = tr.getStack().requestFinishActivityLocked(token, resultCode,
                    resultData, "app-request", true);
            if (!res) {
                Slog.i(TAG, "Failed to finish by app-request");
            }
        }
        return res;
    } 
}

finish 方法中通过 tr.getStack 方法返回 ActivityStack,并执行 ActivityStackrequestFinishActivityLocked 方法。requestFinishActivityLocked 方法里又执行了 finishActivityLocked 方法。

final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData,
        String reason, boolean oomAdj, boolean pauseImmediately) {
    ...
    finishActivityResultsLocked(r, resultCode, resultData);
    ...
}

finishActivityLocked 方法中执行了 finishActivityResultsLocked 方法。

private void finishActivityResultsLocked(ActivityRecord r, int resultCode, Intent resultData) {
    // send the result
    ActivityRecord resultTo = r.resultTo;
    if (resultTo != null) {
        if (resultTo.userId != r.userId) {
            if (resultData != null) {
                resultData.prepareToLeaveUser(r.userId);
            }
        }
        if (r.info.applicationInfo.uid > 0) {
            mService.grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid,
                    resultTo.packageName, resultData,
                    resultTo.getUriPermissionsLocked(), resultTo.userId);
        }
        resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode,
                                    resultData);
        r.resultTo = null;
    }
    ...
}

finishActivityResultsLocked 方法的注释 send the result 已经表明了该方法会将 setResult 信息传递到原始启动 ActivityfinishActivityResultsLocked 的第一个参数 ActivityRecord 对象代表的是当前 Activity(相当于上面例子的 SecondActivity),它的 resultTo 表示另外一个 ActivityRecord 对象,代表的是原始启动 Activity(相当于上面例子的 MainActivity),找到原始启动 ActivityRecord 对象后,就执行了 ActivityRecordaddResultLocked 方法。

void addResultLocked(ActivityRecord from, String resultWho,
        int requestCode, int resultCode,
        Intent resultData) {
    ActivityResult r = new ActivityResult(from, resultWho,
            requestCode, resultCode, resultData);
    if (results == null) {
        results = new ArrayList<ResultInfo>();
    }
    results.add(r);
}

addResultLocked 方法中构造了一个 ActivityResult 对象,并将其存储在 ActivityRecord 对象的一个 ArrayList 对象中。这也表明原始启动的 Activity 可以接收多个由其启动的 Activity 传递回来的 resultCoderesultData

由上面的分析可知,在当前 Activity 执行 finish 时会将 setResult 方法中的 resultCoderesultData 存储到它的原始启动 ActivityActivityRecord 对象中。所以我们得出这样的一个结论:

  • setResult 方法必须要在 finish 方法之前执行,否则 setResult 方法执行无效。

所以在上面例子中,在 onPauseonStoponDestroy 方法执行 setResult 方法, 为什么MainActivityonActivityResult 执行不到???

因为 onPauseonStoponDestroy 方法执行之前 finish 方法已经执行了,因此 setResult 方法执行无效。

至此已经分析完了调用 Activity 调用 setResult 方法后,数据存储到原始 ActivityActivityRecord 对象的整个流程,但什么时候触发原始 ActivityonActivityResult 方法执行呢?这就涉及到 Activity 的生命周期方法了。

熟悉 Activity 的生命周期方法的同学都知道,生命周期方法都是在 ActivityThread 中分发的。对于 ActivityonActivityResult 方法的执行,在 ActivityThread 中会先执行 handleSendResult 方法。

@Override
public void handleSendResult(IBinder token, List<ResultInfo> results, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    if (r != null) {
        ...
        deliverResults(r, results, reason);
        ...
    }
}

handleSendResult 方法的第二个参数 results 就是上面我们分析的 ActivityRecord 对象中的 ArrayList\<ResultInfo\> results,方法里又调用了 deliverResults 方法。

private void deliverResults(ActivityClientRecord r, List<ResultInfo> results, String reason) {
    final int N = results.size();
    for (int i=0; i<N; i++) {
        ResultInfo ri = results.get(i);
        try {
            if (ri.mData != null) {
                ri.mData.setExtrasClassLoader(r.activity.getClassLoader());
                ri.mData.prepareToEnterProcess();
            }
            r.activity.dispatchActivityResult(ri.mResultWho,
                    ri.mRequestCode, ri.mResultCode, ri.mData, reason);
        } catch (Exception e) {
            ...
        }
    }
}

deliverResults 方法中,遍历了List<ResultInfo> results,并调用 ActivitydispatchActivityResult方法,该方法最终会执行 ActivityonActvivityResult 方法,并将 resultCoderesultData 传递过去。

总结

setResult 方法用来将数据回传给其启动页的 Activity,但是它的执行时机是有讲究的,我们必须保证在 finish 方法之前调用 setResult 方法,也同时需要避免在 onPauseonStoponDestroy 方法中调用 setResult,否则 setResult 方法即使被执行了,数据也不能回传给其启动页 Activity

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

推荐阅读更多精彩内容