android 6.0 Marshmallow版本之后,系统不会在软件安装的时候就赋予该app所有其申请的权限,对于一些危险级别的权限,app需要在运行时一个一个询问用户授予权限。

From android M permissions will be granted at runtime. User consent is not required for Normal permissions but for Dangerous permissions user is required to grant the permission to application.

Normal permissions:
https://developer.android.com/guide/topics/security/normal-permissions.html

Dangerous permissions:
Dangerous permissions cover areas where the app wants data or resources that involve the user’s private information https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous

Normal permissions

android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT

Dangerous permissions(覆盖面是APP想要的那些用户的隐私信息)

  1. android.permission-group.CALENDAR
  • android.permission.READ_CALENDAR
  • android.permission.WRITE_CALENDAR
  1. android.permission-group.CAMERA
  • android.permission.CAMERA
  1. android.permission-group.CONTACTS
  • android.permission.READ_CONTACTS
  • android.permission.WRITE_CONTACTS
  • android.permission.GET_ACCOUNTS
  1. android.permission-group.LOCATION
  • android.permission.ACCESS_FINE_LOCATION
  • android.permission.ACCESS_COARSE_LOCATION
  1. android.permission-group.MICROPHONE
  • android.permission.RECORD_AUDIO
  1. android.permission-group.PHONE
  • android.permission.READ_PHONE_STATE
  • android.permission.CALL_PHONE
  • android.permission.READ_CALL_LOG
  • android.permission.ADD_VOICEMAIL
  • android.permission.WRITE_CALL_LOG
  • android.permission.USE_SIP
  • android.permission.PROCESS_OUTGOING_CALLS
  1. android.permission-group.SENSORS
  • android.permission.BODY_SENSORS
  1. android.permission-group.SMS
  • android.permission.SEND_SMS
  • android.permission.RECEIVE_SMS
  • android.permission.READ_SMS
  • android.permission.RECEIVE_WAP_PUSH
  • android.permission.RECEIVE_MMS
  1. android.permission-group.STORAGE
  • android.permission.READ_EXTERNAL_STORAGE
  • android.permission.WRITE_EXTERNAL_STORAGE

三星手机 打开开发者模式 android studio 扫描不到_html

授权操作说明

只有那些targetSdkVersion 设置为23及以上的应用才会出现异常,在使用危险权限的时候系统必须要获得用户的同意才能使用,要不然应用就会崩溃,出现这个错误java.lang.SecurityException: Permission Denial...所以targetSdkVersion如果没有设置为23版本或者以上,系统还是会使用旧规则:在安装的时候赋予该app所申请的所有权限。

如果以前的老应用申请的权限被用户手动关闭了,不会抛出异常,不会崩溃,只不过调用那些被用户禁止权限的api接口返回值都为null或者0。

在Android M的api中,我们可以通过ContextCompat.checkSelfPermission检测软件是否有某一项权限。如果没有该权限,可以使用ActivityCompat.requestPermissions去请求一组权限。请求完成,会有相对应的回调方法,通知软件用户是否授予了权限。通过在Activity或者Fragment中重写onRequestPermissionsResult方法。如果用户一旦拒绝过某权限的授权。下一次弹框时,用户会有一个“不再提醒(Never ask again)”的选项的来防止app以后继续请求授权。如果这个选项在拒绝授权前被用户勾选了。下次为这个权限请求requestPermissions时,对话框就不弹出来了,系统会直接回调onRequestPermissionsResult函数,回调结果为最后一次用户的选择。所以为了应对这种情况,系统提供了一个shouldShowRequestPermissionRationale()函数,这个函数的作用是帮助开发者找到需要向用户额外解释权限的情况。

应用安装后第一次访问,直接返回false;
第一次请求权限时,用户拒绝了,下一次shouldShowRequestPermissionRationale()返回 true,这时候可以显示一些为什么需要这个权限的说明;
第二次请求权限时,用户拒绝了,并选择了“不再提醒”的选项时:shouldShowRequestPermissionRationale()返回 false;
设备的系统设置中禁止当前应用获取这个权限的授权,shouldShowRequestPermissionRationale()返回false,此时可以弹出dialog,提醒用户该权限的重要性;
ActivityCompat.shouldShowRequestPermissionRationale()在6.0之前版本调用,永远返回false。

项目实战

AndroidManifest.xml添加如下代码

<uses-sdk
        android:minSdkVersion="23"
        android:targetSdkVersion="25" />

<!-- Dangerous Permissions Start -->
<!-- android.permission-group.CALENDAR -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<!-- android.permission-group.CAMERA -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- android.permission-group.CONTACTS -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- android.permission-group.LOCATION -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- android.permission-group.MICROPHONE -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- android.permission-group.PHONE -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.ADD_VOICEMAIL" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<!-- android.permission-group.SENSORS -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<!-- android.permission-group.SMS -->
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
<uses-permission android:name="android.permission.RECEIVE_MMS" />
<!-- android.permission-group.STORAGE -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Dangerous Permissions End -->

DangerousPermissions.java

package com.example.permissionhelperdemo;

import android.Manifest;

