
权限弹窗的自动化处理几乎是每个搞移动应用自动化的人都会撞上的墙。你脚本写得好好的一跑到真机上系统突然弹出“是否允许获取定位”“允许相机拍摄吗”整个流程就卡死在那里等待超时、断言失败、Appium报找不到元素一查日志全是权限弹窗的锅。我之前在多个自动化项目里反复跟这些弹窗打交道踩了不少坑也沉淀了一套相对成熟的处理方案出来。这篇文章就把这套方案完整拆开讲——从弹窗的本质、工具选型、配置处理到代码模块化实现和常见问题排查一次性说清楚。不管是做App测试的同学还是刚入门移动应用开发、打算参加中职移动应用开发相关技能竞赛的选手这套思路都能直接拿过去用。1. 权限弹窗自动化处理的难点拆解1.1 权限弹窗为什么是自动化测试的常见拦路虎移动应用在首次启动或者首次使用某个功能时系统会要求用户对敏感权限进行授权比如定位、相机、麦克风、通讯录、存储空间等。这类弹窗跟应用内的自定义弹窗完全不同它属于操作系统层面的对话框由系统进程托管根本不归App自己控制。自动化框架通过WebDriver协议去操作屏幕上的元素时弹出框跟被测App不在同一个上下文里于是你的定位指令、点击指令很容易落空。实际项目里最头疼的地方在于权限弹窗的出现时机并不固定。我遇到过的情况包括冷启动就弹、登录后才弹、进入某个页面才弹、点击某个按钮才弹甚至有的机型在后台恢复时也会弹。你没办法只在一开始处理一次。而且弹窗的文案会随着系统语言、ROM版本、厂商定制而变化比如华为的“允许”“拒绝”小米的“仅在使用中允许”“使用期间允许一次”魅族的“允许”“本次允许”不同品牌的按钮名称和位置都不太一样。按固定坐标点击的方式换一台设备就废了。再加上现在的应用普遍把权限请求的流程做得比较复杂先弹系统权限再弹应用内的引导说明或者反过来中间还可能夹杂着“不同意则无法使用”的强制弹窗。这就不只是权限弹窗本身的问题而是整条流程链路的问题。所以我的结论是权限弹窗自动化处理不是一个“处理一次”的动作而是一套完整的策略组合要兼顾前置规避、运行中兜底、失败恢复三个层面。1.2 权限弹窗的类型差异系统级与应用级要把权限弹窗处理清楚第一步是认清弹窗的类型。按来源和性质我通常把权限弹窗分成四类。第一类是系统原生授权弹窗就是Android和iOS系统级的那几个对话框。Android上典型的比如“允许App拍摄照片和录制视频吗”“允许App获取此设备的位置信息吗”iOS上则是“允许访问相机”“允许访问相册”这类。它们的特征是由系统渲染元素树可以读取但不同系统的处理方式完全不同Android可以通过capability直接跳过显示iOS则相对更难自动处理。第二类是厂商定制的权限弹窗。很多国产ROM在原生权限弹窗之上还会加一层自己的风格比如小米的MIUI、华为的EMUI、OPPO的ColorOS、vivo的OriginOS弹窗样式和授权选项都有定制。更麻烦的是个别ROM在第一次拒绝之后之后再次弹出时文案会变化甚至直接转向“设置页引导”让自动化脚本措手不及。第三类是应用内引导弹窗比如“开启通知权限可以及时收到消息”“开启定位权限体验更好”。这种属于App自定义的对话框通常使用应用自己的UI组件实现可以在常规元素树里找到。但里面的按钮文案五花八门“去开启”“知道了”“以后再说”“立即设置”处理方式不可能写死。第四类是系统权限设置页里的开关项比如从首页直接跳到系统设置页去打开权限或者被拒绝多次后系统要求“前往设置中手动授权”。这是最隐蔽的一种因为此时已经不在App上下文里了元素树在Appium里抓不到。这四类弹窗的处理策略是明显不同的。原生弹窗优先用前置配置规避厂商定制和系统设置页弹窗用元素属性定位加规则匹配应用内引导弹窗则按普通UI元素处理。区分清楚类型后面写代码才不会乱成一团。2. 工具选型解析不同方案的取舍2.1 Appium与UIAutomator2的关键能力做Android端的权限弹窗自动化我优先选择的组合是Appium加UIAutomator2这算是目前移动端自动化测试最主流的搭配。Appium提供了大量的desired capabilities其中有一组跟权限处理直接相关能大幅减少运行时弹窗处理的压力。以Android为例有几个关键的capability字段必须吃掉autoGrantPermissions设为true时在安装App后自动授予所有运行时权限。这是最省事的一招直接把权限弹窗的出现从源头掐掉实测下来绝大多数原生权限弹窗都能被这一项干掉。autoAcceptAlerts这一个本来是iOS端的Alerts处理Android上也有类似的兜底逻辑但兼容性不如iOS那么稳定实际项目里我一般只在辅助层面使用。skipUnlock、skipDeviceInitialization避免设备解锁和初始化流程干扰测试。noReset/fullReset控制App的数据和权限状态是否保留。fullReset设为true时会卸载App再重装配合autoGrantPermissions可以每次都拿到一个干净的授权状态。还需要注意的是autoGrantPermissions只在App安装阶段生效。如果你的测试过程中有多次安装、覆盖安装或者App在运行中被用户手动撤销了权限那权限弹窗还是会照常出现。所以这个方案是“减少弹窗”不是“消灭弹窗”。UIAutomator2作为Appium默认的自动化引擎它对Android原生弹窗的识别能力目前是最强的可以拿到弹窗的resourceId、text、bounds等属性。需要注意国内很多App测试团队会绕过Appium直接用uiautomator2这个Python库那个库里有quick直接控制设备的能力配合ADB命令也能处理弹窗但在弹窗出现时机的异步处理上不如Appium的完整驱动层那么稳定。我个人的经验是快速验证用uiautomator2库跑正式测试任务用Appium。2.2 Airtest等图像识别方案的适用场景图像识别方案在权限弹窗处理上也有它的存在意义。我之所以把它列为兜底手段而不是首选是因为它的优缺点非常分明。先看优点图像识别不依赖元素树只要屏幕上出现了“允许”或者“始终允许”这类按钮的截图特征就能通过Template匹配找到位置并点击。因此在一些奇葩ROM元素树读取不全、WebView与Native混合的弹窗、或者自定义绘制控件的情况下图像识别反而能救场。Airtest本身支持Android和iOS连Windows桌面都能做学习成本低很多中职移动应用开发竞赛的选手也习惯用Airtest跑快速演示。再看缺点图像识别对环境极度敏感手机分辨率变了、字体大小变了、系统语言变了匹配率会直线下降。而且它识别的是静态截图如果弹窗带动态动画效果第一次扫描时画面还在飞入动画中就很容易定位失败。我建议的用法是先用Appium跑主流程遇到Appium抓不到元素的边界场景时再调用Airtest的截图匹配作为最后的兜底两个工具之间通过系统端口通信衔接。没有哪个方案是万能的组合起来反而最可靠。2.3 基于ADB命令的底层方案这里要重点聊一下ADB命令方案。之前我提到权限弹窗的一个核心痛点是出现时机不可预知、不同ROM文案不一而ADB方案的存在意义就是绕开弹窗界面直接从权限管理器层面把权限状态改掉。常用的是pm grant和pm revoke命令语法大致如下adb shell pm grant com.example.app android.permission.CAMERA adb shell pm grant com.example.app android.permission.ACCESS_FINE_LOCATION运行在Android 11以上时有些权限不能直接用pm grant需要改用appops set命令比如adb shell appops set com.example.app CAMERA allow adb shell appops set com.example.app ACCESS_FINE_LOCATION allow这套方案的判断逻辑很清晰与其等弹窗弹出后再想办法点击“允许”不如在每次启动App之前先把权限全部授权好让系统根本不需要弹窗。这跟capabilities里的autoGrantPermissions思路是一样的但ADB命令更灵活可以对不同权限做更细粒度的控制缺点是没有Appium那种一键配置需要自己写脚本去批量执行。我自己实际项目里经常这样配合Appium负责启动和操作UIADB命令负责每次测试启动前重置权限状态并预先授权二者协作稳定性和可控性都很高。3. 核心实现流程从配置到代码3.1 一句话配置利用desired capabilities减少弹窗Appium处理权限弹窗的第一步永远不是写点击逻辑而是用配置把弹窗挡在门外。落实到代码里capabilities的设置是有讲究的。我用Python写Appium用例的时候capabilities会这样配置from appium import webdriver caps { platformName: Android, appPackage: com.example.app, appActivity: .MainActivity, automationName: UiAutomator2, deviceName: emulator-5554, autoGrantPermissions: True, noReset: True, unicodeKeyboard: True, resetKeyboard: True, newCommandTimeout: 300, } driver webdriver.Remote(http://127.0.0.1:4723/wd/hub, caps)autoGrantPermissions设为True之后Appium在安装阶段会通过pm命令自动授予安装时声明的权限大部分原生权限弹窗就不会再出现了。这里值得强调的是noReset和autoGrantPermissions的组合如果你设了noResetApp进程数据会保留已经授权过的权限继续有效自然也不会弹窗但如果你用fullReset每次都会重装就必须靠autoGrantPermissions兜底。顺带说一句unicodeKeyboard和resetKeyboard虽然不是权限弹窗的处理项但它们是Android自动化输入中文的标配配置经常被忽略放一起列出来方便直接抄。3.2 ADB授权与预置处理让权限状态可控capabilities只能解决“安装时自动授予”解决不了“测试过程中权限状态被人为改变”的问题。比如有的测试用例要验证拒绝授权后的业务逻辑或者要连续多轮重复测试这时就需要在每次用例开始前重置权限状态。我通常在测试夹具里使用pytest的fixture来完成权限预置import subprocess PACKAGE_NAME com.example.app def reset_and_grant_permissions(package): permissions [ android.permission.CAMERA, android.permission.ACCESS_FINE_LOCATION, android.permission.RECORD_AUDIO, android.permission.READ_CONTACTS, android.permission.WRITE_EXTERNAL_STORAGE, android.permission.READ_EXTERNAL_STORAGE, ] for perm in permissions: subprocess.run( [adb, shell, pm, grant, package, perm], capture_outputTrue, ) subprocess.run( [adb, shell, pm, clear, package], capture_outputTrue, )这里有一个顺序问题容易踩坑我先把权限grant好再去pm clear应用数据会把你刚授权的结果清掉反过来先pm clear再grant权限才是保留的。所以上面的顺序是正确路径先clear再grant。当然如果你希望保留登录态、但不希望权限被清掉就得根据项目实际情况调整顺序。还有些测试场景需要特定权限是拒绝状态那就应该用pm revoke来撤销。要注意Android 6.0以上普通运行时权限可以revoke成功后部分敏感权限比如定位在部分自定义ROM上revoke后并不会立刻弹出重新授权而是直接返回“未授权”这时App自己的权限状态判断逻辑能否正确处理就需要在测试用例里做额外的断言。总之ADB命令方案的核心价值是权限状态完全可控。测试用例想要什么样的权限状态脚本就给你准备什么状态而不是被动等弹窗蹦出来。3.3 弹窗兜底捕获规则驱动的点击策略不管前置配置做得多完美项目跑久了总会遇到漏网之鱼某台设备第一次启动时弹了“仅在使用中允许”某个机型弹了“获取设备标识信息”这些在安装阶段不一定能被autoGrantPermissions自动处理掉。所以运行时的兜底捕获是必须写的。我的做法是把“权限弹窗识别”写成一个统一规则模块不针对某个弹窗固定元素而是按特征匹配。思路是循环查找一组策略节点以下是简化版的处理流程def handle_permission_dialog(driver, timeout10): 按策略列表依次尝试识别并点击权限弹窗。 识别到的按钮点击后重新检查当前页面状态。 strategies [ # (控件文本关键字列表, 点击动作) ([允许, 始终允许, 使用期间允许], click), ([仅在使用中允许, 使用应用期间允许], click), ([允许一次, 本次允许], click), ([确定, 好, OK], click), ([拒绝, 不允许, 取消], click), ] start_time time.time() while time.time() - start_time timeout: try: for keywords, action in strategies: for keyword in keywords: # 尝试通过 text 定位 els driver.find_elements(AppiumBy.ANDROID_UIAUTOMATOR, fnew UiSelector().text({keyword})) if not els: # 再尝试通过 content-desc 定位 els driver.find_elements(AppiumBy.ANDROID_UIAUTOMATOR, fnew UiSelector().description({keyword})) if els: els[0].click() time.sleep(0.5) break except Exception as e: # 元素不存在或不可点击忽略继续循环 pass time.sleep(0.5)这段代码的思路是按关键词优先点击“允许”类选项而不是直接点“拒绝”因为自动化测试大多数场景默认是在模拟积极授权。但也有一部分场景专门要测拒绝授权时的表现那就需要按参数控制点击“拒绝”选项。这个模块的核心是循环 动态查找 关键词匹配。与固定坐标定位相比它能自适应不同分辨率、不同语言和不同ROM的弹窗样式。只要弹窗里有那三个字不管它在屏幕哪个位置都能识别并点击。实测下来这套策略在几十种主流机型上效果都不错误点率也很低。但对那种图标式权限弹窗比如某些游戏里相机权限弹窗没有文字按钮就需要配合图像识别兜底了。4. 代码实现一个完整的权限弹窗处理模块4.1 模块设计思路前面几节的内容单看都是零散的策略实际工程里要把它们组合成一个真正能用的模块。我给自己的工程里抽象了一个PermissionGuard类职责集中在“启动前预授权、运行中兜底处理、操作后状态校验”三件事上。设计上有几个关键决策权限弹窗的处理不能依赖单一机制必须前置配置、ADB预授权、运行时兜底三管齐下。运行时兜底处理要设计成独立线程或子任务因为在多步业务操作中弹窗往往会在你不知道的某个时刻弹出来。与其每一步操作前去检查不如在自动化驱动的空闲间隙循环检测。模块暴露的接口要足够简单最终用户在用例里只需要一行代码就能启动守护比如在pytest的fixture里启动。4.2 权限守卫模块的完整实现下面给出一个相对完整的Python版本使用的库是Appium的官方Python客户端这个代码可以直接放进自己的框架里改一改用import time import subprocess import logging import threading from appium.webdriver.common.appiumby import AppiumBy logger logging.getLogger(__name__) class PermissionGuard: def __init__(self, driver, package_name): self.driver driver self.package package_name self._stop_flag False self._watch_thread None # ---------- 1. 前置权限预处理 ---------- def grant_all_permissions(self): 预授予所有常用运行时权限 perms [ android.permission.CAMERA, android.permission.ACCESS_FINE_LOCATION, android.permission.ACCESS_COARSE_LOCATION, android.permission.RECORD_AUDIO, android.permission.READ_CONTACTS, android.permission.WRITE_EXTERNAL_STORAGE, android.permission.READ_EXTERNAL_STORAGE, android.permission.READ_PHONE_STATE, android.permission.REQUEST_INSTALL_PACKAGES, ] for perm in perms: result subprocess.run( [adb, shell, pm, grant, self.package, perm], capture_outputTrue, textTrue, ) if result.returncode 0: logger.info(granted: %s, perm) else: logger.warning(grant failed: %s, %s, perm, result.stderr.strip()) # ---------- 2. 常见权限弹窗文案规则 ---------- staticmethod def _allow_keywords(): return [ 允许, 始终允许, 使用期间允许, 仅在使用中允许, 使用应用期间允许, 允许一次, 本次允许, 好, 确定, Allow, Always allow, While using the app, Only while using the app, Allow once, OK, Agree ] staticmethod def _deny_keywords(): return [ 拒绝, 不允许, 取消, 以后再说, 暂不, Deny, Dont allow, Cancel, Not now, Later ] # ---------- 3. 运行时兜底单次检测 ---------- def _detect_and_click_one(self, keywords, click_typeallow): if click_type allow: words self._allow_keywords() else: words self._deny_keywords() # 优先点击“允许”类的按钮按优先级顺序 for word in words: if word in keywords: continue # 通过 text 查找 uiauto_selector fnew UiSelector().text({word}) try: ele self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, uiauto_selector) if ele.is_displayed(): ele.click() return True except Exception: pass # 通过 content-desc 查找 try: desc_selector fnew UiSelector().description({word}) ele self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR, desc_selector) if ele.is_displayed(): ele.click() return True except Exception: pass return False # ---------- 4. 运行时兜底持续守护线程 ---------- def start_watchdog(self, interval1.0): if self._watch_thread and self._watch_thread.is_alive(): return self._stop_flag False def loop(): logger.info(permission watchdog started) while not self._stop_flag: try: # 优先点击允许 clicked self._detect_and_click_one( keywords[], click_typeallow, ) if not clicked: # 没有允许按钮才检查拒绝按钮一般只用于特殊情况 self._detect_and_click_one( keywords[], click_typedeny, ) except Exception as exc: # 页面没有控件时异常直接忽略 logger.debug(watchdog loop exception: %s, exc) time.sleep(interval) logger.info(permission watchdog stopped) self._watch_thread threading.Thread(targetloop, daemonTrue) self._watch_thread.start() def stop_watchdog(self): self._stop_flag True if self._watch_thread and self._watch_thread.is_alive(): self._watch_thread.join(timeout3) # ---------- 5. 综合入口 ---------- def prepare_app_environment(self): 启动测试前的完整环境准备 # 先清数据保证干净 subprocess.run([adb, shell, pm, clear, self.package], capture_outputTrue) # 再授予权限 self.grant_all_permissions() # 清掉可能残留的任务栈 subprocess.run([adb, shell, am, force-stop, self.package], capture_outputTrue) # 启动App self.driver.launch_app() # 启动守护线程 self.start_watchdog()再给一个pytest里使用的示例这样整个模块直接可用import pytest from appium import webdriver from permission_guard import PermissionGuard pytest.fixture(scopefunction) def app_driver(): caps { platformName: Android, appPackage: com.example.app, appActivity: .MainActivity, automationName: UiAutomator2, deviceName: emulator-5554, autoGrantPermissions: True, noReset: False, newCommandTimeout: 300, } driver webdriver.Remote(http://127.0.0.1:4723/wd/hub, caps) yield driver driver.quit() def test_permission_flow(app_driver): guard PermissionGuard(app_driver, com.example.app) guard.prepare_app_environment() # 业务测试逻辑 # ... guard.stop_watchdog()实际使用中我一般不在每个测试用例都启动watchdog因为守护线程频繁轮询会拖慢用例执行。更好的方式是在整个测试会话的fixture里启动一次业务用例就不用管弹窗了。运行时间长的任务注意定期检查守护线程是否还活着万一异常中断了弹窗堆积会导致整条用例挂掉。5. 常见问题与排查技巧实录5.1 弹窗出现时机不稳定如何处理异步弹窗权限弹窗最让人头疼的就是时机不固定而且很多弹窗跟网络请求强相关。比如App启动后先并发请求数据数据回来了才弹“开启定位用于推荐附近内容”这就会造成不同网络状态下弹窗时间差异很大用例里按固定sleep去等弹窗是不靠谱的。我摸索出的方案是“用状态机代替固定等待”。简单说就是把等待弹窗当成一个独立步骤这个步骤的判定条件是界面上出现了权限弹窗的特征。在没有Appium的显式等待方法专门处理弹窗时我用的是轮询判断def wait_for_permission_dialog(driver, timeout20): 等待权限弹窗出现返回True/False start time.time() while time.time() - start timeout: if PermissionGuard(driver, )._detect_and_click_one([], click_typeallow): return True time.sleep(0.5) return False这样异步弹窗哪怕晚5秒弹出来守护线程也能接得住。另外一个细节是启动App之后不要急着查找业务元素先运行一次弹窗检测如果检测到弹窗就点掉然后再进入业务操作。没有弹窗就继续往下走。这个“先探测、再操作”的顺序能直接避免后面95%的误报。5.2 不同ROM权限弹窗样式差异怎么统一处理国产手机厂商的ROM权限弹窗差异是整个方案里最恶心的部分。我举几个实际部署测试时见过的真实例子小米MIUI系统权限弹窗底部有“仅在使用中允许”“使用时允许”“拒绝”文案跟原生Android不完全一样。“仅在使用中允许”是默认选项跟原生“仅在使用中允许”位置接近。华为EMUI部分版本弹窗文案是“在使用应用时允许”“始终允许”“拒绝”比原生多了一行说明文字。OPPO ColorOS有“允许”“本次使用允许”“拒绝”而且“本次使用允许”在某些版本里会单独占一行容易把脚本里找“允许”的逻辑搞混。vivo OriginOS个别版本权限弹窗会出现两个步骤第一步是允许相机使用第二步是询问“拍摄照片期间允许使用相机吗”。三星OneUI权限弹窗部分机型会用“允许仅一次”替代“允许一次”。所以我在方案里从来不做单点匹配规则列表里尽量把常见变体文案都维护进去并按优先级排列。“允许”永远排在前面“本次使用允许”排在后面因为带有“允许”关键词的按钮如果先被点了后续的选项就不会再出现这样命中的概率最高。用正则或者in关键词匹配也可以但要注意“使用期间允许”和“期间允许”这类包含关系会误点。另外如果同一台设备上反复测试ROM会在用户拒绝多次后把权限弹窗转成设置页引导这时候再检测弹窗已经没有意义了需要检测设置页的特征比如“打开设置”“权限管理”“允许”等控件然后进入设置页手动打开。这个场景我在代码里单独留了一个分支实际触发概率不低尤其是做权限拒绝测试的时候。5.3 权限授权与业务逻辑的冲突还有一类问题不是弹窗点不掉而是弹窗即使点掉了后续业务依然报错。这类问题源于权限授权状态和App内部状态没有同步。举个例子App在启动时通过requestPermissions请求了定位权限但是系统弹窗被自动化工具提前点击了“允许”。按道理权限应该已经授予但App内部没有收到回调因为权限请求可能还在进行中页面上没有任何提示但功能不工作。这种情况往往是App代码里把权限结果回调跟页面跳转耦合在一起了没有回调就卡在某个空白页自动化脚本根本找不到元素。这种问题用Uiautomator2查元素是定位不了的因为它不是元素查找的问题而是业务状态问题。排查思路有两个方向一是用adb shell dumpsys package com.example.app查看当前权限状态确认权限是否真的被授予二是App自身可能有一个权限状态页面截图比对是否停在空白页。我在多轮自动化测试中遇到这类问题基本都是App把权限回调的逻辑写得太死导致的测试侧的解法只能是调整用例设计对这类App需要单独走“先手动授权再启动”的模式。5.4 语言和本地化环境的影响权限弹窗的文案不只跟ROM有关还跟系统语言有关。如果测试用例要覆盖多语言场景把系统切成英文那么你的“允许”“拒绝”关键字就全部失配了。所以我在方案里内置了中文、英文、日文三套关键词列表切换语言后自动加载对应规则。英文的弹窗文案变形也多比如“While using the app”“Only this time”“Don’t allow”按钮上的文案大小写、缩写也不同。把这些常见变体维护进规则列表之后多语言环境下基本不需要额外写处理逻辑了。顺带提醒一句字符匹配时最好先判断一下text的大小写Appium的text(Allow)是精确匹配但按钮文案有时候会是Allow有时候是ALLOW。我建议用textContains替代text做匹配更稳。5.5 权限弹窗处理的日志与复现记录最后分享一个排查技巧权限弹窗处理模块一定要打完整日志因为权限弹窗是系统级UI一旦测试失败你甚至分辨不出来是弹窗点慢了还是App崩溃了。我每个关键动作都会记时间戳、当前activity名称、点击的关键词、点击坐标例如[2025-01-15 10:23:45.123] [INFO] watchdog: click 允许 at (540, 1230), activity: com.example.app/.MainActivity [2025-01-15 10:23:45.621] [INFO] watchdog: click 允许一次 at (540, 1230), activity: com.example.app/.PermissionActivity再加上adb logcat里ActivityManager的弹窗日志子系统的输出基本能还原当时的完整状态。日志是排查定位问题最直接的手段别节省这部分成本。权限弹窗处理得好不是靠一次两次的运气而是靠一套稳定的策略体系和扎实的现场日志累积出来的。我在前面也提到过这套方案不仅是用在普通的自动化测试项目里。像一些移动应用开发的技能竞赛比如中职移动应用开发相关赛事中也会把“应用的权限管理、弹窗处理”列为实操考核点。很多选手平时只练功能开发一上真机就被系统权限弹窗打乱节奏这其实挺可惜的。提前把权限弹窗的自动化处理思路融入自己的项目里在竞赛演示或作品答辩时反而会成为一个很亮眼的加分点。实操中我的体会是权限弹窗自动化这事本身没什么魔法关键就是两件事一个是在源头把弹窗尽量规避掉另一个是运行时用规则化兜底稳稳接住那些漏网的。把这个思路想明白了代码只是顺手的事。方案里的细节你可以根据自己的项目去调整比如增加App特有的权限文案、适配更多ROM但整体框架是通用的拿来即用基本不踩坑。如果你在接入这套方案的时候遇到我这篇文章里没覆盖到的机型或者弹窗样式欢迎按照日志思路先定位再往规则表里补关键词很快就能形成属于你自己项目的那一份权限弹窗字典。