寫過動態(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為什么沒有被調用呢?想要弄清楚這個問題,必須先弄清楚另外兩個問題:
- Activity 在 finish之后是如何將setResult的結果傳遞到前一個Activity中去的也即setResult中的resultData去向如何?
- 原來的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()完成的,時序如下圖所示:

在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源碼。