删一个项目,程序先问四个问题
删除是这个工具里最"啰嗦"的动作之一

如果你去看「安卓修改大师智改工坊」的删除逻辑,会发现一件有点意外的事:它删一个项目之前,要先过四道检查。——检查目标路径是不是空的、项目根目录有没有确定、目标是不是根目录本身、目标是不是根目录的直接子目录。——四道全过,才真正动手。

为什么要这么小心?因为删除是这个工具里唯一一个"不可逆"的动作。——改错了可以再改,打包失败可以重打,反编译失败项目还在——而删掉的东西不会回来。——并且它删的不是一个文件,是一整个目录——里面有你导入的源包、反编译出来的工程、每一轮改动的历史、每次打包的日志。

更麻烦的是:要删的那个路径,不是程序自己记住的,是从项目的 config.ini 里读出来的。——而 config.ini 是个文本文件——它可能被手工编辑过、可能被别的程序改坏过、可能被同步工具覆盖过。——如果程序无条件相信这个路径,那"删一个项目"就可能变成"删一个盘"。

所以那四道检查不是洁癖,是一条底线:不管配置里写了什么,只允许删项目目录下面那个直接子目录。——写的是 C 盘根目录?拒绝。写的是项目目录本身?拒绝。写的是别的盘上某个文件夹?拒绝。——而这条底线恰好只需要两行判断:目标不能等于根,目标的父目录必须等于根。

只需说话,就能让应用变成你想要的样子。而"说话"这件事的前提是:你说的话不会把东西弄丢。想看看一个项目目录里到底有什么——值得你在删之前看一眼——可以从 https://www.apkeditor.cn/ai-version.aspx 下载「安卓修改大师智改工坊」,建一个自己的项目,然后打开它的工作目录对照本文看一遍。

一、项目列表是怎么来的:扫目录,不是查数据库

要理解删除为什么这么设计,先得知道"项目列表"是怎么来的。答案很简单:程序去项目根目录下面,把每个子目录列出来,逐个进去读 config.ini。

读到了,就是一个项目;读不到,那个目录就不出现在列表里。——而读的过程中如果某个项目的配置坏了,程序的处理是"跳过它"——一个坏项目不影响其它项目正常显示。——最后按创建时间倒序排——最新的在最上面。

这个设计带来三个很实际的后果,值得记住。

后果一:项目的"存在"就是那个目录的存在。——目录在,项目在;目录没了,项目就没了——不存在"删了目录但记录还在"的状态。——所以你没有"回收站",也没有"从列表移除但保留文件"这种中间选项。

后果二:你手工往项目根目录里放一个带 config.ini 的文件夹,它也会出现在列表里。——这既是灵活性(可以整体搬迁项目),也是风险(放错了东西进去,列表里会多一条你看不懂的记录)。

后果三:备份项目就是备份目录。——把那个八位名字的文件夹整个拷走,就拷走了这个项目的一切。——而"拷回来"也一样简单:放回项目根目录,刷新列表,它就在那儿了。

这个"目录即项目"的模型,解释了很多看起来奇怪的现象。——比如为什么改项目名不影响目录名——因为名字存在 config.ini 里,目录名是另一回事;比如为什么两个同名项目能共存——因为它们在不同的目录里。——而下面要讲的八位随机目录名,也是这个模型的一部分。

顺便回答一个常被问到的问题:为什么不做个回收站?——因为项目目录动辄几百兆(apktool 展开之后文件成千上万),回收站会把这些一起留着——而"删了但还占着"是最让人困惑的状态:你看不到它,但它占着你的盘。——而这个工具选的是另一条路:不留中转站,但把"删之前该看的东西"都摆在你眼前——图标、名字、版本、时间。——换句话说,它把"后悔的机会"放在了动手之前,而不是动手之后。

二、为什么目录名是八位随机字符

第一次打开项目目录的人,通常会愣一下:里面全是 abc123xy 这种八位的小写字母加数字,一个都看不出是什么应用。——而这个"看不出",恰恰是设计目标之一。

