介绍
在Android开发中,应用程序通常需要获取一些危险权限以便正常运行,例如访问摄像头、存储设备或者获取设备位置。Android系统引入了运行时权限机制,用户可以在应用运行时授予或拒绝这些权限。本文将对动态运行时权限机制进行源码分析,并介绍如何封装动态权限请求,并针对Android 9.0权限改动进行说明。
动态运行时权限机制源码分析
权限申请流程
动态运行时权限的申请流程主要涉及以下几个类:
ActivityCompat
:提供了一些静态方法,用于动态请求权限。FragmentActivity
:扩展自AppCompatActivity
,封装了动态权限请求的逻辑。Fragment
:用于与FragmentActivity
配合完成权限请求的逻辑。
动态运行时权限的申请流程如下:
- 调用
ActivityCompat.requestPermissions()
方法请求权限。 FragmentActivity
将请求转发给具体的Fragment
。Fragment
通过ActivityCompat.requestPermissions()
方法发起权限请求。- 系统显示权限申请弹窗,用户可以授予权限。
- 用户选择授予或者拒绝权限后,系统会回调
Fragment
的onRequestPermissionsResult()
方法。 Fragment
处理权限授予结果并通过回调通知申请权限的Activity
。
权限申请源码分析
我们来看一下ActivityCompat
和Fragment
中动态权限申请的相关源码。
ActivityCompat
在ActivityCompat
中,请求权限的关键方法是void requestPermissions(@NonNull final Activity activity, @NonNull final String[] permissions, final int requestCode)
。该方法用于发起权限请求,并在收到权限授予结果后回调。
源码中的关键部分如下:
public static void requestPermissions(@NonNull final Activity activity, @NonNull final String[] permissions, final int requestCode) {
if (Build.VERSION.SDK_INT >= 23) {
if (activity instanceof RequestPermissionsRequestCodeValidator) {
((RequestPermissionsRequestCodeValidator) activity).validateRequestPermissionsRequestCode(requestCode);
}
activity.requestPermissions(permissions, requestCode);
} else if (activity instanceof OnRequestPermissionsResultCallback) {
HandlerCompat.postDelayed(new Runnable() {
@Override
public void run() {
int[] grantResults = new int[permissions.length];
PackageManager packageManager = activity.getPackageManager();
String packageName = activity.getPackageName();
final int permissionCount = permissions.length;
for (int i = 0; i < permissionCount; i++) {
grantResults[i] = packageManager.checkPermission(permissions[i], packageName);
}
((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}, 0);
}
}
- 在Android 23及以上版本,直接调用
activity.requestPermissions()
方法发起权限请求。 - 在Android 23以下版本,通过
HandlerCompat
延迟一段时间后模拟权限请求结果,并回调OnRequestPermissionsResultCallback
接口。
Fragment
在Fragment
中,请求权限的关键方法是void requestPermissions(@NonNull String[] permissions, int requestCode)
。该方法用于发起权限请求,并在收到权限授予结果后回调。
源码中的关键部分如下:
public void requestPermissions(@NonNull final String[] permissions, final int requestCode) {
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
ActivityCompat.requestPermissions(mHost, permissions, requestCode);
}
Fragment
将权限请求转发给其所属的FragmentActivity
。
动态权限请求封装
为了简化动态权限请求的过程,我们可以封装一个权限请求的工具类。
PermissionUtils
public class PermissionUtils {
private static final int PERMISSION_REQUEST_CODE = 1;
public static boolean isGranted(Activity activity, String permission) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ActivityCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED;
}
public static void requestPermission(Activity activity, String permission, PermissionCallback callback) {
if (isGranted(activity, permission)) {
callback.onPermissionGranted(permission);
} else {
String[] permissions = {permission};
PermissionFragment.requestPermissions(activity, permissions, PERMISSION_REQUEST_CODE, callback);
}
}
public interface PermissionCallback {
void onPermissionGranted(String permission);
void onPermissionDenied(String permission);
}
}
isGranted()
方法用于判断指定权限是否已经被授予。requestPermission()
方法用于发起权限请求,如果权限已经被授予,则直接回调onPermissionGranted()
方法;否则,通过PermissionFragment
发起权限请求,并在权限回调中根据用户授权状态分别回调onPermissionGranted()
和onPermissionDenied()
方法。
PermissionFragment
public class PermissionFragment extends Fragment {
private static final int PERMISSION_REQUEST_CODE = 1;
private PermissionUtils.PermissionCallback callback;
public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionUtils.PermissionCallback callback) {
PermissionFragment fragment = new PermissionFragment();
fragment.callback = callback;
FragmentTransaction transaction = activity.getFragmentManager().beginTransaction();
transaction.add(fragment, null);
transaction.commit();
fragment.requestPermissions(permissions, PERMISSION_REQUEST_CODE);
}
...
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PERMISSION_REQUEST_CODE) {
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
callback.onPermissionGranted(permissions[i]);
} else {
callback.onPermissionDenied(permissions[i]);
}
}
}
}
}
requestPermissions()
方法用于发起权限请求,并通过FragmentTransaction
将PermissionFragment
添加到Activity
。onRequestPermissionsResult()
方法用于处理权限授予结果,并根据结果回调PermissionUtils.PermissionCallback
接口。
Android 9.0权限改动
在Android 9.0及以上版本,动态权限申请机制有所改动。引入了一种新的权限申请模式:分区存储权限。分区存储权限将应用的文件、媒体和其他大部分内容限制到其专用存储空间中。
注意:分区存储权限不会受到运行时动态权限的影响。即使应用在AndroidManifest.xml中声明了某个权限,但没有向用户请求该权限或用户拒绝了该权限,应用仍然可以通过分区存储访问许多受保护的存储目录和文件。
为了适应Android 9.0权限改动,我们需要修改封装的权限请求工具类。
PermissionUtils
public class PermissionUtils {
...
public static boolean isGranted(Activity activity, String permission) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return Environment.isExternalStorageManager() || (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ActivityCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED);
} else {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ActivityCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED;
}
}
public static void requestPermission(final Activity activity, final String permission, final PermissionCallback callback) {
if (isGranted(activity, permission)) {
callback.onPermissionGranted(permission);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Manifest.permission.MANAGE_EXTERNAL_STORAGE.equals(permission)
&& !Environment.isExternalStorageManager()) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.permission_request_title);
builder.setMessage(R.string.permission_request_message);
builder.setPositiveButton(R.string.settings, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:" + activity.getPackageName()));
activity.startActivity(intent);
}
});
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
callback.onPermissionDenied(permission);
}
});
builder.setCancelable(false);
builder.show();
} else {
String[] permissions = {permission};
PermissionFragment.requestPermissions(activity, permissions, PERMISSION_REQUEST_CODE, callback);
}
}
}
...
}
isGranted()
方法在Android 9.0及以上版本添加了对分区存储权限的支持。requestPermission()
方法在Android 9.0及以上版本检查是否需要请求分区存储权限,并在需要请求时,显示对话框引导用户前往系统设置中打开分区存储权限。
总结
本文对Android动态运行时权限机制进行了源码分析,并封装了动态权限请求工具类。同时,由于Android 9.0引入了分区存储权限,对封装的权限请求工具类进行了相应的修改。通过封装工具类,我们可以更加方便地进行动态权限的申请和处理。
希望本文能帮助读者理解Android动态运行时权限机制,并在实际开发中能够灵活运用。如果大家对于源码分析或者权限改动有任何疑问或建议,欢迎大家留言讨论。
本文来自极简博客,作者:破碎星辰,转载请注明原文链接:Android: 动态运行时权限(危险权限)源码分析、封装、及9.0权限改动