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 developer平台给出的请求权限工作流
简单描述即:
- 在AndroidManifest.xml文件中声明应用需要请求的权限。
2-3. 设计业务交互流程,等待用户触发需要权限的操作。 - 检查用户是否已授予应用所需的运行时权限。如果已授权,那么应用可以继续业务操作。如果没有,请继续执行下一步。
5-7. 检查是否需要向用户展示请求权限的原因,需要则做个交互,请求应用的运行时权限。 - 如果授权,那么应用可以继续业务操作。如果用户拒绝,则请适当降低应用体验,使应用在未获得受该权限保护的信息时也能向用户提供功能。
核心代码
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中进行回调处理,否则该请求将丢失,无法触发权限弹窗。
遇到的坑
- 权限请求无响应
这个坑就是没有在正确的位置处理权限请求回调,使用Activity方式和Fragment方式都要在本类中进行处理。ActivityCompat则需要在传入的Activity中进行处理。 -
shouldShowRequestPermissionRationale
一直为false
该接口使用Android提供出来判定是否用户已经永久拒绝该权限而设定的,根据不同的厂商有不同的表现。如我用的Emui系统就将所有的拒绝设置为不再提示,且该方法返回为false。为了兼容性考虑,该方法我一般会放弃掉,直接根据权限结果进行提示。