<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Android on Trevor's Blog</title><link>http://mings.in/tags/android/</link><description>Recent content in Android on Trevor's Blog</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Tue, 20 Oct 2026 10:49:18 +0800</lastBuildDate><atom:link href="http://mings.in/tags/android/index.xml" rel="self" type="application/rss+xml"/><item><title>Google Play 账号被封，我重开了一遍</title><link>http://mings.in/posts/gplay-reopen/</link><pubDate>Tue, 20 Oct 2026 10:49:18 +0800</pubDate><guid>http://mings.in/posts/gplay-reopen/</guid><description>账号被封那天，我的第一反应和你一样：再注册一个。</description><content:encoded><![CDATA[<h2 id="先别急着注册先看你那个号是怎么没的">先别急着注册，先看你那个号是怎么没的</h2>
<p>账号被封那天，我的第一反应和你一样：再注册一个。</p>
<p>但翻完官方文档我才发现，重开这件事根本不是一个流程问题。它分两种情况，结果差得很远。</p>
<p>第一种是休眠。账号长期没有发布更新、没有查看统计、没有管理商品详情，Google 会把它停用。帮助中心的原话是：休眠账号被停用后，您日后仍可创建新账号并在 Google Play 上发布内容。这种号，重开就是正常操作。</p>
<p>第二种是违规终止。同一篇文章里写着：如果您屡次或严重违反这些政策或开发者分发协议，您的开发者账号或相关的 Google Play 开发者账号可能会被终止。</p>
<p>请留意「相关的」这三个字。另一处措辞更直白：多次收到警示可能会导致个人以及相关的 Google Play 开发者账号被终止。</p>
<p>所以真正的问题不是「怎么重开」，是「重开之后能不能活」。</p>
<p><img alt="先判断你的号属于哪种情况" loading="lazy" src="/images/gplay-reopen/fig1.png"></p>
<h2 id="我为什么还是选了个人号">我为什么还是选了个人号</h2>
<p>重开的时候，账号类型这一步比第一次注册更要紧。</p>
<p>组织号要交邓白氏编码、组织注册文件、授权代表身份证，三份材料还得互相对得上。D-U-N-S 申请最多要等 30 天。号已经没了，再等一个月，太慢。</p>
<p>个人号只要一份政府签发的带照片证件，材料少，出错的点也少。</p>
<p>但不能为了省事硬选。官方有一份清单：金融产品与服务、健康类应用、使用 VpnService 的应用、政府应用，这四类必须用组织号。你的应用沾上其中任何一条，个人号阶段看不出来，审核到后面一样会被打回来。</p>
<h2 id="重开的六个动作">重开的六个动作</h2>
<p>顺序有讲究，尤其第二步。第一步是把 Google 账号准备好，用干净的号，别拿注册过别的开发者账号的，也别买号。</p>
<p>第二步，先去 Google Payments 建付款资料。国家/地区选中国，法定姓名和地址按身份证原文填。这一步不能省，也不能放到后面补。</p>
<p>第三步，用这个账号在 Play 管理中心注册开发者账号，交 25 美元。卡种限 MasterCard、Visa、American Express、Discover（仅限美国）和 Visa Electron（非美国），不收预付卡。</p>
<p>第四步，选个人号。</p>
<p>第五步，身份验证，交政府签发的带照片身份证件。</p>
<p>第六步，设备验证。找一台 Android 10 以上、没 root 的真手机，装 Play 管理中心手机应用扫二维码，官方说这一分钟就能完成。</p>
<p><img alt="重开的六步，第二步是地基" loading="lazy" src="/images/gplay-reopen/fig2.png"></p>
<p>顺序为什么不能跳？电话号码验证排在身份验证和设备验证之后。前面没做完，电话验证的入口压根不给你开。</p>
<h2 id="通过之后首页长这样">通过之后，首页长这样</h2>
<p>验证过了那天进 Console，第一眼容易失望：正式版 0 个应用、政策任务 0 个应用、发布提交内容 0 个应用，三张卡片全是零。</p>
<p>这是对的。账号通过只代表你现在能创建应用，不代表应用能上架。真正的起点是右上角那个「创建应用」。</p>
<p>这一页有两个地方值得记一下。顶上那排按钮里有个「政策状态」，账号信誉出问题会先出现在那里。下面列表里那个带「草稿」标签的，是建好还没提交审核的应用。中间那条绿色横幅只是 Play 的改版通知，跟你的账号状态没有任何关系。</p>
<p><img alt="审核通过后的 Play Console 中文首页" loading="lazy" src="/images/gplay-reopen/fig4-console.jpg"></p>
<h2 id="地址这一段我花的时间最多">地址这一段，我花的时间最多</h2>
<p>第一次注册时随手填的地址，这次不敢了。</p>
<p>官方在「按国家/地区划分的必要文件」那页写了一句话，我给抄下来：</p>
<blockquote>
<p>提交的所有文件都必须与您的支付资料中的信息完全一致，包括：个人身份详细信息、组织名称（针对组织账号）、地址信息。如果您发现自己的证件和支付资料之间存在任何不一致，可在执行验证任务期间更新支付资料信息。</p>
</blockquote>
<p>这句话解释了大部分人被驳回的原因。验证不是核对材料齐不齐，是核对材料和你自己填的资料对不对得上。</p>
<p>有三条线要逐字对。</p>
<p>姓名这条，证件上是中文就写中文，别一处写「张伟」一处写「Zhang Wei」。</p>
<p>地址这条，付款资料里的地址才是法定地址，Play 对外展示的国家和地区也按它算。证件写「XX 区 XX 路 128 号 3 栋 502」，你只写到「XX 路」，就算不一致。省市区街道门牌，能写到哪层写哪层。</p>
<p>证件这条，官方给了四条硬要求：有效未过期、彩色不能黑白、清晰可辨且明暗适中、不能是复印件。</p>
<p><img alt="三条线要逐字对上" loading="lazy" src="/images/gplay-reopen/fig3.png"></p>
<p>已经被驳回了也不用重新注册。同一句话的后半段写着，验证任务期间可以更新支付资料，把地址改成和证件一致，再交一次。</p>
<h2 id="新号怎么不被连坐">新号怎么不被连坐</h2>
<p>这一段是我这次最想写清楚的部分，因为它没有技巧，只有事实。</p>
<p>官方说「相关的开发者账号可能会被终止」，意思是 Google 会看几个东西是不是同一套：同一个人、同一张付款卡、同一台设备、同一批包名、同一个收款账户。</p>
<p>重开一个号，这些痕迹如果原封不动搬过去，等于自己举手说「还是我」。</p>
<p>真正能减少风险的，是原号被封的原因本身不严重，或者已经申诉和解。如果是恶意软件、欺诈这类严重违规，换十个号也一样。这种情况下重开，25 美元不退，号还会再被封一次。</p>
<p>说难听点，官方那句「相关的开发者账号」就是专门堵这条路的。</p>
<h2 id="交材料之前先在本地看两眼">交材料之前，先在本地看两眼</h2>
<p>官方那四条照片要求，有两条在 Mac 上就能验：是不是彩色、分辨率够不够。系统自带 sips，不用装东西。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sips -g pixelWidth -g pixelHeight -g space 你的证件.jpg
</span></span></code></pre></div><p>看 space 那一行。写 RGB 是彩色，写 Gray 就是黑白，黑白图一定被退。短边低于 1000 像素也建议重拍，微信转存过的图经常只剩几百像素。</p>
<p>我写了个小脚本把这两条串起来，跑出来是这样：</p>
<pre tabindex="0"><code>id-color.png        1600×1000  space=RGB   ✓ 通过
id-gray-small.png   640×400    space=Gray  ✗ 短边 400px  ✗ 灰度
</code></pre><h2 id="最后一句实话">最后一句实话</h2>
<p>重开这件事，材料和流程都是次要的，第一位的永远是你上一个号为什么没了。</p>
<p>休眠停用的，按上面六步走一遍就行。严重违规被终止的，别指望换号能翻篇，先把问题解决，再谈注册。</p>
<p>你重开过吗，卡在哪一步？欢迎评论区聊聊。</p>
]]></content:encoded></item><item><title>Google Play 注册：地址要和身份证一致</title><link>http://mings.in/posts/gplay-register/</link><pubDate>Tue, 20 Oct 2026 10:00:45 +0800</pubDate><guid>http://mings.in/posts/gplay-register/</guid><description>上周又有人问我 Play 开发者账号被拒了怎么办。翻他的材料，身份证拍了、信用卡也绑了、25 美元扣款成功，问题出在地址，他那张卡的开户地址是公司，身份证上的地址是老家。</description><content:encoded><![CDATA[<h2 id="驳回最多的原因不是材料不齐">驳回最多的原因，不是材料不齐</h2>
<p>上周又有人问我 Play 开发者账号被拒了怎么办。翻他的材料，身份证拍了、信用卡也绑了、25 美元扣款成功，问题出在地址，他那张卡的开户地址是公司，身份证上的地址是老家。</p>
<p>中文教程讲注册流程的很多，讲地址对齐的几乎没有。而 Google 自己把这件事写在了帮助中心最显眼的位置。</p>
<h2 id="官方原话">官方原话</h2>
<blockquote>
<p>提交的所有文件都必须与您的支付资料中的信息完全一致，包括：个人身份详细信息、组织名称（针对组织账号）、地址信息。如果您发现自己的证件和支付资料之间存在任何不一致，可在执行验证任务期间更新支付资料信息。</p>
</blockquote>
<p>紧接着还有一句，说的是失败原因：</p>
<blockquote>
<p>提交不受支持的文件是开发者验证失败的主要原因，而提供经过修改或伪造的文件则可能会导致您的账号和应用被立即移除。</p>
</blockquote>
<p>这两句话把规则说透了：验证不是看你有没有材料，是看你交的材料能不能和你自己填的资料对上。对不上，材料再齐全也是错的。</p>
<p><img alt="三条线必须逐字对上" loading="lazy" src="/images/gplay-register/fig1.png"></p>
<h2 id="地址不是你填在注册页的那个">地址不是你填在注册页的那个</h2>
<p>很多人以为地址是注册表单里随手填的一栏。不是。</p>
<p>Play 的法定全名和法定地址，是从你账号关联的 Google 付款资料里取的。也就是说，你在 Google Payments 里存的那份地址，才是最终被拿去和身份证比对的东西。Play 页面上对外展示的国家和地区，也是按这份法定地址来算的。</p>
<p>顺序就清楚了：先把付款资料写对，再开开发者账号。反过来做，等于给自己的审核埋雷。</p>
<h2 id="三条线必须对得上">三条线必须对得上</h2>
<p>第一条是姓名。证件上的名字是中文，付款资料里也写中文。如果系统只吃拉丁字符，就用一套统一的转写规则，别一处写「张伟」一处写「Zhang Wei」，更别一处写全拼一处写缩写。</p>
<p>第二条是地址。省、市、区、街道、门牌号，能写到哪一层就写到哪一层，和证件上的写法逐字对。证件写「XX 区 XX 路 128 号 3 栋 502」，你付款资料只写「XX 路」，这就算不一致。</p>
<p>第三条是证件照片。官方给的四条要求很具体：证件有效未过期、彩色而非黑白、清晰可辨且明暗适中、不能是复印件。四项里任何一项不过，都算不受支持的文件。</p>
<p>有一条官方原文里没有、但实操中反复踩到的：绑定的银行卡账单地址最好也落在同一套地址上。这条没有官方依据，是经验判断，仅供你参考。</p>
<h2 id="全中文注册的六步顺序">全中文注册的六步顺序</h2>
<p>第一步，先做一个干净的 Google 账号。别用之前注册过别的开发者账号的号，也别用买来的号，后面身份验证会认人。</p>
<p>第二步，进 Google Payments 建付款资料，把国家/地区选中国，法定姓名和地址按身份证原文填。这一步的地址，后面所有验证都以它为准。</p>
<p>第三步，在 Play 管理中心用这个账号注册开发者账号，交 25 美元。卡种限 MasterCard、Visa、American Express、Discover（仅限美国）和 Visa Electron（非美国），不收预付卡。</p>
<p>第四步，选账号类型。个人号选个人，别为了「看起来正式」硬选组织，组织号要多交 D-U-N-S 编码和公司证件，材料差一样就卡住。</p>
<p>第五步，做身份验证，交政府签发的带照片身份证件。个人号只要这一份。</p>
<p>第六步，设备验证。拿一台 Android 10 以上、没 root 的真手机，装 Play 管理中心手机应用扫二维码，一分钟。</p>
<p><img alt="六步顺序，第二步是地基" loading="lazy" src="/images/gplay-register/fig2.png"></p>
<p>顺序不能跳。电话号码验证被排在这几项之后，身份验证没做完，电话验证的入口根本不给你开。</p>
<h2 id="组织号要多两份地址同样要对齐">组织号要多两份，地址同样要对齐</h2>
<p>组织号除了代表人的身份证，还要组织注册文件（营业执照这类）和邓白氏编码。三份材料里的组织名称和地址，必须和付款资料里写的逐字一致。</p>
<p>D-U-N-S 在邓白氏官网免费申请，官方特意提醒过有第三方收高价代办费，别上当。申请最多要等 30 天，所以准备做组织号的，这件事要提前一个月动手。</p>
<h2 id="交上去之前自己先过一遍">交上去之前，自己先过一遍</h2>
<p>官方那四条照片要求，在本机就能验两条：是不是彩色、分辨率够不够。macOS 自带 sips，不用装东西。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sips -g pixelWidth -g pixelHeight -g space 你的证件.jpg
</span></span></code></pre></div><p>看 <code>space</code> 那一行。写着 <code>RGB</code> 是彩色，写着 <code>Gray</code> 就是黑白，黑白图一定会被退回。短边低于 1000 像素也建议重拍，压缩过的微信图片经常只有几百像素。</p>
<p>我把这几条写成了一个小脚本，跑两个样图给你看效果：</p>
<pre tabindex="0"><code>id-color.png        1600×1000  space=RGB   ✓ 通过
id-gray-small.png   640×400    space=Gray  ✗ 短边只有 400px  ✗ 色彩空间是 Gray
</code></pre><p><img alt="本机自检的两条" loading="lazy" src="/images/gplay-register/fig3.png"></p>
<h2 id="已经被驳回了怎么办">已经被驳回了怎么办</h2>
<p>不用重新注册。官方说的是，在执行验证任务期间可以更新支付资料信息，把地址改成和证件一致，再重新提交一次。</p>
<p>真正没救的是伪造。用 PS 改过地址的证件、借来的身份证、买来的营业执照，被查出来是账号和应用直接移除，25 美元也不退。</p>
<p>地址这件事说小很小，一行字。说大也很大，它决定了你后面所有验证能不能过。你注册时在地址上踩过坑吗？欢迎评论区聊聊。</p>
]]></content:encoded></item><item><title>Google Play 开发者账号：25 美元之后的五道关</title><link>http://mings.in/posts/gplay-dev-account/</link><pubDate>Mon, 19 Oct 2026 15:38:12 +0800</pubDate><guid>http://mings.in/posts/gplay-dev-account/</guid><description>中文教程里关于 Play 开发者账号的说法，大多停在两年前：25 美元、一张信用卡、填个地址、等一天。这套现在不够用了。</description><content:encoded><![CDATA[<h2 id="二十五美元只是买张门票">二十五美元只是买张门票</h2>
<p>中文教程里关于 Play 开发者账号的说法，大多停在两年前：25 美元、一张信用卡、填个地址、等一天。这套现在不够用了。</p>
<p>官方帮助中心现在写的是：年满 18 周岁，接受开发者分发协议，用信用卡或借记卡交 25 美元，卡种限 MasterCard、Visa、American Express、Discover（仅限美国）和 Visa Electron（非美国），明确不收预付卡。</p>
<p>交完钱才刚进门。后面还有身份验证、设备验证、十二个人十四天的封闭测试，以及 9 月 30 日生效的那套新规则。</p>
<p><img alt="开一个号要走的六步" loading="lazy" src="/images/gplay-dev-account/fig2.png"></p>
<h2 id="个人号和机构号先选对">个人号和机构号，先选对</h2>
<p>Play 的开发者账号只有两种：个人、组织。功能一样，都能创收，也都能加协作者。</p>
<p>差别在材料。组织号必须提供邓白氏编码，也就是 D-U-N-S，那是邓白氏发的九位企业标识，能免费申请，但要等。官方另给了一份清单：金融服务、健康类应用、使用 VpnService 的应用、政府应用，这四类必须走组织号。</p>
<p>个人号注册快，代价是后面几条测试要求专门冲着个人号来。</p>
<h2 id="身份验证证件和卡得是同一个人">身份验证：证件和卡得是同一个人</h2>
<p>官方原话是，为处理注册申请，可能要求提供由政府签发的有效身份证件和信用卡信息，持有人必须都是你本人。核实后发现信息无效，注册费不退。</p>
<p>这条在国内常被忽略。拿别人的卡付款、或者用亲戚的证件补材料，都算信息不一致。25 美元不算多，但退不回来。</p>
<h2 id="设备验证一分钟但要一台真手机">设备验证：一分钟，但要一台真手机</h2>
<p>2024 年初起，新个人号在上架前必须先证明自己有一台真实的 Android 设备。做法是用 Play 管理中心的手机应用扫二维码，登录同一个 Google 账号，选中开发者账号，点验证。</p>
<p>门槛是 Android 10 或更高、没有 root 的实体设备。模拟器不算，root 过的机器也不算。官方说这一步不该超过一分钟，同一台设备可以验证多个账号，验证过程不会采集设备手机号。</p>
<h2 id="十二个人十四天">十二个人，十四天</h2>
<p>这条最容易被过期教程坑。很多文章还写着找二十个测试者连续测十四天，官方页面现在写的是：至少 12 名测试人员，在过去至少 14 天内选择连续参与封闭测试。</p>
<p>这个数字变过，中文教程大面积没跟上。按官方当前口径就是 12。</p>
<p>规则只对 2023 年 11 月 13 日之后创建的个人号生效。满足之后才能在信息中心申请正式版发布权限，申请时要回答应用设计、测试流程、上线准备这几类问题。</p>
<h2 id="9-月-30-日起验证的范围超出了-play">9 月 30 日起，验证的范围超出了 Play</h2>
<p>这段是这次真正的新东西。Android 开发者验证在 2026 年 9 月 30 日进入执行阶段，首批是巴西、印度尼西亚、新加坡、泰国四个国家，覆盖七家商店的分发：Google Play、荣耀应用市场、OPPO 软件商店、三星 Galaxy Store、传音 Palm Store、vivo 应用商店、小米 GetApps。</p>
<p>换句话说，认证设备上装应用从此要问一句，这个包是谁签的。官方给的时间线是 2027 年全球铺开。</p>
<p><img alt="规则是一层一层加上去的" loading="lazy" src="/images/gplay-dev-account/fig1.png"></p>
<p>它把开发者分成三条路，身份验证、费用、能分发到哪，各不相同。</p>
<p><img alt="三条分发路径的取舍" loading="lazy" src="/images/gplay-dev-account/fig3.png"></p>
<p>三个关键细节。</p>
<p>完整分发要在 Android Developer Console 单独开号，费用同样是 25 美元，和 Play 那笔是两回事。有限分发免费、不需要身份证件，但只能覆盖 20 台设备，用户还得接受邀请才能安装。未验证侧载那条路，用户可以自己走高级流程放行，官方把它描述成给用户争取时间和空间的一道缓冲。</p>
<p>还有一个细节对写代码的人最重要。官方 FAQ 写得很直白：用 ADB 安装应用不需要注册，也不需要验证。开发、调试、自己刷机测试，这条通道没关。企业托管设备上的应用同样不用走验证，IT 管理员已经审过了。</p>
<h2 id="注册包名要交的东西本机能先备好">注册包名要交的东西，本机能先备好</h2>
<p>新验证体系的第三步是注册包名，官方要求提供用你的私钥签名的 APK，以此证明这个包归你。这一步不用等账号批下来，本机就能预演。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">keytool -genkeypair -v -keystore demo.jks -storetype PKCS12 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -alias upload -keyalg RSA -keysize <span class="m">4096</span> -validity <span class="m">10000</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -dname <span class="s2">&#34;CN=Demo Dev, O=Demo, C=CN&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">keytool -list -v -keystore demo.jks <span class="p">|</span> grep SHA256
</span></span></code></pre></div><p>跑出来是一串 SHA-256，我这边生成的是 52:05:B5:73:…，后面还有三十个字节。这串指纹就是以后核对包名归属的东西，私钥一丢，它就再也对不上。所以从第一天起，密钥文件和它的备份要当成同一件东西管。</p>
<h2 id="跨平台开发者要算的账">跨平台开发者要算的账</h2>
<p>如果你用 Kotlin Multiplatform 或者 Flutter 一套代码打安卓，这里的账要分开算。Play 上架 25 美元，Play 之外分发再走 Android Developer Console，又 25 美元，两个号都要身份验证。</p>
<p>能省的地方在别处：设备验证、十二人十四天的测试，只跟个人号的正式版发布权限绑。组织号不背这一条。</p>
<p>真正省事的做法是上架照旧走 Play，同时在 Android Developer Console 把包名和签名密钥登记好。这样以后无论用户从哪个商店下、还是从你官网直装，安装时都不会被拦下来。</p>
<p>25 美元买的是一张门票。门票后面这几道关，才是今年真正花时间的地方。你开号时卡在哪一步？欢迎评论区聊聊。</p>
]]></content:encoded></item><item><title>Agent 的官方命令行：Android CLI 1.0</title><link>http://mings.in/posts/android-cli/</link><pubDate>Wed, 14 Oct 2026 14:36:53 +0800</pubDate><guid>http://mings.in/posts/android-cli/</guid><description>你让 agent 写了一段 Kotlin，它说改好了。然后呢？</description><content:encoded><![CDATA[<h2 id="先说一个断掉的地方">先说一个断掉的地方</h2>
<p>你让 agent 写了一段 Kotlin，它说改好了。然后呢？</p>
<p>你得自己开 Android Studio，等 Gradle 转圈，等模拟器起来，再用鼠标戳一下界面，看那句文案是不是真的变了。命令行里 agent 跑得飞快，到了看屏幕这一步就断了，剩下的活儿归你的眼睛。</p>
<p>Google 这次补的就是这一段。Android CLI 进了 1.0 稳定版，官方博客的原话是这样：</p>
<blockquote>
<p>无论您是在 Android Studio、Antigravity 2.0、Antigravity CLI 中使用 Gemini，还是使用 Anthropic 的 Claude Code 或 OpenAI 的 Codex 等第三方代理，我们的使命始终如一：确保随时随地都能进行高质量的 Android 开发。</p>
</blockquote>
<p>意思很直白：不管你用哪个 agent，官方都想让你别离开命令行。我在本机跑了一遍，从建工程一直跑到看屏幕。</p>
<h2 id="它交付了什么">它交付了什么</h2>
<p>下面这份清单，我按「它能替你的 agent 少做什么」来说。</p>
<h3 id="一个-android-命令">一个 android 命令</h3>
<p>装上之后你得到 <code>android</code> 这一个命令，19 个顶层子命令，建工程、起模拟器、装应用、看屏幕都在里面：</p>
<pre tabindex="0"><code>auth completion create describe device docs emulator info init
install layout run screen sdk skills studio update help
</code></pre><p>以前 agent 要记 adb、emulator、gradle 三套参数，现在一个入口对着一个 SDK。它只往用户目录装一个 3.6 MB 的单文件程序，装完 <code>android info</code> 就能报出 SDK 路径和版本。</p>
<h3 id="官方技能库">官方技能库</h3>
<p><code>android init</code> 这步有点意思。它没有任何参数，跑完却把一份 24,922 字节的 <code>android-cli</code> 技能写进了你机器上每一个 agent 的技能目录。我这边命中了 10 个：</p>
<pre tabindex="0"><code>~/.claude/skills          ~/.gemini/skills           ~/.agents/skills
~/.commandcode/skills     ~/.hermes/skills           ~/.junie/skills
~/.openclaw/skills        ~/.config/opencode/skills  ~/.pi/agent/skills
~/.gemini/antigravity/skills
</code></pre><p>Hermes 在里面，Claude Code、Codex、Gemini、OpenClaw、OpenCode 这些也都在。跑完建议看一眼它都往哪些目录写了东西，毕竟是往你的 home 里落文件。</p>
<p>技能库里现在有 24 个技能，<code>android skills list</code> 能列全。界面类的有 adaptive（自适应界面）、camerax（相机迁移）、edge-to-edge、wear-compose-m3（Wear OS）。</p>
<p>工程治理类的也有几个：play-policy-insights（政策解读）、restore-credentials、testing-setup、r8-analyzer。装上就一句 <code>android skills add &lt;技能名&gt;</code>，还能指定装给哪个 agent。</p>
<h3 id="离线的官方文档库">离线的官方文档库</h3>
<p><code>android docs search &quot;edge to edge&quot;</code> 第一次跑会下载索引，实测 5,319 条。返回的不是网址，是 <code>kb://android/...</code> 这种协议地址。</p>
<p>连技能里的示例代码都在里面，比如 <code>kb://android/agents/skills/wear/wear-compose-m3/references/material3/EdgeButtonSample.kt</code>。agent 不用再去搜博客，官方文档本地可查。</p>
<h3 id="看屏幕这组命令">看屏幕这组命令</h3>
<p>这组是整篇里最实用的。<code>screen capture --annotate</code> 会把界面上的元素标成 <code>#1</code>、<code>#2</code>，<code>screen resolve</code> 再把标号换成坐标：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">android --no-metrics screen capture --annotate --output shots/12-annotated.png
</span></span><span class="line"><span class="cl">android --no-metrics screen resolve --screenshot shots/12-annotated.png --string <span class="s2">&#34;tap #1 and #2&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 输出：tap 72 31 and 835 30</span>
</span></span></code></pre></div><p>agent 不必再算像素，指着 <code>#3</code> 说点这个就行。同一组的 <code>layout</code> 直接返回界面 JSON 树，每个控件的 <code>bounds</code> 和中心点都在。</p>
<h3 id="借用-android-studio-的语义分析">借用 Android Studio 的语义分析</h3>
<p>新增的 <code>android studio</code> 子命令有这些：<code>find-declaration</code> 找定义、<code>find-usages</code> 找引用、<code>analyze-file</code> 跑检查、<code>render-compose-preview</code> 渲染 Compose 预览、<code>version-lookup</code> 查依赖版本、<code>open-file</code> 在 IDE 里开文件。</p>
<p>等于 agent 能借到 IDE 攒了十几年的语义分析，不用靠正则猜。这条有门槛，要求 Android Studio Quail 1 或更高。</p>
<h3 id="预约一台真机">预约一台真机</h3>
<p><code>android device remote</code> 可以预约远程真机来跑，官方博客贴的例子是连着一台 Pixel 10 Pro。这条路要先 <code>android auth</code> 登录 Google 账号。</p>
<h2 id="我在-macos-上从零跑了一遍">我在 macOS 上从零跑了一遍</h2>
<p>能不能跑通才算数。下面这套是我本机实跑的命令，一条没省：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 装：先把脚本下下来读一遍，别直接 pipe 进 shell</span>
</span></span><span class="line"><span class="cl">curl -fsSL -o /tmp/android-cli-install.sh <span class="se">\
</span></span></span><span class="line"><span class="cl">  https://dl.google.com/android/cli/latest/darwin_arm64/install.sh
</span></span><span class="line"><span class="cl">bash /tmp/android-cli-install.sh            <span class="c1"># → ~/.local/bin/android，3,650,880 字节</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">android --no-metrics -V                     <span class="c1"># 1.0.16500706，首次要过一次 Android SDK 条款</span>
</span></span><span class="line"><span class="cl">android init                                <span class="c1"># 把技能写进各 agent 的技能目录</span>
</span></span><span class="line"><span class="cl">android --no-metrics skills list            <span class="c1"># 官方技能目录，实测 24 个</span>
</span></span><span class="line"><span class="cl">android --no-metrics docs search <span class="s2">&#34;edge to edge&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 端到端：建工程 → 起模拟器 → 出包 → 装上去 → 看屏幕</span>
</span></span><span class="line"><span class="cl">android --no-metrics create --name CliDemo <span class="se">\
</span></span></span><span class="line"><span class="cl">  --application-id com.example.clidemo --min-sdk <span class="m">36</span> -o ~/android-cli-repro/CliDemo
</span></span><span class="line"><span class="cl">android --no-metrics emulator list          <span class="c1"># 我这儿已有的 AVD：Medium_Phone_API_36.1</span>
</span></span><span class="line"><span class="cl">android --no-metrics emulator start Medium_Phone_API_36.1   <span class="c1"># 等到就绪才返回，约 17 秒</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/android-cli-repro/CliDemo
</span></span><span class="line"><span class="cl">./gradlew :app:assembleDebug                <span class="c1"># 首次：BUILD SUCCESSFUL in 1m 24s</span>
</span></span><span class="line"><span class="cl">android --no-metrics run --apks app/build/outputs/apk/debug/app-debug.apk
</span></span><span class="line"><span class="cl">android --no-metrics screen capture --output shots/10-before.png
</span></span><span class="line"><span class="cl">android --no-metrics layout
</span></span></code></pre></div><p>先说建工程。<code>create</code> 出来的工程是 Compose + AGP 9 + Kotlin 2.3.20，我都没去 SDK Manager 里点，它自己发现缺 <code>platforms/android-36</code> 就装上了。</p>
<p><code>emulator start</code> 这个设计挺舒服：一直等到设备完全启动才返回，中间只打印剩余秒数，不用自己轮询 adb。我这边 17 秒就绪。</p>
<p>真正让我意外的是第二次。我改了一行文案，把数据源里那句 <code>listOf(&quot;Android&quot;)</code> 改成 <code>listOf(&quot;Android CLI&quot;)</code>，重跑一轮：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">./gradlew :app:assembleDebug                <span class="c1"># 增量：BUILD SUCCESSFUL in 8s</span>
</span></span><span class="line"><span class="cl">android --no-metrics run --apks app/build/outputs/apk/debug/app-debug.apk
</span></span><span class="line"><span class="cl"><span class="c1"># Delta Install: Sent 167.94 KB patch instead of 28.67 MB (1 APK, 99.43% savings)</span>
</span></span></code></pre></div><p>第一次装传了 28.64 MB，第二次只传了 167.94 KB 的补丁，省了 99.43%。增量安装默认就是开的（<code>--use-delta-install</code> 默认 true），改文案、看效果这种来回折腾的活儿，省下的全是等待时间。</p>
<p>改完再用 <code>layout</code> 读界面树，文案已经换新的了：</p>
<pre tabindex="0"><code>应用窗口: CliDemo  焦点=True  边界=[0,0][1080,2400]
  - android.widget.TextView | text=&#39;Hello Android CLI!&#39; | center=[220,136]
