先介绍一下关于Android动态权限和targetSdkVersion背景:
targetSdkVersion:自2018年11月开始,GooglePlay以及国内大部分应用市场要求app编译目标SDK必须为26及以上,否则不予提交审核;有许多已有app转到APICloud开发后,因targetSdkVersion降级而导致无法覆盖安装;2020年以来,国家网信办等监管机构也加强了对app权限合规的监管。
动态权限:Android自系统6.0开始,提供动态权限机制,对于敏感权限(存储,定位,录音,拍照,录像等),需要在app运行过程中动态向用户申请,这就和iOS系统的权限使用体验保持一致了(iOS一直以来就是动态权限)。
使用APICloud开发平台开发app时,如果需要获取权限,需要动态申请。因此APICloud开发平台统一了Android和iOS两个平台的动态权限操作,提供两个API:hasPermission 和 requestPermission。文档地址为:https://docs.apicloud.com/Client-API/api
在Android上使用动态权限,要求app编译的目标SDK(即targetSdkVersion)为23及以上(对应为android6.0及以上系统),22及以下系统会执行缺省处理(手机厂商也可能定制处理),APICloud为满足更普遍的开发需求,默认配置targetSdkVersion为22,即权限走系统缺省处理。
开启动态权限,需要按照以下说明操作:
1、新建manifest.xml文件,添加如下代码:
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<application name="targetSdkVersion" value="28"/>
</manifest>
将其中的targetSdkVersion更新为目标值,例如30;
2、将manifest.xml置于你的/项目代码/res/目录下(widget/res/manifest.xml);
3、将你的app代码中所有涉及到需要动态权限的操作,参照示例中的代码,改造一遍(例如进行拍照录制视频等需要使用摄像头,以前的缺省处理中不需要申请摄像头权限,而开启动态权限后,必须在进行拍照之前,判断是否有摄像头权限,没有则进行申请,只有用户同意了摄像头权限才能进行接下来拍照的操作);
4、提交代码;
5、云编译界面勾选app所需的权限;
6、云编译app或自定义loader即可。
在这里需要注意的是,当你设置的targetSdkVersion大于等于23时,即意味着开启了动态权限,如果你的app带有获取IMEI、定位、录音、拍照、录像等敏感功能时,必须使用动态权限机制,先判断是否具有该功能操作权限,再进行操作,如果不具备相应的权限,对应的功能是失效的,也可能导致app崩溃。
为保证动态权限尽可能适配更多厂商的手机以及顺利上线Google Play,targetSdkVersion目前推荐设置为30。
以下为代码示例:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="maximum-scale=1.0, minimum-scale=1.0, user-scalable=0, initial-scale=1.0, width=device-width" />
<meta name="format-detection" content="telephone=no, email=no, date=no, address=no">
<title>权限管理</title>
<link rel="stylesheet" type="text/css" href="./css/api.css" />
<link rel="stylesheet" type="text/css" href="./css/box.css" />
<script type="text/javascript" src="./script/public.js"></script>
<style>
.marg {
margin: 3px 15px;
font-size: 18px;
}
</style>
<script type="text/javascript">
apiready = function () {
}
function hasPermission(one_per) {
var perms = new Array();
if (one_per) {
perms.push(one_per);
} else {
var prs = document.getElementsByName("p_list");
for (var i = 0; i < prs.length; i++) {
if (prs[i].checked) {
perms.push(prs[i].value);
}
}
}
var rets = api.hasPermission({
list: perms
});
if (!one_per) {
apialert('判断结果:' + JSON.stringify(rets));
return;
}
return rets;
}
function reqPermission(one_per, callback) {
var perms = new Array();
if (one_per) {
perms.push(one_per);
} else {
var prs = document.getElementsByName("p_list_r");
for (var i = 0; i < prs.length; i++) {
if (prs[i].checked) {
perms.push(prs[i].value);
}
}
}
api.requestPermission({
list: perms,
code: 100001
}, function (ret, err) {
if (callback) {
callback(ret);
return;
}
var str = '请求结果:\n';
str += '请求码: ' + ret.code + '\n';
str += "是否勾选\"不再询问\"按钮: " + (ret.never ? '是' : '否') + '\n';
str += '请求结果: \n';
var list = ret.list;
for (var i in list) {
str += list[i].name + '=' + list[i].granted + '\n';
}
apialert(str);
console.log(JSON.stringify(ret));
});
}
function opWithPermission(perm) {
if (!confirmPer(perm)) {
return;
}
if ('calendar' == perm) {
//操作日历
} else if ('camera' == perm) {
api.getPicture({
sourceType: 'camera',
mediaValue: 'pic',
destinationType: 'url',
}, function (ret, err) {
if (ret) {
apialert(JSON.stringify(ret));
} else {
apialert(JSON.stringify(err));
}
});
} else if ('contacts' == perm) {
api.openContacts({
test: true
}, function (ret, err) {
if (ret && ret.status) {
apialert(JSON.stringify(ret));
} else {
apialert(JSON.stringify(err));
}
});
} else if ('location' == perm) {
api.getLocation(function (ret, err) {
if (ret && ret.status) {
apialert(JSON.stringify(ret));
} else {
apialert(JSON.stringify(err));
}
});
} else if ('microphone' == perm) {
api.startRecord({
path: 'fs://perm-test.amr'
});
} else if ('phone' == perm) {
api.call({
type: 'tel',
number: '10086'
});
} else if ('sensor' == perm) {
//操作身体传感器
} else if ('sms' == perm) {
api.sms({
numbers: ['10086'],
text: '余额',
silent: true
});
} else if ('storage' == perm) {
api.readFile({
path: 'fs://test.txt'
}, function (ret, err) {
if (ret.status) {
console.log('readFile: ' + ret.data);
} else {
apialert(err.msg + ": \n" + api.fsDir);
}
});
}
}
function confirmPer(perm) {
var has = hasPermission(perm);
if (!has || !has[0] || !has[0].granted) {
api.confirm({
title: '提醒',
msg: '没有获得 ' + perm + " 权限\n是否前往设置?",
buttons: ['去设置', '取消']
}, function (ret, err) {
if (1 == ret.buttonIndex) {
reqPermission(perm);
}
});
return false;
}
return true;
}
</script>
</head>
<body>
<div>
<div id="wrap">
<div id='header'>
<div class="back" tapmode="back-aconclick="api.closeWin()">返回</div>
<h1>权限管理测试</h1>
<div class="adpt"></div>
</div>
<div class='itemtitle'>一、判断权限</div>
<div class='marg'>请选择一个或者多个权限进行判断:</div>
<div class='marg'>日历   <input type="checkbox" name="p_list" value="calendar" /></div>
<div class='marg'>相机   <input type="checkbox" name="p_list" value="camera" /></div>
<div class='marg'>通讯录  <input type="checkbox" name="p_list" value="contacts" /></div>
<div class='marg'>位置信息 <input type="checkbox" name="p_list" value="location" /></div>
<div class='marg'>麦克风  <input type="checkbox" name="p_list" value="microphone" /></div>
<div class='marg'>电话   <input type="checkbox" name="p_list" value="phone" /></div>
<div class='marg'>身体传感器<input type="checkbox" name="p_list" value="sensor" /></div>
<div class='marg'>短信   <input type="checkbox" name="p_list" value="sms" /></div>
<div class='marg'>存储空间 <input type="checkbox" name="p_list" value="storage" /></div>
<div class="clickbtn" tapmode="active" onclick="hasPermission()">点击开始判断</div>
<div class='itemtitle'>二、请求权限</div>
<div class='marg'>请选择一个或者多个权限进行请求:</div>
<div class='marg'>日历   <input type="checkbox" name="p_list_r" value="calendar" /></div>
<div class='marg'>相机   <input type="checkbox" name="p_list_r" value="camera" /></div>
<div class='marg'>通讯录  <input type="checkbox" name="p_list_r" value="contacts" /></div>
<div class='marg'>位置信息 <input type="checkbox" name="p_list_r" value="location" /></div>
<div class='marg'>麦克风  <input type="checkbox" name="p_list_r" value="microphone" /></div>
<div class='marg'>电话   <input type="checkbox" name="p_list_r" value="phone" /></div>
<div class='marg'>身体传感器<input type="checkbox" name="p_list_r" value="sensor" /></div>
<div class='marg'>短信   <input type="checkbox" name="p_list_r" value="sms" /></div>
<div class='marg'>存储空间 <input type="checkbox" name="p_list_r" value="storage" /></div>
<div class="clickbtn" tapmode="active" onclick="reqPermission()">点击开始请求</div>
<div class='itemtitle'>三、需要权限的API操作</div>
<div class='marg'>1、日历</div>
<div class="clickbtn" tapmode="active" onclick="opWithPermission('calendar')">点击操作日历</div>
<div class='marg'>2、相机</div>
<div class="clickbtn" tapmode="active" onclick="opWithPermission('camera')">点击操作照相机</div>
<div class='marg'>3、通讯录</div>
<div class="clickbtn" tapmode="active" onclick="opWithPermission('contacts')">点击操作通讯录</div>
<div class='marg'>4、位置信息</div>
<div class="clickbtn" tapmode="active" onclick="opWithPermission('location')">点击操作位置信息</div>
<div class='marg'>5、麦克风</div>
<div class="clickbtn" tapmode="active" onclick="opWithPermission('microphone')">点击操作麦克风</div>
<div class='marg'>6、电话</div>
<div class="clickbtn" tapmode="active" onclick="opWithPermission('phone')">点击操作电话</div>
<div class='marg'>7、身体传感器</div>
<div class="clickbtn" tapmode="active" onclick="opWithPermission('sensor')">点击操作身体传感器</div>
<div class='marg'>8、短信</div>
<div class="clickbtn" tapmode="active" onclick="opWithPermission('sms')">点击操作短信</div>
<div class='marg'>9、存储空间</div>
<div class="clickbtn" tapmode="active" onclick="opWithPermission('storage')">点击操作存储空间</div>
<br>
</div>
</div>
</body>
</html>