当前位置: 首页 > news >正文

Android Studio Gradle多渠道打包

 原理使用Android Studio打一次渠道包,用反编译工具反编译后,修改渠道信息重新编译

准备文件

分渠道配置文件:channel.txt ↓

# 多渠道配置里“统计平台”、“市场名称”、“渠道编号”分别代表什么意思?
# 统计平台:即android name,应用中集成的数据分析sdk的公司名称,例:umeng_channel(下拉列表里提供了若干选项);
# 市场名称:各大安卓应用分发市场(下拉列表里提供了Top20的市场供选择),以帮助开发者区分不同渠道包特征上传相对应市场;
# 渠道编号:即android value,一般填写相关channel id。用户可自行定义区分各大市场的关键字,尽量避免使用特殊字符。
BaiduMobAd_CHANNEL yingyonghui yingyonghui
BaiduMobAd_CHANNEL oppo oppo
BaiduMobAd_CHANNEL 360 360
BaiduMobAd_CHANNEL baidu baidu
BaiduMobAd_CHANNEL xiaomi xiaomi
BaiduMobAd_CHANNEL huawei huawei
BaiduMobAd_CHANNEL lianxiang lianxiang
BaiduMobAd_CHANNEL yingyongbao yingyongbao
BaiduMobAd_CHANNEL aliyun aliyun
BaiduMobAd_CHANNEL sanxing sanxing
BaiduMobAd_CHANNEL vivo vivo
BaiduMobAd_CHANNEL honor honor

(反编译+对齐+签名)文件:↓

// 可以在Android SDK目录里面找到D:\Android\sdk\build-tools\30.0.3\lib

apksigner.jar

// Mac就找【zipalign】,windows就找【zipalign.exe】

zipalign

zipalign.exe

// 官网:下载Apktool官网

apktool_2.9.3.jar

app build.gradle文件中这样配置

 下面需要自己自行调整

要打多渠道包时点击运行那个绿色的小三角

核心文件:release.gradle

