Android: 动态运行时权限(危险权限)源码分析、封装、及9.0权限改动

破碎星辰 2024-05-30 ⋅ 45 阅读

介绍

在Android开发中,应用程序通常需要获取一些危险权限以便正常运行,例如访问摄像头、存储设备或者获取设备位置。Android系统引入了运行时权限机制,用户可以在应用运行时授予或拒绝这些权限。本文将对动态运行时权限机制进行源码分析,并介绍如何封装动态权限请求,并针对Android 9.0权限改动进行说明。

动态运行时权限机制源码分析

权限申请流程

动态运行时权限的申请流程主要涉及以下几个类:

  • ActivityCompat:提供了一些静态方法,用于动态请求权限。
  • FragmentActivity:扩展自AppCompatActivity,封装了动态权限请求的逻辑。
  • Fragment:用于与FragmentActivity配合完成权限请求的逻辑。

动态运行时权限的申请流程如下:

  1. 调用ActivityCompat.requestPermissions()方法请求权限。
  2. FragmentActivity将请求转发给具体的Fragment
  3. Fragment通过ActivityCompat.requestPermissions()方法发起权限请求。
  4. 系统显示权限申请弹窗,用户可以授予权限。
  5. 用户选择授予或者拒绝权限后,系统会回调FragmentonRequestPermissionsResult()方法。
  6. Fragment处理权限授予结果并通过回调通知申请权限的Activity

权限申请源码分析

我们来看一下ActivityCompatFragment中动态权限申请的相关源码。

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);
    }
}
  1. isGranted()方法用于判断指定权限是否已经被授予。
  2. 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]);
                }
            }
        }
    }
}
  1. requestPermissions()方法用于发起权限请求,并通过FragmentTransactionPermissionFragment添加到Activity
  2. 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);
            }
        }
    }

    ...
}
  1. isGranted()方法在Android 9.0及以上版本添加了对分区存储权限的支持。
  2. requestPermission()方法在Android 9.0及以上版本检查是否需要请求分区存储权限,并在需要请求时,显示对话框引导用户前往系统设置中打开分区存储权限。

总结

本文对Android动态运行时权限机制进行了源码分析,并封装了动态权限请求工具类。同时,由于Android 9.0引入了分区存储权限,对封装的权限请求工具类进行了相应的修改。通过封装工具类,我们可以更加方便地进行动态权限的申请和处理。

希望本文能帮助读者理解Android动态运行时权限机制,并在实际开发中能够灵活运用。如果大家对于源码分析或者权限改动有任何疑问或建议,欢迎大家留言讨论。


全部评论: 0

    我有话说: