美文网首页
从动态权限请求再看startActivityForResult

从动态权限请求再看startActivityForResult

作者: 岁月神偷_4676 | 来源:发表于2021-05-20 14:13 被阅读0次

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

    相关文章

      网友评论

          本文标题:从动态权限请求再看startActivityForResult

          本文链接:https://www.haomeiwen.com/subject/ynzujltx.html