理由一:不会重名,也不需要问你怎么处理重名。——如果目录名来自项目名,那"报修应用"建两次就会撞——而撞了之后要么加后缀、要么问你——两种处理都很烦。——用随机名,撞的概率极低;而程序还留了个兜底:建目录时会循环试,最多试六十四次,直到找到一个不存在的。

理由二:路径永远不会因为名字出事。——项目名是给人看的,人可以输入任何东西:空格、括号、斜杠、各种符号、甚至表情。——而路径里出现某些字符,会让工具链里的命令解析出错——这类错误极难查,因为它只在特定的名字上出现。——用固定字符集(小写字母加数字)生成目录名,这件事从根上不会发生。

理由三:改项目名不该动文件系统。——你今天叫它"报修应用",明天觉得该叫"内部报修 v2"——这只是一个显示名。——而如果目录跟着改名,那所有记着旧路径的地方全要跟着改——config.ini 里的、历史记录里的、你脑子里的。——目录名固定,改名就变成一件只改一行配置的小事。

理由四:删错了也不心疼——因为它看起来就不重要。——这一条听起来有点绕,但很真实:一个叫"报修应用"的目录被误删,你会立刻意识到;而八个随机字符的目录,你反而会先确认一下再动手。——而删除这个动作最需要的,恰恰就是那一秒的犹豫。

那项目名去哪了?——它被"清洗"过之后写进 config.ini。——清洗的规则很朴素:去掉文件名里不允许出现的字符,去掉首尾空白。——为什么还要清洗?因为它不只是显示名——导出、另存、拷贝到桌面这些动作,都可能拿它当文件名用。

这里有一个很值得体会的搭配:显示名随便你,路径名不让碰。——两个名字各管一件事:一个管"你认得出",一个管"系统认得出"。——而很多工具把这两件事混成一件,结果是"名字好看了,路径出问题了"。

而这个设计的代价,得诚实说一句:你在资源管理器里认不出哪个目录是哪个应用。——所以如果你要手工备份、手工清理,别靠猜——回工具里对着列表看,或者打开目录里的 config.ini 看一眼应用名。——而这个代价换来的,是"不会有任何一次改名把路径搞坏"。

顺带说,这八个字符的字符集也值得留意:小写字母加数字,没有大写、没有符号。——因为它要出现在路径里,而路径可能被大小写不敏感的文件系统处理、可能被命令行工具解析、可能被拼进某个参数。——这套字符集是"没有任何解析风险"的集合——而少用几个字符换来的稳定,非常划算。

三、四道拒绝:每一道都在挡一种真实的意外

删除的检查一共四道,顺序是固定的。而这四道检查的提示语都写得很直白——因为它们的读者是"正在困惑的人"。

第一道:目标路径是不是空的。——提示是"项目的工作目录是空的,没法删除"。——什么时候会这样?config.ini 里那行路径被改成了空。——这一道挡的是"拿空串去拼路径"——空串拼出来的路径,可能是当前目录,而"删当前目录"就是一场灾难。

第二道:项目根目录有没有确定。——提示是"项目目录还没确定,没法删除"。——这种情况出现在程序还没决定工作目录的时候——比如工作目录所在的盘突然不可用了。——这一道挡的是"在不知道边界的情况下动手"——没有根目录,就没法判断什么在边界内。

第三道:目标是不是根目录本身。——提示是"拒绝删除:目标就是项目根目录本身"。——这一道对应的场景比你想的更常见:配置里那行路径被手工改成了项目根目录——看起来只是"改了个路径",实际含义变成了"删掉所有项目"。

第四道:目标的父目录是不是根目录。——提示是"拒绝删除:目标不是项目根目录的直接子目录(某某路径)"。——这一道是最关键的一道,因为它把"能删的范围"钉死在一个层级上:只允许删根目录下面的直接子目录。

为什么"直接子目录"这个限定这么好用?——因为它同时排除了两种危险:往上一级(项目根目录本身、根目录的父目录、整个盘)和往下一级(项目里的 apktool 子目录、build 子目录)。——而两种都不该由"删除项目"这个动作碰。

