从动态权限请求再看startActivityForResult

写过动态权限请求代码的小伙伴一定知道,请求动态权限会导致当前Activity再次调用onResume,究其原因,是因为在请求权限的时候启动了一个新的Activity导致当前Activity被暂停,当请求权限的窗口退出后,当前Activity又重新resume。可以看一下requestPermissions()的实现:

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
        if (requestCode < 0) {
            throw new IllegalArgumentException("requestCode should be >= 0");
        }
        if (mHasCurrentPermissionsRequest) {
            Log.w(TAG, "Can reqeust only one set of permissions at a time");
            // Dispatch the callback with empty arrays which means a cancellation.
            onRequestPermissionsResult(requestCode, new String[0], new int[0]);
            return;
        }
        Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
        startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
        mHasCurrentPermissionsRequest = true;
    }

从源码里我们看到了熟悉的startActivityForResult,果然是启动了一个Activity,这个新启动的Activity也就是显示授权的窗口,叫做GrantPermissionsActivity。但是到了这里就又有一个疑问了:平时我们使用startActivityForRresult启动一个新Activity的时候,当从新的Activity返回,通常都会回调onActivityResult这个方法,可是在动态权限请求的时候这个方法并没有被回调!这是什么原因呢?难道是GrantPermissionsActivity并没有调用setResult()方法?答案是否定的。我们来看一下GrantPermissionsActivity相关的源码:

448    @Override
449    public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {
450        GroupState groupState = mRequestGrantPermissionGroups.get(name);
451        if (groupState != null && groupState.mGroup != null) {
452            if (granted) {
453                groupState.mGroup.grantRuntimePermissions(doNotAskAgain,
454                        groupState.affectedPermissions);
455                groupState.mState = GroupState.STATE_ALLOWED;
456            } else {
457                groupState.mGroup.revokeRuntimePermissions(doNotAskAgain,
458                        groupState.affectedPermissions);
459                groupState.mState = GroupState.STATE_DENIED;
460
461                int numRequestedPermissions = mRequestedPermissions.length;
462                for (int i = 0; i < numRequestedPermissions; i++) {
463                    String permission = mRequestedPermissions[i];
464
465                    if (groupState.mGroup.hasPermission(permission)) {
466                        EventLogger.logPermission(
467                                MetricsProto.MetricsEvent.ACTION_PERMISSION_DENIED, permission,
468                                mAppPermissions.getPackageInfo().packageName);
469                    }
470                }
471            }
472            updateGrantResults(groupState.mGroup);
473        }
474        if (!showNextPermissionGroupGrantRequest()) {
475            setResultAndFinish();
476        }
477    }

再来看一下setResultAndFinish()

581    private void setResultIfNeeded(int resultCode) {
582        if (!mResultSet) {
583            mResultSet = true;
584            logRequestedPermissionGroups();
585            Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS);
586            result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, mRequestedPermissions);
587            result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, mGrantResults);
588            setResult(resultCode, result);
589        }
590    }
591
592    private void setResultAndFinish() {
593        setResultIfNeeded(RESULT_OK);
594        finish();
595    }

从源码可以看到,不管用户是选择允许一项还是拒绝权限,GrantPermissionsActivity在finish自己之前的的确确调用了setResult(),那我们的onActivityResult为什么没有被调用呢?想要弄清楚这个问题,必须先弄清楚另外两个问题:

  1. Activity 在 finish之后是如何将setResult的结果传递到前一个Activity中去的也即setResult中的resultData去向如何?
  2. 原来的Activity在拿到结果之后是如何处理的?
    答案就在这两个问题中。
    先来看第一个问题,resultData的去向,来看Activity finish()方法的源码:
5593    private void finish(int finishTask) {
5594        if (mParent == null) {
5595            int resultCode;
5596            Intent resultData;
5597            synchronized (this) {
5598                resultCode = mResultCode;
5599                resultData = mResultData;
5600            }
5601            if (false) Log.v(TAG, "Finishing self: token=" + mToken);
5602            try {
5603                if (resultData != null) {
5604                    resultData.prepareToLeaveProcess(this);
5605                }
5606                if (ActivityManager.getService()
5607                        .finishActivity(mToken, resultCode, resultData, finishTask)) {
5608                    mFinished = true;
5609                }
5610            } catch (RemoteException e) {
5611                // Empty
5612            }
5613        } else {
5614            mParent.finishFromChild(this);
5615        }
5616
5617        // Activity was launched when user tapped a link in the Autofill Save UI - Save UI must
5618        // be restored now.
5619        if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
5620            getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_RESTORE,
5621                    mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN));
5622        }
5623    }
5624
5625    /**
5626     * Call this when your activity is done and should be closed.  The
5627     * ActivityResult is propagated back to whoever launched you via
5628     * onActivityResult().
5629     */
5630    public void finish() {
5631        finish(DONT_FINISH_TASK_WITH_ACTIVITY);
5632    }