</code></pre><p><img loading="lazy" src="/images/android-cli/fig-loop.png"></p>
<p><img loading="lazy" src="/images/android-cli/fig-agent.png"></p>
<h2 id="上手门槛和几个坑">上手门槛和几个坑</h2>
<p>最容易绊人的是 <code>run</code>。它的说明写「构建、部署、启动」，我这里不带 <code>--apks</code> 直接失败：</p>
<pre tabindex="0"><code>Error: No apks specified. Use --apks &lt;path1,path2,...&gt;
Component check failed
</code></pre><p>APK 已经构建好、<code>describe</code> 也刚定位到它，<code>run</code> 还是不肯自己动手。实际分工是出包归工程自己的 <code>./gradlew</code>，<code>run</code> 负责装和起。</p>
<p>还有一个更隐蔽的。包部署上去了，界面却没变，旧进程还活着，<code>run</code> 不会替你强杀。补一条 <code>adb shell am force-stop com.example.clidemo</code> 再跑就对了。</p>
<p>版本门槛也在。<code>android studio</code> 那组命令要 Android Studio Quail 1 或更高，版本不够报的是 <code>No running Studio instances found</code>，还附一句让你去官网下新版。</p>
<p>另外两点：<code>android init</code> 是往你的 home 目录里写文件，不是只读操作，机器上 agent 多的话建议先看清名单；子命令是单数 <code>device</code>，写成 <code>android device list</code> 会报 <code>'list' is not a valid command</code>。</p>
<p>还有 <code>android completion</code> 会往 shell 配置里写自动补全，不想动配置就别跑它。</p>
<p>遥测也得提一句。这个 CLI 默认会收数据，官方文档写了收什么：命中的子命令、用到的参数名，以及一部分固定取值，比如模拟器模板名和 agent 名（<code>GEMINI</code>、<code>CLAUDE</code>、<code>CODEX</code>）。不想要的加 <code>--no-metrics</code>，我上面所有命令都带着。</p>
<h2 id="最后一句">最后一句</h2>
<p>这版没有另搞一套给 agent 用的流程，改的是原本那套东西：文档、技能、模拟器、真机、IDE 的语义分析，都换成了机器能读的接口。</p>
<p>细节上也照顾到了，像素坐标换成 <code>#3</code> 这种标号，UI 树直接给 JSON。</p>
<p>你手上那套工具链，敢不敢把「改代码到看屏幕」这一段整个交给 agent？评论区聊聊。</p>
]]></content:encoded></item><item><title>安卓侧载收紧，跨平台开发者的分发账要重算了</title><link>http://mings.in/posts/android-distro-cost/</link><pubDate>Fri, 09 Oct 2026 07:39:00 +0800</pubDate><guid>http://mings.in/posts/android-distro-cost/</guid><description>一个跨平台项目到了发版的时候，分发路径通常不止一条。Google Play 一条，官网直发一条，有时候还要给测试同学一个内测包。安卓这套开发者验证，把每条路的准入成本重新标了一次价，而这三笔账的算法完全不一样。</description><content:encoded><![CDATA[<p>一个跨平台项目到了发版的时候，分发路径通常不止一条。Google Play 一条，官网直发一条，有时候还要给测试同学一个内测包。安卓这套开发者验证，把每条路的准入成本重新标了一次价，而这三笔账的算法完全不一样。</p>
<h2 id="三条路三笔账">三条路，三笔账</h2>
<p>先说走 Google Play 这条。对已经有 Play 开发者账号的团队，这笔账几乎是零。应用已经在验证流程里，用了 Play App Signing 的包名还会被自动认领，你要做的只是去开发者验证页确认状态。新增的成本是时间，审核周期照旧。</p>
<p>再说侧载这条。用户在浏览器里下你的 APK 直接装，2026 年 9 月 30 日之后在四个国家要过身份校验：包名必须注册过，而且这个包得由你的私钥签名。</p>
<p>官方留了出口，Android Debug Bridge 的开发安装不受影响，advanced flow 让用户可以一次性放行，免费的有限分发账号能覆盖 20 台设备。代价在于，advanced flow 一旦关掉，未注册应用的更新就会失败。留给测试同学的那些包，得算进这 20 台里。</p>
<p>然后是走企业内部分发这条。托管设备从组织商店安装应用，官方给了豁免，前提是设备真的被管着。如果同一个应用还会流到员工自己没托管的手机上，建议照样注册。这条对跨平台团队的意义在于，内部测试包和演示包也算包名和身份，不能靠「内部用」蒙过去。</p>
<h2 id="跨平台团队的账为什么更复杂">跨平台团队的账为什么更复杂</h2>
<p>第一是包名数量。KMP 和 Flutter 项目一个仓库常常出好几个产物，主包、各种 flavor、内测包，每个都有自己的包名。验证是按包名和签名密钥走的，所以清单要覆盖所有产物和所有密钥，而不是只覆盖上架那一个。</p>
<p>第二是密钥来源。同一个团队里可能同时存在 CI 机器上生成的 keystore、某位同学本地留着的 keystore、以及 Play App Signing 托管的那份。名字一多，就容易出现「发出去的包不是这把密钥签的」。</p>
<p>第三是提交物。这套验证最终要你提交的只有两样：包名，和用你私钥签名的 APK。前者要查重，后者要能自证。</p>
<p>顺便说清楚边界，这套验证只管 Android 认证设备上的包名。如果同一个项目还发鸿蒙或者 iOS，那两边的签名材料是另一套体系，不在这套规则里。但「归档密钥加能自查」这个动作，两边都该做。</p>
<h2 id="第一件事密钥归档并且能自证">第一件事：密钥归档，并且能自证</h2>
<p>注册时要证明包名归你，官方给的凭据是「用你的私钥签名的 APK」。也就是说签名密钥从构建缓存里的东西，升格成了身份证。官方 FAQ 专门写了一条，密钥丢了就没法注册包名，建议用密钥管理方案。</p>
<p>光归档还不够，你得能随时自证。我写了一个 30 行的小脚本，跑一次就能看出一个 APK 的包名和证书指纹，还能跟归档的指纹清单对比。同一份代码、同一个包名，换一把密钥签，输出的结果就分叉了：</p>
<p><img loading="lazy" src="/images/android-distro-cost/terminal.png"></p>
<p>包名一模一样，指纹完全不同，这就是「用错了密钥」的样子。这种事在团队里发生的概率比想象中高，尤其在换了 CI 机器或者有人重新生成了 keystore 之后。</p>
<p>归档建议就三条。keystore 文件、别名、指纹清单放一起，进密码管理器或者团队保险库；指纹清单用纯文本，一行一个 SHA-256，方便机器比对；这一步挂进发布流程当门禁，比对不过就不许发。官方文档里还提到，一个包名可以登记多把签名密钥，团队做密钥轮换时用得上。</p>
<h2 id="第二件事上架前先查包名有没有被占">第二件事：上架前先查包名有没有被占</h2>
<p>包名是先到先得认领的，撞名这件事要在起名的时候查，不是在上架那天查。</p>
<p>Google 开了 Android Developer ID Status API 专门查这个，返回三种状态：已注册、未注册、以及已注册但对应的是另一把证书指纹。最后一种最麻烦，说明包名被别人占了。</p>
<p>撞名之后官方给的路是申请注册，但要额外审核，而且文档里明说了，那个包名可能同时被其他开发者使用。两个应用共用一个包名意味着什么，做过 Android 的都清楚。</p>
<p>这个接口有配额，每个项目每天 1000 次，要挂在 Google Cloud 项目上：先建项目、开通对应的 API、配好鉴权。我本机没有这个条件，所以这条只按文档写，我没有实际发起过请求。日常看包名状态，Play Console 和 Android Developer Console 的包名列表里直接就有，显示为已注册、未注册或者草稿。</p>
<h2 id="第三件事账号和材料能办的先办">第三件事：账号和材料，能办的先办</h2>
<p>个人开发者就是身份验证这一步。组织要多两样：D-U-N-S 编号，以及一个用 Search Console 验证过的官网。</p>
<p>D-U-N-S 免费，去邓白氏官网就能申请，但流程最长 28 天。这是整份清单里唯一一个等待时间压不掉的环节，官方帮助文档还特意提醒，当心第三方服务拿这件事收高额手续费。</p>
<p>费用方面，完整分发账号 25 美元一次性，和 Play 的注册费一个价位；有限分发免费。钱和填表都不是大问题，真正的时间成本在前面那 28 天。</p>
<h2 id="三个会让你卡住的坑">三个会让你卡住的坑</h2>
<p>第一个是密钥丢失。这不是「麻烦一下」，是没法注册包名，等于这个包名你再也认不回来。</p>
<p>第二个是包名被别人先注册。申请之后可能和对方共用，等于埋了一颗不知道什么时候炸的雷。</p>
<p>第三个藏在有限分发这条路上。advanced flow 关掉之后，未注册应用的更新就会失败。官方 FAQ 写得很明确，未注册的应用只能靠 advanced flow 或者 Android Debug Bridge 安装与更新。那 20 台设备的场景，记得别把开关关掉。</p>
<h2 id="哪些现在做哪些可以等">哪些现在做，哪些可以等</h2>
<p>已经生效的是四个国家、七家参与商店（Google Play、三星 Galaxy Store、小米 GetApps 这些），覆盖 Android 7 以上的认证设备，第一阶段只管手机和平板两种形态。</p>
<p>现在就能做的三件事：归档密钥并接上自查、查包名有没有被占、把 D-U-N-S 和账号材料办起来。</p>
<p>可以等的部分：不在名单里的商店，以及用户直接侧载，在这一轮铺开前不受管。官方给的时间表是 2027 年及以后全球铺开，也建议开发者在铺开前完成验证；媒体口径里的节奏是先到美英。上了 Play 的应用要对所有形态注册，只是执行层面先落手机和平板。</p>
<p>有个措辞值得记住，官方 FAQ 写的是「暂时不适用」，不是「永远不会」。这两句话之间的差距，就是 2027 那一格。</p>
<h2 id="最后">最后</h2>
<p>这三笔账算下来，最贵的不是那 25 美元，是身份材料的准备时间，以及签名密钥的不可替代性。填表十分钟，D-U-N-S 要 28 天，密钥丢了就没有第二把。所以对一个跨平台团队来说，它不该是上架前才想起来的事，而该是发布流程里的一步。</p>
<p>你手上的项目走的是哪条路？那把签名密钥现在找得到吗，评论区聊聊。</p>
<p>参考：</p>
<ul>
<li>Android developer verification 官方指南 <a href="https://developer.android.com/developer-verification/guides">https://developer.android.com/developer-verification/guides</a></li>
<li>官方 FAQ <a href="https://developer.android.com/developer-verification/guides/faq">https://developer.android.com/developer-verification/guides/faq</a></li>
<li>查包名注册状态（Developer ID Status API）https://developer.android.com/developer-verification/guides/check-registration-status</li>
<li>用 Console API 注册包名 <a href="https://developer.android.com/developer-verification/guides/developer-console-api">https://developer.android.com/developer-verification/guides/developer-console-api</a></li>
</ul>
]]></content:encoded></item><item><title>Android 侧载的苹果化只走了一半：要身份，不审内容</title><link>http://mings.in/posts/android-verify/</link><pubDate>Thu, 08 Oct 2026 08:14:49 +0800</pubDate><guid>http://mings.in/posts/android-verify/</guid><description>如果你有朋友在巴西、印尼、新加坡或者泰国，9 月 30 日之后他可能会遇到一种新情况：从某个应用商店点安装，先弹一句开发者未验证，然后装不上。同一个时间点，你手上这台国行安卓机，装什么都不会有变化。</description><content:encoded><![CDATA[<p>如果你有朋友在巴西、印尼、新加坡或者泰国，9 月 30 日之后他可能会遇到一种新情况：从某个应用商店点安装，先弹一句开发者未验证，然后装不上。同一个时间点，你手上这台国行安卓机，装什么都不会有变化。</p>
<p>中文技术圈对这件事聊得不多，英文社区已经吵了一些日子。Hacker News 上 10 月 1 日那条帖子拿到 338 分、141 条评论，自由软件阵营的 F-Droid 联合电子前哨基金会（Electronic Frontier Foundation）发了公开信，标题就一句，反对 Android developer verification。</p>
<p>所以这条值得拆开看。它改了什么，谁真的会疼。</p>
<h2 id="官方原话应用要由已验证的开发者注册">官方原话：应用要由已验证的开发者注册</h2>
<p>Google 的说法很直接，<a href="https://developer.android.com/developer-verification/guides">官方指南</a>第一句就是，Android 要求所有应用都由已验证的开发者注册，用户才能把它装进认证 Android 设备。</p>
<p>几个边界先划清楚：</p>
<ul>
<li>生效时间 9 月 30 日，先落巴西、印尼、新加坡、泰国四个国家</li>
<li>覆盖七家商店，Google Play、HONOR 应用市场、OPPO 应用市场、三星 Galaxy Store、传音 Palm Store、vivo 应用商店、小米 GetApps</li>
<li>管的是 Android 7 以上的认证设备，检查通过 Google Play services 下发</li>
<li>这一轮只覆盖手机和平板两种形态</li>
<li>2027 年全球铺开，并把验证能力扩展到所有第三方 Android 商店</li>
</ul>
<p>有个细节比表面重要：这轮只管上面这七家商店。官方 FAQ 写得很清楚，如果你从其他渠道分发，或者用户直接侧载，这些新要求暂时不管你。</p>
<p>也就是说，侧载的路没被堵，是入口处多了个证件查验。</p>
<p><img loading="lazy" src="/images/android-verify/timeline.png"></p>
<p>Google 自己给的比喻也挺坦白，他们说这像机场查证件，和安检是两件事，确认的是开发者是谁，不审应用的内容，也不审它从哪儿来。</p>
<h2 id="三条路你得挑一条">三条路，你得挑一条</h2>
<p>官方给了三条路径，差别在身份验证这一栏。</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>完整分发</th>
					<th>有限分发</th>
					<th>侧载未注册应用</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>适合谁</td>
					<td>组织与职业开发者</td>
					<td>学生、爱好者、个人</td>
					<td>不愿验证的开发者</td>
			</tr>
			<tr>
					<td>要不要身份验证</td>
					<td>要</td>
					<td>不要</td>
					<td>不要</td>
			</tr>
			<tr>
					<td>分发范围</td>
					<td>任何商店或自有网站</td>
					<td>最多 20 台设备</td>
					<td>任何渠道，但用户要开 advanced flow</td>
			</tr>
			<tr>
					<td>用户体验</td>
					<td>和今天一样</td>
					<td>接受邀请后安装</td>
					<td>一次设置，之后能装</td>
			</tr>
	</tbody>
