android郭霖博客,Runtime Permissions(郭霖CSDN公开课)

article/2025/9/17 8:25:27

运行时权限

Api23开始,Android权限机制更改,有一部分权限不再是简单的在AndroidManifest.xml中声明即可。而是需要在运行时让用户去选择是否允许该项权限的操作。

那么哪些权限需要在运行时申请呢?危险权限需要这么做,而普通权限仍然和以前一样。具体的分类可以看之前的文章Runtime Permissions

普通权限一半时不会威胁安全、隐私;而危险权限一般涉及用户隐私,设备安全问题。

AndroidManifest声明权限

无论是危险权限还是普通权限都必须在AndroidManifest.xml中声明,这一步的作用是什么?主要有两方面:

程序安装时告知用户需要的权限,由用户决定是否安装。

在设置的应用选项中,点击应用查看信息可以看到应用获取的权限。由用户决定是否保留应用。

在build.gradle(app)中targetSdkVersion的值低于23时,应用运行在Android6.0及以上系统时,会默认打开在AndroidManifest.xml声明的权限。

如果升级应用,修改了targetSdkVersion为23及以上,再次运行时,依旧默认允许所有AndroidManifest.xml中所用声明的权限。

危险权限导致Crash

如果在Android6.0及以上运行程序执行危险权限相关操作,没有运行时检查权限获取情况,如果没有获取会导致程序crash。例如Console输出:Caused by: java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.CALL dat=tel:xxxxxxxxxxxx cmp=com.android.server.telecom/.components.UserCallActivity } from ProcessRecord{cae02cf 30814:android.example.com.permissionusage/u0a154} (pid=30814, uid=10154) with revoked permission android.permission.CALL_PHONE

有时候需要对执行危险权限操作进行封装,例如打电话操作:

Intent intent = new Intent(Intent.ACTION_CALL);

intent.setData(Uri.parse("tel://1234567890"));

startActivity(intent);

直接写成一个方法编译器会报错,即便不去处理也可以运行。可以通过捕获上面Console输出的异常来解决编译器报错:

private void makeCall() {

try{

Intent intent = new Intent(Intent.ACTION_CALL);

intent.setData(Uri.parse("tel://1234567890"));

startActivity(intent);

}catch (SecurityException e) {

e.printStackTrace();

}

}

运行时权限基础写法

单个运行时权限申请

/**

* 单个权限授权

* @param view

*/

public void btnClick(View view) {

if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)

!= PackageManager.PERMISSION_GRANTED) {

ActivityCompat.requestPermissions(

this, new String[]{Manifest.permission.CALL_PHONE}, CALL_REQUEST);

}else {

makeCall();

}

}

权限申请回调

public void onRequestPermissionsResult(

int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

switch(requestCode) {

case CALL_REQUEST:

if(grantResults.length > 0

&& grantResults[0] == PackageManager.PERMISSION_GRANTED){

makeCall();

}else {

Snackbar.make(mContainer, "权限被拒绝了", Snackbar.LENGTH_SHORT).show();

}

break;

default:

break;

}

}

其实当grantResults数组长度为0时,程序某个地方一定出现问题。

多个运行时权限申请

/**

* 多个权限同时授权

* @param v

*/

public void btnMorePermissions(View v) {

List permissions = new ArrayList<>();

//安全权限,无需运行时检查

if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_NETWORK_STATE)

!= PackageManager.PERMISSION_GRANTED) {

permissions.add(Manifest.permission.ACCESS_NETWORK_STATE);

}

if(ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)

!= PackageManager.PERMISSION_GRANTED) {

permissions.add(Manifest.permission.CALL_PHONE);

}

if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)

!= PackageManager.PERMISSION_GRANTED) {

permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);

}

if(!permissions.isEmpty()) {

ActivityCompat.requestPermissions(

this,

permissions.toArray(new String[permissions.size()]),

MORE_PERMISSIONS_REQUEST);

}else {

doSomething();

}

}

权限申请回调