配置里的路径变成 结果
空 拒绝:工作目录是空的
项目根目录 拒绝:目标就是根目录本身
根目录的上一级 拒绝:不是直接子目录
别的盘上某个文件夹 拒绝:不是直接子目录
项目里的 apktool 子目录 拒绝:不是直接子目录
根目录下一个八位随机名目录 通过,开始删除

看这张表,你会发现一个有意思的现象:前五行全是"拒绝"。——也就是说,这段代码的主要工作量不在"怎么删",在"怎么证明该删"。——而这两种工作量的比例,正好反映了一个成熟的判断:不可逆的动作,成本几乎全在放行之前。

顺带说一个实现上的细节:比较路径的时候,用的是"归一化之后比较"。——所谓归一化,就是去掉尾部的斜杠、取绝对路径——因为 D:\AiApkEditor\Project\abc123xy 和 D:\AiApkEditor\Project\abc123xy\ 是同一个目录,但字符串不相等。——而这种"看起来一样其实不相等"的坑,正是安全判断最容易漏的地方。

归一化要处理的还不止斜杠。——相对路径要变成绝对路径、大小写要统一(Windows 上 D://A 和 d://a 是同一个目录)、"点"和"点点"这种相对段要消掉。——而每一项漏掉,都可能让"看起来在边界内"变成"其实在边界外"——或者反过来,把该删的判成不该删。——这类细节在功能上体现不出来,只在出事或不出事上体现。

四、删了一半怎么办:两条"失败之后"的处理

四道检查过了,开始删。而删的过程里还有两种意外,程序各自准备了处理方式。

第一种意外:目录其实已经没了。——你手工删过,或者上一次删到一半失败——这时候点删除,目录不存在。——程序的处理是:不报错,直接当成功。

为什么"不存在"也算成功?——因为删除的目标是"让这个东西不存在"——它已经不存在了,那目标就达成了。——这个性质有个名字叫幂等:同一个操作做一次和做十次,结果一样。——而幂等带来的实际好处是:你可以放心地重复点——不会因为"上次删了一半"就永远删不干净。

对比一下不幂等的设计会怎样:——删除时先检查"目录必须存在",不存在就报"要删除的项目不存在"。——听起来更严谨,实际更烦人——因为你看到的是一个错误提示,而你的目标其实已经达成了。——更糟的是,你会开始怀疑"是不是还有残留"——于是去手工翻目录,花掉五分钟。

第二种意外:文件带只读属性,删不掉。——这个坑在改包场景里特别常见——因为反编译出来的文件里,有一部分是带只读属性的。——普通删除遇到只读文件会抛一个"访问被拒绝"——而程序的处理是:先把这个目录下所有文件的只读属性清掉,再删一次。

这个"清一次再重试"的做法,看着像兜底,其实是对现实的准确描述。——现实是:反编译产物的属性不由你控制——它反映的是原包里那些文件的属性。——而你不能要求"每个包解出来的文件都可写"——那就只能在自己这一侧让步。

顺便说清一件事:这两条处理都不会问你"要不要继续"。——因为问你也没用——你能回答的只有"删"或"不删",而你已经点过删了。——好的错误处理不是"多问一句",是"在不改变你意图的前提下把事办成"。

而两件事都会写进 dock.log。——"删除项目:某某路径"、"删除项目:目录本来就不存在 某某路径"——两条不同的话。——为什么日志要区分这两种?因为它回答的是"这次删除到底发生了什么"——是删掉了东西,还是本来就没东西。——而当你怀疑"我是不是删错了项目"时,这条日志是唯一能回答的。

这里可以引出一个小小的建议:如果你在批量清理项目,删完看一遍 dock.log 的尾部。——它会告诉你每一个"删除项目"后面跟的是路径还是"本来就不存在"——而这能帮你确认"我以为删掉的那几个,是不是真的删掉了"。——这条建议的成本是十秒,收益是"不用打开资源管理器一个个核对"。

这条日志还有个用法:删错之后的复盘。——如果你怀疑自己删的是 A 结果删了 B,dock.log 会给出一条带完整路径的记录——而路径能反推出目录名,目录名能对上创建时间,创建时间再对上列表里那一条。——整条链走完,你能确认"删的到底是哪个"——而这件事,是任何"撤销"按钮都给不了的。

五、删掉一个项目,到底删掉了什么

这个问题看着简单,但很少有人真的答得全。一个八位随机名的目录里,通常有七样东西。

第一样:config.ini。——应用名、包名、版本名、版本号、minSdk、targetSdk、启动 Activity、原始文件名、创建时间、签名设置。——它是这个项目的身份证——没有它,目录就只是一个文件夹。

第二样:history.ini。——这个项目每一轮改动的那句原话,一条一条按时间排着。——它是这个项目"为什么长成现在这样"的唯一记录——而它通常是最容易被低估、删掉之后最想要回来的东西。

第三样:原始包的副本(source 加原扩展名)。——你导入时拷进来的那一份。——它的意义是"留底":工程改坏了、改乱了、想回到最初——只要它还在,就能重新解一次。

第四样:图标。——从原包里取出来存的那张,用来在列表里区分项目。——删了不影响功能,只影响你一眼认出它是谁。

第五样:apktool 目录。——反编译出来的工程——清单、资源、smali。——这是所有改动真正发生的地方,也是体积最大的一块。

第六样:build 目录。——打包的产物:unsigned、aligned、signed 三个包。——如果你之前把某个版本发给过别人、后来又找不到了,它可能就在这儿。

第七样:日志。——apktool.log、pack.log——反编译和打包各自的全过程。——它们占的空间不大,但价值不小:那是"当时到底发生了什么"的唯一现场。

一个项目目录里的七样东西
删掉的是七样,不是一样

把这七样分一下类,会看得更清楚:有三样是"你的"(原话历史、原始包、某次发出去的包),有三样是"过程的"(工程、build、日志),一样是"身份的"(配置)。——而"你的"那三样,是删除真正让人心疼的部分——因为它们不能从任何地方重建。

为什么"能重建"这件事这么关键?——因为工程能重建(源包还在,重新解一次就行)、build 能重建(重新打一次)、日志能重建(重新跑一遍就有了)。——而历史不能——那是你当时说的话,只有一次。——所以如果只允许保留一样东西,该留 history.ini。

顺便说一句容量这件事,因为它常被误判。——很多人以为项目占地方,是因为包大——其实真正占地方的是 apktool 目录:它把一个压缩包展开成了成千上万个文件。——所以"磁盘快满了想清点空间"的时候,删一个旧项目能腾出的空间,通常比删十个包还多。——而这也意味着:如果你只是想省空间,删掉 apktool 目录就够了——但那样这个项目就不能回编了,得重新解。

如果确定要省空间,顺序也有讲究:先删 build,再删 apktool 目录,最后才考虑整项目。——因为 build 里的三个包随时能重打;apktool 目录能靠源包重解;而 config.ini 和历史只有一份。——按这个顺序删,任何一步停手,你都还有一个"能恢复"的项目——只是需要多花几分钟。

六、两个例子:从"在资源管理器里删"到"在界面里删"

删除这件事,我们内部也经历过一次习惯改变。而改变的原因,不是"界面更方便",是"手工删会丢东西"。

例子一:清磁盘时的手工删除。

以前怎么做:——每个季度清一次磁盘,方法是打开项目目录,按修改时间排序,把三个月没动过的文件夹整个删掉。——这个方法看着很合理——毕竟"没动过"就等于"没用了"。——而它有一个我们当时没想到的问题:那次我们删掉了一个"看起来三个月没动"的项目——而它恰好是我们一个内部应用的"基准版"。

丢了什么?——丢了 history.ini 里那十几条改动记录——那是这个应用从第一版到现在的完整脉络;也丢了 build 目录里那个"给合作方发过的包"——后来对方说想要同一版,我们只能重打。——而重打出来的包,因为签名配置那期间换过一次,指纹和当初发出去的不一样——对方那边的用户得卸载重装。

现在怎么做:——在工具的列表里删——而这么做带来两个不一样的地方。——第一,删之前你会看到那个项目的图标、应用名、版本号和创建时间——四个信息足够你判断"这是不是那个基准版"。——第二,删除时走的那四道检查,会让"删的对象"永远只是项目根目录下的一个直接子目录——你不会在资源管理器里手滑多选一个。

改完怎么确认:——删完刷新一下列表,看那条记录是不是真的没了;再打开 dock.log 看最后一行是不是"删除项目:某某路径"。——顺便说一句:这两步我们后来变成了习惯——因为这个动作不可逆,多花五秒确认,很值。

例子二:一次差点删错的险情。

那次的情况:——同事在清理测试项目,选中了一个准备删——而那个项目名和另一个正式项目几乎一样(只差一个字),图标也一样(因为改的是同一个应用的两次尝试)。——他在点删除前犹豫了一下,去看了一眼创建时间——发现这个是他三天前建的、那个是三个月前的。——于是没有删错。

这件事让我们的习惯变了一点点:现在建测试项目,名字里一定带日期——比如"报修-试改圆角-0312"。——因为列表是按创建时间倒序排的,名字带日期之后,"哪个是临时的"一眼就能看出来。——而这个习惯的成本几乎是零,收益是"不用在删除按钮前犹豫"。

那次险情还说明了另一件事:四道检查挡得住"路径被改坏",挡不住"人选错了"。——前者是代码的责任,后者是你的责任。——所以"名字里带日期"这种小习惯,其实是四道检查的补充——它在人这一侧加了一道。

把这两个例子放在一起看,会得到一条挺朴素的结论:删除的风险不在"程序会不会删错地方",在"你会不会选错对象"。——而应对它的办法不是更谨慎地盯按钮,是让对象本身更容易区分——图标、名字、创建时间、版本号,四个里至少有两个不同的,就不会选错。

七、什么时候该删,什么时候该留

这是最实际的一章。而判断标准可以浓缩成一张表——表的左列是"这个项目还可能被回头看吗"。

情况 建议
试改失败、当天的临时项目 删
反编译失败、还没开始改 先留着,日志有用
某个应用唯一的一个项目 留
同一个应用的多个尝试里的旧分支 确认主分支后再删
发给过别人的那一版 先导出包再删
只是占空间、历史很短 删

表里第五行是最容易吃亏的一行:"发给过别人的那一版"。——因为那个包不只是"一个文件"——它是"某台机器上正在跑的那个版本"。——而如果对方后来要同一版(回滚、加装到新设备、给别人对照),你需要一模一样的包,不是"同一版代码重新打一个"。——而后者在签名、时间戳、甚至资源打包顺序上都可能不同。

所以有一个动作值得变成肌肉记忆:删之前,先去 build 目录看一眼那个 signed.apk 是不是"曾经发出去的那个"。——如果是,先把它拷到别处(桌面、共享盘、归档目录——随你),再删项目。——而这个动作的成本是十秒,收益是"不用向对方解释为什么这版和上版不一样"。

八、想留着又不想占地方:备份就是拷目录

好消息是,"留"这件事不需要占用项目列表。因为这个工具的模型是"目录即项目"——所以备份就是拷贝,恢复就是放回去。

备份:把那个八位名字的文件夹整个拷到别处。——拷完之后它就是一个普通的文件夹——不会因为你没有"导出"就少什么。——而里面那些配置用的是相对路径(图标文件、源包名都是相对项目目录写的),所以换个位置也能读得出来。

恢复:把它拷回项目根目录,刷新列表。——它会重新出现在列表里——因为列表就是扫目录。——而如果那个八位名字已经被新项目占用了呢?改个名就行——目录名只是个标识,config.ini 里的项目名才是你看到的名字。

这个性质还解决了另一个常见需求:换机器。——把整个项目目录拷到新机器的工作目录下面,刷新,项目都在。——而真正需要重新准备的不是项目,是环境和签名:工具链要在这台机器上装好,签名文件如果放在工作目录根目录,也得一起带过来。

顺便说清一个容易混的事:项目根目录在哪,是程序按挑盘规则定的。——它按 D、E、F、G、C 的顺序找第一个可读写且剩余空间足够的盘,然后拼一个固定的目录名。——所以如果新机器上没有 D 盘,项目根目录就跑到 E 盘去了——这时候"放回原位"这句话的含义变了:要放到新机器的那一个根目录下。

而"拷目录"这件事还有一个隐性好处:它让"归档"变成了一个你可以自己定义的动作。——你可以按年份归档、可以只归档 history.ini 和 signed.apk、可以把 apktool 目录排除在外以省空间——因为这些都由你决定,工具不参与。——而工具不参与的好处是:你不需要学一套新的导出格式——你只需要会拷文件夹。

既然是拷文件夹,命名就该由你定——而这里有个很省事的小规则:年月加应用名,再加一句这次改了什么。——比如"2026-03 报修应用 换底部入口"。——为什么值得这么起名?因为三个月后你翻归档目录时,唯一想知道的是"哪个是我要的那份"——而这个答案,靠八位随机名是给不出来的。

九、这四道检查里的三个通用原则

删除这件事的代码不多,但里面的判断方式可以搬到很多地方用。挑三个最值得记的。

原则一:不要相信"数据里的路径",要相信"你自己算出来的边界"。——config.ini 里的那个路径是数据——数据可能被改、被覆盖、被同步坏。——而"项目根目录"是程序自己算出来的——它不由任何文件决定。——四道检查的本质,就是拿"数据里的路径"去和"自己算出来的边界"比——比不上的,一律拒绝。

这条原则在很多地方都成立:——上传文件时不要相信文件名里的路径、解压时不要相信压缩包里的相对路径、按配置删日志时不要相信配置里的目录。——凡是"要拿一个外来字符串去操作文件系统"的地方,都该先算一条边界出来。

原则二:限制"能碰的范围",比检查"要不要碰"更可靠。——如果只做前者,你需要穷举所有危险情况;而做了后者,你只需要一条规则:只碰根目录的直接子目录。——一条规则能挡住的情况,比一百个 if 都多——因为它挡的是"你没想到的那种"。

原则三:不可逆的动作,要做到"重复执行也没关系"。——目录不存在也当成功,这条设计让删除变成了幂等操作。——而幂等带来的不止是方便——它让"失败之后重试"这件事变得安全。——反过来,如果一个动作重试会有副作用,那它每次失败都会让你更不敢动。

三条原则:算自己的边界、限制范围、让动作幂等
边界、范围、幂等——三件比检查清单更耐用的事

这三条原则放在一起,会得到一个有点反直觉的结论:让危险动作变安全的方法,不是"加更多确认弹窗",是"缩小它能造成的后果"。——确认弹窗拦的是"手滑",而范围限制拦的是"设计漏洞"。——而后者才是真正会让你丢数据的那种。

顺便说一句:这个工具在删除上并没有加一堆"你确定吗"。——它做的是"把能删的范围钉死"+"把日志写清楚"——而这两件事都不打扰你。——这是个挺成熟的分工:机器负责不越界,人负责选对对象。

十、删除前的一份四步核对

落到操作上,其实只有四步。而它们的顺序是按"后悔成本"排的:越后悔的越先做。

第一步:看一眼 history.ini 的最后几条。——不用打开文件——进详情页的修改历史看一眼就行。——这一步回答的是"这个项目到底做过什么"——而很多时候你会在这一步发现自己记错了对象。

第二步:看一眼 build 目录里有没有"发出去过的包"。——如果有,先拷走。——这一步是整份清单里唯一可能"补不回来"的一步——因为重打出来的包在签名和内容上都可能和当初不同。

第三步:确认它不是某个应用"唯一的一个项目"。——判断方法是按应用名在列表里扫一眼——如果这个应用只有这一条记录,那就先别删。——因为删了之后,这个应用在你这里就"没有历史"了——下次要改,得从原包重新开始。

第四步:删,然后刷新列表,再看一眼 dock.log。——刷新确认它真的没了;日志确认删的是哪个路径。——两步加起来五秒,但它们让"删除"这个动作有了一个明确的收尾。

这份清单里,第二步最容易被跳过,而它恰好最重要。——因为前三步里只有它涉及"补不回来的东西"——历史可以凭记忆重建一部分、项目可以从原包重来,而"某个特定版本的包"没了就是没了。——所以如果时间只够做一步,做第二步。

唯一可以整份跳过的情况,是你在批量清"当天建的测试项目"。——因为它们的共同特征是:没有历史可看、没有包发出去过、也不可能是某个应用唯一的项目。——而这种批量清理之所以能跳过,恰恰是因为你事先给它们起了带日期的名字——所以"跳过检查"这件事本身,也是前面那个小习惯换来的。

十一、三个"我建议你先别删"的场景

上面那份清单是通用的。而下面这三个场景,值得单独提一句——因为它们都"看起来该删"。

场景一:那个反编译失败的项目。——它占着地方、列表里点进去什么都不完整——看起来毫无价值。——而它的价值在 apktool.log 里:那是一份"这个包解不开"的证据。——只要这个应用还在你手上,这份证据就可能还有用——因为下一次你还会拿到同类形态的包。——建议:留着,直到这个应用的改包工作彻底结束。

场景二:改了十几轮之后的那个项目。——它的 apktool 目录可能已经有几百兆,看起来"太占地方了"。——而它的 history.ini 里是十几条原话——那是这个应用完整的需求演变史。——建议:如果只是想省空间,删 build 目录和 apktool 目录都比删整个项目划算——因为前两者能重来,后者不能。

场景三:那个"只改过一次"的项目。——看起来最没价值——只有一条历史。——但如果那一轮改动很关键(比如就是定下现在这套视觉的那一轮),那它是"从旧到新"的分界点。——建议:把那一轮的 signed.apk 拷出来,然后再删——这样你既省了空间,又留住了"分界点上的那个包"。

这三个场景有一个共同点:它们的价值不在"能不能继续用",在"能不能回答一个问题"。——"这个包为什么解不开"、"这个应用是怎么变成现在这样的"、"当时那个版本长什么样"——这三个问题都需要过去的东西来回答。——而删除这件事最残酷的地方,就是它把"过去"一起删掉了。

十二、关于"不可逆",值得想清的一件事

写到这里,可以聊一点不那么技术的东西。为什么"删除"这个功能,在所有工具里都值得被认真对待?

因为它是唯一一个"你越熟练,越容易出错"的动作。——新手删东西很谨慎,因为不熟;熟手删东西很快,因为"我知道我在干什么"。——而事故往往发生在熟手身上:手快、路径熟、不看提示。

所以对工具来说,"防呆"真正要防的不是新手,是熟练带来的松懈。——而防松懈的办法不是"多弹一个窗"(弹窗会被条件反射地点掉),是"让错误做不到"。——四道检查就是这种思路:它不需要你保持警惕,它让"删错地方"这件事在代码层面不成立。

而剩下的那部分——"选错对象"——只能靠你自己。——而这部分也有办法:让对象更容易区分。——测试项目名字里带日期、正式项目图标别乱换、给不同应用的项目用不同的版本号前缀——这些都是很土的办法,但它们真的有用。

最后一句话,是关于"删"和"留"的心态。——我们内部现在的做法是:删之前问一句"如果三个月后有人问我这个应用当时怎么改的,我还能答上来吗"。——答得上来,就删;答不上来,就先拷一份 history.ini 出去再删。——而这个问题的好处是:它不需要你判断"有没有用"——只需要你判断"要不要交代"。

顺便说,这个问题还有一个好处:它逼你把"交代"这件事想清楚。——如果要交代的对象是同事,那留 history.ini 就够;如果是客户或审计,那还得留当时的 signed.apk 和 pack.log。——而两种"留"的量差得很远——先想清对象,再决定留多少,比"什么都留着"省很多空间。

十三、项目目录是可以交接的东西

"目录即项目"这个模型还有一个很实际的用途,很多团队没想到:它让"把这个应用交给别人"变成了一件可以一次性完成的事。

交接的时候,你给对方三样东西就够了:那个八位名字的文件夹、工具链的说明、签名文件。——第一样里有他的全部上下文——原包、工程、每一轮说过的话、每次打包的日志。——第二样决定他能不能在自己机器上跑起来。——第三样决定他打出来的包和你打出来的包是不是同一个指纹。

这三样里,最容易漏的是第三样——签名。——因为签名文件通常不在项目目录里,而在工作目录根目录(内置测试签名就是这么放的)。——所以如果只拷项目目录,对方打出来的包会用他机器上那份签名——指纹和你不同——于是之前装过你包的机器,装他的包就得先卸载。

反过来,这个模型也让"接手的人"少走很多弯路。——他不需要问你"这个应用以前改过什么"——他打开修改历史就能看到每一轮的原话。——他不需要问你"那次为什么那样改"——如果那轮改得不顺,pack.log 里还留着当时的失败。——而这些信息之所以还在,只是因为没人把它删掉。

所以"删项目"这个动作,在团队里还多了一层含义:它是"这个应用在我们这里的历史到此为止"。——如果你只是换个人接手,那是交接,不是删除。——而交接和删除的区别,只是一次拷贝。

十四、用过的人怎么说

"我以前在资源管理器里按时间清项目,删过一次季度里没动过的——结果那是我们一个应用的基准版。历史全没了。现在只在列表里删,删前先看版本号。"

—— 老徐 · 教育科技公司产品经理

"我最开始不理解目录名为什么是一串乱码,后来发现好处是改名不折腾——项目名叫什么都不影响文件夹。就是清理的时候得回工具里看,不能靠猜。"

—— 小唐 · 独立开发者

"有次删一个项目,删完刷新列表它还在,我以为是没删掉。后来才知道是我看错了那一行——两个项目名字太像。现在测试项目名字里都带日期。"

—— 小林 · 电商公司内容负责人

"我把整个项目目录拷到家里的机器上,刷新一下项目就在,挺意外。省了我重新导入、重新反编译那一套。就是签名得自己再配一次。"

—— 阿凯 · 小型软件工作室负责人

"它删不掉的时候我一开始以为是 bug,后来发现是我开着那个目录里的图片在看。关掉就好了。现在删之前先把自己开的窗口都关了。"

—— 老周 · 做了十几年运维

五条里,有三条讲的是"误判"——以为是没删掉、以为删不掉是 bug、以为乱码目录名没道理。————而三次误判都指向同一件事:这个工具的删除逻辑和直觉不太一样,但每一条不一样都有理由。————知道理由之后,那些"奇怪"就变成了"可靠"。

还有一条提到"签名得自己再配一次"——这正是上一章说的那件事。————拷项目目录能带走工程和历史,带不走签名和环境——而后两者恰好是"打出来的包能不能覆盖安装"的关键。————所以换机器这件事,真正的清单是三行:项目目录、工具链、签名文件。

如果要把这三行写成一句能直接发出去的话,大概是这样:"项目文件夹在 D 盘那个工作目录下,名字是这八个字符;工具链用程序里的更新环境装一遍;签名用这个 keystore,别名和口令另外发你。"——三句话,接的人当天就能干活——而这三句话之所以说得出来,是因为项目本身被设计成"可以整个搬走"的东西。

合规提醒

本文讨论的所有改动,都应在自己拥有版权、或已获得权利人明确授权的应用上进行。请勿用于破解他人软件、绕过授权验证、去除版权标识或任何侵犯他人权益的用途。文中提到的自家应用实例,均为我们有权修改的项目。另外提醒一点:删除项目会连同 history.ini 一起删掉——如果那里面写着内部系统名称、客户名称或者业务细节,交接或外发之前请先看一眼再决定怎么处理。反过来也一样:清理不是"删干净就好",而是"删该删的、留该留的"——留错东西和删错东西,代价一样大。


安卓修改大师智改工坊

只删该删的,只碰该碰的范围

导入 → 一句话描述 → 打包 → 装机确认,四步走完
官网 www.apkeditor.cn

立即下载智改工坊(AI 版)

支持 apk / apks / xapk / apkm 等包型 · Windows 桌面端

只需说话,就能让应用变成你想要的样子。——而这句话能一直成立的前提是:你说过的每句话都还留着。这就是为什么安卓修改大师智改工坊在删除这件事上,宁可多四道检查,也不愿少一条历史。