写过动态权限请求代码的小伙伴一定知道,请求动态权限会导致当前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为什么没有被调用呢?想要弄清楚这个问题,必须先弄清楚另外两个问题:
- 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()完成的,时序如下图所示:
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源码。
网友评论