概述

Android安全架构规定:默认情况下,任何应用都没有权限执行对其他应用、操作系统或用户有不利影响的任何操作。这包括读写用户的私有数据(如联系人或电子邮件等)、读写其他应用的文件、执行网络访问、使设备保持唤醒状态等等。
如果要使用这些受保护的设备功能,首先要在应用的清单文件(AndroidManifest.xml)中添加一个或多个 <uses-permission>标记:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.android.app.myapp" ><uses-permission android:name="android.permission.RECEIVE_SMS" />...
</manifest>

如果您的应用在其清单中列出正常权限(即不会对用户隐私或设备操作造成很大风险的权限),系统会自动授予这些权限。如果您的应用在其清单中列出危险权限(即可能影响用户隐私或设备正常操作的权限),系统会要求用户明确授予这些权限。Android 发出权限请求的方式取决于系统版本:

  • 如果设备运行的是Android 6.0(Marshmallow,API 23)或更高版本,并且应用的targetSdkVersion是23或更高版本,则应用将在运行时向用户请求权限(Runtime Permissions)。用户可随时撤销权限,因此应用每次运行时都应该检查自身是否具备所需的权限。
  • 如果设备运行的是Android 5.1(LOLLIPOP_MR1,API 22)或更低版本,并且应用的targetSdkVersion是22或更低版本,则系统在用户安装应用时就要求用户授予权限。如果更新应用时又新增了权限,系统会在用户更新应用时要求授予该权限。用户一旦安装应用,他们撤销权限的唯一方式是卸载应用。

通常,权限失效会导致SecurityException被扔回应用。但不能保证每个地方都是这样。例如,sendBroadcast(Intent)方法在数据传递到每个接收者时会检查权限,在方法调用返回后,即使权限失效,您也不会收到异常。但在几乎所有情况下,权限失效都会打印到系统日志。

注意:从Android 6.0(Marshmallow,API 23)开始,用户可以在任何时候撤销应用的某个权限,即使应用的targetSdkVersion小于23。因此你一定要好好地测试一下你的应用以保证在请求授权失败时应用表现良好,无论你的应用的API等级是多少。

