Android系统在6.0以后加强了对应用权限的管控,不能再像以前一样随意通过静态声明而获取终端高危权限了;在18年前后的Android 10更是用户隐私信息保护加码,彻底绝断了一波用户核心敏感信息的获取途径。随着行业对用户隐私的关注越来越强,如何写好权限申请交互也变得十分重要。

常用高危权限

权限名

功能

备注

android.permission.CAMERA

使用相机

用户可能会在您的应用中使用预安装的系统相机应用来拍摄照片。在这种情况下,请勿声明 CAMERA 权限,而是改为调用 ACTION_IMAGE_CAPTURE intent 操作。

用户可能会在您的应用中使用预安装的系统相机应用来录制视频。在这种情况下,请勿声明 CAMERA 权限,而是改为调用 ACTION_VIDEO_CAPTURE intent 操作。

android.permission.ACCESS_FINE_LOCATION

android.permission.ACCESS_COARSE_LOCATION

使用位置信息

android.permission.READ_CALENDAR

android.permission.WRITE_CALENDAR

读写日历数据

android.permission.READ_CONTACTS

android.permission.WRITE_CONTACTS

读写用户联系人数据

android.permission.RECORD_AUDIO

使用麦克风

android.permission.WRITE_EXTERNAL_STORAGE

android.permission.READ_EXTERNAL_STORAGE

读写设备存储

android.permission.READ_PHONE_STATE

获取设备信息

Android 10以后该权限遭禁,无法通过该权限获取用户DeviceId相关数据,不同厂家表现不同,请慎用

高危权限此前共有9组24个权限,现在随着Android的迭代,应该又有增加,故本文仅将常用的列于表中,其余的可以查看Android开发者平台#Manifest.permission中的dangerous权限,有更加明确详尽的解释。

动态请求权限工作流

android动态权限被拒绝 动态权限申请_权限

上图是Android developer平台给出的请求权限工作流
简单描述即:

  1. 在AndroidManifest.xml文件中声明应用需要请求的权限。
    2-3. 设计业务交互流程,等待用户触发需要权限的操作。
  2. 检查用户是否已授予应用所需的运行时权限。如果已授权,那么应用可以继续业务操作。如果没有,请继续执行下一步。
    5-7. 检查是否需要向用户展示请求权限的原因,需要则做个交互,请求应用的运行时权限。
  3. 如果授权,那么应用可以继续业务操作。如果用户拒绝,则请适当降低应用体验,使应用在未获得受该权限保护的信息时也能向用户提供功能。

核心代码

APP获得动态权限的方式有ActivityCompat、Activity、Fragment三种。主要流程都是通过具体requestPermissions方法向系统发起权限请求,弹出权限请求窗体,并根据onRequestPermissionsResult回调结果进行后续业务操作,代码如下:

ActivityCompat方式
public class ActivityCompatPermissionTestActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback {
    private static final int PERMISSION_REQUEST_CAMERA = 0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_permission_test);
        useCamera();
    }

    private void useCamera() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED) {
            toast(R.string.camera_permission_granted);
        } else {
            requestCameraPermission();
        }
    }

    private void requestCameraPermission() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.CAMERA)) {
            toast(R.string.camera_permission_rationale);
        }
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == PERMISSION_REQUEST_CAMERA) {
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                toast(R.string.camera_permission_granted);
            } else {
                toast(R.string.camera_permission_denied);
            }
        }
    }

    private void toast(int msgId) {
        Toast.makeText(this, getResources().getString(msgId), Toast.LENGTH_SHORT).show();
    }
}
Activity方式
public class ActivityPermissionTestActivity extends Activity {
    private static final int PERMISSION_REQUEST_CAMERA = 0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_permission_test);
        useCamera();
    }

    private void useCamera() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED) {
            toast(R.string.camera_permission_granted);
        } else {
            requestCameraPermission();
        }
    }

    private void requestCameraPermission() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.CAMERA)) {
            toast(R.string.camera_permission_rationale);
        }
        requestPermissions(new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == PERMISSION_REQUEST_CAMERA) {
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                toast(R.string.camera_permission_granted);
            } else {
                toast(R.string.camera_permission_denied);
            }
        }
    }

    private void toast(int msgId) {
        Toast.makeText(this, getResources().getString(msgId), Toast.LENGTH_SHORT).show();
    }
}
Fragment方式
public class FragmentPermission extends Fragment {
    private static final int PERMISSION_REQUEST_CAMERA = 0;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        useCamera();
        return inflater.inflate(R.layout.fragment_permission, container, false);
    }

    private void useCamera() {
        if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED) {
            toast(R.string.camera_permission_granted);
        } else {
            requestCameraPermission();
        }
    }

    private void requestCameraPermission() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(),
                Manifest.permission.CAMERA)) {
            toast(R.string.camera_permission_rationale);
        }
        requestPermissions(new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == PERMISSION_REQUEST_CAMERA) {
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                toast(R.string.camera_permission_granted);
            } else {
                toast(R.string.camera_permission_denied);
            }
        }
    }

    private void toast(int msgId) {
        Toast.makeText(getContext(), getResources().getString(msgId), Toast.LENGTH_SHORT).show();
    }
}

可以看出三种方式大同小异,需要注意的是使用ActivityCompat方式进行请求时,需要在传入的Activity中进行回调处理,否则该请求将丢失,无法触发权限弹窗。

遇到的坑

  1. 权限请求无响应
    这个坑就是没有在正确的位置处理权限请求回调,使用Activity方式和Fragment方式都要在本类中进行处理。ActivityCompat则需要在传入的Activity中进行处理。
  2. shouldShowRequestPermissionRationale一直为false
    该接口使用Android提供出来判定是否用户已经永久拒绝该权限而设定的,根据不同的厂商有不同的表现。如我用的Emui系统就将所有的拒绝设置为不再提示,且该方法返回为false。为了兼容性考虑,该方法我一般会放弃掉,直接根据权限结果进行提示。