</table>
<p>完整分发要交身份信息，开发者控制台账号 25 美元一次，注册成组织还要 D-U-N-S 编号，申请最长要等 28 天，另外得有一个用 Google Search Console 验证过的官网。</p>
<p>有限分发是给不想交政府证件的人留的口子，免费，不限应用数量，但只能装到 20 台设备上，用户得接受你的邀请。学生、爱好者、自己写给自己用的人走这条最省事。</p>
<p>第三条路是给用户的：未注册开发者的应用仍然能装，但用户要先做一次 advanced flow 设置，官方说这些保障是给用户留出中断胁迫的时间。</p>
<p>有一处官方说法要留意，advanced flow 一旦关掉，未注册应用的更新就会失败。这条路只适合当终点，不适合当日常。</p>
<h2 id="我拆了一个-apk验证的对象其实就两样东西">我拆了一个 APK，验证的对象其实就两样东西</h2>
<p>讲概念不如自己跑一遍。我本机的 Android SDK 里有 aapt2、d8、zipalign、apksigner 和 keytool，够手搓一个 APK 了，没借助 Android Studio。</p>
<p>流程是：写一个 AndroidManifest.xml，里面只有一行包名 com.example.handmade，javac 编一个类，d8 转成 classes.dex，aapt2 link 生成 APK，最后用 keytool 现造一对 RSA 密钥，apksigner 签名。</p>
<p>签完把身份拆出来看：</p>
<pre tabindex="0"><code>Signer #1 certificate DN: CN=Handmade Identity Demo, O=none, C=CN
Signer #1 certificate SHA-256 digest:
0a220aaa9b719f34f97e88cb46a95de020fba812fee540251cc2cae7a188a269
Verified using v2 scheme (APK Signature Scheme v2): true

