從動態(tài)權限請求再看startActivityForResult

寫過動態(tài)權限請求代碼的小伙伴一定知道,請求動態(tài)權限會導致當前Activity再次調用onResume,究其原因,是因為在請求權限的時候啟動了一個新的Activity導致當前Activity被暫停,當請求權限的窗口退出后,當前Activity又重新resume??梢钥匆幌聄equestPermissions()的實現:

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這個方法,可是在動態(tài)權限請求的時候這個方法并沒有被回調!這是什么原因呢?難道是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源碼。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容