系统权限分为几个保护级别。需要了解的两个最重要保护级别是正常权限危险权限

  • 正常权限(Normal permissions):正常权限涵盖了应用需要访问其沙盒外部的数据或资源但对用户隐私或其他应用操作风险很小的区域。例如,设置时区的权限就是正常权限。如果应用声明其需要正常权限,系统会自动向应用授予该权限。

    ACCESS_LOCATION_EXTRA_COMMANDS
    ACCESS_NETWORK_STATE
    ACCESS_NOTIFICATION_POLICY
    ACCESS_WIFI_STATE
    BLUETOOTH
    BLUETOOTH_ADMIN
    BROADCAST_STICKY
    CHANGE_NETWORK_STATE
    CHANGE_WIFI_MULTICAST_STATE
    CHANGE_WIFI_STATE
    DISABLE_KEYGUARD
    EXPAND_STATUS_BAR
    GET_PACKAGE_SIZE
    INSTALL_SHORTCUT
    INTERNET
    KILL_BACKGROUND_PROCESSES
    MODIFY_AUDIO_SETTINGS
    NFC
    READ_SYNC_SETTINGS
    READ_SYNC_STATS
    RECEIVE_BOOT_COMPLETED
    REORDER_TASKS
    REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
    REQUEST_INSTALL_PACKAGES
    SET_ALARM
    SET_TIME_ZONE
    SET_WALLPAPER
    SET_WALLPAPER_HINTS
    TRANSMIT_IR
    UNINSTALL_SHORTCUT
    USE_FINGERPRINT
    VIBRATE
    WAKE_LOCK
    WRITE_SYNC_SETTINGS

  • 危险权限(Dangerous permissions):危险权限涵盖了应用需要那些涉及用户隐私信息的数据或资源或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如,读取用户的联系人的权限就属于危险权限。如果应用声明其需要危险权限,则用户必须明确向应用授予该权限。
    为了更好地对权限进行管理和描述,将权限进行分组,所有危险的Android系统权限都属于权限组。
    如果设备运行的是Android 6.0(Marshmallow,API 23)或更高版本,并且应用的targetSdkVersion是23或更高版本,则当用户请求危险权限时系统会发生以下行为:

    • 如果应用请求一个已经在其清单文件中列出的危险权限,并且应用当前没有拥有该权限组的任何权限,那么系统就会向用户显示一个对话框询问用户是否授权,该对话框的描述应用想要访问的权限组而不是组内的特定权限。例如,如果一个应用请求READ_CONTACTS权限,系统会弹出对话框告知用户应用需要访问设备的联系人,如果用户允许授权,那么系统将授予应用所需的权限。
    • 如果应用请求一个已经在其清单文件中列出的危险权限,并且应用当前已经拥有了该权限组的其它危险权限,系统会立即授予该权限而不需要通知用户。例如,如果一个应用之前已经请求过并已经被授予了READ_CONTACTS权限,那么之后它请求WRITE_CONTACTS时系统将立即授予该权限。

    如果设备运行的是Android 5.1(LOLLIPOP_MR1,API 22)或更低版本,并且应用的targetSdkVersion是22或更低版本,则系统会在安装时要求用户授予权限。再次强调,系统只告诉用户应用需要的权限组,而不告知具体权限。
    任何权限都可以属于一个权限组,包括正常权限和应用自定义的权限。但权限组仅当权限危险时才影响用户体验。可以忽略正常权限的权限组。

    Permission Group Permissions
    CALENDAR
    • READ_CALENDAR
    • WRITE_CALENDAR
    CAMERA
    • CAMERA
    CONTACTS
    • READ_CONTACTS
    • WRITE_CONTACTS
    • GET_ACCOUNTS
    LOCATION
    • ACCESS_FINE_LOCATION
    • ACCESS_COARSE_LOCATION
    MICROPHONE
    • RECORD_AUDIO
    PHONE
    • READ_PHONE_STATE
    • CALL_PHONE
    • READ_CALL_LOG
    • WRITE_CALL_LOG
    • ADD_VOICEMAIL
    • USE_SIP
    • PROCESS_OUTGOING_CALLS
    SENSORS
    • BODY_SENSORS
    SMS
    • SEND_SMS
    • RECEIVE_SMS
    • READ_SMS
    • RECEIVE_WAP_PUSH
    • RECEIVE_MMS
    STORAGE
    • READ_EXTERNAL_STORAGE
    • WRITE_EXTERNAL_STORAGE

运行时权限(RuntimePermissions )

从Android 6.0(Marshmallow,API 23)开始,用户就可以在应用运行时而不是安装时授予应用权限了。由于用户不需要在安装和升级应用时给应用授权,应用的安装升级也更加流畅了。
也就是说,第一次请求权限时,系统会向用户弹出一个权限请求对话框,如:

如果用户点击了拒绝,那么之后shouldShowRequestPermissionRationale()方法将返回true,并且再次请求该权限时会出现"不再询问"复选框(Don't ask again):

如果没有勾选"不再询问"并点击了拒绝,那么再次请求该权限时系统依然会向用户显示权限请求对话框。如果此时勾选了"不再询问"并点击了拒绝,那么之后shouldShowRequestPermissionRationale()方法将返回false并且请求该权限时系统将不会向用户显示权限请求对话框(系统会立即拒绝该权限请求并调用你的onRequestPermissionsResult() 回调方法并传递PERMISSION_DENIED):

为了简化版本判断等代码逻辑,我们可以直接使用Android支持库中的API来检查和请求权限。

检查权限

如果你的应用需要一个危险权限,你每次执行该权限下的操作时都必须检查你是否被授予了该权限。用户可以随便撤销某个权限,所以即使应用昨天还能使用相机,那也没法保证今天也有权使用。
为了检查当前是否拥有该权限,可以调用ContextCompat.checkSelfPermission()方法,例如,下面代码就是检查该Activity是否有权写calendar日程:

// 假设thisActivity就是当前activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.WRITE_CALENDAR);

如果应用当前拥有该权限,该方法返回PackageManager.PERMISSION_GRANTED,应用可以继续接下来的操作。如果应用当前没有该权限,该方法返回PackageManager.PERMISSION_DENIED,应用就必须明确地向用户请求授权。

请求权限

解释为什么应用需要这些权限

一些情况下,你可能想要帮助用户理解为什么你的应用需要某个权限。例如,如果一个用户安装了一个拍照App,那么用户不会因App请求相机权限而惊讶,但用户可能不会理解为什么App还要访问他的位置或联系人。因此,在请求权限之前,你应该考虑给用户一个解释。但要注意,如果你解释的太多用户可能会很厌烦并卸载应用。
一种情景,就是用户已经关闭了权限的请求。如果用户始终尝试使用该权限下的功能,但是每次都关闭该权限请求,这就表明用户不理解为什么app需要这个权限才能工作,在这种情况下,向用户弹窗解释一下可能是个很好的主意:

为了有助于在合适场景给用户一个解释,Android提供了一个工具方法shouldShowRequestPermissionRationale(),如果应用之前请求过该权限但用户拒绝了该方法就会返回true

注意:如果用户之前拒绝了权限请求并且勾选了权限请求对话框的”不再询问”,该方法会返回false,如果设备策略禁止该应用获得该权限也会返回false

请求你需要的权限

如果你的应用没有获得所需的权限,应用就必须调用requestPermissions()方法请求相应的权限。传过去权限列表和一个整型权限请求码,该方法是异步的:在用户操作完请求授权对话框后系统会调用应用的回调方法传给结果和之前requestPermissions()中的权限请求码:

// 这里的thisActivity就是当前activity
if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED) {// 我们应该给用户个解释?if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,Manifest.permission.READ_CONTACTS)) {// 向用户显示一个解释,要以异步非阻塞的方式// 该线程将等待用户响应!等用户看完解释后再继续尝试请求权限} else {// 不需要向用户解释了,我们可以直接请求该权限ActivityCompat.requestPermissions(thisActivity,new String[]{Manifest.permission.READ_CONTACTS},MY_PERMISSIONS_REQUEST_READ_CONTACTS);// MY_PERMISSIONS_REQUEST_READ_CONTACTS 是应用自定义的一个int常量,用户唯一标识一个权限请求以便回调时进行判断}
}

注意:当你的应用调用requestPermissions()方法时,系统会向用户展示一个标准对话框,你的应用不能修改也不能自定义这个对话框,如果你需要给用户一些额外的信息和解释你就需要在调用requestPermissions()之前像上面一样" 解释为什么应用需要这些权限"。

处理权限请求的响应结果

当你的应用请求权限时,系统会向用户显示一个权限请求对话框。当用户响应时,系统会调用应用的onRequestPermissionsResult()方法并把用户响应传给它。这就要求你的应用重写该方法以判断权限是否被授予了。这个回调也会接受之前调用requestPermissions()方法时的请求码。例如如果你的应用请求READ_CONTACTS了权限那么回调方法可能会是这样:

