在移动端自动化脚本的实际落地过程中,权限配置往往是决定脚本能否稳定运行的首要瓶颈。尤其是小米(MIUI)等深度定制的Android系统,其独有的省电策略、自启动管理和后台限制机制,常常导致自动化服务被系统回收或功能受限。本文将深入剖析一套专为小米手机设计的自动化权限配置脚本,并给出完整可用的源码实现。
一、为什么需要自动化权限配置?
冰狐智能辅助基于Android无障碍服务实现控件识别与模拟操作。然而,小米的MIUI系统对后台应用有着严格的管控策略——省电模式会限制APP后台运行、锁屏后可能断开数据连接、自启动管理未开启则应用无法在后台被唤醒。这些限制如果未在系统设置中逐一解除,脚本执行过程中随时可能因服务被杀死而中断。
手动完成这些配置不仅繁琐,而且在不同MIUI版本和机型上路径可能略有差异。通过冰狐提供的findView、switchPage、scroll、click等API,我们可以编写脚本自动遍历系统设置界面,完成所有必要权限的一键配置。
二、脚本核心API解析
在深入源码之前,有必要了解本脚本使用到的几个核心冰狐API:
1. launchApp(packageName)
启动指定包名的应用。本脚本通过launchApp('com.android.settings')打开系统设置。
2. findView(tag, options)
根据tag查找UI控件。tag支持多种格式:
-
txt:设置—— 查找文本为"设置"的控件 -
txt$:个应用程序—— 查找文本以"个应用程序"结尾的控件 -
cn:miui.widget.ClearableEditText—— 按类名查找
返回值包含views数组,每个元素为node对象。
3. switchPage(fromTag, toTag, options)
从当前页面切换到目标页面。本质是在当前界面查找toTag对应的控件并点击,若找不到则返回空数组。
4. scroll(direction, options)
在屏幕上执行滚动操作。direction为'up'或'down',distance控制滚动幅度。
5. click(view, options)
点击指定的控件节点。
6. back2Page(tag, options)
通过返回键退回到包含指定tag的页面。
7. paste(view, text, options)
向输入框粘贴文本,用于在搜索框中输入"冰狐智能辅助"的应用名称。
三、脚本功能模块详解
脚本的主流程在main()函数中,整体采用目标驱动的设计模式:
var target = 'txt:我的设备|txt:双卡和移动网络|txt:锁屏|txt:电池与性能|txt:应用设置|txt:授权管理|';
脚本依次尝试在设置主界面查找这六个目标项,每找到一个就执行对应的配置函数,然后从目标字符串中移除该项,继续查找下一个。这种设计使得脚本具有很强的容错性——即使某个配置项因MIUI版本差异而位置不同,也不会影响其他配置的执行。
模块一:update() —— 系统更新配置
该函数负责进入「我的设备」→「MIUI版本」→「系统更新设置」,关闭「自动下载」、「智能更新」、「自动更新」和「加入优先升级计划」等选项。
function update() {
var ret = switchPage('txt:我的设备', 'txt:MIUI 版本', {click: true, failed: exitPopup});
// ... 逐层进入并关闭自动更新相关开关
}
设计意图:系统自动更新可能在不恰当的时机重启设备或占用大量资源,导致自动化脚本中断。关闭自动更新可确保脚本运行环境的稳定性。
模块二:network() —— 移动网络配置
该函数进入「双卡和移动网络」→「启用数据网络」,确保数据网络开关处于开启状态,并关闭「始终接收彩信」以节省流量。
function network() {
var ret = switchPage('txt:双卡和移动网络', 'txt:启用数据网络', {click: true, failed: exitPopup});
// 检查开关状态,若未开启则点击开启
}
模块三:lockScreen() —— 锁屏配置
该函数进入「锁屏」→「自动锁屏」,将锁屏时间设置为「屏幕永不休眠」。
function lockScreen() {
var ret = switchPage('txt:锁屏', 'txt:自动锁屏', {click: true, failed: exitPopup});
// 若当前不是"永不",则选择"永不"选项
}
设计意图:屏幕锁屏后,部分自动化操作(尤其是涉及界面渲染和控件识别的操作)可能受到影响。保持屏幕常亮可提升脚本执行的可靠性。
模块四:performance() —— 电池与性能配置
该函数进入「电池与性能」→「省电模式」→「锁屏后断开数据」,将选项设置为「从不」。
function performance() {
var ret = switchPage('txt:电池与性能', 'txt:省电模式', {click: true, failed: exitPopup});
// 进入锁屏后断开数据设置,确保选择"从不"
}
设计意图:MIUI的省电策略会在锁屏后自动断开数据连接,这会导致依赖网络的自动化任务失败。将此选项设为「从不」可保证后台网络连接的持续性。
模块五:app() —— 应用权限与自启动配置
这是脚本中最复杂的模块,负责两大部分配置:
第一部分:应用权限管理
-
从「应用设置」进入「授权管理」→「应用权限管理」→「应用管理」
-
在搜索框中粘贴"冰狐智能辅助"的应用名称进行搜索
-
进入应用详情页,将「允许应用在锁屏上显示」和「允许应用在后台弹出界面」设置为允许
第二部分:自启动管理
-
返回「授权管理」页面,进入「自启动管理」
-
在应用列表中滚动查找"冰狐智能辅助"
-
找到后将开关设置为开启状态
function app(doFirst=true) {
// 第一部分:权限管理
var ret = switchPage('txt:应用设置', 'txt:授权管理', {click: true, failed: exitPopup});
// ... 逐层进入,搜索应用,设置权限
// 第二部分:自启动管理
ret = switchPage('txt:自启动管理', 'txt:更多', {click: true, failed: exitPopup});
// 在RecyclerView中遍历查找目标应用
var container = findView('cn:androidx.recyclerview.widget.RecyclerView');
// 遍历每个item,匹配应用名称,开启开关
}
设计意图:自启动管理是MIUI后台管控的核心机制之一。未开启自启动的应用在后台会被系统强制回收,导致无障碍服务中断。将此开关开启是保证冰狐智能辅助在后台持续运行的关键。
辅助函数:exitPopup() —— 弹窗处理
脚本在执行过程中可能遇到各种系统弹窗(如「关于自启动的说明」、「暂不开启」等),exitPopup函数作为findView的failed回调,会在找不到目标控件时自动处理这些弹窗:
function exitPopup() {
var ret = findView('txt:暂不开启|txt:关于自启动的说明|txt:进入首页', {maxStep:1});
if (ret.length > 0) {
// 点击弹窗按钮关闭它
click(ret.views[0], {click: true, afterWait: 1000});
return true;
}
return false;
}
这种弹窗自动处理机制大大增强了脚本的鲁棒性,避免了因意外弹窗导致的执行中断。
四、完整源码
function main() {
var result = 0;
if (!launchApp('com.android.settings')) {
console.error('启动失败');
return result;
}
var ret = findView('txt:设置', {failed: exitPopup, maxStep:50});
if (ret.length <= 0) {
console.error('找不到容器');
return result;
}
var target = 'txt:我的设备|txt:双卡和移动网络|txt:锁屏|txt:电池与性能|txt:应用设置|txt:授权管理|', scrollCount = 0, successCount = 0;
while (scrollCount <= 3 && target != '') {
ret = findView(target, {failed: exitPopup});
if (ret.length <= 0) {
scrollCount++;
console.log('scroll');
scroll('up', {distance: 0.99, afterWait: 1000});
} else {
var text = ret.views[0].text;
console.log('text:' + text);
if (text == '我的设备') {
if (update()) {
successCount++;
} else {
result = -4;
console.error('我的设备设置失败');
}
} else if (text == '双卡和移动网络') {
if (network()) {
successCount++;
} else {
result = -8;
console.error('移动网络设置失败');
}
} else if (text == '锁屏') {
if (lockScreen()) {
successCount++;
} else {
result = -5;
console.error('锁屏设置失败');
}
} else if (text == '电池与性能') {
if (performance()) {
successCount++;
} else {
result = -6;
console.error('电池与性能失败');
}
} else if (text == '应用设置') {
target = target.replace('授权管理|', '');
result = app();
if (result == 1) {
successCount++;
}
} else if (text == '授权管理') {
target = target.replace('应用设置|', '');
result = app(false);
}
target = target.replace(text+'|', '');
}
}
if (successCount == 4) {
result = 1;
}
console.log('end:' + result);
exitAppDeal();
return result;
}
function app(doFirst=true) {
var result = -7, success = true;
if (doFirst) {
var ret = switchPage('txt:应用设置', 'txt:授权管理', {click: true, failed: exitPopup});
success = ret.length > 0;
}
if (success) {
var ret = switchPage('txt:授权管理', 'txt:应用权限管理', {click: true, failed: exitPopup});
if (ret.length > 0) {
ret = switchPage('txt:应用权限管理', 'txt:应用管理', {click: true, failed: exitPopup});
if (ret.length > 0) {
ret = findView('txt$:个应用程序');
if (ret.length > 0) {
click(ret.views[0], {click: true});
ret = findView('cn:miui.widget.ClearableEditText');
if (ret.length > 0) {
console.log('do paste:' + getAppName());
paste(ret.views[0], getAppName(), {type: 'paste', afterWait: 4000});
var container = findView('cn:androidx.recyclerview.widget.RecyclerView');
if (container.length > 0 && container.views[0].size > 0) {
console.log('click sub item');
click(container.views[0][0], {click: true, afterWait: 1000});
scroll('up', {distance: 0.99, afterWait: 1000});
if (setAppPermission('允许应用在锁屏上显示')) {
if (setAppPermission('允许应用在后台弹出界面')) {
result = 1;
} else {
console.log('设置 允许应用在后台弹出界面失败');
}
} else {
console.log('设置 锁屏上显示应用失败');
}
} else {
console.log('can not find recyclerView:' + container.views[0].size);
}
} else {
console.log('can not find edit');
}
}
if (result == 1) {
result = -2;
if (back2Page('txt:授权管理', {failed: exitPopup})) {
ret = switchPage('txt:自启动管理', 'txt:更多', {click: true, failed: exitPopup});
if (ret.length > 0) {
console.log('start find');
var container = findView('cn:androidx.recyclerview.widget.RecyclerView', {failed: exitPopup});
var count = 0, appName = getAppName();
while (container.views[0].size > 0 && count < 20) {
var find = false;
for (var item of container.views[0]) {
if (item[1].className=='android.widget.TextView' && item[1].text==appName) {
if (!item[2].checked) {
click(item[2], {click:true});
}
find = true;
break;
} else if (item[1][0][0].className=='android.widget.TextView' && item[1][0][0].text==appName) {
if (!item[2].checked) {
click(item[2], {click: true});
}
find = true;
break;
}
}
if (find) {
console.log('ok----');
result = 1;
break;
}
console.log('scroll--');
count ++;
scroll('up', {root: container.views[0], type: 2, afterWait:1000});
container = findView('cn:androidx.recyclerview.widget.RecyclerView', {failed: exitPopup});
}
} else {
console.error('进入自启动失败');
}
} else {
console.log('没有进入授权管理');
}
}
} else {
console.log('没进入 应用管理');
}
}
}
return result;
}
function setAppPermission(itemName) {
var result = false;
var ret = findView('txt:' + itemName, {maxStep: 6});
if (ret.length > 0) {
console.log('找到 锁屏显示');
if (ret.views[0].parent.parent[1].text == '拒绝') {
ret = switchPage('txt:' + itemName, 'txt:取消', {click: true, failed: exitPopup});
if (ret.length > 0) {
ret = findView('txt:始终允许|txt:允许');
if (ret.length > 0) {
click(ret.views[0], {click: true, afterWait: 2000});
result = true;
}
}
} else {
result = true;
console.log('已经不是拒绝,所以不再');
}
} else {
console.error('无法找到:' + itemName);
}
return result;
}
function performance() {
var result = false;
var ret = switchPage('txt:电池与性能', 'txt:省电模式', {click: true, failed: exitPopup});
if (ret.length > 0) {
ret = switchPage('txt:设置', 'txt:锁屏后断开数据', {click: true, failed: exitPopup});
if (ret.length > 0) {
if (ret.views[0].parent.parent[1][0][0][0].text!='从不') {
ret = switchPage('txt:锁屏后断开数据', 'txt:30分钟后', {click: true, failed: exitPopup});
if (ret.length > 0) {
click(ret.views[0].parent.parent.parent.parent.parent[3], {click: true});
result = true;
}
} else {
result = true;
}
} else {
console.log('找不到 锁屏后断开数据');
}
back2Page('txt:隐私保护|txt:锁屏', {failed: exitPopup});
} else {
console.log('进入性能失败');
}
return result;
}
function update() {
var result = false;
var ret = switchPage('txt:我的设备', 'txt:MIUI 版本', {click: true, failed: exitPopup});
if (ret.length > 0) {
ret = switchPage('txt:MIUI 版本', 'txt:菜单|txt:发现新版本', {click: true, failed: exitPopup});
if (ret.length > 0) {
if (ret.tag == '菜单') {
ret = switchPage('txt:菜单', 'txt:系统更新设置', {click: true, failed: exitPopup});
if (ret.length > 0) {
ret = switchPage('txt:系统更新设置', 'txt:自动下载', {click: true, failed: exitPopup});
if (ret.length > 0) {
if (ret.views[0].parent.parent[1][0].checked) {
click(ret.views[0], {click: true, afterWait: 1000});
}
ret = findView('txt:智能更新');
if (ret.length > 0 && ret.views[0].parent.parent[1][0].checked) {
click(ret.views[0], {click: true, afterWait: 1000});
}
ret = findView('txt:自动更新');
if (ret.length > 0 && ret.views[0].parent.parent[1][0].checked) {
click(ret.views[0], {click: true, afterWait: 1000});
}
ret = findView('txt:加入优先升级计划');
if (ret.length > 0 && ret.views[0].parent.parent[1][0].checked) {
click(ret.views[0], {click: true, afterWait: 1000});
}
result = true;
} else {
console.log('找不到自动下载');
}
} else {
console.log('找不到系统更新设置');
}
} else {
console.log('直接找到 发现新版本');
result = true;
}
} else {
console.log('进入设置失败');
}
back2Page('txt:蓝牙', {failed: exitPopup});
} else {
console.log('进入我的失败');
}
return result;
}
function network() {
var result = false;
var ret = switchPage('txt:双卡和移动网络', 'txt:启用数据网络', {click: true, failed: exitPopup});
if (ret.length > 0) {
if (ret.views[0].parent.parent[1][0].checked) {
click(ret.views[0], {click: true, afterWait: 1000});
ret = findView('txt:始终接收彩信');
if (ret.length > 0 && ret.views[0].parent.parent[1][0].checked) {
click(ret.views[0], {click: true, afterWait: 1000});
}
result = true;
}
back2Page('txt:蓝牙', {failed: exitPopup});
}
return result;
}
function lockScreen() {
var result = false;
var ret = switchPage('txt:锁屏', 'txt:自动锁屏', {click: true, failed: exitPopup});
if (ret.length > 0) {
if (ret.views[0].parent.parent[1][0][0].text!='屏幕永不休眠') {
ret = switchPage('txt:自动锁屏', 'txt:永不', {click: true, failed: exitPopup});
if (ret.length > 0) {
click(ret.views[0], {click: true});
result = true;
}
} else {
result = true;
}
back2Page('txt:蓝牙', {failed: exitPopup});
}
return result;
}
function exitPopup() {
var ret = findView('txt:暂不开启|txt:关于自启动的说明|txt:进入首页', {maxStep:1});
if (ret.length > 0) {
console.log('text:' + ret.tag);
if (ret.views[0].text == '关于自启动的说明') {
clickByTag('txt:确定', {click: true});
} else {
click(ret.views[0], {click: true, afterWait: 1000});
}
return true;
}
return false;
}
五、总结
本文通过一个完整的冰狐智能辅助自动化脚本,展示了如何利用平台提供的findView、switchPage、scroll、click等原生API,在小米手机上自动完成系统更新、移动网络、锁屏、电池性能、应用权限和自启动管理等六大类权限配置。
脚本的核心设计思路可以概括为三点:目标驱动的逐项配置(通过动态维护目标字符串)、弹窗的自动容错处理(通过failed回调机制)、多层级的页面导航(通过switchPage和back2Page的组合使用)。这种设计模式不仅适用于小米手机的权限配置,稍作修改即可适配其他品牌Android设备的系统设置自动化。
在实际使用中,建议先在冰狐智能辅助的在线调试环境中逐段运行脚本,观察控件的查找和点击是否准确——不同MIUI版本可能在界面布局上存在细微差异,需要根据实际情况微调findView的tag参数。权限配置完成后,冰狐智能辅助即可在后台稳定运行,为后续的自动化任务提供可靠的环境保障。
6360

被折叠的 条评论
为什么被折叠?