import java.nio.file.Files
import java.util.regex.Matcher
import java.util.regex.Pattern
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStreamext {SIGN_JAR = rootDir.getPath() + "/tools/apksigner.jar"ZIPALIGN = rootDir.getPath() + "/tools/zipalign"APKTOOL_JAR = rootDir.getPath() + "/tools/apktool_2.9.3.jar"storeFile = rootDir.getPath() + "/app.keystore" //密钥路径storePassword = "dmx1234567890" //密钥密码keyAlias = "Alias" //密钥别名keyPassword = "dmx1234567890"//别名密码// 反编译目录unApkPath = buildDir.getPath() + '/outputs/release/unapk'// 渠道Apk输出路径channel_apks_path = buildDir.getPath() + '/outputs/release/channels/'// 保存渠道配置CHANNEL_CONFIG = rootDir.getPath() + "/channel.txt"
}
/*** Apk渠道类*/
class ApkChannel {/*** 统计平台,即 android name*/String name;/*** 市场名称,即应用分渠道包时,会加上这个名称 列:app_1.0.0_5_{market}_sign.apk*/String market;/*** 统计渠道,即 android value*/String value;public ApkChannel(String name, String market, String value) {this.name = name;this.market = market;this.value = value;}}static def isWindows() {return org.gradle.internal.os.OperatingSystem.current().isWindows()
}static def isMacOsX() {return org.gradle.internal.os.OperatingSystem.current().isMacOsX()
}
// 这个在MacOS上有时候会删除不到,会导致编译出来的包存在.DS_Store文件,懂的可以自己研究处理一下
def deleteDS_Store() {if (isMacOsX()) {exec {commandLine "/bin/sh", "-c", "find ${unApkPath} -name '.DS_Store' -depth -exec rm {} +"}}
}static def String readXml(File xmlFile) {String result = nulltry (InputStream stream = new FileInputStream(xmlFile)) {// 创建byte数组byte[] buffer = new byte[stream.available()]// 将文件中的数据读到byte数组中stream.read(buffer)result = new String(buffer, "UTF-8")} catch (Exception e) {throw new Exception(e)}return result
}static def boolean writeXml(File xmlFile, String xmlString) {boolean isWriteXml = false// 写入文件try (BufferedWriter writer = new BufferedWriter(new FileWriter(xmlFile))) {writer.write(xmlString);isWriteXml = true} catch (IOException e) {throw new Exception(e)}return isWriteXml
}/*** 根据统计平台名称匹配,是否存在该统计平台的 meta-data* @param xmlString* @param channelName* @return*/
private static boolean isExistsMetaData(String xmlString, String channelName) {String metaDataReg = "\\<meta-data android:name=\\\"" + channelName + "\\\" android:value=\".*?\"/\\>"Pattern pattern = Pattern.compile(metaDataReg)return pattern.matcher(xmlString).find()
}
/*** 替换指定的统计平台的 meta data* @param xmlString* @param channelName* @param metaData*/
private static String replaceMetaData(String xmlString, String channelName, String metaData) {String metaDataReg = "\\<meta-data android:name=\\\"" + channelName + "\\\" android:value=\".*?\"/\\>"Pattern pattern = Pattern.compile(metaDataReg)Matcher matcher = pattern.matcher(xmlString)if (matcher.find()) {return xmlString.replace(matcher.group(), metaData)}return xmlString
}
/*** 生成 meta data* @param channelName* @param channelValue* @return*/
private static String generateMetaData(String channelName, String channelValue) {return String.format("<meta-data android:name=\"%s\" android:value=\"%s\"/>", channelName, channelValue)
}def List<ApkChannel> initChannels() {List<ApkChannel> channels = new ArrayList<ApkChannel>()println("并初始化channel.txt文件...")File channelFile = new File(CHANNEL_CONFIG)if (!channelFile.exists()) {throw new FileNotFoundException(channelFile.getPath() + "文件不存在!")}try (BufferedReader reader = new BufferedReader(new FileReader(channelFile))) {String linewhile ((line = reader.readLine()) != null) {// 处理每一行数据if (line.startsWith("#")) {println(line.replace("# ", ""))} else {String[] arr = line.split(" ")channels.add(new ApkChannel(arr[0], arr[1], arr[2]))}}println("初始化成功,渠道数:" + channels.size())} catch (Exception e) {e.printStackTrace()}return channels
}/*** 反编译Apk* @param inApkFile 要反编译的Apk文件* @return 返回反编译后的文件目录*/
def File decompileApk(File inApkFile) {println "*************** apktool decompile start ***************"File outDecompileFile = new File(unApkPath, inApkFile.name.replace(".apk", ""))if (outDecompileFile.exists()) {if (!delete(outDecompileFile)) {throw new RuntimeException("delete apktoolOutputDir failure!")}}if (!outDecompileFile.mkdirs()) {throw new RuntimeException("make apktoolOutputDir failure!")}println("apktool decompile out file: " + outDecompileFile)// 解压APK命令String unApkCommand = String.format("java -jar %s d -o %s %s -f -s",APKTOOL_JAR,outDecompileFile.getPath(),inApkFile.getPath())exec {if (isWindows()) {commandLine "powershell", unApkCommand} else if (isMacOsX()) {commandLine "/bin/sh", "-c", unApkCommand} else {throw new RuntimeException("Please confirm your platform command line!")}}deleteDS_Store()println "*************** apktool decompile finish ***************"return outDecompileFile
}
/*** 编译Apk* @param inDecompileApkFileDir 输入反编译后的文件目录* @param outCompileApkFileDir 输出编译Apk文件存储目录* @param outFileName 编译后的Apk文件名* @return*/
def File compileApk(File inDecompileApkFileDir, File outCompileApkFileDir, String outFileName) {println "*************** apktool compile start ***************"if (!inDecompileApkFileDir.exists()) {throw new FileNotFoundException("no " + inDecompileApkFileDir.getPath() + " has found!")}if (!outCompileApkFileDir.exists()) {outCompileApkFileDir.mkdirs()}File outCompileApkFile = new File(outCompileApkFileDir, outFileName)String buildApkCommand = String.format("java -jar %s b %s -o %s",APKTOOL_JAR,inDecompileApkFileDir.getPath(),outCompileApkFile.getPath())exec {if (isWindows()) {commandLine "powershell", buildApkCommand} else if (isMacOsX()) {commandLine "/bin/sh", "-c", buildApkCommand} else {throw new RuntimeException("Please confirm your platform command line!")}}println "*************** apktool compile finish ***************"return outCompileApkFile
}/*** 对齐Apk* @param inApkFile 要对齐的Apk文件* @return 返回对齐后的Apk文件*/
def File zipalignApk(File inApkFile) {println "*************** zipalign optimize start ***************"String zipalignApkFilename = inApkFile.name.replace(".apk", "_unsigned.apk")File outZipalignApkFile = new File(inApkFile.getParent(), zipalignApkFilename)exec {if (isWindows()) {// zipalign.exe -p -f -v 4 infile.apk outfile.apkString alignApkCommand = String.format("%s.exe -p -f -v 4 %s %s",ZIPALIGN,inApkFile.getPath(),outZipalignApkFile.getPath())commandLine "powershell", alignApkCommand} else if (isMacOsX()) {// zipalign -p -f -v 4 infile.apk outfile.apkString alignApkCommand = String.format("%s -p -f -v 4 %s %s",ZIPALIGN,inApkFile.getPath(),outZipalignApkFile.getPath())commandLine "/bin/sh", "-c", alignApkCommand} else {throw new RuntimeException("Please confirm your platform command line!")}}println "*************** zipalign optimize finish ***************"return outZipalignApkFile
}
/*** 签名Apk* @param inApkFile 要签名的Apk文件* @return 返回签名后的Apk文件*/
def File signerApk(File inApkFile) {println "*************** start apksigner ***************"File outSignerApkFile = new File(inApkFile.getPath().replace("_unsigned.apk", "_signed.apk"))String apksignerCommand = String.format("java -jar %s sign -verbose --ks %s --v1-signing-enabled true --v2-signing-enabled true --v3-signing-enabled false --ks-key-alias %s --ks-pass pass:%s --key-pass pass:%s --out %s %s",SIGN_JAR,storeFile,keyAlias,storePassword,keyPassword,outSignerApkFile.getPath(),inApkFile.getPath())exec {if (isWindows()) {commandLine "powershell", apksignerCommand} else if (isMacOsX()) {commandLine "/bin/sh", "-c", apksignerCommand} else {throw new RuntimeException("Please confirm your platform command line!")}}println "*************** finish apksigner ***************"return outSignerApkFile
}private def processingChannel(File decompileApkFile, String channelMarket) {// 删除恶心的.DS_StoredeleteDS_Store()// 编译File compileApkFile = compileApk(decompileApkFile, new File(channel_apks_path), decompileApkFile.name + "_" + channelMarket + ".apk")// 对齐File zipalignApkFile = zipalignApk(compileApkFile)if (zipalignApkFile.exists()) {delete(compileApkFile)}// 签名File signerApkFile = signerApk(zipalignApkFile)if (signerApkFile.exists()) {delete(zipalignApkFile)}
}task assemblePackageChannel() {dependsOn('assembleAdRelease')doLast {if (isMacOsX()) {exec { commandLine "/bin/sh", "-c", "chmod +x " + SIGN_JAR }exec { commandLine "/bin/sh", "-c", "chmod +x " + ZIPALIGN }exec { commandLine "/bin/sh", "-c", "chmod +x " + APKTOOL_JAR }}List<ApkChannel> channels = initChannels()if (channels.size() <= 0) {throw new Exception("没有渠道信息!")}FilenameFilter filter = new FilenameFilter() {@Overrideboolean accept(File dir, String name) {return name.endsWith(".apk")}}// 获得release包地址,暂时固定死String sourceApkDir = buildDir.getPath() + "/outputs/apk/ad/release"File inSourceApkFile = new File(sourceApkDir).listFiles(filter).first()if (inSourceApkFile == null || !inSourceApkFile.exists()) {throw new FileNotFoundException("no apk files has found!")}File decompileApkFile = decompileApk(inSourceApkFile)// 检测AndroidManifest.xml是否存在File xmlFile = new File(decompileApkFile, "AndroidManifest.xml")if (!inSourceApkFile.exists()) {throw new FileNotFoundException("no AndroidManifest.xml files has found!")}String xmlString = readXml(xmlFile)if (xmlString == null || xmlString.length() <= 0) {throw new NullPointerException("read AndroidManifest.xml is null.")}// 多渠道处理for (ApkChannel channel : channels) {// 检测是否存在多个平台if (channel.name.split("\\|").length > 1) {String[] channelNames = channel.name.split("\\|")String[] channelValues = channel.value.split("\\|")for (int i = 0; i < channelNames.length; i++) {String channelName = channelNames[i]String channelValue = channelValues[i]if (isExistsMetaData(xmlString, channelName)) {// 替换渠道信息xmlString = replaceMetaData(xmlString, channelName, generateMetaData(channelName, channelValue))// 写入渠道信息writeXml(xmlFile, xmlString)processingChannel(decompileApkFile, channel.market)}}} else {if (isExistsMetaData(xmlString, channel.name)) {// 替换渠道信息xmlString = replaceMetaData(xmlString, channel.name, generateMetaData(channel.name, channel.value))// 写入渠道信息writeXml(xmlFile, xmlString)processingChannel(decompileApkFile, channel.market)}}}}
}

相关文章:

Android Studio Gradle多渠道打包

原理使用Android Studio打一次渠道包&#xff0c;用反编译工具反编译后&#xff0c;修改渠道信息重新编译 准备文件 分渠道配置文件&#xff1a;channel.txt ↓ # 多渠道配置里“统计平台”、“市场名称”、“渠道编号”分别代表什么意思&#xff1f; # 统计平台&#xff1a;…...

什么是DNS缓存?DNS缓存有哪些作用和危害?

在互联网世界的运转机制中&#xff0c;DNS&#xff08;域名系统&#xff09;是其中的关键&#xff0c;而DNS缓存则是这一系统的重要环节。它既能加快网站的访问速度&#xff0c;同时也会对网络安全造成影响&#xff0c;因此了解DNS缓存对于网站的日常管理至关重要。 什么是DNS…...

web基础与http协议与配置

目录 一、web基础 1.1 DNS与域名&#xff08;详解看前面章节&#xff09; 1.2 网页的概念&#xff08;HTTP/HTTPS&#xff09; 1.2.1 基本概念 1.2.2 HTML文档结构(了解) 1.2.3 web相关重点 1.2.4 静态资源和动态资源 二、http协议 2.1 概述 2.2 cookie和session&…...

机械学习—零基础学习日志(python编程2)

零基础为了学人工智能&#xff0c;正在艰苦的学习 这里把&#xff0c;函数以及类相关的知识做一个笔记&#xff0c;放在这里。 期待与大家交流~ 变量作用域 Python 中&#xff0c;程序的变量并不是在哪个位置都可以访问的&#xff0c;访问权限决定于这个变量是在哪里赋值的…...

element-plus的表单输入框有清除按钮的,文字输入前后宽度不一致怎么解决

输入内容之后多了一个可清除的图标&#xff0c;输入框的宽度也被撑开了 根据输入前后的dom对比发现&#xff0c;多了一个图标的span标签 :deep(.el-input__wrapper) {position: relative;.el-input__inner {padding-right: 18px;}.el-input__suffix {position: absolute;right:…...

解决Docker拉取镜像时 i/o timeout错误

目录 一&#xff0c;设置Docker镜像源&#xff08;推荐&#xff09; 1.1 解决方案1&#xff1a;配置加速地址 1.2 解决方案2&#xff1a;使用代理拉取镜像 1.3 解决方案3&#xff1a;备用办法&#xff1a;直接传送镜像 二&#xff0c;目前可用的镜像源 一&#xff0c;设置…...

面壁的智能开源 MiniCPM-V 2.6 边缘人工智能多模态功能与 GPT-4V 不相上下

"MiniCPM-V2.6 "是一个边缘多模态人工智能模型&#xff0c;仅拥有 80 亿个参数&#xff0c;却在单图像、多图像和视频理解任务中取得了低于 200 亿个参数的三项 SOTA&#xff08;艺术境界&#xff09;成绩&#xff0c;显著增强了边缘多模态能力&#xff0c;并与 GPT-…...

dhcp+checkkickstar的实验理解

文章目录 实验介绍使用的服务介绍PXE服务dhcp服务Kickstart 服务tftp服务 第一部分&#xff08;基础部分&#xff09;代码展示注意点第一点![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/13c0f4aabb664655a4dd285dd8e5527a.png)第二点 结果展示 第二部分&#xff08…...

Android网络安全:如何防止中间人攻击

文章目录 引言一、中间人攻击概述二、预防中间人攻击的方法2.1 使用HTTPS2.2 证书锁定&#xff08;Certificate Pinning&#xff09;2.3 使用SSL/TLS最佳实践2.4 验证主机名 三、总结 引言 中间人攻击&#xff08;Man-in-the-Middle&#xff0c;简称MITM&#xff09;是一种常见…...

NOI Linux 2.0 的安装说明以及使用指南

关于 NOI Linux 2.0 NOI Linux 是 NOI 竞赛委员会基于 Ubuntu 操作系统开发的一款 Linux 桌面发行版&#xff0c;是一套免费的、专门为信息学奥林匹克竞赛选手设计的操作系统&#xff0c;是 NOI 系列赛事指定操作系统&#xff0c;适用于常见笔记本电脑和桌面电脑。 新建虚拟机…...

07、MySQL-多表查询

目录 1、内连接 1.1 隐式内连接 1.2 显式内连接 2、外连接 2.1 左外连接 2.2 右外连接 3、自连接 4、联合查询 5、子查询 5.1 标量子查询 5.2 列子查询 5.3 行子查询 5.4 表子查询 1、内连接 概念&#xff1a;相当于查询A、B表交集的部分数据 1.1 隐式内连接 语法&…...

20240809 每日AI必读资讯

乒乓球AI机器人赢了人类&#xff01;正反手灵活转换&#xff0c;擦网球高球都能接 - 谷歌发布首个达到人类竞技水平的机器人Agent&#xff0c;挑战乒乓球赛场。 - 机器人通过学习大量乒乓球状态数据&#xff0c;掌握了正手上旋球、反手瞄准等技能&#xff0c;展现出高速运动…...

《投资的原理》阅读笔记一

这是我准备集中学习投资类书籍后阅读的第8本书&#xff0c;但是是第一本读到一半决定从新开始、每章都写笔记的第一本书。 《投资的原理》的作者陈嘉禾先生是一位资深的价值投资者&#xff0c;书中也是大力弘扬着价值投资&#xff0c;跟我倾向于量化投资方向的想法并不合拍&am…...

金九银十,全网最详细的软件测试面试题总结

前面看到了一些面试题&#xff0c;总感觉会用得到&#xff0c;但是看一遍又记不住&#xff0c;所以我把面试题都整合在一起&#xff0c;都是来自各路大佬的分享&#xff0c;为了方便以后自己需要的时候刷一刷&#xff0c;不用再到处找题&#xff0c;今天把自己整理的这些面试题…...

ActiveMQ任意文件写入漏洞(CVE-2016-3088)复现

一.环境配置 腾讯云的ubuntu操作系统&#xff0c;已经安装有docker 和 vulhub 直接来到 启动环境docker-compose&#xff0c;要在root权限下运行。 docker-compose up -d 若出现等待时间过长的情况&#xff0c;请参考这篇文章http://t.csdnimg.cn/SYhbE 访问http://公网ip:8…...

网络协议四 物理层,数据链路层

从这一节开始学习 五层模型。学习方法是从最底层物理层开始学习 七层模型 五层模型 各个层用的协议&#xff0c;以及加上协议后的称谓 各个层的作用 应用层&#xff1a;可以认为是原始数据&#xff0c;该数据称为 报文&#xff0c;用户数据。 运输层&#xff1a;也叫传输层&am…...

Python知识点:如何使用Twisted进行异步网络编程

Twisted是一个事件驱动的网络编程框架&#xff0c;支持异步编程&#xff0c;适用于处理大量并发网络连接的应用。使用Twisted进行异步网络编程可以分为以下几个步骤&#xff1a; 安装Twisted&#xff1a; 首先需要安装Twisted库&#xff0c;可以使用pip进行安装&#xff1a; pi…...

循环神经网络

一、RNN神经网络 基本网络结构 RNN神经网络数学模型 RNN反向传播 二、LSTM神经网络 LSTM的遗忘门 对数据进行筛选&#xff0c;有的抛弃&#xff0c;有的保留 LSTM的输入门 LSTM输出门 LSTM缓解RNN梯度消失问题...

SQL进阶技巧:有序数据合并问题之如何按照指定的规则对数据进行合并?【详解collect_list函数有序性保证】

目录 0 问题描述【小红书面试题】 1 数据准备 2 问题分析 3 小结 0 问题描述【小红书】 有如下需求,需要将左边的表变换成右边的表,注意字段内容的顺序及对应内容的一致性。 第一个字段为name,第二个字段为subject,第三个字段为score,变换后要求subject按照语文、数学…...

windows和office微软官方免费激活教程

微软提供了windows系统和office的官方免费激活&#xff0c;其实不用去买什么激活码&#xff0c;官方提供了激活方式&#xff0c;完全免费。目前测试没发现什么问题&#xff0c;windows还支持永久激活&#xff0c;比一些乱七八糟的kms激活工具还省心。 github地址&#xff1a;Gi…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

ssc377d修改flash分区大小

1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

多模态图像修复系统:基于深度学习的图片修复实现

多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...