在 Android 开发领域,代码不仅是功能实现的载体,更是团队协作与项目迭代的基础。一套完善的编码规范,能让代码从 “可运行” 升级为 “易维护、可扩展、低风险”。本文基于 Google、Square 等顶尖团队的实践经验,结合国内 Android 开发的实际场景,从核心价值、基础规范、语言特有规范、组件规范到落地执行,全方位构建 1W 字 + 的详细编码指南,助力团队打造高质量代码。
一、编码规范的核心价值与底层逻辑
编码规范绝非 “格式强迫症” 的产物,而是经过千万个项目验证的 “降本增效” 工具。理解其底层逻辑,才能真正重视并落地执行。
1.1 为什么需要编码规范?
(1)从 “个人代码” 到 “团队资产” 的转化
一个项目的代码往往由多名开发者共同编写,若没有规范,会出现 “每人一套风格” 的混乱局面:
- 变量命名:有人用userName,有人用mUser,有人用yonghuXingming;
- 格式排版:有人用 2 空格缩进,有人用 Tab,有人在大括号前换行;
- 逻辑组织:有人把网络请求写在 Activity,有人写在工具类。
这种混乱会导致:新人接手需花 30% 时间适应风格,而非理解业务;跨模块修改时,因风格差异频繁误解逻辑。规范的本质是 “统一语言”—— 让团队用同一套 “代码语法” 沟通,将代码从 “个人作品” 转化为 “团队资产”。
(2)降低认知负荷:大脑的 “节能模式”
人类大脑处理信息的能力有限,杂乱的代码会消耗额外认知资源。例如:
- 看到a b c这样的变量名,需逐行追溯其含义;
- 面对无注释的复杂逻辑,需反复调试才能理解设计意图;
- 格式混乱的代码,需花费时间梳理结构。
规范通过 “标准化表达” 降低认知负荷:mUserDao一眼可知是用户数据访问对象;统一的缩进让代码层级一目了然;注释清晰的逻辑无需反复调试。据 Google 开发者调查,遵循规范的团队,代码理解效率提升 40% 以上。
(3)提前规避风险:从 “事后修复” 到 “事前预防”
多数线上问题源于 “不规范的编码习惯”:
- Activity中定义静态变量持有Context,导致内存泄漏;
- Fragment用构造函数传参,屏幕旋转后数据丢失;
- 网络请求未在onDestroy中取消,导致 Activity 销毁后回调崩溃。
规范将这些 “坑” 转化为 “禁止项”,例如强制Fragment用newInstance传参、要求网络请求绑定生命周期。据统计,严格执行规范的项目,线上崩溃率可降低 30% 以上。
1.2 优秀编码规范的三大特征
(1)实用性:基于场景而非 “教条”
好的规范必须结合项目实际。例如:
- 小团队快速迭代项目:可简化注释要求,优先保证开发效率;
- 大型商业项目:需严格规范异常处理、权限校验等关键环节;
- 开源项目:必须强化文档注释,方便外部开发者使用。
避免 “为规范而规范”—— 例如要求 “所有变量必须加注释”,反而会导致 “//userName:用户名” 这样的无效注释泛滥。
(2)渐进性:从 “基础” 到 “进阶” 的分层
规范应分层次:
- 基础层:命名、格式、注释(必须严格执行);
- 进阶层:逻辑组织、设计模式(团队达成共识后执行);
- 优化层:性能优化、可读性提升(资深开发者引导)。
例如新人入职可先掌握基础层,避免因规则过多产生抵触;随着经验积累,逐步掌握高阶规范。
(3)可执行:有工具辅助而非 “人工检查”
纯靠人工检查的规范注定失败。优秀规范需配套工具:
- 自动格式化:Android Studio 的 Code Style 配置;
- 静态检查:Lint、Checkstyle 自动检测违规;
- 代码审查:将规范纳入 PR(Pull Request)检查清单。
例如通过配置 Android Studio,保存时自动格式化代码,从源头避免格式问题。
二、基础规范:命名、格式与注释的终极指南
基础规范是所有开发者的 “共同语言”,需做到 “无歧义、易理解、可验证”。
2.1 命名规范:让代码 “自解释”
命名的终极目标是 “通过名称完全理解含义”,无需依赖注释。以下是各类型元素的命名规则及原理。
(1)包名(Package):体现项目结构的 “地址”
包名是代码的 “文件夹地址”,需清晰反映模块划分,遵循 “从粗到细” 的层级逻辑。
- 核心规则:
- 全部小写,禁止下划线、大写字母;
- 以反转的公司域名开头(避免重名);
- 后续按 “项目名→模块名→功能名” 划分。
- 结构示例:
com.company.app.模块.功能
- 电商 APP 示例:
- com.shop.app.main(主模块)
- com.shop.app.goods.detail(商品详情模块)
- com.shop.app.cart(购物车模块)
- com.shop.app.user.login(用户登录模块)
- 常见错误:
- 用缩写导致歧义:com.shop.app.gd(gd可能是 “goods” 或 “guide”);
- 层级混乱:com.shop.app.login.user(登录是用户模块的子功能,应改为com.shop.app.user.login);
- 包含版本号:com.shop.app.v2.goods(版本迭代后需大规模修改包名,不合理)。
(2)类名(Class):明确职责的 “身份牌”
类是代码的基本单元,类名需准确反映其 “职责范围”,避免模糊表述。
- 核心规则:
- 采用帕斯卡命名法(PascalCase):每个单词首字母大写,无下划线;
- 必须包含 “核心职责词”:如LoginActivity中的 “Login”;
- 用后缀区分类型:通过统一后缀让类的角色一目了然。
- 详细类型与命名示例:
类类型 |
命名规则 |
正面示例 |
反面示例 |
设计原理 |
Activity |
功能 + Activity |
LoginActivity(登录页面)、OrderDetailActivity(订单详情) |
MainActivity(无具体功能)、Activity2(无意义) |
明确页面用途,避免后期维护时需打开类查看内容 |
Fragment |
功能 + Fragment |
UserProfileFragment(用户资料碎片)、CommentListFragment(评论列表) |
MyFragment(个人风格)、FragmentA(无意义) |
Fragment 可复用,名称需体现其独立功能 |
ViewModel |
数据主题 + ViewModel |
OrderViewModel(订单数据)、CartViewModel(购物车数据) |
DataViewModel(模糊)、MainVM(缩写不规范) |
明确 ViewModel 管理的数据范围,与 UI 逻辑分离 |
数据类(Java) |
实体名 |
User(用户实体)、OrderInfo(订单信息) |
UserData(冗余,“Data” 可省略)、Info1(无意义) |
数据类以实体为核心,无需额外修饰 |
数据类(Kotlin) |
实体名 + 后缀(Entity/DTO) |
UserEntity(数据库实体)、GoodsDTO(网络传输对象) |
User(未区分层级)、GoodsData(模糊) |
区分数据在存储、传输中的角色,避免混淆 |
适配器(Adapter) |
数据类型 + Adapter |
GoodsListAdapter(商品列表)、CommentAdapter(评论项) |
MyAdapter(无意义)、ListAdapter(模糊) |
明确适配的数据类型,避免复用时错误绑定 |
工具类(Utils) |
功能 + Utils/Helper |
ToastUtils(Toast 工具)、DateFormatter(日期格式化) |
CommonUtils(功能模糊)、Tool(太笼统) |
工具类需单一职责,名称体现具体功能 |
网络请求 |
接口 + Service/Api |
UserApiService(用户相关接口)、PaymentService(支付接口) |
HttpService(模糊)、NetClass(不规范) |
明确接口所属业务域,便于后期接口管理 |
数据库操作(Dao) |
实体 + Dao |
UserDao(用户数据操作)、OrderDao(订单数据操作) |
DbHelper(模糊)、DataAccess(不具体) |
清晰对应实体与数据库操作,符合 ORM 设计思想 |
- 特殊类命名:枚举与接口:
- 枚举(Enum):帕斯卡命名,用 “状态 / 类型” 相关词,如OrderStatus(订单状态)、PaymentMethod(支付方式);
- 接口(Interface):功能 + able/er,如Downloadable(可下载的)、DataProvider(数据提供器);避免I前缀(如IUser),Google 规范已摒弃此风格。
(3)方法名(Method):“动词 + 宾语” 的精准表达
方法是执行具体操作的单元,命名需让读者在不看实现的情况下,知道 “做什么”“输入什么”“输出什么”。
- 核心规则:
- 驼峰命名法(camelCase):首字母小写,后续单词首字母大写;
- 以动词开头:明确操作类型(如get“获取”、set“设置”);
- 包含核心宾语:明确操作对象(如getUserById中的 “User”)。
- 详细场景与命名示例:
操作类型 |
动词选择 |
正面示例 |
反面示例 |
设计原理 |
获取数据 |
get(直接获取)、load(加载,可能耗时)、fetch(远程获取) |
getUserById(String id)(根据 ID 获取用户)、loadOrderList()(加载订单列表) |
getData(int a)(模糊)、get1()(无意义) |
区分数据来源(内存 / 本地 / 远程),便于理解性能特性 |
设置数据 |
set(直接设置)、update(部分更新) |
setUserName(String name)(设置用户名)、updateOrderStatus(int status)(更新订单状态) |
change(String s)(模糊)、set2(String x)(无意义) |
区分 “全量设置” 与 “部分更新”,避免误用 |
提交 / 保存 |
save(保存到本地)、submit(提交到远程)、commit(确认事务) |
saveUserToDb(User user)(保存到数据库)、submitOrder(Order order)(提交订单) |
send(User u)(模糊)、saveData(Object o)(类型不明确) |
区分数据流向(本地 / 远程),避免数据存储错误 |
点击事件 |
on + 事件源 + 动作 |
onLoginButtonClick(View view)(登录按钮点击)、onItemSelected(int position)(列表项选中) |
click(View v)(无事件源)、onClick1(View v)(无意义) |
明确事件触发源,便于调试时定位交互逻辑 |
判断逻辑 |
is(是否符合状态)、has(是否拥有)、can(是否可以) |
isUserLoggedIn()(用户是否已登录)、hasPermission(String permission)(是否有权限) |
check()(模糊)、judge()(不具体) |
布尔方法用明确前缀,一眼可知返回值含义 |
数据转换 |
parse(解析)、convert(转换)、format(格式化) |
parseJsonToUser(String json)(JSON 转 User)、formatDate(Date date)(日期格式化) |
change(String s)(模糊)、trans(Object o)(不具体) |
明确转换前后的类型,避免类型错误 |
- 方法参数与返回值命名:
-
- 参数名:需包含 “类型 + 含义”,如getUserById(String userId)(而非getUserById(String id));
-
- 返回值:布尔方法需包含 “判断结果”,如isUserNameValid()(而非checkUserName())。
(4)变量与常量:精准描述 “数据内容”
变量与常量是代码中最基础的 “数据载体”,命名需让读者立刻知道 “存储的是什么数据”。
- 变量命名规则:
变量类型 |
命名规则 |
正面示例 |
反面示例 |
设计原理 |
成员变量(Java) |
m + 驼峰(m 表示 member) |
mUserDao(用户数据访问对象)、mLoginStatus(登录状态) |
userDao(未加前缀)、mX(无意义) |
区分成员变量与局部变量,避免命名冲突 |
成员变量(Kotlin) |
驼峰(无需前缀) |
userDao、loginStatus |
mUserDao(冗余,Kotlin 不推荐)、a(无意义) |
Kotlin 通过语法区分作用域,无需前缀 |
局部变量 |
驼峰(简洁明了) |
currentPosition(当前位置)、tempUser(临时用户对象) |
i(需追溯含义)、data(模糊) |
局部变量作用域小,以 “当前语境下的含义” 为核心 |
集合变量 |
复数形式 |
users(用户列表)、orders(订单集合) |
userList(冗余,“List” 可省略)、dataArray(模糊) |
复数形式直观体现 “多个元素”,比加 “List” 更简洁 |
布尔变量 |
is/has/can + 状态 |
isLoggedIn(是否已登录)、hasUnreadMessages(是否有未读消息) |
login(非动词)、flag(完全模糊) |
布尔变量需明确 “判断的状态”,避免逻辑反转错误 |
- 常量命名规则:
- 全部大写,下划线分隔单词;
- 必须包含 “领域 + 具体值”;
- 避免魔法数字(直接写数字而不定义常量)。
常量类型 |
命名规则 |
正面示例 |
反面示例 |
设计原理 |
通用常量 |
领域 + 值描述 |
MAX_LOGIN_ATTEMPTS = 5(最大登录尝试次数)、DEFAULT_PAGE_SIZE = 20(默认分页大小) |
MAX = 5(无领域)、NUM1 = 20(无意义) |
明确常量的适用场景,避免修改时影响无关逻辑 |
状态码 |
类型 + 状态 + CODE |
ORDER_STATUS_PAID = 1(订单已支付)、NETWORK_ERROR_CODE = -1(网络错误) |
STATUS1 = 1(无状态描述)、CODE = -1(模糊) |
状态码需与业务含义绑定,避免调试时查文档 |
路径 / 键名 |
类型 + 名称 |
PREFERENCE_USER_ID = "user_id"(偏好设置中的用户 ID 键)、URL_LOGIN = "https://api/login"(登录接口 URL) |
KEY1 = "id"(无含义)、URL = "..."(模糊) |
明确键的用途,避免存储 / 读取时键名错误 |
2.2 格式规范:让代码 “赏心悦目” 的排版逻辑
格式规范的核心是 “视觉一致性”—— 通过统一的排版,让代码的结构 “可视化”,减少阅读时的视觉疲劳。
(1)缩进与换行:层级清晰的 “视觉骨架”
- 缩进规则:
- 统一使用 4 个空格(而非 Tab):Android Studio 默认配置(Settings→Editor→Code Style→Java→Tabs and Indents);
- 每个代码块(如if for class)缩进一次:子逻辑在父逻辑右侧 4 个空格处;
- 禁止 “混合缩进”(部分用空格,部分用 Tab):会导致不同编辑器显示不一致。
- 换行规则:
- 左大括号{必须紧跟语句,不单独成行:
// 正确 if (isLogin) { // 逻辑 } // 错误 if (isLogin) { // 逻辑 }
设计原理:左大括号与语句同行,可减少垂直空间占用,同时明确归属关系。
- 方法之间必须空一行:
// 正确 public void login() { // 登录逻辑 } public void logout() { // 退出逻辑 } // 错误(无空行) public void login() { // 登录逻辑 } public void logout() { // 退出逻辑 }
设计原理:方法是独立功能单元,空行分隔可明确边界,便于快速定位。
- 长语句必须换行(超过 120 字符):
// 正确(链式调用换行) User user = userApiService .getUserById(userId) .setName("新名称") .setAge(20); // 正确(参数过多换行) submitOrder( orderId, userId, Arrays.asList(product1, product2), new PayCallback() { ... } ); // 错误(超长语句不换行) User user = userApiService.getUserById(userId).setName("新名称").setAge(20);
- 逻辑块之间空一行:
// 正确 public void processOrder() { // 第一步:校验参数 if (order == null) { return; } // 第二步:查询库存 int stock = stockDao.query(order.getProductId()); // 第三步:更新订单状态 if (stock > 0) { order.setStatus(ORDER_STATUS_PAID); } else { order.setStatus(ORDER_STATUS_OUT_OF_STOCK); } } // 错误(无空行,逻辑块混淆) public void processOrder() { if (order == null) { return; } int stock = stockDao.query(order.getProductId()); if (stock > 0) { order.setStatus(ORDER_STATUS_PAID); } else { order.setStatus(ORDER_STATUS_OUT_OF_STOCK); } }
设计原理:按逻辑步骤分隔,便于理解代码的执行流程。
(2)括号与空格:消除 “视觉噪音” 的细节处理
- 括号规则:
- if for while等关键字后必须加空格,再跟(:
// 正确 if (isLogin) { ... } for (int i = 0; i < 10; i++) { ... } // 错误(无空格) if(isLogin) { ... } for(int i = 0; i < 10; i++) { ... }
设计原理:关键字与括号间的空格,可区分 “语法关键字” 与 “方法调用”(如if(易误读为方法)。
- 方法调用的(前无空格:
// 正确 getUserById(userId); Toast.makeText(context, "提示", Toast.LENGTH_SHORT).show(); // 错误(有空格) getUserById (userId); Toast.makeText (context, "提示", Toast.LENGTH_SHORT).show();
设计原理:区分 “关键字” 与 “方法”,保持调用语法的一致性。
- 空格规则:
- 二元运算符(+ - = ==等)前后必须加空格:
// 正确 int total = price + count * 2; if (a == b && c > d) { ... } // 错误(无空格) int total=price+count*2; if (a==b&&c>d) { ... }
设计原理:运算符前后空格可增强表达式的可读性,避免因运算符密集导致误读。
- 逗号,后必须加空格:
// 正确 getUser(id, name, age); String[] fruits = {"apple", "banana", "orange"}; // 错误(无空格) getUser(id,name,age); String[] fruits = {"apple","banana","orange"};
设计原理:分隔参数或元素,避免视觉上的 “粘连”。
- 类型转换后的(后不加空格:
// 正确 String str = (String) object; // 错误(有空格) String str = (String) object;
设计原理:类型转换是一个整体操作,避免空格破坏连贯性。
(3)空行与对齐:划分逻辑单元的 “视觉分隔”
- 空行规则:
- 类成员之间空一行(静态变量与实例变量、方法与方法):
public class UserManager { private static final String TAG = "UserManager"; private UserDao mUserDao; public UserManager(UserDao userDao) { mUserDao = userDao; } public User getUser(String id) { return mUserDao.query(id); } }
设计原理:区分类的不同成员,避免代码 “堆在一起”。
- 同一方法内的逻辑块之间空一行:
public void login(String username, String password) { // 第一步:校验参数 if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) { throw new IllegalArgumentException("用户名或密码为空"); } // 第二步:调用登录接口 User user = userApi.login(username, password); // 第三步:保存用户信息 if (user != null) { saveUserToLocal(user); } }
设计原理:按执行步骤分隔,便于理解方法的执行流程。
- 禁止连续空行(最多空一行):
// 错误(连续空行) public void method1() { ... } public void method2() { ... }
设计原理:过多空行会增加滚动距离,降低阅读效率。
- 对齐规则(可选):
- 赋值语句可垂直对齐(非强制,但团队内需统一):
// 推荐(对齐后更美观) private String mUserName = "default"; private int mUserAge = 18; private boolean mIsVip = false; // 允许(但不一致) private String mUserName = "default"; private int mUserAge = 18; private boolean mIsVip = false;
设计原理:对齐可增强变量初始化的视觉一致性,但需避免为对齐添加过多空格。
2.3 注释规范:代码的 “补充说明” 而非 “重复描述”
注释的核心是 “补充代码无法表达的信息”,而非重复代码逻辑。好的注释应回答 “为什么这么做”,而非 “做了什么”。
(1)类注释:类的 “说明书”
每个类必须有类注释,说明其核心职责、设计意图、使用场景等 “宏观信息”。
- 核心要素:
- 功能描述:该类的核心作用(1-2 句话);
- 核心逻辑:关键实现思路(如 “通过本地缓存 + 网络请求实现数据获取”);
- 注意事项:使用时的限制(如 “需先调用 init () 方法”);
- 作者与日期(可选,大型项目推荐)。
- 示例(Java):
/** * 用户登录逻辑处理类 * * 核心功能: * 1. 校验登录参数(用户名/密码格式) * 2. 调用登录接口(支持普通登录/验证码登录) * 3. 登录成功后保存用户信息到SP和数据库 * * 设计思路: * - 采用策略模式,区分不同登录方式(普通/验证码) * - 登录结果通过回调返回,避免阻塞UI线程 * * 注意事项: * - 必须在Application初始化后使用(依赖全局Context) * - 登录过程中调用cancel()可取消请求 * * @author 张三 * @date 2024-05-10 */ public class LoginManager { // 类内容 }
- 示例(Kotlin):
/** * 商品列表数据管理ViewModel * * 负责从Repository获取商品数据,并暴露给UI层: * - 支持下拉刷新(forceRefresh=true) * - 支持上拉加载更多(分页加载) * - 数据变化通过goodsLiveData通知UI * * 数据流程: * UI触发加载 → ViewModel调用Repository → Repository返回数据 → ViewModel更新LiveData * * 注意: * - 调用loadGoods()前需设置categoryId(商品分类ID) */ class GoodsListViewModel : ViewModel() { // 类内容 }
- 常见错误:
- 无注释:新人需通读代码才能理解类的作用;
- 重复代码:如 “用户管理类,用于管理用户”(无实际信息);
- 过时注释:类逻辑修改后,注释未更新,导致误解。
(2)方法注释:方法的 “使用手册”
公共方法(尤其是对外暴露的 API)必须有注释,说明参数含义、返回值、异常情况等 “使用细节”。
- 核心要素:
- 功能描述:方法的核心作用(如 “根据用户 ID 获取用户信息”);
- 参数说明:每个参数的含义、约束(如 “userId:非空,长度为 11 位”);
- 返回值说明:返回数据的含义(如 “User:用户信息,null 表示用户不存在”);
- 异常说明:可能抛出的异常及触发条件(如 “NetworkException:网络不可用时抛出”)。
- 示例(Java):
/** * 根据用户ID查询用户信息 * * @param userId 用户唯一标识(非空,必须为11位数字字符串) * @param forceRefresh 是否强制刷新(true:忽略本地缓存,直接请求网络;false:优先返回缓存) * @return User 用户完整信息(包含姓名、头像、手机号等),null表示用户不存在 * @throws IllegalArgumentException 当userId为空或格式错误时抛出 * @throws NetworkException 当forceRefresh=true且网络不可用时抛出 */ public User getUserById(String userId, boolean forceRefresh) throws NetworkException { // 方法内容 }
- 示例(Kotlin):
/** * 提交订单并获取支付链接 * * @param order 订单信息(必须包含商品ID、数量、收货地址) * @param payType 支付方式(1:微信支付,2:支付宝,其他值会抛出异常) * @return String 支付链接(有效期30分钟) * @throws OrderException 订单校验失败时抛出(如库存不足) */ fun submitOrder(order: Order, payType: Int): String { // 方法内容 }
- 简化规则:
- 私有方法:若逻辑简单,可无注释;若逻辑复杂,需加逻辑注释;
- 简单方法:如getUserName(),可仅用一行注释:
/** 获取用户名(从当前登录用户中读取) */ public String getUserName() { ... }
(3)逻辑注释:复杂逻辑的 “解密钥匙”
当代码逻辑因 “特殊需求”“性能优化”“历史兼容” 等原因偏离常规时,必须用逻辑注释说明 “为什么这么做”。
- 必须添加逻辑注释的场景:
场景 |
示例 |
设计原理 |
特殊业务逻辑 |
java // 为什么减去1? // 因服务器返回的索引从1开始,客户端需转为0开始的索引 int position = serverIndex - 1; |
解释业务规则,避免后期修改时误删 “减 1” 操作 |
性能优化 |
java // 为什么用SparseArray而非HashMap? // 因key为int类型,SparseArray内存效率更高 SparseArray<User> userMap = new SparseArray<>(); |
说明优化依据,避免他人改为 “更熟悉” 但性能差的实现 |
兼容处理 |
java // 为什么加版本判断? // 因API 23以下不支持setBackgroundTintList方法 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { view.setBackgroundTintList(tint); } else { // 低版本兼容逻辑 } |
解释兼容原因,避免后期删除低版本逻辑导致崩溃 |
临时解决方案 |
java // TODO:临时方案(2024-06-30前修复) // 因服务器返回的日期格式错误(少了时区),暂时手动添加"+8" String correctTime = serverTime + "+08:00"; |
标记临时逻辑,提醒后续优化,避免成为 “永久临时方案” |
- 禁止添加的无意义注释:
// 错误示例1:重复代码 int count = 0; // 初始化计数为0 // 错误示例2:显而易见的逻辑 // 循环10次 for (int i = 0; i < 10; i++) { ... } // 错误示例3:废话注释 // 用户管理类 public class UserManager { ... }
2.4 常见格式问题与自动化工具
(1)高频格式错误及修正
错误类型 |
错误示例 |
正确示例 |
修正工具 |
缩进不一致 |
java if (a > b) { int c = 1; int d = 2; // 缩进错误 } |
java if (a > b) { int c = 1; int d = 2; // 正确缩进 } |
Android Studio:Code→Reformat Code |
大括号位置错误 |
java if (a > b) { ... } (正确示例应为左大括号紧跟) |
见上文缩进规则 |
同上 |
空行过多 |
java public void method1() { ... } public void method2() { ... } |
方法间只空一行 |
同上 |
运算符无空格 |
java int a=1+2; |
java int a = 1 + 2; |
同上 |
(2)自动化格式化工具
- Android Studio 内置格式化:
- 快捷键:Ctrl+Alt+L(Windows)/ Cmd+Option+L(Mac);
- 配置路径:File→Settings→Editor→Code Style→Java/Kotlin,可导入团队统一配置。
- 格式化时机:
- 提交代码前必须执行格式化;
- 大型重构后执行格式化;
- 推荐配置 “保存时自动格式化”(Settings→Editor→General→Save Files On Frame Deactivation→Format file)。
三、Java 特有编码规范
Java 作为 Android 开发的传统语言,有其特有的编码陷阱与最佳实践,需针对性规范。
3.1 类与对象设计:避免 “过度设计” 与 “设计不足”
(1)类的单一职责原则
一个类应只负责一项职责,避免 “万能类”。例如:
- 反面示例:
// 错误:一个类承担过多职责(UI、网络、数据存储) public class UserActivity extends AppCompatActivity { private TextView mNameView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_user); mNameView = findViewById(R.id.tv_name); // 职责1:UI初始化 mNameView.setOnClickListener(v -> { ... }); // 职责2:网络请求 new Thread(() -> { String result = HttpUtil.get("https://api/user"); // 更新UI }).start(); // 职责3:数据存储 SharedPreferences sp = getSharedPreferences("user", MODE_PRIVATE); sp.edit().putString("name", "张三").apply(); } }
- 正面示例:
// 正确:拆分职责 public class UserActivity extends AppCompatActivity { private UserViewModel mViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 仅负责UI相关逻辑 mViewModel = new ViewModelProvider(this).get(UserViewModel.class); mViewModel.getUser().observe(this, user -> { updateUI(user); }); } private void updateUI(User user) { ... } } // 数据逻辑 public class UserViewModel extends ViewModel { private UserRepository mRepository; public LiveData<User> getUser() { return mRepository.loadUser(); } } // 数据获取与存储 public class UserRepository { public LiveData<User> loadUser() { // 网络请求+本地存储逻辑 } }
设计原理:单一职责可降低类的复杂度,便于测试和复用 —— 例如修改网络请求逻辑时,无需改动 UI 代码。
(2)构造方法与初始化
- 禁止在构造方法中做耗时操作:
// 错误 public class UserManager { private User mUser; public UserManager() { // 构造方法中做网络请求(耗时) mUser = HttpUtil.get("https://api/user"); } }
问题:构造方法被调用时(如new UserManager()),若耗时会阻塞当前线程(如 UI 线程导致 ANR)。
正确做法:提供初始化方法,在合适时机调用:
public class UserManager {
private User mUser;
public UserManager() {
// 仅做简单初始化
}
// 单独的初始化方法
public void init() {
new Thread(() -> {
mUser = HttpUtil.get("https://api/user");
}).start();
}
}
- 避免在构造方法中依赖外部状态:
// 错误 public class OrderManager { public OrderManager() { // 依赖全局状态,导致测试困难 if (AppConfig.isDebug()) { // 调试逻辑 } } }
正确做法:通过参数传入依赖,而非直接依赖全局状态:
public class OrderManager {
private boolean mIsDebug;
// 通过构造方法传入依赖,便于测试(可传入mock值)
public OrderManager(boolean isDebug) {
mIsDebug = isDebug;
}
}
3.2 异常处理:从 “崩溃” 到 “可控”
Java 的异常处理是保证程序稳定性的关键,需避免 “吞噬异常” 或 “粗暴处理”。
(1)必须捕获并处理异常
- 禁止空 catch 块:
// 错误:吞噬异常,导致问题无法排查 try { String json = readFile("user.json"); User user = new Gson().fromJson(json, User.class); } catch (Exception e) { // 空catch块,无任何处理 }
- 正确处理方式:
try { String json = readFile("user.json"); User user = new Gson().fromJson(json, User.class); } catch (FileNotFoundException e) { // 特定异常:文件不存在,可创建默认文件 createDefaultFile(); Log.e("UserManager", "用户文件不存在,已创建默认文件", e); } catch (JsonSyntaxException e) { // 特定异常:JSON格式错误,提示用户 showErrorToast("数据格式错误,请重新登录"); Log.e("UserManager", "JSON解析失败", e); } catch (Exception e) { // 其他异常:记录日志,避免崩溃 Log.e("UserManager", "未知错误", e); // 可选:上报异常到监控平台 ExceptionReporter.report(e); }
设计原理:不同异常有不同处理方式,需针对性处理;即使无法恢复,也需记录日志便于排查。
(2)异常传递与封装
- 避免 “异常链断裂”:
// 错误:丢失原始异常信息,难以定位根因 try { // 数据库操作 } catch (SQLException e) { // 仅抛出新异常,未携带原始异常 throw new BusinessException("数据操作失败"); }
- 正确传递异常:
try { // 数据库操作 } catch (SQLException e) { // 携带原始异常,保留堆栈信息 throw new BusinessException("数据操作失败", e); }
- 封装底层异常:
对调用者暴露 “业务异常”,隐藏底层实现(如数据库、网络):
// 底层异常(数据库)
public class DbException extends Exception { ... }
// 业务异常(对上层暴露)
public class UserNotFoundException extends BusinessException { ... }
// 封装逻辑
public User getUser() throws UserNotFoundException {
try {
return db.queryUser();
} catch (DbException e) {
// 转换为业务异常
throw new UserNotFoundException("用户不存在", e);
}
}
设计原理:上层调用者无需关心底层实现(如用数据库还是网络),只需处理业务异常。
3.3 集合与数组:避免 “隐性错误”
(1)集合初始化与使用
- 指定初始容量:
// 正确:已知大小,指定初始容量(减少扩容次数) List<User> users = new ArrayList<>(100); // 预计存储100个用户 // 错误:默认容量(10),存储100个元素需多次扩容 List<User> users = new ArrayList<>();
- 禁止在循环中修改集合:
// 错误:遍历中删除元素会抛出ConcurrentModificationException List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); for (String s : list) { if (s.equals("b")) { list.remove(s); } }
- 正确方式:
// 方式1:用迭代器删除 Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { if (iterator.next().equals("b")) { iterator.remove(); // 安全删除 } } // 方式2:用Stream过滤(Java 8+) List<String> newList = list.stream() .filter(s -> !s.equals("b")) .collect(Collectors.toList());
(2)数组使用规范
- 优先用集合而非数组:
// 推荐:集合支持动态扩容、便捷操作 List<String> names = new ArrayList<>(); names.add("张三"); names.contains("张三"); // 不推荐:数组长度固定,操作繁琐 String[] names = new String[10]; names[0] = "张三"; // 判断是否包含需手动循环
- 数组转集合的坑:
// 错误:Arrays.asList返回的是固定大小集合,不能添加/删除 List<String> list = Arrays.asList("a", "b", "c"); list.add("d"); // 抛出UnsupportedOperationException // 正确:转为可修改的ArrayList List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
3.4 线程与并发:避免 “线程安全” 问题
(1)禁止在 UI 线程做耗时操作
- 错误示例:
// 错误:在主线程做网络请求(导致ANR) public void onClick(View view) { String result = HttpUtil.get("https://api/data"); // 耗时操作 updateUI(result); }
- 正确示例:
public void onClick(View view) { // 开启子线程 new Thread(() -> { String result = HttpUtil.get("https://api/data"); // 切回主线程更新UI runOnUiThread(() -> updateUI(result)); }).start(); }
(2)线程安全与同步
- 共享变量需同步:
// 错误:多线程修改共享变量,可能导致数据不一致 public class Counter { private int count = 0; public void increment() { count++; // 非原子操作,多线程下可能出错 } }
- 正确方式:
public class Counter { private int count = 0; // 方法同步 public synchronized void increment() { count++; } // 或用原子类(更高效) private AtomicInteger atomicCount = new AtomicInteger(0); public void atomicIncrement() { atomicCount.incrementAndGet(); } }
四、Kotlin 特有编码规范
Kotlin 作为 Android 开发的推荐语言,其空安全、扩展函数等特性需正确使用才能发挥优势。
4.1 空安全:从 “防崩溃” 到 “代码清晰”
(1)非空与可空的正确使用
- 优先声明非空类型:
// 正确:已知非空,声明为非空 val userName: String = "张三" // 错误:可空类型但实际非空,增加调用成本 val userName: String? = "张三" // 无需用可空
- 可空类型必须处理空值:
// 正确:安全处理可空类型 fun getUserAge(user: User?): Int { // 方式1:?.let(非空才执行) user?.let { return it.age } // 方式2:?:(空值默认) return user?.age ?: 0 } // 错误:可空类型未处理,可能抛出NPE fun getUserAge(user: User?): Int { return user.age // 编译错误(Kotlin强制检查) }
- 禁止滥用!!非空断言:
// 错误:用!!强制非空,等同于回到Java的NPE风险 fun getUserAge(user: User?): Int { return user!!.age // 若user为空,抛出NPE }
替代方案:明确处理空值,或重新设计类型(是否真的需要可空)。
(2)空安全的高级技巧
- 用isNullOrEmpty判断空集合:
// 推荐:Kotlin扩展函数,简洁明了 if (userList.isNullOrEmpty()) { // 空处理 } // 不推荐:Java风格判断 if (userList == null || userList.isEmpty()) { ... }
- 用requireNotNull做参数校验:
fun login(username: String?, password: String?) { // 校验非空,为空则抛出异常(带明确信息) val name = requireNotNull(username) { "用户名不能为空" } val pwd = requireNotNull(password) { "密码不能为空" } // 后续可安全使用非空变量 }
4.2 函数与属性:简洁而不晦涩
(1)函数简化与可读性平衡
- 单表达式函数的使用:
// 正确:简单逻辑用单表达式,简洁 fun add(a: Int, b: Int) = a + b // 错误:复杂逻辑用单表达式,可读性差 fun calculate(a: Int, b: Int) = if (a > b) a * 2 else (b - a) / 3 + 5 // 逻辑复杂应拆分
- 默认参数替代重载:
// 正确:默认参数替代多个重载函数 fun getUser( id: String, forceRefresh: Boolean = false, timeout: Int = 5000 ) { ... } // 错误:Java风格重载,代码冗余 fun getUser(id: String) = getUser(id, false) fun getUser(id: String, forceRefresh: Boolean) = getUser(id, forceRefresh, 5000) fun getUser(id: String, forceRefresh: Boolean, timeout: Int) { ... }
(2)扩展函数与属性
- 扩展函数的合理范围:
// 正确:通用功能,扩展给View fun View.setVisible(visible: Boolean) { visibility = if (visible) View.VISIBLE else View.GONE } // 错误:业务逻辑放入扩展函数,导致扩散 fun View.showUserInfo(user: User) { // 业务逻辑应放在ViewModel或Presenter (this as TextView).text = user.name }
- 避免扩展函数命名冲突:
// 风险:不同模块对同一类扩展同名函数 // 模块A fun String.formatUser(): String { ... } // 模块B fun String.formatUser(): String { ... } // 冲突
解决:添加前缀区分(如formatUserInfo),或放入不同包名。
五、Android 组件特有规范
Android 组件(Activity、Fragment 等)有其生命周期特性,规范需结合生命周期设计。
5.1 Activity 规范:页面的 “生命周期管理”
(1)生命周期与初始化
- 初始化放onCreate,资源释放放onDestroy:
public class MainActivity extends AppCompatActivity { private TextView mTitleView; private BroadcastReceiver mReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 初始化视图 setContentView(R.layout.activity_main); mTitleView = findViewById(R.id.tv_title); // 初始化数据 initData(); // 注册广播(在onCreate注册) mReceiver = new MyReceiver(); registerReceiver(mReceiver, intentFilter); } @Override protected void onDestroy() { super.onDestroy(); // 释放资源(与onCreate对应) unregisterReceiver(mReceiver); // 解注册 // 取消网络请求 if (mCall != null) { mCall.cancel(); } } }
- 禁止在onResume做初始化:
// 错误:onResume会多次调用(如锁屏后解锁),导致重复初始化 @Override protected void onResume() { super.onResume(); // 错误:每次可见都初始化(应放onCreate) mAdapter = new UserAdapter(); }
(2)避免内存泄漏
- 禁止静态引用 Activity/View:
// 错误:静态引用导致Activity无法回收 public class LeakActivity extends AppCompatActivity { // 静态引用Activity public static LeakActivity sInstance; // 静态引用View(持有Activity引用) private static TextView sTitleView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); sInstance = this; // 危险 sTitleView = findViewById(R.id.tv_title); // 危险 } }
- 内部类使用弱引用:
// 正确:内部类用弱引用避免泄漏 public class SafeActivity extends AppCompatActivity { private MyHandler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandler = new MyHandler(this); } // 静态内部类(不持有外部类引用) private static class MyHandler extends Handler { private WeakReference<SafeActivity> mActivityRef; public MyHandler(SafeActivity activity) { mActivityRef = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { SafeActivity activity = mActivityRef.get(); if (activity != null && !activity.isFinishing()) { // 安全操作 } } } }
5.2 Fragment 规范:可复用的 “迷你页面”
(1)创建与传参
- 必须用newInstance传参:
// 正确:用newInstance+Bundle传参 public class UserFragment extends Fragment { private static final String ARG_USER_ID = "user_id"; private String mUserId; // 工厂方法 public static UserFragment newInstance(String userId) { UserFragment fragment = new UserFragment(); Bundle args = new Bundle(); args.putString(ARG_USER_ID, userId); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 从Arguments获取参数 if (getArguments() != null) { mUserId = getArguments().getString(ARG_USER_ID); } } } // 使用方式 UserFragment fragment = UserFragment.newInstance("123");
- 禁止用构造函数传参:
// 错误:屏幕旋转后参数丢失(Fragment重建时用默认构造) public class BadFragment extends Fragment { private String mUserId; // 错误:非默认构造,重建时参数丢失 public BadFragment(String userId) { mUserId = userId; } }
(2)视图与生命周期
- 视图初始化放onViewCreated:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // 仅加载布局,不做视图操作 return inflater.inflate(R.layout.fragment_user, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // 视图初始化(在onViewCreated中操作View) mNameView = view.findViewById(R.id.tv_name); mNameView.setOnClickListener(v -> { ... }); // 加载数据 loadData(); }
- 视图销毁清理:
@Override public void onDestroyView() { super.onDestroyView(); // 清理视图引用(避免内存泄漏) mNameView = null; // 取消视图相关任务(如图片加载) if (mImageRequest != null) { mImageRequest.cancel(); } }
5.3 ViewModel 与数据管理:UI 与数据的 “解耦”
(1)ViewModel 设计
- ViewModel 不持有 UI 引用:
// 错误:ViewModel持有Activity引用 class BadViewModel(private val activity: Activity) : ViewModel() { fun updateUI() { activity.findViewById<TextView>(R.id.tv_name).text = "更新" } } // 正确:通过LiveData与UI通信 class GoodViewModel : ViewModel() { private val _userName = MutableLiveData<String>() val userName: LiveData<String> = _userName fun loadUser() { // 加载数据后更新LiveData _userName.value = "张三" } }
- 数据请求放 Repository:
// 正确:ViewModel仅管理数据,不做具体请求 class UserViewModel(private val repository: UserRepository) : ViewModel() { fun getUser(id: String) { viewModelScope.launch { val user = repository.getUser(id) // 具体请求在Repository _user.value = user } } } // Repository负责数据获取 class UserRepository { suspend fun getUser(id: String): User { return api.getUser(id) // 网络请求 } }
六、规范落地:从 “文档” 到 “习惯”
编码规范的关键在于 “落地执行”,否则只是一纸空文。
6.1 团队规范制定流程
1.收集现有问题:分析团队现有代码的高频问题(如命名混乱、格式不统一);
2.制定核心规则:优先规范 “必须执行” 的基础规则(命名、格式);
3.工具配置:将规则配置到 Android Studio、CI 流程中;
4.培训与示例:通过示例代码、常见错误对比培训团队;
5.定期 Review:代码审查时将规范作为必查项,持续优化规则。
6.2 自动化检查工具
- Lint:Android 内置静态检查工具,可配置自定义规则:
// build.gradle
android {
lintOptions {
// 发现错误时中断构建
abortOnError true
// 配置检查规则(xml文件)
lintConfig file("lint.xml")
}
}
- Checkstyle(Java):
- 配置文件:checkstyle.xml(定义命名、格式规则);
- 集成到构建:通过 Gradle 插件在编译时检查。
- Detekt(Kotlin):
- 类似 Checkstyle,针对 Kotlin 的静态检查工具;
- 支持自定义规则,可检查空安全、命名等问题。
6.3 持续优化与文化建设
- 定期更新规范:随着项目发展(如引入新框架),更新规范;
- 正面激励:表彰遵循规范的代码,树立榜样;
- 将规范融入文化:让 “写规范代码” 成为团队共识,而非强制要求。
结语
编码规范不是 “束缚创造力的枷锁”,而是 “释放创造力的工具”—— 当开发者无需在命名、格式等基础问题上花费精力,才能更专注于业务逻辑与架构设计。