/**
 * Dangerous permissions
 * <ol>
 * <li>android.permission-group.CALENDAR
 * <ul>
 * <li>android.permission.READ_CALENDAR</li>
 * <li>android.permission.WRITE_CALENDAR</li>
 * </ul>
 * </li>
 * 
 * <li>android.permission-group.CAMERA
 * <ul>
 * <li>android.permission.CAMERA</li>
 * </ul>
 * </li>
 * 
 * <li>android.permission-group.CONTACTS
 * <ul>
 * <li>android.permission.READ_CONTACTS</li>
 * <li>android.permission.WRITE_CONTACTS</li>
 * <li>android.permission.GET_ACCOUNTS</li>
 * </ul>
 * </li>
 * 
 * <li>android.permission-group.LOCATION
 * <ul>
 * <li>android.permission.ACCESS_FINE_LOCATION</li>
 * <li>android.permission.ACCESS_COARSE_LOCATION</li>
 * </ul>
 * </li>
 * 
 * <li>android.permission-group.MICROPHONE
 * <ul>
 * <li>android.permission.RECORD_AUDIO</li>
 * </ul>
 * </li>
 * 
 * <li>android.permission-group.PHONE
 * <ul>
 * <li>android.permission.READ_PHONE_STATE</li>
 * <li>android.permission.CALL_PHONE</li>
 * <li>android.permission.READ_CALL_LOG</li>
 * <li>android.permission.WRITE_CALL_LOG</li>
 * <li>android.permission.ADO_VOICEMAIL</li>
 * <li>android.permission.USE_SIP</li>
 * <li>android.permission.PROCESS_OUTGOING_CALLS</li>
 * </ul>
 * </li>
 * 
 * <li>android.permission-group.SENSORS
 * <ul>
 * <li>android.permission.BODY_SENSORS</li>
 * </ul>
 * </li>
 * 
 * <li>android.permission-group.SMS
 * <ul>
 * <li>android.permission.SEND_SMS</li>
 * <li>android.permission.RECEIVE_SMS</li>
 * <li>android.permission.READ_SMS</li>
 * <li>android.permission.RECEIVE_WAP_PUSH</li>
 * <li>android.permission.RECEIVE_MMS</li>
 * <li>android.permission.READ_CELL_BROADCASTS</li>
 * </ul>
 * </li>
 * 
 * <li>android.permission-group.STORAGE
 * <ul>
 * <li>android.permission.READ_EXTERNAL_STORAGE</li>
 * <li>android.permission.WRITE_EXTERNAL_STORAGE</li>
 * </ul>
 * </li>
 * 
 * <ol>
 */
public class DangerousPermissions {

    private static final String CALENDAR = Manifest.permission.READ_CALENDAR;
    private static final String CAMERA = Manifest.permission.CAMERA;
    private static final String CONTACTS = Manifest.permission.READ_CONTACTS;
    private static final String LOCATION = Manifest.permission.ACCESS_FINE_LOCATION;
    private static final String MICROPHONE = Manifest.permission.RECORD_AUDIO;
    private static final String PHONE = Manifest.permission.READ_PHONE_STATE;
    private static final String SENSORS = Manifest.permission.BODY_SENSORS;
    private static final String SMS = Manifest.permission.SEND_SMS;
    private static final String STORAGE = Manifest.permission.READ_EXTERNAL_STORAGE;

    public static final String[] PERMISSIONS_IN_DIFFERENT_GROUP = new String[] {
            CALENDAR,
            CAMERA,
            CONTACTS,
            LOCATION,
            MICROPHONE,
            PHONE,
            SENSORS,
            SMS,
            STORAGE,
    };

    public static final String[] PERMISSIONS_IN_SAME_GROUP = new String[] {
        Manifest.permission.READ_CALENDAR,
        Manifest.permission.WRITE_CALENDAR
    };
}

PermissionHelper.java

package com.example.permissionhelperdemo;

import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;

public class PermissionHelper {

    public static final int PERMISSION_REQUEST_CODE = 1;

    /**
     * 如果API小于23就不用检查权限了,获取不到就返回0或null<br>
     * 但如果API大于23,用户未同意便会出现下面错误: java.lang.SecurityException: Permission Denial...
     */
    public boolean needCheckPermissions() {
        boolean result = Build.VERSION.SDK_INT < 23;
        System.out.println("Build.VERSION.SDK_INT < 23 = " + result);
        if (result) {
            return false;
        }
        return true;
    }

    /**
     * checkSelfPermissions函数是否有效仅跟Build.VERSION.SDK_INT有关,与targetVersion无关
     */
    public boolean needCheckPermissions(Context context) {
        boolean result = context.getApplicationInfo().targetSdkVersion < 23;
        System.out.println("context.getApplicationInfo().targetSdkVersion < 23 = " + result);
        if(result) {
            return false;
        }
        return true;
    }

