Android 编码规范全指南

发布于:2025-07-27 ⋅ 阅读:(18) ⋅ 点赞:(0)

在 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 持续优化与文化建设

  • 定期更新规范:随着项目发展(如引入新框架),更新规范;
  • 正面激励:表彰遵循规范的代码,树立榜样;
  • 将规范融入文化:让 “写规范代码” 成为团队共识,而非强制要求。

结语

编码规范不是 “束缚创造力的枷锁”,而是 “释放创造力的工具”—— 当开发者无需在命名、格式等基础问题上花费精力,才能更专注于业务逻辑与架构设计。


网站公告

今日签到

点亮在社区的每一天
去签到