尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

荣耀应用商店自动更新:API抓包与Python+ADB批量安装实战

荣耀应用商店自动更新:API抓包与Python+ADB批量安装实战 如果你手上有一台荣耀手机或者管着十来台测试机、演示机你一定遇到过这种场景应用商店里的App版本又更新了但手机没有开启自动更新你只能一台一台点开商店去手动升级。偶尔一次还好次数多了就特别浪费时间。我最早做测试机集群管理时踩过这个坑后来干脆写了一个Python脚本通过API分析HONOR应用商店的更新逻辑把“检查更新-下载APK-安装到手机”整条链路自动化。这套方案我实际跑了一个多月稳定性和效率都比预期好很多所以整理出来分享给你。这篇文章不是那种“看起来很有道理但没法落地”的教程而是我从抓包到代码、从单机到多设备、从手动到定时任务一路踩过来的完整记录。适合三类人看一是手里有多台荣耀设备需要统一维护的人比如测试工程师、演示机管理员二是正在做App兼容性测试、需要频繁升级被测试App的同学三是想用Python折腾手机自动化的小白玩家。你会用到的基础工具不多一台荣耀手机、一根数据线、电脑上装好Python和一个抓包工具剩下的事跟着下面一步步走就行。1. 方案选型与整体设计为什么用“APIADB”而不是“模拟点击”1.1 荣耀应用商店自动更新的真实痛点很多人以为手机里的应用商店会自动更新App尤其是荣耀这种大厂系统级商店不应该很省心吗实际用下来并不是这样。荣耀应用商店的自动更新策略受电量、WiFi、账号状态、应用安装来源等多重因素影响经常出现“商店通知栏有红点点进去一看好几个App没更新”的情况。在多设备场景下这个问题会被放大。我在测试机集群里维护过十几台荣耀设备每台机器可能装了20个常用App每周都有几个App发新版。如果人工点更新一台机器至少得花五六分钟十几台就是将近一小时而且这个活没有任何技术含量纯粹是机械劳动。更麻烦的是MagicOS 8.0、Android 14之后的安装校验变严了。商店外下载的APK安装时会弹“未知来源”风险提示甚至某些系统应用不允许直接覆盖安装。如果你只是为了测试某个新版本功能手动绕过这些弹窗的工作量会让人崩溃。所以真正的问题不是“能不能自动更新”而是“更新过程能不能脱离人肉操作”并且还要保证安装的是商店官方渠道包而不是从乱七八糟的第三方下载站拿来的版本。1.2 三种方案的对比与取舍我最早试过用Appium或UIAutomator自动点击应用商店的“更新”按钮结果发现这条路看着简单实际非常脆。商店界面改版、弹窗出现时机不同、网络慢导致列表没加载完都会让脚本点错位置。维护脚本的时间成本比手动更新还高果断放弃。也考虑过从第三方应用市场或一些聚合下载站抓APK但渠道包差异是个大坑。同一款App荣耀商店的渠道包和官网包、其他应用市场的包可能在versionCode、签名、内置参数上有差异覆盖安装时经常会提示签名不一致装不上。最终我选择了“APIADB”的组合方案先通过分析HONOR应用商店App发出的网络请求拿到它检查更新时的API接口再用Python直接调用这个接口得到官方渠道APK的下载地址下载完成后用adb命令批量安装到手机。三个方案大概对比如下方案稳定性维护成本渠道包一致性适合场景Appium自动点击商店低依赖UI高高但更新慢单台偶尔用第三方下载站抓包中中低渠道易混乱不推荐APIADB自动化高中接口变化需维护高来源官方商店多设备批量管理选API这条路最核心的理由是快和批量。接口一次请求就能知道某台设备的应用列表里哪些App需要更新不需要一台台去翻界面。下载安装也是可以用并行的整体效率提升不是一点半点。1.3 整个自动化流程是怎么串起来的这套自动化系统我拆成了四个独立模块更新检查器请求商店更新接口判断当前安装的版本是不是最新版。下载器从返回结果里拿到APK下载地址下载到电脑本地并校验文件完整性。设备管理器通过ADB连接一台或多台荣耀手机把下载好的APK安装进去。调度与通知定时执行任务完成后把结果推送到钉钉、邮件之类的渠道。数据流大致是先维护一份“应用包名当前版本号”的清单然后拿着清单去请求API如果有新版本就下载APK、校验MD5、调ADB安装最后把安装成功或失败的设备名单汇总输出。这里要提前说清楚一件事HONOR应用商店并没有公开开放“检查更新”这个API给开发者使用所以下面的抓包和接口整理属于个人学习、个人设备管理的范畴。我不建议用高并发方式去刷商店接口更不能把这种方案拿去商业化批量分发App。如果你是企业要管理大量设备应该走MDM或企业应用管理平台。本文示例代码里的域名和密钥都是脱敏的实际使用时要换成你自己设备抓到的真实数据。2. 抓取HONOR应用商店API从抓包到参数整理2.1 抓包环境搭建要拿到应用商店的更新接口第一步是抓包。原理很简单手机发起的网络请求都会经过电脑上的代理程序我们把HTTPS流量解密后就能看到接口地址、请求头和响应内容。我用的工具是Charles因为它对新手最友好能看到清晰的请求列表和JSON解析。你如果更习惯命令行可以用mitmproxy原理一样。宏观步骤是电脑和手机连同一个WiFi。电脑上打开Charles默认代理端口8888。手机上进入WiFi设置把代理改成手动填上电脑的局域网IP和端口8888。手机浏览器访问http://charlesproxy.com/getssl下载并安装证书。iOS和Android的证书信任逻辑不一样荣耀手机MagicOS需要在“设置-安全-更多安全设置-加密与凭据-受信任的凭据”里确认用户凭据里已经有Charles的证书。做完这些后建议先用手机浏览器随便打开一个网页看Charles里能不能看到请求。能看到说明代理和证书都通了。很多同学走到这里就没继续其实问题基本都出在证书没装好或者某一步代理没生效。2.2 找到“检查更新”接口环境就绪后操作顺序很重要。先把荣耀应用商店关掉然后打开Charles的录制再进入商店的“更新”页面让它去拉取待更新列表。因为商店App会同时请求很多接口包括首页推荐、分类、广告、用户信息等。为了快速定位我会在Charles底部Filter栏输入关键词过滤比如update、check、version、upgrade这些。荣耀应用商店的更新接口通常是POST请求URL里可能带update或checkVersion响应里能直接看到一串应用列表。点开某一条请求后重点看三个东西。请求URL记录下协议、域名、路径。请求头特别是带签名、时间戳、随机数、设备ID的字段。请求体和响应体请求体里会有packageName、versionCode、渠道号响应体里会有最新版本号、版本名、APK下载地址、MD5和文件大小。我在实际操作中注意到荣耀应用商店的HTTPS请求不是每次都一样的有些设备首次检查更新时还会额外上报一堆设备信息。但对我们自动化更新来说真正要用到的核心参数就那几个应用包名、当前版本号、设备标识、平台标识、时间戳和签名。2.3 整理成Python可用的接口文档抓到接口后别急着写代码先把它整理成一张接口卡片方便以后对照。我在自己项目里是这么记录的参数项值/说明请求方法POST接口地址https://api.honor.example.com/router/rest以实际抓包为准公共参数appId、timestamp、nonce、deviceId、sign业务参数methodapp.checkUpdate、packageName、versionCode、channel返回关键字段code、data.updateInfo.versionName、versionCode、downloadUrl、md5、size注意上面的域名是脱敏示例不要直接照抄。每个荣耀设备的地区和系统版本可能拿到不同域名但请求结构不会有太大差异。整理的时候我还有个习惯把一次典型的请求和响应完整复制下来保存到一个JSON文件里提交到代码仓库。以后接口升级了、代码报错了直接把这个文件和最新的抓包结果对比能快速看出来是哪个字段变了。2.4 签名校验怎么处理荣耀应用商店的接口大概率是有签名机制的。你在请求头里会看到类似sign、token、timestamp之类的字段服务端会根据参数和密钥算出一个签名来校验合法性。对于这种未开放接口想要完全从零复现签名算法需要去逆向App的so库或者用Hook手段这已经超出大多数人的需求了。我的处理思路比较务实抓包时拿到一条真实成功的请求把里面的参数、时间戳、签名都记录下来先搞清楚签名生成需要哪些字段参与。然后在Python里写一个通用签名函数占位规则先用最常见的MD5排序拼接md5(secret kv按key排序拼接 secret)。密钥怎么来可以从抓包到的请求里观察如果同一个设备多次请求某些固定字符串始终不变再结合渠道号、设备ID反推一下如果实在推不出来就先用抓到的原始签名直接回放测试接口是否对临时nonce有强校验。我这里必须提醒一句签名算法涉及App内部实现不同版本、不同渠道可能都不一样。不要为了绕过限制去伪造设备信息更不要把这个脚本架在公网上。只在你自己的设备管理环境里跑出了问题也容易控制。如果接口返回签名错误优先检查你的系统时间是不是比真实时间快了或慢了几分钟很多签名校验卡的都是时间戳偏差。3. Python核心代码实现从检查更新到自动安装3.1 配置层与公共请求封装项目结构我用最简单的平铺方式不用复杂框架。配置文件加四个Python文件逻辑清晰方便在Linux服务器或者本地Windows上跑。# config.py HONOR_API_BASE https://api.honor.example.com/router/rest HONOR_APP_ID your_app_id HONOR_SECRET your_secret_from_capture HONOR_DEVICE_ID your_device_id_from_capture CHANNEL honor DOWNLOAD_DIR ./apks APPS [ {name: 微信, package: com.tencent.mm, version_code: 1700}, {name: 抖音, package: com.ss.android.ugc.aweme, version_code: 1900}, ]配置里version_code是从手机上读取的当前版本号。你有没有注意到我没有把应用列表放在数据库或者Excel里而是直接写在Python文件里因为测试机群里要维护的核心App就二十个左右写在配置里最直观。公共请求封装是核心。我把签名、时间戳、随机数、设备ID都集中处理保证所有接口调用走同一个逻辑。# api_client.py import hashlib import random import time import requests import config def make_sign(params: dict, secret: str) - str: sorted_keys sorted(params.keys()) raw secret .join(f{k}{params[k]} for k in sorted_keys) secret return hashlib.md5(raw.encode(utf-8)).hexdigest() def build_request(body: dict) - dict: params { appId: config.HONOR_APP_ID, timestamp: str(int(time.time())), nonce: str(random.randint(100000, 999999)), deviceId: config.HONOR_DEVICE_ID, } params.update(body) params[sign] make_sign(params, config.HONOR_SECRET) return params def post(path: str, body: dict) - dict: url config.HONOR_API_BASE path headers { User-Agent: HONORAppStore/xxx, Content-Type: application/json, } resp requests.post(url, jsonbuild_request(body), headersheaders, timeout10) resp.raise_for_status() return resp.json()你可能会问为什么签名前要先排序这是很多接口的约定俗成服务端校验时也会按同样的排序方式拼接如果两端排序规则不一致签名就永远对不上。这个细节在我第一次写的时候踩过坑排序后立马成功。3.2 版本检查与结果解析有了请求封装写版本检查就简单了。核心逻辑是循环配置里的应用列表逐个调用app.checkUpdate方法把返回结果解析成结构化的更新信息。# updater.py import config from api_client import post def check_update(package: str, current_version: int) - dict | None: body { method: app.checkUpdate, packageName: package, versionCode: current_version, channel: config.CHANNEL, } data post(/update, body) if data.get(code) ! 0: raise RuntimeError(fAPI error: {data.get(code)} {data.get(message)}) update_info data.get(data, {}).get(updateInfo) if not update_info: return None return { package: package, version_name: update_info[versionName], version_code: update_info[versionCode], url: update_info[downloadUrl], md5: update_info.get(md5, ), size: update_info.get(size, 0), }代码里我先判断code是否为0再做空值判断。实际抓包时我发现商店接口在“没有更新”的情况下也会返回code0只是updateInfo为空。所以代码里不能只判断code必须处理updateInfo为None的情况不然很容易把“已是最新”误判成“接口报错”。3.3 APK下载与完整性校验拿到下载地址后下一步是下载到本地。这个环节有几个小坑一是应用商店的下载地址经常带签名参数响应头会校验User-Agent不能随便改二是大文件下载容易断流需要流式写入三是下载完必须做MD5校验防止下载到半个包就安装那种情况在ADB安装时只会看到莫名其妙的报错。# downloader.py import hashlib import os import requests import config def md5_of_file(path: str, chunk_size: int 1024 * 1024) - str: h hashlib.md5() with open(path, rb) as f: while block : f.read(chunk_size): h.update(block) return h.hexdigest() def download_apk(url: str, dest: str, expected_md5: str ) - str: headers { User-Agent: HONORAppStore/xxx, Referer: https://appstore.honor.cn/, } with requests.get(url, streamTrue, headersheaders, timeout(10, 120)) as r: r.raise_for_status() with open(dest, wb) as f: for chunk in r.iter_content(chunk_size1024 * 256): if chunk: f.write(chunk) if expected_md5: actual md5_of_file(dest) if actual ! expected_md5: os.remove(dest) raise RuntimeError(fMD5 mismatch, file removed: {dest}) return dest这个函数有个细节我特意加了校验失败后把文件删掉而不是留在磁盘上。因为不完整的APK只会干扰后续判断下一次更新时会重复下载同一个包占用磁盘空间。3.4 ADB安装与多设备并行处理下载完APK就到了真正触碰手机的一步。荣耀手机的ADB调试和Google原生机型略有区别但命令是通用的。先验证设备是否在线再逐个安装。# installer.py import subprocess def get_devices() - list[str]: out subprocess.check_output([adb, devices]).decode(utf-8) devices [] for line in out.splitlines()[1:]: if line.strip() and device in line: serial line.split()[0] devices.append(serial) return devices def install_apk(serial: str, apk_path: str) - str: cmd [adb, -s, serial, install, -r, -t, apk_path] proc subprocess.run(cmd, capture_outputTrue, textTrue, timeout180) if proc.returncode ! 0: raise RuntimeError(finstall failed on {serial}: {proc.stderr}) return proc.stdoutinstall -r是保留数据和缓存做覆盖安装这个参数在升级场景里必须带不然会先卸载再安装用户数据全没了。-t是允许安装测试包如果APK是测试签名或带debug标志不加这个参数会直接报INSTALL_FAILED_TEST_ONLY。多设备并行安装我用的concurrent.futures但并发数控制在3以内。因为同时向多台手机推大APK包会占用大量USB带宽和CPU资源超过3台以后不仅速度没有提升反而可能出现某个设备操作超时。4. 定时任务与结果通知真正实现无人值守4.1 用APScheduler做定时扫描手动执行脚本只能算半自动化真正省心的是让它定期扫描。比如每天早上8点和晚上8点各检查一次有更新就自动更新没更新就安静睡觉。如果是在本机跑用APScheduler最灵活它是Python社区里很成熟的定时任务库。示例代码是这样# scheduler.py from datetime import datetime from apscheduler.schedulers.blocking import BlockingScheduler def job(): print(f[{datetime.now()}] start check update) run_update_pipeline() if __name__ __main__: scheduler BlockingScheduler() scheduler.add_job(job, interval, hours6, next_run_timedatetime.now()) scheduler.start()next_run_timedatetime.now()的意思是启动后立即跑一次这样你不用等第一个周期结束就能确认脚本本身没写错。如果在Linux服务器上跑也可以用cron一行配置搞定0 8,20 * * * cd /opt/honor_updater /usr/bin/python3 main.py logs/update.log 21定时扫描的频率我建议别太高。应用商店接口毕竟是未公开接口频繁请求容易触发风控6到12小时一次足够了。测试机上的App版本更新晚半天发现并不会造成什么后果但接口被封了就得重新抓包。4.2 结果通知钉钉机器人/邮件定时任务跑起来后还有一个问题如果脚本挂在服务器上怎么知道它执行成功还是失败我习惯把执行结果推到钉钉群机器人。创建钉钉机器人很简单拿到一个Webhook地址后往那个地址POST一条JSON消息就行。# notifier.py import requests def send_dingtalk(webhook: str, text: str) - None: payload { msgtype: text, text: {content: text}, } requests.post(webhook, jsonpayload, timeout5)调用方式也很直接send_dingtalk( webhookhttps://oapi.dingtalk.com/robot/send?access_tokenxxx, text荣耀商店自动更新完成成功3台失败1台\n失败设备: TEST123 )如果没有钉钉邮件也可以。Python的smtplib发信不复杂但对于这种工具型脚本我反而推荐用钉钉或Server酱这种即时消息通道因为你在干活的时候通常不会盯着邮箱但会看一眼群消息。通知内容不要只写“成功”或“失败”要把设备编号、应用名、版本号、异常原因带出来否则收到了通知还得翻日志就失去了通知的意义。5. 常见问题与排查技巧实录5.1 荣耀手机连接电脑adb devices不显示设备这个问题太典型了尤其是在MagicOS 8.0、Android 14的荣耀90系列上。很多人把USB调试打开后输入adb devices依然看不到设备或者显示unauthorized。排错步骤按我的习惯来先确认手机和电脑有没有用支持数据传输的数据线。很多第三方充电线只通电不通数据这是最容易被忽视的问题。在手机上打开“开发者选项”确认“USB调试”开启。荣耀的入口是“设置-关于手机-连续点击版本号7次”然后返回“设置-系统与更新-开发者选项”。插上数据线后手机通知栏会弹出USB连接方式默认是“仅充电”必须手动改成“传输文件”或“USB调试”。这里有个细节MagicOS可能会把通知折叠起来要点开才能看到选项。首次连接时手机会弹窗询问“允许USB调试吗”必须勾选“始终允许”并点击允许。如果之前点了拒绝需要先取消配对再重新插拔。如果以上都做了还是不行在电脑上执行adb kill-server adb start-server adb devicesWindows系统还差一个驱动。建议安装荣耀手机助手或者官方USB驱动装完后重新插拔手机。如果adb devices输出里设备状态是unauthorized说明手机端弹窗被忽略或拒绝了。我处理的方法是拔掉数据线在开发者选项里点“撤销USB调试授权”重新插上再授权一次。5.2 API返回400/签名校验失败接口返回400最常见的不是代码逻辑问题而是请求体结构和服务端期望的不一致。你可以先做一件事把组装好的请求体原样打印出来和抓包里看到的请求体逐字段对比。如果请求体完全一致但依然400再看签名。我实际遇到的签名问题有这么几类系统时间不准导致服务端认为时间戳过期。nonce一次性使用重放上一次请求的nonce会被拒绝。参与签名的字段顺序与排序规则不一致。secret里包含特殊字符拼接时没有做quote编码。调试签名最直接的办法是打印发出去的完整请求import json print(json.dumps(build_request(body), ensure_asciiFalse, indent2))然后和抓包里的请求体、请求头做diff。不要凭感觉改代码把两份JSON上下排在一起很快就能看出差异。5.3 APK下载后安装失败的典型报错安装这个环节最容易遇到各种奇怪报错我整理了一张高频问题速查表报错关键词含义解决方向INSTALL_FAILED_UPDATE_INCOMPATIBLE签名不一致确认下载包是否为荣耀商店的官方渠道包INSTALL_FAILED_VERSION_DOWNGRADE新包版本比当前低检查versionCode解析是否错误INSTALL_FAILED_USER_RESTRICTEDUSB安装权限受限在开发者选项里开启“USB安装”INSTALL_FAILED_NO_MATCHING_ABISCPU架构不匹配检查APK是否包含arm64-v8a适配INSTALL_FAILED_INSUFFICIENT_STORAGE手机存储不足清理设备空间后再重试需要注意的是INSTALL_FAILED_USER_RESTRICTED在荣耀MagicOS上非常常见。这不是ADB命令的问题而是系统安全策略要求用户在手机上手动允许“USB安装”。首次安装时手机屏幕会弹窗需要点一下允许之后同一台设备就不再弹了。多设备批量安装时最好先把所有手机都手动触发一次USB安装授权不然脚本会卡在弹窗上。5.4 HTTPS抓包看不到应用商店流量这个问题比想象中常见。明明证书装了、代理也设了浏览器流量能看到但应用商店就是没有请求出现。可能的原因有三个应用商店是系统应用某些系统版本默认不信任用户安装的CA证书。应用商店本身开启了SSL Pinning也就是证书固定。手机上的代理设置对系统应用不生效部分系统应用使用了独立的网络栈。第一种情况可以尝试通过“设置-安全-加密与凭据-受信任的凭据”检查用户凭据证书是否启用但MagicOS不同版本差异大。第二种情况要处理起来就麻烦一些需要Hook应用的证书验证逻辑果断放弃更省心。我后来换了个思路不直接抓手机商店而是先在电脑上装一个荣耀官方模拟器或者用一台老的、系统版本较低的荣耀备用机来抓包。老系统的信任策略通常松一些接口结构大同小异抓到后拿同一个接口在新设备上直接请求成功率也挺高。5.5 接口变化后的维护建议这个自动化方案最脆弱的地方就是接口本身。荣耀应用商店升级大版本时有可能会调整API路径、字段名甚至签名算法。但这不是坏事至少说明系统有安全边界意识。我的做法是每隔一段时间重新抓一次包抓完和代码里的接口卡片做对比字段变了就更新配置文件。另外不要把下载的APK一次性全删了。建议保留最近两个版本万一新版本有兼容性问题还能回滚到上一个版本重新安装。我在DOWNLOADS目录下会按应用名/版本号建子目录保留最近两版再老的就清理掉。这样虽然只是一个小细节但在调试问题时会特别有用。还有一点整套脚本跑久了之后建议在配置里加一个“当前脚本版本号”。每次发布新配置服务器上自动更新后通过通知消息能看到当前跑的版本避免出现“我改了代码但没生效结果在旧逻辑上继续跑了一周”的情况。最后再分享一个我个人的使用习惯这套自动更新工具我并没有让它每天频繁跑而是设置了每天两次早上点到晚上点。因为对测试机来说App版本更新不是越及时越好稳定性才更重要。让系统稳定跑上一两周比追求时时最新要靠谱得多。自动化解决的是重复劳动而不是把手机变成一台永不停机的更新机器。
返回列表