public void onRequestPermissionsResult(

int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

switch(requestCode) {

case MORE_PERMISSIONS_REQUEST:

if(grantResults.length > 0) {

for(int i : grantResults) {

if(i != PackageManager.PERMISSION_GRANTED) {

Snackbar.make(mContainer, "某个权限没有授权", Snackbar.LENGTH_SHORT).show();

return;

}

}

doSomething();

}else {

}

default:

break;

}

}

为什么读写外部存储属于危险权限

在Android6.0以前,读写外部存储只需要在AndroidManifest.xml中声明即可,但是由于应用的强制行为,导致用户的外部存储中文件杂乱,权限被滥用。所以将其制定为危险权限。

在外部存储目录Android/data/packgae_name属于应用私有的目录,不需要运行时申请读写外部存储危险权限,甚至不需要在AndroidManifest.xml中声明就可以读写。应用可以随意支配自身的文件存储(cache目录经常会被清理软件清理,主要文件放入files目录下)。

这样既保证了外部存储目录的整洁,又不会给应用开发者带来不必要的麻烦。具体操作可以看文章Android数据存储之File总结。而且应用卸载后,相应包名的目录也会删除。

封装

由于运行时权限申请的繁琐,所以封装饰必须的。但是申请权限的操作必须建立在Activity之上。只有在Activity中才可以弹出申请权限对话框。而且申请回调函数属于Activity的方法,所以申请权限的操作和Activity藕合度非常高。可以通过以下办法:

自定义一个PermissionActivity,专门用于处理申请运行时权限操作。该Activity背景透明,用户无法察觉。执行完后finish掉。

参照RxPermissions第三方库的实现。

创建一个BaseActivity去实现运行时权限申请方法,然后所有Activity继承BaseActivity,需要时调用方法即可。BaseActivity对于一个项目可以提高Activity类的扩展性,在里面实现自己的方法供子类使用。

BaseActivity

public class BaseActivity extends AppCompatActivity{

private static final int REQUEST_CODE = 1;

private PermissionListener mListener;

public void requestRuntimePermissions(String[] permissions, PermissionListener listener) {

mListener = listener;

List permissionList = new ArrayList<>();

for(String permission : permissions) {

if(ContextCompat.checkSelfPermission(this, permission)

!= PackageManager.PERMISSION_GRANTED) {

permissionList.add(permission);

}

}

if(!permissionList.isEmpty()) {

ActivityCompat.requestPermissions(

this,

permissionList.toArray(new String[permissionList.size()]),

REQUEST_CODE);

}else {

mListener.onGranted();

}

}

@Override

public void onRequestPermissionsResult(

int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

switch (requestCode) {

case REQUEST_CODE:

if(grantResults.length > 0) {

List deniedPermission = new ArrayList<>();

for(int i = 0; i < grantResults.length; i++) {

int grantResult = grantResults[i];

if(grantResult == PackageManager.PERMISSION_DENIED) {

deniedPermission.add(permissions[i]);

}

}

if(deniedPermission.isEmpty()) {

mListener.onGranted();

}else {

mListener.onDenied(deniedPermission);

}

}

break;

default:

break;

}

}

}

PermissionListener用于将申请结果返回给调用的Activity。让Activity去实现权限申请结果相应的操作。

public interface PermissionListener {

void onGranted();

void onDenied(List deniedPermissions);

}

最后在Activity中使用:

public class SecondActivity extends BaseActivity{

private LinearLayout mContainer;

@Override

protected void onCreate(@Nullable Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_second);

initView();

}

private void initView() {

mContainer = (LinearLayout) findViewById(R.id.id_second_container);

}