    /**
     * 校验权限<br>
     * 如果API小于23,ContextCompat.checkSelfPermission和ActivityCompat.requestPermissions并不会正常工作,并总是返回0(PERMISSION_GRANTED),即使你是在Android 6.0(API23)上运行
     */
    public boolean checkSelfPermissions(Context context, String... permissions) {
        if (permissions == null || permissions.length == 0) {
            return true;
        }
        for (String permission : permissions) {
            boolean result = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
            System.out.println("ContextCompat.checkSelfPermission " + permission + " -> " + result);
            if (!result) {
                return false;
            }
        }
        return true;
    }

    public void requestPermissions(Activity activity, String... permissions) {
        ActivityCompat.requestPermissions(activity, permissions, PERMISSION_REQUEST_CODE);
    }
}

MainActivity.java

package com.example.permissionhelperdemo;

import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity implements OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button permissionInSameGroupBtn = (Button) findViewById(R.id.permission_in_same_group_btn);
        Button permissionInDifferentGroupBtn = (Button) findViewById(R.id.permission_in_different_group_btn);
        permissionInSameGroupBtn.setOnClickListener(this);
        permissionInDifferentGroupBtn.setOnClickListener(this);
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        for(int i=0; i<permissions.length; i++) {
            boolean result = grantResults[i] == PackageManager.PERMISSION_GRANTED;
            System.out.println("onRequestPermissionsResult " + permissions[i] + " -> " + result);
            boolean shouldShow = shouldShowRequestPermissionRationale(permissions[i]);
            System.out.println("shouldShowRequestPermissionRationale " + permissions[i] + " -> " + shouldShow);
        }
    }

    private void checkPermission(String... permissions) {
        PermissionHelper permissionHelper = new PermissionHelper();
        permissionHelper.needCheckPermissions();
        permissionHelper.needCheckPermissions(this);
        boolean result = permissionHelper.checkSelfPermissions(this, permissions);
        System.out.println("permissionHelper.checkSelfPermissions -> " + result);
        permissionHelper.requestPermissions(this, permissions);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
        case R.id.permission_in_same_group_btn:
            checkPermission(DangerousPermissions.PERMISSIONS_IN_SAME_GROUP);
            break;
        case R.id.permission_in_different_group_btn:
            checkPermission(DangerousPermissions.PERMISSIONS_IN_DIFFERENT_GROUP);
            break;
        }
    }
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.permissionhelperdemo.MainActivity" >

    <Button
        android:id="@+id/permission_in_same_group_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="PERMISSIONS_IN_SAME_GROUP" />

    <Button
        android:id="@+id/permission_in_different_group_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="PERMISSIONS_IN_DIFFERENT_GROUP" />

</LinearLayout>

可能会遇到的问题

如果大家使用eclipse编译项目有可能会报错,这是因为libs/android-support-v4.jar包太旧,可以将appcompat_v7项目的libs/android-support-v4.jar复制到本项目相应目录。

appcompat_v7的目录为 sdk\extras\android\support\v7\appcompat

如果还是不行,请更新SDK。

运行截图

# 测试手机三星SM-A9000,Android 6.01, API 23



三星手机 打开开发者模式 android studio 扫描不到_permission_02

三星手机 打开开发者模式 android studio 扫描不到_permission_03

三星手机 打开开发者模式 android studio 扫描不到_java_04

三星手机 打开开发者模式 android studio 扫描不到_java_05

三星手机 打开开发者模式 android studio 扫描不到_android_06

三星手机 打开开发者模式 android studio 扫描不到_android_07

三星手机 打开开发者模式 android studio 扫描不到_permission_08

三星手机 打开开发者模式 android studio 扫描不到_permission_09

三星手机 打开开发者模式 android studio 扫描不到_html_10


总结

Android在赋予权限的时候采取了一个非常取巧的办法——将相似的权限分为一组,用户只能对组进行授权。一旦授权则意味着允许该组的所有权限行为。

  • checkSelfPermission用户赋予权限后返回true,否则返回false
    该方法只能check单个permission(Manifest.permission.*),不能check权限组(Manifest.permission_group.*)。并且仅在手机API >= 23时有效,与target API 或 Build target API均无关,当手机API < 23时总是返回true。
  • ActivityCompat.requestPermissions与onRequestPermissionsResult(Project Build target API >= 23)配合使用,用来获取用户是否授权的返回结果。并且当手机API < 23时,这2个方法无效。
  • shouldShowRequestPermissionRationale(min API >= 23)在第一次拒绝时返回true;再次拒绝或用户确认赋予权限时,均返回false。

参考链接

补充说明

看了这么多,可能有些人还是不懂这个到底有什么用。

举个实际的例子:

有的手机,比如乐1S,升级到Android 6.0 后,默认安装应用之后是不会赋予任何权限的。这时候我打开该款蓝牙应用就会搜不到任何设备。当然你可以提示用户去“系统设置”打开“位置权限”(Android 6.0之后蓝牙需要位置权限,但蓝牙不属于危险权限,默认会帮你开启)。但是更好的做法是直接在代码中判断是否具有该权限,如果没有弹窗提示用户开启。

另外,还需要注意的一点是,该授权机制虽然可以判断是否具有没有在AndroidManifest.xml中声明的权限(结果肯定是否啦。。。),但是即使调用相关函数,也不能让系统弹窗提示用户授予该权限。