@Override
public void onRequestPermissionsResult(int requestCode,String permissions[], int[] grantResults) {switch (requestCode) {case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {// 如果请求被取消了,那么结果数组就是空的if (grantResults.length > 0&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 权限被授予了,你可以随便访问联系人了} else {// 权限请求被拒绝了,不能继续依赖该权限的相关操作了}return;}// case其它权限请求...}
}

当系统请求用户授予权限时,用户可以选择告诉系统不要再请求该权限了。这种情况下,无论应用在什么时候使用requestPermissions()再次请求该权限,系统都会立即拒绝该请求。系统会调用你的onRequestPermissionsResult() 回调方法并传递PERMISSION_DENIED,如果用户再次明确拒绝了你的请求,系统将采用相同方式操作。这意味着当你调用requestPermissions()时,你不能假设已经发生了与用户的任何直接交互。

最佳实践

为了让应用有更好的用户体验,动态请求权限有两个场景要特别注意,一个就是用户多次拒绝权限申请,一个就是用户点击了拒绝并勾选了不再询问或者设备策略禁止该应用获得该权限。这两种情况下,如果用户还想使用该权限下的功能就需要你的应用自己定制用户引导了。
第一种情况,是因为用户不理解你为什么要申请这个权限才拒绝你的权限申请,所以这种情况下,你可以在请求权限(弹出权限请求对话框)之前向用户显示一个对话框向用户解释为什么你的应用需要这个权限:

当用户点击确定后再请求权限(弹出权限请求对话框)用户就很容易授予该权限了。至于什么时候显示该对话框上面的"解释为什么应用需要这些权限"已经说得很清楚的,即当shouldShowRequestPermissionRationale()返回true时显示这个对话框:

public class RationaleDialogFragment extends DialogFragment {public static final String TAG = "RationaleDialogFragment";private static final String ARG_POSITIVE_BUTTON = "positiveButton";private static final String ARG_NEGATIVE_BUTTON = "negativeButton";private static final String ARG_RATIONALE_MESSAGE = "rationaleMsg";private static final String ARG_REQUEST_CODE = "requestCode";private static final String ARG_PERMISSIONS = "permissions";private int positiveButton;private int negativeButton;private String rationaleMsg;private int requestCode;private String[] permissions;private PermissionCallbacks mPermissionCallbacks;public RationaleDialogFragment() {}public static RationaleDialogFragment newInstance(@StringRes int positiveButton, @StringRes int negativeButton,@NonNull String rationaleMsg, int requestCode, @NonNull String[] permissions) {RationaleDialogFragment fragment = new RationaleDialogFragment();Bundle args = new Bundle();args.putInt(ARG_POSITIVE_BUTTON, positiveButton);args.putInt(ARG_NEGATIVE_BUTTON, negativeButton);args.putString(ARG_RATIONALE_MESSAGE, rationaleMsg);args.putInt(ARG_REQUEST_CODE, requestCode);args.putStringArray(ARG_PERMISSIONS, permissions);fragment.setArguments(args);return fragment;}@Overridepublic void onAttach(Context context) {super.onAttach(context);boolean isAtLeastJellyBeanMR1 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;if (isAtLeastJellyBeanMR1&& getParentFragment() != null&& getParentFragment() instanceof PermissionCallbacks) {mPermissionCallbacks = (PermissionCallbacks) getParentFragment();} else if (context instanceof PermissionCallbacks) {mPermissionCallbacks = (PermissionCallbacks) context;} else {throw new RuntimeException(context.toString()+ " must implement PermissionCallbacks");}}@Overridepublic void onDetach() {super.onDetach();mPermissionCallbacks = null;}@NonNull@Overridepublic Dialog onCreateDialog(Bundle savedInstanceState) {if (getArguments() != null) {positiveButton = getArguments().getInt(ARG_POSITIVE_BUTTON);negativeButton = getArguments().getInt(ARG_NEGATIVE_BUTTON);rationaleMsg = getArguments().getString(ARG_RATIONALE_MESSAGE);requestCode = getArguments().getInt(ARG_REQUEST_CODE);permissions = getArguments().getStringArray(ARG_PERMISSIONS);}setCancelable(false);AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());builder.setCancelable(false).setPositiveButton(positiveButton, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {Object host;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {host = getParentFragment() != null ?getParentFragment() :getActivity();} else {host = getActivity();}if (host instanceof Fragment) {((Fragment) host).requestPermissions(permissions, requestCode);} else if (host instanceof FragmentActivity) {ActivityCompat.requestPermissions((FragmentActivity) host, permissions, requestCode);}}}).setNegativeButton(negativeButton, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {if (mPermissionCallbacks != null) {mPermissionCallbacks.onPermissionsDenied(requestCode,Arrays.asList(permissions));}}}).setMessage(rationaleMsg);return builder.create();}public interface PermissionCallbacks extends ActivityCompat.OnRequestPermissionsResultCallback {void onPermissionsGranted(int requestCode, List<String> perms);void onPermissionsDenied(int requestCode, List<String> perms);}}
public class MainActivity extends AppCompatActivity implements RationaleDialogFragment.PermissionCallbacks {public static final String TAG = "MainActivity";private static final int RC_CAMERA = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);Log.i(TAG, "onRequestPermissionsResult:" + requestCode + "," + Arrays.toString(permissions) + "," + Arrays.toString(grantResults));switch (requestCode) {case RC_CAMERA: {// 如果请求被取消了,那么结果数组就是空的if (grantResults.length > 0&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 权限被授予了showCameraPreview();}return;}}}public void showCamera(View view) {if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED) {// 我们应该给用户个解释?if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.CAMERA)) {// 向用户显示一个解释,要以异步非阻塞的方式// 该线程将等待用户响应!等用户看完解释后再继续尝试请求权限RationaleDialogFragment.newInstance(android.R.string.ok, android.R.string.cancel,getString(R.string.rationale_camera), RC_CAMERA,new String[]{Manifest.permission.CAMERA}).show(getSupportFragmentManager(), RationaleDialogFragment.TAG);} else {// 不需要向用户解释了,我们可以直接请求该权限ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},RC_CAMERA);}} else {showCameraPreview();}}private void showCameraPreview() {getSupportFragmentManager().beginTransaction().replace(R.id.content_fragment, CameraPreviewFragment.newInstance()).addToBackStack("camera").commit();}@Overridepublic void onPermissionsGranted(int requestCode, List<String> perms) {Log.i(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());}@Overridepublic void onPermissionsDenied(int requestCode, List<String> perms) {Log.i(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());}}

第二种情况,系统已经无法在申请权限时向用户弹出权限申请框,你可以显示一个对话框引导用户跳转到"设置>应用>[你的应用]"页面让用户手动授予该权限:

当用户点击确定后跳转到应用详情页让用户手动授予相应权限:

至于什么时候显示该对话框也很明显,即该权限申请被永久拒绝(可能是因为用户之前拒绝了该权限申请并勾选了不再询问,也可能是设备策略禁止了该应用的授权请求)时显示该对话框,也就是当系统调用你的onRequestPermissionsResult() 回调方法并传递PERMISSION_DENIEDshouldShowRequestPermissionRationale返回false时显示该对话框:

public class AppSettingsDialogFragment extends DialogFragment {public static final String TAG = "AppSettingsDialogFragment";public static final int DEFAULT_SETTINGS_REQ_CODE = 16061;private static final String ARG_POSITIVE_BUTTON = "positiveButton";private static final String ARG_NEGATIVE_BUTTON = "negativeButton";private static final String ARG_RATIONALE_TITLE = "rationaleTitle";private static final String ARG_RATIONALE_MESSAGE = "rationaleMsg";private static final String ARG_REQUEST_CODE = "requestCode";private int positiveButton;private int negativeButton;private String rationaleTitle;private String rationaleMsg;private int requestCode;public AppSettingsDialogFragment() {}public static AppSettingsDialogFragment newInstance(@StringRes int positiveButton, @StringRes int negativeButton,@NonNull String rationaleTitle, @NonNull String rationaleMsg,int requestCode) {AppSettingsDialogFragment fragment = new AppSettingsDialogFragment();Bundle args = new Bundle();args.putInt(ARG_POSITIVE_BUTTON, positiveButton);args.putInt(ARG_NEGATIVE_BUTTON, negativeButton);args.putString(ARG_RATIONALE_TITLE, rationaleTitle);args.putString(ARG_RATIONALE_MESSAGE, rationaleMsg);args.putInt(ARG_REQUEST_CODE, requestCode);fragment.setArguments(args);return fragment;}@NonNull@Overridepublic Dialog onCreateDialog(Bundle savedInstanceState) {if (getArguments() != null) {positiveButton = getArguments().getInt(ARG_POSITIVE_BUTTON);negativeButton = getArguments().getInt(ARG_NEGATIVE_BUTTON);rationaleTitle = getArguments().getString(ARG_RATIONALE_TITLE);rationaleMsg = getArguments().getString(ARG_RATIONALE_MESSAGE);requestCode = getArguments().getInt(ARG_REQUEST_CODE);}setCancelable(false);AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());builder.setCancelable(false).setTitle(rationaleTitle).setMessage(rationaleMsg).setPositiveButton(positiveButton, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {if (getContext() instanceof Activity) {Activity activity = (Activity) getContext();Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);Uri uri = Uri.fromParts("package", activity.getPackageName(), null);intent.setData(uri);activity.startActivityForResult(intent, requestCode);}}}).setNegativeButton(negativeButton, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {}});return builder.create();}}
public class MainActivity extends AppCompatActivity implements RationaleDialogFragment.PermissionCallbacks {public static final String TAG = "MainActivity";private static final int RC_CAMERA = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);Log.i(TAG, "onRequestPermissionsResult:" + requestCode + "," + Arrays.toString(permissions) + "," + Arrays.toString(grantResults));switch (requestCode) {case RC_CAMERA: {// 如果请求被取消了,那么结果数组就是空的if (grantResults.length > 0) {if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 权限被授予了showCameraPreview();} else {// 权限被拒绝了if (!ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.CAMERA)) {// 向用户显示一个对话框引导用户跳转到"设置>应用>[你的应用]"页面// 让用户手动授予该权限AppSettingsDialogFragment.newInstance(android.R.string.ok, android.R.string.cancel,getString(R.string.permissions_required),getString(R.string.rationale_ask_again),AppSettingsDialogFragment.DEFAULT_SETTINGS_REQ_CODE).show(getSupportFragmentManager(), AppSettingsDialogFragment.TAG);}}}return;}}}public void showCamera(View view) {if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED) {// 我们应该给用户个解释?if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.CAMERA)) {// 向用户显示一个解释,要以异步非阻塞的方式// 该线程将等待用户响应!等用户看完解释后再继续尝试请求权限RationaleDialogFragment.newInstance(android.R.string.ok, android.R.string.cancel,getString(R.string.rationale_camera), RC_CAMERA,new String[]{Manifest.permission.CAMERA}).show(getSupportFragmentManager(), RationaleDialogFragment.TAG);} else {// 不需要向用户解释了,我们可以直接请求该权限ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},RC_CAMERA);}} else {showCameraPreview();}}private void showCameraPreview() {getSupportFragmentManager().beginTransaction().replace(R.id.content_fragment, CameraPreviewFragment.newInstance()).addToBackStack("camera").commit();}@Overridepublic void onPermissionsGranted(int requestCode, List<String> perms) {Log.i(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());}@Overridepublic void onPermissionsDenied(int requestCode, List<String> perms) {Log.i(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == AppSettingsDialogFragment.DEFAULT_SETTINGS_REQ_CODE) {Log.i(TAG, "onActivityResult:" + requestCode + ":" + data);}}
}

当然,Framework和Support Library中的Activity/Fragment的代码实现细节会有些不一样,针对多个权限的同时请求也需要额外的判断和处理,可以写一些Helper类封装一下核心逻辑,这一点要特别推荐EasyPermissions这个Lib,封装的也比较优雅。

最后,是一些建议:

  • 尽可能少的申请权限。避免在清单文件中列出不需要的权限。
  • 考虑使用Intent。如果你的应用的某些功能完全可以由系统App或其他第三方App完成,而不需要你精确地控制,你可以考虑直接发一个Intent调起其他应用完成,而你只需在onActivityResult()方法中接收结果就可以了。
  • 在各种条件下测试你的App。虽然从Android 6.0(Marshmallow,API 23)开始才会支持运行时权限管理,但你还是要在各个版本和各个ROM中测试你的App以保证在未授权时应用依然能够正确运行。

References

  • Develop>API Guides>Introduction>System Permissions
  • Develop>Training>Getting Started>Working with System Permissions

Android权限管理详解相关推荐

  1. Linux账号和权限管理详解(超详细示例操作)!

    Linux账号和权限管理详解 一.用户账号和组账号概述 1.1 Linux基于用户身份对资源访问进行控制 1.2 用户账号 1.3 组账号 二.用户账号文件 2.1 用户账号文件 /etc/passw ...

  2. kubernetes(k8s)之rbac权限管理详解

    kubernetes(k8s)之rbac权限管理详解 RBAC简介 RBAC(Role-Based Access Control) [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 ...

  3. Shiro权限管理详解(授权和注解开发)【面试点】

    Shiro权限管理详解 1. 权限管理 1.1什么是权限管理 1.2用户身份认证 1.2.1 概念 1.2.2 用户名密码身份认证流程 1.2.3 关键对象 1.3 授权 1.3.1 概念 1.3.2 ...

  4. 麦克风android权限管理,android权限处理详解

    写在前面 对于android 6.0来说,增加了权限的管理,能够更好的保护用户的隐私,当用户需要某权限时,才动态的去申请.用户也可以在应用权限管理里面关闭和打开.为了方便以后使用,这里对权限使用相关做 ...

  5. Linux—目录文件属性和权限管理详解

    关注微信公众号:CodingTechWork,一起学习进步. 引言   Linux中了解用户和用户组概念后,我们就需要对文件目录进行赋权操作,以便于文件访问权限的管控,这就涉及到文件的属性,那用户和用 ...

  6. rbac权限管理5张表_PHP之常用的RBAC权限管理详解

    文章正文 在说权限管理前,应该先知道权限管理要有哪些功能: (1).用户只能访问,指定的控制器,指定的方法 (2).用户可以存在于多个用户组里 (3).用户组可以选择,指定的控制器,指定的方法 (4) ...

  7. mysql服务器权限说明,MySQL用户权限管理详解

    用户权限管理主要有以下作用: 1. 可以限制用户访问哪些库.哪些表 2. 可以限制用户对哪些表执行SELECT.CREATE.DELETE.DELETE.ALTER等操作 3. 可以限制用户登录的IP ...

  8. mysql用户_MySQL用户权限管理详解

    用户权限管理主要有以下作用: 1. 可以限制用户访问哪些库.哪些表 2. 可以限制用户对哪些表执行SELECT.CREATE.DELETE.DELETE.ALTER等操作 3. 可以限制用户登录的IP ...

  9. linux权限管理详解

    用户权限管理     文件目录权限         访问权限             可读(read) -->r             可写(write) -->w            ...

最新文章

  1. 数字化校园passport
  2. android应用程序的混淆打包
  3. mysql对执行结果进行html格式的输出?输出html格式?
  4. 2017-06-18 前端日报
  5. unity 模型渐变消失_Unity 雨水滴到屏幕效果
  6. jasmine.objectContaining 的单步调试
  7. 双绞线直连法如何才能使两台电脑实现共享
  8. 红米k30 android版本,Redmi K30 Pro 推送 MIUI 12.2.1 稳定版:为安卓跨版本升级
  9. [转]HTTP协议简单原理资源
  10. mswinsck.ocx 一个文件丢失或无效_AutoCAD文件修复的10种方法
  11. 英文名称(缩写)汇总
  12. 7.26 1004度度熊的午饭时光 百度之星题解
  13. Anaconda如何安装PyTorch?
  14. 快速复制一个网站的前端代码的工具-仿站工具
  15. EDA技术及应用实验2 h_adder程序
  16. java 裁剪 pdf_java拆分pdf文档
  17. android中正则表达式截取html中的video标签
  18. Qt 实现 别踩白块儿。
  19. 快递企业设长租公寓解决住宿 降低快递员流动率
  20. echarts地图闪烁点大小设置

热门文章

  1. 基于ssm老街坊网站、购物网站的实现与设计mysql
  2. 谷歌浏览器提示:您要访问的网站包含恶意软件(解决方案)
  3. 留学生在英文写作中如何巧妙避免重复问题?
  4. 腾讯云使用phpStudy部署网站(附腾讯云优惠券)
  5. 使用U盘为虚拟机安装系统
  6. SpringBoot返回的 JSON 带有斜杠 转义
  7. canvas画板涂鸦动画进度条动画
  8. 保持冷静、继续前行——《白说》读后感
  9. 如何添加JavaScript代码
  10. 成立一家投资管理有限公司的资本是多少?