从动态权限请求再看startActivityForResult

2021-05-20  本文已影响0人  岁月神偷_4676

写过动态权限请求代码的小伙伴一定知道,请求动态权限会导致当前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源码。

上一篇下一篇

猜你喜欢

热点阅读