分类: UpgradeAll

一款用于应用最新版本追踪的 Android 应用

  • 代码即操作系统(UpgradeAll 2 开发计划)

    随着我使用的电子产品越来越多,跨度涉及 Linux 桌面/服务器,Android。我发现,备份还原的操作系统配置是有极限的,极限在于它未曾理解,也无法理解数据的意义。

    DNS hosts 与软件数据库缓存,在它眼里看来,都是一样的“文件”。

    过去

    这带来一个想法,就是代码即架构,也就是 Ansible、Terraform 等,但是它们在“代码”,这个概念的理解上,却出了大差错。软件代码,在软件停止运行后,软件的大部分副作用会消失,而那一小部分副作用也可以以测试代码的形式覆盖。

    现状

    操作系统,并不会随着 Ansible 或者 Terraform 停止运行而关机,其副作用,也不会因为我删除了 Ansible 里的一行代码并重新运行就会消失,于是这时候,便产生了所谓的“配置偏移”,这些传统的代码即架构的软件以“幂等性”来“自欺欺人”式的解决了这个问题,确实“幂等性”很重要,它会避免重复配置或者不必要的重复导致的错误,但是并不能解决这个回滚问题。

    然而,大部分操作都是可以回滚的,甚至配置得当,文件删除也是可以回滚的。现代操作系统已经为我们提供了很多回滚工具,git、文件系统快照、甚至最简单的文件回收站。但是,因为从运维角度编码,可复现与回滚,直接萎缩成了一次性快照,也就是 OCI 与 NixOS 之流。

    未来

    我在这里提出一个新的构建系统,也就是从程序员的角度运维。为一切可以回滚的操作添加支持,如果你在配置代码中删除了一个操作,那么在下次运行时将会对那一个特定操作进行自动回滚。

    此外,我也认为安装软件,维护系统这种经验,应该作为群体智慧被编程的方式保留下来,而不是让人们一次又一次的去重复学习简单的劳动。

    运维的未来应当像 NixOS,面向功能,而绝非面向软件与包。

    这个系统应当满足以下特性:

    1. 面对终端的普通用户
    2. 面对目标,也就是面对一些“单元测试”,因为操作系统应当为功能服务,而不是软件这个概念
    3. 可回滚
    4. 单机应用,可以远程运行,但是应当首要支持单机运行
    5. 与现有的运维技术兼容,并尽量使用现有的运维技术进行配置(初版暂定使用 Ansible),我们的世界不需要更多轮子了
    6. 可自动检查一个现有的,已经配置的操作系统,并生成配置代码
    7. 逻辑代码应当灵活,这个项目提出的一部分原因就是希望推进 Gentoo 的发展。(源码的自由是选择的自由,也是保证自由的基石)

    对于 Android

    而这个系统在 Android 上的体现会是 UpgradeAll

    因为我希望 UpgradeAll 可以拥有这些特性:

    1. 从源码编译(你可以随意使用开发者没有合并的分支与功能)
    2. 面对开发者友好(你可以编程你的更新流程)
    3. 面对用户友好(你可以只考虑需要什么功能,而不是寻找应用,这应该让开发者来做,参考 quickenergy)
    4. 促进 Android 应用开放化(Android 应用的封闭性在我看来有很大部分是因为深刻骨髓的二进制分发,tui)
  • dom4j 的 proguard-rules 配置分享

    前言

    有用户反馈 UpgradeAll 的 F-Droid 配置无法解析,查看日志后发现 dom4j 部分报错。但是直接 debug 无法复现,只有 release 混淆才会出错。

    排错

    原始错误

    2022-09-06 11:35:33 ClientProxyApi E/ClientProxyApi: org.dom4j.InvalidXPathException: Invalid XPath expression: .//application[@id="com.nextcloud.client"] org.jaxen.saxpath.base.XPathReader
    	at org.dom4j.xpath.DefaultXPath.parse(DefaultXPath.java:355)
    	at org.dom4j.xpath.DefaultXPath.<init>(DefaultXPath.java:59)
    	at org.dom4j.DocumentFactory.createXPath(DocumentFactory.java:222)
    	at org.dom4j.tree.AbstractNode.createXPath(AbstractNode.java:202)
    	at org.dom4j.tree.AbstractNode.selectSingleNode(AbstractNode.java:178)
    	at net.xzos.upgradeall.core.websdk.api.client_proxy.hubs.FDroid.getRelease(FDroid.kt:32)
    	at net.xzos.upgradeall.core.websdk.api.client_proxy.ClientProxyApi.getAppReleaseList(ClientProxyApi.kt:55)
    	at net.xzos.upgradeall.core.websdk.api.ServerApi$getAppReleaseList$value$1$1.invoke(ServerApi.kt:57)
    	at net.xzos.upgradeall.core.websdk.api.ServerApi$getAppReleaseList$value$1$1.invoke(ServerApi.kt:57)
    	at net.xzos.upgradeall.core.websdk.api.ServerApiKt.callOrBack(ServerApi.kt:97)
    	at net.xzos.upgradeall.core.websdk.api.ServerApiKt.access$callOrBack(ServerApi.kt:1)
    	at net.xzos.upgradeall.core.websdk.api.ServerApi$getAppReleaseList$value$1.invoke(ServerApi.kt:57)
    	at net.xzos.upgradeall.core.websdk.api.ServerApi$getAppReleaseList$value$1.invoke(ServerApi.kt:56)
    	at net.xzos.upgradeall.core.utils.data_cache.DataCacheManager.get(DataCacheManager.kt:47)
    	at net.xzos.upgradeall.core.utils.data_cache.DataCacheManager.get$default(DataCacheManager.kt:37)
    	at net.xzos.upgradeall.core.websdk.api.ServerApi.getAppReleaseList(ServerApi.kt:56)
    	at net.xzos.upgradeall.core.websdk.api.ServerApiProxy.getAppReleaseList(ServerApiProxy.kt:27)
    	at net.xzos.upgradeall.core.module.Hub.getAppReleaseList$core_debug(Hub.kt:124)
    	at net.xzos.upgradeall.core.module.app.data.DataGetter.renewVersionList(DataGetter.kt:48)
    	at net.xzos.upgradeall.core.module.app.data.DataGetter.doGetVersionList(DataGetter.kt:34)
    	at net.xzos.upgradeall.core.module.app.data.DataGetter.getVersionList(DataGetter.kt:29)
    	at net.xzos.upgradeall.core.module.app.data.DataGetter.doUpdate(DataGetter.kt:41)
    	at net.xzos.upgradeall.core.module.app.data.DataGetter.update(DataGetter.kt:24)
    	at net.xzos.upgradeall.core.module.app.Updater.update(Updater.kt:59)
    	at net.xzos.upgradeall.core.module.app.App.update(App.kt:74)
    	at net.xzos.upgradeall.core.manager.AppManager.renewApp(AppManager.kt:183)
    	at net.xzos.upgradeall.core.manager.AppManager$renewAppList$5$1.invokeSuspend(AppManager.kt:166)
    	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    	at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
    	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
    	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
    	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
    	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
    	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)

    原始错误分析

    看见 org.dom4j.InvalidXPathException: Invalid XPath expression: 的第一反应就是 XPath 写错了,这里的 XPath 是 .//application[@id="com.nextcloud.client"],所以,按照菜鸟教程,修改为 //application[@id="com.nextcloud.client"],解决方法无效。

    直接搜索 org.dom4j.InvalidXPathException: Invalid XPath expression: 关键词,从 Why does dom4j throw InvalidXPathException for a valid XPath only in my test environment? 得知可能是 Dom4j 隐藏了原始错误。

    尝试取消混淆后 Dom4j 正常运行,所以排除是 release 混淆之外的错误。

    编写混淆规则

    鉴于对 DOM4J 进行源码修改过于复杂,所以并未选择修改,而是尝试分析 XPath 出错的可能,考虑到混淆后出错,添加 consumer-rules(这里是因为处于模块中)取消对 Dom4j 的混淆。

    现存的混淆规则

    在 Dom4j 的 Issues 列表中搜索关键词 proguard 发现有相同问题,回答中的 -dontwarn org.dom4j.** 只能忽略 gradle 编译时的警告,自欺欺人行为,忽略。

    参考 news-readerproguard-rules.txt 添加,测试的错误日志同上,错误并未解决。

    -keep class org.dom4j.** { *; }
    -keep interface org.dom4j.** { *; }

    指定 XML 驱动

    在网上还有另一种说法 dom4j 问题解决Can‘t create default XMLReader; is system property org.xml.sax.driver set groovy,手动指定 SaxReader 驱动。

    参考 porting to Android: why am I getting “Can’t create default XMLReader; is system property org.xml.sax.driver set?”? 添加代码:

    System.setProperty("org.xml.sax.driver","org.xmlpull.v1.sax2.Driver");

    编译得到的软件时而可用时而报错,后预计为 gradle 编译混淆原因,因此 rebuild 后测试,均失败。

    手动排除 Dom4j 依赖

    所以考虑到应该只是混淆规则的问题,进行范围排除(这里使用了 Android Studio 补全判断项目有哪些库)

    -keep class org.** { *; }
    -keep class com.** { *; }
    -keep class java.** { *; }
    -keep class javax.** { *; }
    -keep class net.** { *; }

    测试发现错误排除。故尝试一个个取消,检查软件运行状态。这里需要注意在修改后 rebuild 整个项目,因为 gradle 可能会偷懒不混淆刚刚取消不混淆的项目。

    最终测试出:

    -keep class javax.** { *; }
    -keep class org.** { *; }

    proguard-rules 配置

    得到上面的 javax 与 org,在 Dom4j 项目库中使用 Github 的代码搜索 import 依赖,最终得到以下规则:

    -keep class org.dom4j.** { *; }
    -keep interface org.dom4j.** { *; }
    -keep class javax.xml.** { *; }
    -keep class org.w3c.** { *; }
    -keep class org.xml.** { *; }
    -keep class org.xmlpull.** { *; }
    -keep class org.jaxen.** { *; }

    解决。

  • 修改一个历史提交的父提交

    在半年前,合并热修复分支时,合并方向弄反了,导致热修复分支成为了新的主分支。但一直没有什么办法,今天重拾起来,尝试去修复。

    (更多…)
🌍 Language