package: name=&#39;com.example.handmade&#39; versionCode=&#39;1&#39; versionName=&#39;1.0&#39;
</code></pre><p>一个 8619 字节的 APK，身份就是这两行，包名 com.example.handmade，加证书指纹 0a22 开头那串 SHA-256。Google 要你登记的就是这一对东西，官方说法是提供用你私钥签名的 APK，把这个 APK 和你的开发者账号连起来。</p>
<p><img loading="lazy" src="/images/android-verify/terminal.png"></p>
<p>再做一个对照就更有意思了。</p>
<p>我拿没签名的那个 APK 去验，直接报 DOES NOT VERIFY，缺 META-INF/MANIFEST.MF。然后我用第二把密钥签同一份 APK，代码一个字没改，包名也没变，证书指纹从 0a22 开头换成了 ee56 开头。</p>
<p>所以验证验的是人，不是代码。这也解释了官方 FAQ 为什么专门写一条，签名密钥丢了就没法注册包名。那把 keystore 文件不是构建缓存，是身份证，得跟隐私数据一样归档。</p>
<h2 id="这不是-app-store-化是-macos-公证化">这不是 App Store 化，是 macOS 公证化</h2>
<p>很多人第一反应是 Google 苹果化了。这个判断只说对了一半。</p>
<p>像的地方确实是「分发要挂账号」。苹果那边每个安装路径都挂在 Apple Developer Program 上，99 美元一年，组织同样要 D-U-N-S。macOS 上更直接，Gatekeeper 首次运行联网查一次公证票据，苹果在欧盟放开的第三方市场，也要求在安装时确认来源是授权渠道。</p>
<p>不像的地方才是关键，而且有两处。</p>
<p>第一，Google 不审内容。官方帮助文档里明确写了，不收集应用内容和功能信息。苹果那套是审查加抽成，内容规则一大堆，欧盟最新的条款里，走应用内购买抽 26%，走替代支付抽 20%，跳链接出去抽 15%，走第三方市场或网页分发再收 5%。</p>
<p>第二，Google 留了免身份的出口，20 台设备的有限分发、advanced flow、还有完全不受影响的 adb 开发安装，苹果那边没有对应物。</p>
<p>方向上还有个对照挺有意思。苹果在欧盟是被数字市场法按着往开走，Google 这边是主动往收。这不是谁学谁，是两条方向相反的曲线在中间碰头。</p>
<p>真正值得盯的是那句话，验证能力会扩展到所有第三方 Android 商店。到时候 Google 不只是自己院子的门卫，还是别人家商店的身份签发机关。</p>
<h2 id="谁会疼">谁会疼</h2>
<p>普通用户不会。体验真的没变，这也是反对者最难讲通的地方。</p>
<p>会疼的是另外三拨人。F-Droid 这样的非营利仓库首当其冲，他们的公开信里写得很直白：</p>
<blockquote>
<p>在认证设备上安装或启动任何应用，都会回连 Google，验证开发者和应用是否已被批准。</p>
</blockquote>
<p>他们托管几千个应用，验证按包名绑定，意味着每个志愿者都要把身份交给 Google。他们的结论是建议开发者不要注册。</p>
<p>第二拨是没法完成验证的人。官方 FAQ 自己承认，制裁地区的开发者无法完成验证。离线环境、拿不出证件、或者所在地区根本走不通流程的人，等于被排除在分发之外。</p>
<p>第三拨是未来任何想跟 Play 竞争的分发方，他们连上线都要先过 Google 的注册流程。</p>
<h2 id="对国内开发者三条实际动作">对国内开发者，三条实际动作</h2>
<p>第一，如果你的应用只上 Google Play，而且用了 Play App Signing，你已经在这套流程里了，官方会自动认领，不需要额外操作。想确认状态，去 Play Console 的开发者验证页看一眼就行。</p>
<p>第二，如果你有海外用户、又走非 Play 渠道，现在就该做的事是把签名密钥归档管理起来，把 D-U-N-S 提前申请了，把 2027 当真正的截止日期，别盯着今年九月。</p>
<p>第三，只写给自己或小圈子用的应用，adb 这条官方保留，advanced flow 也能用，但记住更新未注册应用要靠它，别关掉。</p>
<p>还有个查询接口值得知道，Google 开了 Android Developer ID Status API，可以查某个包名有没有被注册，每天 1000 次。它要挂在 Google Cloud 项目上才能调，我本机没这个条件，所以只当线索记着。对做分发的人来说，它意味着装机前可以先问一句这个包名归谁。</p>
<h2 id="最后">最后</h2>
<p>这不是一次安全检查，是分发的守门权从操作系统挪进了 Google 的账号体系。对普通用户几乎无感，对非主流分发生态是结构性打击。</p>
<p>2027 年全球铺开才是分水岭，现在这四国只是一个成本很低、随时可调的试点。</p>
<p>你手上要是有个只在国内分发的应用，会去注册吗，还是等 2027 再说？评论区聊聊。</p>
<p>参考：</p>
<ul>
<li>Android developer verification 官方指南 <a href="https://developer.android.com/developer-verification/guides">https://developer.android.com/developer-verification/guides</a></li>
<li>官方 FAQ <a href="https://developer.android.com/developer-verification/guides/faq">https://developer.android.com/developer-verification/guides/faq</a></li>
<li>F-Droid 公开信 <a href="https://f-droid.org/en/2026/02/24/open-letter-opposing-developer-verification.html">https://f-droid.org/en/2026/02/24/open-letter-opposing-developer-verification.html</a></li>
<li>Hacker News 讨论 <a href="https://news.ycombinator.com/item?id=49917761">https://news.ycombinator.com/item?id=49917761</a></li>
</ul>
]]></content:encoded></item><item><title>Flutter 1.7.8 稳定版本发布</title><link>http://mings.in/posts/flutter-1-7-8/</link><pubDate>Tue, 09 Jul 2019 09:00:00 +0800</pubDate><guid>http://mings.in/posts/flutter-1-7-8/</guid><description>Flutter 1.7.8 稳定版本发布，修复了各种 bug</description><content:encoded><![CDATA[<p>Flutter 1.7.8 最新稳定版发布了, 最主要修复了在<a href="https://github.com/flutter/flutter/issues/18494">同一 APK 同时支持 32 位和 64 位的bug</a>， 这样就不用 使用这种黑科技来打包支持国内市场了，真香！！</p>
<p>修复的其他一些 bug 列表（部分）</p>
<h3 id="v176">v1.7.6</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/34684">#34684</a> The return type of the almost never used <code>ParentDataWidget.debugDescribeInvalidAncestorChain</code> method is now <code>Iterable&lt;DiagnosticsNode&gt;</code> instead of <code>String</code>. Existing uses can call <code>.join('\n')</code> on the return value if they need to continue to use a <code>String</code> instead of a <code>DiagnosticsNode</code>.</li>
</ul>
<h3 id="v174">v1.7.4</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/30069">#30069</a> Text inline widgets allows embedding arbitrary widgets inline with text. <code>TextSpan</code>has also been reworked to inherit from <code>InlineSpan</code>, in order to support <code>WidgetSpan</code>, which is used to define inline widgets. Existing uses of <code>TextSpan</code> should not assume the object is of type <code>TextSpan</code> anymore.</li>
</ul>
<h3 id="v171">v1.7.1</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/issues/33480">#33480</a> The <code>FadeInImage</code> widget no longer supports the <code>placeholderSemanticLabel</code>property.</li>
</ul>
<h3 id="v167">v1.6.7</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/issues/32374">#32374</a> The <code>Image</code> widget now supports two new properties: <code>frameBuilder</code>, and <code>loadingBuilder</code>, which enable callers to easily add a placeholder image which the image ios loading, add effects to the image once it loads (such as fading it in), and monitor the loading progress of the image (e.g. to show the user a loading indicator).</li>
</ul>
<h3 id="v163">v1.6.3</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/33148">#33148</a> ExpandIcon now uses Colors.white60 instead of Colors.white54 when dark theme is used (ThemeData.brightness is set to Brightness.dark).</li>
</ul>
<h3 id="v162">v1.6.2</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/32936">#32936</a> cleaned up the <code>ImageStream</code> listener API. <code>addListener()</code> and <code>removeListener()</code>now take an instance of <code>ImageStreamListener</code>, which contains references to the individual callbacks. See <a href="https://groups.google.com/forum/#!topic/flutter-announce/NWTszrEq9U0">the announcement</a> for more information.</li>
</ul>
<h3 id="v161">v1.6.1</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/32528">#32528</a> Tapping a modal bottom sheet no longer dismisses it by default.</li>
</ul>
<h3 id="v160">v1.6.0</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/30983">#30983</a> To support structured error messages <a href="https://github.com/flutter/flutter/issues/27327">#27327</a> the signature of InformationCollector and the context parameter of the FlutterErrorDetails constructor were changed. This should not impact you unless your package is attempting behave like part of the Flutter framework. See <a href="https://github.com/flutter/flutter/issues/31962#issuecomment-488882515">this comment</a> which discusses how to write code that is forwards and backwards compatible with this change.</li>
<li><a href="https://github.com/flutter/flutter/pull/31569">framework #31569</a>/<a href="https://github.com/flutter/engine/pull/8695">engine #8695</a> Rect and RRect are now const constructable and are backed by 64 bit doubles rather than 32 bit floats internally.</li>
<li><a href="https://github.com/flutter/flutter/pull/31917">framework #31917</a> &lt;- <a href="https://github.com/flutter/engine/pull/8796">engine #8796</a> &lt;- <a href="https://github.com/dart-lang/sdk/commit/1dd0f88c84">dart 1dd0f88c84</a> Fixes bug in StreamIterator which allowed constructor argument to be null. Also allowed await for on a null stream. This is now a runtime error.</li>
<li><a href="https://github.com/flutter/flutter/pull/30884">#30884</a> <code>TabController.animationController</code> changed to be <code>unbounded</code> to allow <code>DefaultTabController</code> to handle changes to number of tabs (after initial build).</li>
</ul>
<h3></h3>
]]></content:encoded></item></channel></rss>