public void btnRuntimePermission(View view) {

requestRuntimePermissions(new String[]{

Manifest.permission.CALL_PHONE,

Manifest.permission.ACCESS_FINE_LOCATION,

Manifest.permission.CAMERA,

Manifest.permission.WRITE_EXTERNAL_STORAGE}, new PermissionListener() {

@Override

public void onGranted() {

Snackbar.make(mContainer, "All Permissions Granted!", Snackbar.LENGTH_SHORT).show();

}

@Override

public void onDenied(List deniedPermissions) {

StringBuilder builder = new StringBuilder(32);

int deniedCount = deniedPermissions.size();

for(int i = 0; i < deniedCount; i++) {

String[] strArray = deniedPermissions.get(i).split("\\.");

builder.append(strArray[strArray.length - 1]);

if(i == (deniedCount - 1)) {

builder.append(".");

}else {

builder.append(",");

}

}

Snackbar.make(

mContainer,

"Denied Permissions:" + builder.toString(),

Snackbar.LENGTH_SHORT

).show();

}

});

}

}

参考

运行效果

6f114a7523b9

RuntimePermission.gif


http://chatgpt.dhexx.cn/article/6Gp3DH0B.shtml

相关文章

看一看Facebook工程师是怎么评价《第一行代码》的

本文同步发表于我的微信公众号&#xff0c;扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注&#xff0c;每个工作日都有文章更新。 大家好&#xff0c;我是一名Facebook的工程师&#xff0c;同时也是《第一行代码——Android》的忠实读者。 虽然我最近几年是在国外读书和工…

郭霖:手把手教你实现 App 360 度旋转看车效果

这是郭神号前阵子的推送&#xff0c;应该有不少人还没有看过&#xff0c;现在分享给大家&#xff0c;希望对大家的Android工作和学习有所帮助。 / 作者简介 / 本篇文章来自Youth Lee的投稿&#xff0c;分享了他自己结合Glide写的一个控件&#xff0c;希望对大家有所帮助&#…

第一行代码-第二版(郭霖著)笔记(初识Android)

系列文章目录 第一章 第一行代码-第二版&#xff08;郭霖著&#xff09;笔记&#xff08;初识Android&#xff09; 目录 一、Android简介 1.android系统架构 2.Android应用开发特色 二、工具准备 Tips:新建项目的时候是否勾选use legacy android.support libraries 三、…

专访郭霖:成长无止境

留意文末赠书活动 嘉宾 | 郭霖 文 | 张霞 郭霖&#xff0c;Android开发工程师&#xff0c;Android GDE&#xff08;Google认证开发者专家&#xff09;。从事Android开发工作9年&#xff0c;有着丰富的项目实战经验&#xff0c;负责及参与开发过多款移动应用与游戏&#xff0c;开…

解决http响应状态为canceled

最近写登录的页面&#xff0c;发现通过ajax请求后台的时候&#xff0c;监控台返回该请求的状态是canceled。 原因 仅仅是由于之前为了在输入账号时让浏览器进行自动补全&#xff0c;而将原先的div更换为了form,而不巧的是之前的登录事件源使用的是button。 而至于为什么stat…

ajax请求导致status为canceled的原因

在使用layui的form表单提交以后&#xff0c;请求状态总是canceled。后来在form表单的后面添加了一行代码&#xff1a; return false; 就可以了。 文档&#xff1a;https://www.layui.com/doc/modules/form.html#onsubmit 错误&#xff1a; 解决方法&#xff1a; 总结一下&…

ajax请求文件状态为 canceled 的解决办法

ajax请求文件状态为 canceled 的解决办法 场景还原原因分析解决 场景还原 最近做一个表单提交的需求时&#xff0c;遇到了这种情况&#xff0c;输完账号密码后回车提交&#xff0c;报错&#xff0c;f12打开看到是请求的status为canceled了&#xff0c;震惊一秒钟。。。如下图&…

chrome同步或登录报错:Request Canceled

原因 因为某个接口连接失败造成&#xff0c;可以摁快捷键F12或者点击开发者工具。 然后选择network&#xff0c;这里面是该页面所有的收发请求 开始登录&#xff0c;登录的时候要注意network中pending或者报错的接口&#xff0c;然后把域名记录下来 解决方式 安装chrome插…

http发送请求,status显示canceled的原因

原因&#xff1a;onSubmit和submit属性比较陈旧&#xff0c;在提交了数据以后会自动刷新页面&#xff0c;导致信息丢失以及请求中止 解决&#xff1a;在 handler里面写入e.preventDefault();阻止onsubmit执行默认的刷新页面行为。