从代码的5606行看到resultCode和resultData在finish()的时候传给了ActivityManagerService的finiActivity.由于篇幅的原因,后面的源码不再贴出,只给出方法调用的时序图:


时序图

从时序图可以看到,经过层层调用,最终resultData与resultCode、requestCode等数据被封装成ActivityResult,并被保存在ActivityRecord中的results列表中,这就是setResut()的最终归宿。

第一个问题弄明白了下面来看第二个问题,这个问题也就是Activity的resume流程问题,Activity的resume最终是由ActivityThread的performResumeActivity()完成的,时序如下图所示:


Activity resume 时序

在performResumeActivity过程中会将之前的resultData通过调用Activity的dispathcActivityResult()方法传回Activity,问题就在一这个方法,下面来看一下这个方法的源码:

7447  void dispatchActivityResult(String who, int requestCode, int resultCode, Intent data,
7448            String reason) {
7449        if (false) Log.v(
7450            TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
7451            + ", resCode=" + resultCode + ", data=" + data);
7452        mFragments.noteStateNotSaved();
7453        if (who == null) {
7454            onActivityResult(requestCode, resultCode, data);
7455        } else if (who.startsWith(REQUEST_PERMISSIONS_WHO_PREFIX)) {
7456            who = who.substring(REQUEST_PERMISSIONS_WHO_PREFIX.length());
7457            if (TextUtils.isEmpty(who)) {
7458                dispatchRequestPermissionsResult(requestCode, data);
7459            } else {
7460                Fragment frag = mFragments.findFragmentByWho(who);
7461                if (frag != null) {
7462                    dispatchRequestPermissionsResultToFragment(requestCode, data, frag);
7463                }
7464            }
7465        } else if (who.startsWith("@android:view:")) {
7466            ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
7467                    getActivityToken());
7468            for (ViewRootImpl viewRoot : views) {
7469                if (viewRoot.getView() != null
7470                        && viewRoot.getView().dispatchActivityResult(
7471                                who, requestCode, resultCode, data)) {
7472                    return;
7473                }
7474            }
7475        } else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) {
7476            Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null;
7477            getAutofillManager().onAuthenticationResult(requestCode, resultData, getCurrentFocus());
7478        } else {
7479            Fragment frag = mFragments.findFragmentByWho(who);
7480            if (frag != null) {
7481                frag.onActivityResult(requestCode, resultCode, data);
7482            }
7483        }
7484        writeEventLog(LOG_AM_ON_ACTIVITY_RESULT_CALLED, reason);
7485    }

此方法体内会对第一个参数who进行判断,who的值不同会走不同的分支,当who为null时会直接调用onActivityResult(requestCode, resultCode, data),而当who.startsWith(REQUEST_PERMISSIONS_WHO_PREFIX)为真时,会调用dispatchRequestPermissionsResult或者dispatchRequestPermissionsResultToFragment,这时我们再回到文章的开头,看一下requestPermissions()方法,里面调用startActivityForResult时正是传的REQUEST_PERMISSIONS_WHO_PREFIX

Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);

到这里,文章开头所提出问题的答案已经呼之欲出了,下面我们再看一下dispatchRequestPermissionsResult方法做了哪些事情,相信大家这个时候已经能猜出来个八九不离十了

7601    private void dispatchRequestPermissionsResult(int requestCode, Intent data) {
7602        mHasCurrentPermissionsRequest = false;
7603        // If the package installer crashed we may have not data - best effort.
7604        String[] permissions = (data != null) ? data.getStringArrayExtra(
7605                PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
7606        final int[] grantResults = (data != null) ? data.getIntArrayExtra(
7607                PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
7608        onRequestPermissionsResult(requestCode, permissions, grantResults);
7609    }

没错,这个方法正是调用了onRequestPermissionsResult,到这里对文章开头提出的问题已经有了答案,虽然在权限请求的时候通过startActivityForResult启动了一个新的Activity,但是因为传入了REQUEST_PERMISSIONS_WHO_PREFIX参数,导致我们不会收到onActiivtyResult回调而是收到了onRequestPermissionsResult回调。

总结一下,平时我们用到的startActivityForResult是不带who参数的重载方法。而上文提到的startActivityForResult是多一个who参数的方法,并且此方法是一个hide方法,通常情况下是调用不到的,两个方法 的签名如下:

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode)
 /**
5264     * @hide
5265     */
5266    @Override
5267    public void startActivityForResult(
5268            String who, Intent intent, int requestCode, @Nullable Bundle options)

使用第一个方法我们一般会收到onActivityResult回调,而第二个方法会根据who的值不同 走不同的回调,具体参见上面贴出的dispatchActivityResult源码。

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

推荐阅读更多精彩内容