使用 npm create vue@3 报错 npm ERR! canceled

问题 之前运行都可以成功创建&#xff0c;但今天运行 npm create vue3 的时候报错了&#xff0c;错误信息如下&#xff1a; 解决方法 在网上找了一堆方法都无效。 npm 版本问题&#xff0c;升级到最新版本 npm i -g npm&#xff0c;然后重试 npm create vue3 【x】npm cac…

Go:read一个已经被canceled的http.Request的应答

Go&#xff1a;read一个已经被canceled的http.Request的应答 1.复现 最近发现项目在处理chunk类型的http应答时&#xff0c;出现读数据异常报错&#xff0c;代码示例如下&#xff1a; server package mainimport ("bytes""net/http" )func main() {http…

Idea通过git拉取代码的时候出现Update canceled问题

当在IDEA中通过Git更新代码时&#xff0c;拉取失败&#xff0c;报如下错误 12:31 Update failedInvocation failed Server returned invalid Response.java.lang.RuntimeException: Invocation failed Server returned invalid Response.at git4idea.GitAppUtil.sendXmlRequest…

Xmodem operation was canceled by remote peer问题已解决

1.Xmodem operation was canceled by remote peer. 传输的时候就会出现注意的问题 2.使用df -h命令查看内存状况&#xff0c;可以发现root已经满了。 3.进入根目录&#xff0c;ls显示&#xff0c;使用rm命名将其中的文件删除 4.显示&#xff0c;可以看见内存占用变少。 5.…

vue proxy发出的post请求出现超时导致的canceled

0 问题 vue的proxy代理好了之后&#xff0c;get请求没问题&#xff0c;post请求出现canceled&#xff0c;如下图所示&#xff1a; 解决方案 参考 https://github.com/chimurai/http-proxy-middleware/issues/40 devServer: {host: 0.0.0.0,port: 8085,proxy: {/api: {targ…

IDEA中git拉取代码的时出现Update canceled问题

IDEA中git拉取代码的时出现Update canceled问题 当在IDEA中通过Git更新代码时&#xff0c;拉取失败&#xff0c;报如下错误 解决办法&#xff1a; 勾选上以后&#xff0c;点击 OK 后拉取代码&#xff1b; 然后就成功了

解决Canceled future for execute_request message before replies were done

报错&#xff1a;Canceled future for execute_request message before replies were done The Kernel crashed while execut 解决办法&#xff0c;在代码中添加 import os os.environ["KMP_DUPLICATE_LIB_OK"]"TRUE"就完美解决了

ajax请求取消状态,Ajax请求响应状态status为canceled

需求:业务数据提交成功之后,根据表单 ‘项目阶段’ 字段的值发送邮件; 我的实现逻辑是这样的:在业务数据提交成功后的回掉函数中发起发送邮件的请求,然后关闭表单页面。 $.ajax({url: url, type: post, data: {...}, dataType: json, success: function (result) {$.ajax(…

前端axios请求form-data,status显示canceled

前端axios请求form-data,status显示canceled 起因改进&报错发现&解决问题 PS&#xff1a;前排提示本文略微啰嗦&#xff0c;解决办法在 “发现&解决问题” 部分。 起因 最近在网上跟着学习axios在vue中的使用&#xff0c;包含axios的基本数据请求&#xff0c;实例…

Http响应状态Status为canceled

现象 Ajax发送请求 在浏览器的Network发现 响应状态 变为 cnaceled 解决方案 1.表单提交时用的是自定义的button 调用ajax 和form表单中的属性action冲突&#xff0c; form action与绑定于button上的click事件会同时触发。form action将表单内容以get请求追加至当前url上&…

iOS xcode无故build canceled解决办法

iOS xcode无故build canceled解决办法 简单说下原因和处理方法&#xff0c;后面有发现具体原因再补充 原因: 代码更改确定没有影响到xcodeproj&#xff0c;但查看确发现project.pbxproj文件有变化&#xff0c;导致LaunchImage设定有变化 Bulid Setting 中查看影响到的是Launc…