├── .gitignore
├── LICENSE
├── README.md
├── 阿里巴巴Android开发手册.pdf
└── 阿里巴巴Java开发手册.pdf
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .idea
3 | .cxx
4 | .externalNativeBuild
5 | build
6 | captures
7 |
8 | ._*
9 | *.iml
10 | .DS_Store
11 | local.properties
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, February 2021
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2021 Huang JinQun
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android 代码规范文档
2 |
3 | * 项目地址:[Github](https://github.com/getActivity/AndroidCodeStandard)
4 |
5 | * 鸿洋力荐:[荐一份 Android 代码规范建议文档](https://mp.weixin.qq.com/s/Zv1zVom69RFrMJ1q8tP8Lw)
6 |
7 | * 做开源几年了,被很多人夸过,你的代码写得比较规范,[甚至有人质疑自己代码的写法](https://github.com/getActivity/AndroidProject/issues/55),但是迟迟没有出一个代码规范,说来惭愧,只是因为我早几年写的代码还不够规范,不敢出来误导大家,而代码规范是后续才慢慢养成的,在这个过程中,我不仅参考了大公司出的代码规范文档,也研究了很多关于谷歌源码的编码规范,同时我也在无时不刻在思考,如何能写出让别人更好理解的代码,自打入行以来,我就在一直在这个问题上面探索。
8 |
9 | * 为什么要做成一个开源项目?因为项目会长期更新,大家如果对里面一些规范表示不能理解的或者感觉写得不太对的,又或者有什么想要补充的,随时欢迎你通过 **[issue](https://github.com/getActivity/AndroidCodeStandard/issues/new)** 反馈给我,大家的建议很重要,是我做好这件事的关键,我会认真对待和思考提出的每一个建议。同时我也相信一份好的代码规范经得住大众的反复推敲和不断实践,在这里也欢迎你提出自己的想法和建议。
10 |
11 | * 这份代码规范文档开始编写的时间是 2020 年 7 月,中间不断地修改和补充,于 2021 年 1 月发布定稿,同年 2 月,纠正和补充了一些代码规范,从 0 到 1,从不成熟到成熟,整个过程耗时 200 多天。原因是我积累了很多开发的好习惯,但是一直没有任何记录,在短时间将这些东西全部输出几乎不可能,所以我前面花了很多时间回忆和总结,这段时间内我做的最多的一件事是,代码写着写着就去写代码规范文档,直到后面发布初稿时,我又投入了大把的时间和精力来做这件事。大家心中可能都有一个疑问,我为什么不用网上写的,直接照抄照搬,又或者直接采用阿里的,这样不是简单轻松多了?关于这个问题,可以跟大家谈谈我的想法,我看过很多关于 Android 代码规范文档,但是我感觉存在一些问题,可以跟大家分享一下:
12 |
13 | 1. 说服力低:是很多代码规范文档只告诉你应该这样写,但是基本没有人提及这样写的好处,那样写不好的地方。他们只会告诉你规则,但是从不告诉你前因后果,这样写出来的代码规范难以服众。
14 |
15 | 2. 不够全面:例如后台接口规范、接口实现规范、异常捕获规范、第三方框架使用规范、代码硬编码规范、资源硬编码规范、多模块规范;这些是我们开发中所避不开的东西,但是现在网上的代码规范文档却很少有人提及。
16 |
17 | 3. 不够细致:我举个最简单的例子,很多代码规范文档都会说 **String ID** 以 **模块 + 作用** 来命名,但是没有人提及过在其他情况下的处理,例如:`确定取消` 这种字符串,虽然在很多模块中使用到了,但是它却不属于任何模块的,这个时候该怎么命名?这个虽然是小细节,但足以看出文档在细节处理上不够完美和严谨。
18 |
19 | #### 更新日志
20 |
21 | * 2023 年 2 月 26 日:[补充字符串类型转换规范](https://github.com/getActivity/AndroidCodeStandard/commit/71cb472afe561b2d895dda0a839ebf1dbcb6c367)
22 |
23 | * 2023 年 2 月 5 日:[
修改变量命名规范
补充 long 类型写法规范
补充时间戳间隔计算规范
补充代码美观性要求规范
新增《代码嵌套规范》类别
新增《Git 版本管理规范》类别](https://github.com/getActivity/AndroidCodeStandard/commit/8f72c683e3731db62f02952e76fafae8896ced4f)
24 |
25 | * 2021 年 3 月 31 日:[
新增 switch case 语句判断资源 id 的规范
补充 Left、Right、Start、End 同时使用规范](https://github.com/getActivity/AndroidCodeStandard/commit/ad6604cecb49325fbd542bf3b89c2327daf931ec)
26 |
27 | * 2021 年 3 月 28 日:[新增代码美观性要求](https://github.com/getActivity/AndroidCodeStandard/commit/692ada016c99db72e61fb75e20a7178d385bf669)
28 |
29 | * 2021 年 3 月 11 日:[新增版本命名规范](https://github.com/getActivity/AndroidCodeStandard/commit/68f242c839ae040aaf4624fb8243137366b89cc9)
30 |
31 | * 2021 年 2 月 23 日:[
补充代码规范原则
补充参数传递规范](https://github.com/getActivity/AndroidCodeStandard/commit/1a1ad4a7c26ff75e588b689c51ab126fe32c78a1)
32 |
33 | * 2021 年 2 月 21 日:[
补充多模块规范
补充代码硬编码规范
补充资源硬编码规范
补充 Color ID 命名规范
补充写代码规范文档的原因
纠正公开成员变量命名方式
添加谷歌代码样式指南链接](https://github.com/getActivity/AndroidCodeStandard/commit/be4847c2c476031484bac97dec036228900e2f60)
34 |
35 | * 2021 年 2 月 11 日:[补充异常捕获规范标准](https://github.com/getActivity/AndroidCodeStandard/commit/80688b1dc84ccf752d6c33e5e57291fd67ca2eef)
36 |
37 | * 2021 年 2 月 9 日:[补充字符串 equals 使用规范](https://github.com/getActivity/AndroidCodeStandard/commit/78348cc554e89010a5c31b3bc05c8beeb3925b43)
38 |
39 | * 2021 年 2 月 7 日:[补充做成开源项目的原因](https://github.com/getActivity/AndroidCodeStandard/commit/578d6e37b640adf9d3687c3eb562ff0ae58a131b)
40 |
41 | * 2021 年 2 月 6 日:[补充注释规范和标准](https://github.com/getActivity/AndroidCodeStandard/commit/db7b469b5fe014d818ecdb14f070c8f58c93113c)
42 |
43 | * 2021 年 2 月 6 日:[纠正后台返回金额的处理方式](https://github.com/getActivity/AndroidCodeStandard/commit/1bb107522a7e440d0632690aca798de1bd6d88a3)
44 |
45 | * 2021 年 2 月 5 日:[新增文本间距的问题阐述](https://github.com/getActivity/AndroidCodeStandard/commit/35ac8b16a6c9066dd1185e733ed12838f7befba5)
46 |
47 | * 2021 年 2 月 5 日:[修改全局变量为成员变量](https://github.com/getActivity/AndroidCodeStandard/commit/f9cd3730e6c08e79be012133e6e4d89b315580aa)
48 |
49 | * 2021 年 2 月 5 日:[优化描述的语言艺术](https://github.com/getActivity/AndroidCodeStandard/commit/2cb35de115327227d37b9efc6477505011d939f5)
50 |
51 | * 2021 年 2 月 5 日:[添加码云地址](https://github.com/getActivity/AndroidCodeStandard/commits/master)
52 |
53 | * 2021 年 2 月 5 日:[第一次提交代码](https://github.com/getActivity/AndroidCodeStandard/commit/490a8d3e3695dd30a952ded71969fda83daaa58c)
54 |
55 | #### 目录
56 |
57 | * [前言](#前言)
58 |
59 | * [代码规范原则](#代码规范原则)
60 |
61 | * [常规规范](#常规规范)
62 |
63 | * [后台接口规范](#后台接口规范)
64 |
65 | * [变量命名规范](#变量命名规范)
66 |
67 | * [包名命名规范](#包名命名规范)
68 |
69 | * [方法命名规范](#方法命名规范)
70 |
71 | * [类文件命名规范](#类文件命名规范)
72 |
73 | * [接口文件命名规范](#接口文件命名规范)
74 |
75 | * [代码嵌套规范](#代码嵌套规范)
76 |
77 | * [接口实现规范](#接口实现规范)
78 |
79 | * [异常捕获规范](#异常捕获规范)
80 |
81 | * [参数传递规范](#参数传递规范)
82 |
83 | * [代码美观性要求](#代码美观性要求)
84 |
85 | * [第三方框架使用规范](#第三方框架使用规范)
86 |
87 | * [多模块规范](#多模块规范)
88 |
89 | * [代码注释规范](#代码注释规范)
90 |
91 | * [代码硬编码规范](#代码硬编码规范)
92 |
93 | * [布局文件命名规范](#布局文件命名规范)
94 |
95 | * [资源文件命名规范](#资源文件命名规范)
96 |
97 | * [String ID 命名规范](#string-id-命名规范)
98 |
99 | * [Color ID 命名规范](#color-id-命名规范)
100 |
101 | * [Anim ID 命名规范](#anim-id-命名规范)
102 |
103 | * [View ID 命名规范](#view-id-命名规范)
104 |
105 | * [Style 命名规范](#style-命名规范)
106 |
107 | * [XML 编码规范](#xml-编码规范)
108 |
109 | * [预览属性约定](#预览属性约定)
110 |
111 | * [资源硬编码规范](#资源硬编码规范)
112 |
113 | * [多线程规范](#多线程规范)
114 |
115 | * [数据库操作规范](#数据库操作规范)
116 |
117 | * [版本名和版本码规范](#版本名和版本码规范)
118 |
119 | * [Git 版本管理规范](#git-版本管理规范)
120 |
121 | * [致谢](#致谢)
122 |
123 | #### 前言
124 |
125 | * 代码规范是我们每个程序员要做的事,假设我们按照自己的喜好来写代码,那么很可能出现的问题就是**我看不懂你的代码**或者**你看不懂我的代码**,这样会给后续维护形成**巨大的障碍**。这个时候问题来了,如何让代码不分你我,或许只需要一套规则,你和我都认可并且遵守的代码规范守则。
126 |
127 | * 那么你的疑问可能又来了,怎么样才能算好的代码规范,答案只有一个,真正好的代码规范就是别人的代码你一眼就能看懂,更不需要反复去看。之所以这样并不是因为看的人 Review 代码的能力有多强,而是写代码的人愿意遵守规则,他知道自己想这么写,但是知道你会看不懂,所以换了一种方式来写,这种方式就是**代码规范**。
128 |
129 | * 代码规范:一个好的代码规范可以帮助我们快速了解和熟悉相关的业务,降低后续维护的成本(二次开发或者排查问题)。
130 |
131 | * 代码不规范:代码不规范会导致项目可读性变差,具体表现为**难分辨和难理解**,在无形之中加大项目后续维护的成本。
132 |
133 | * 经验总结:**编码不规范,同行泪两行**
134 |
135 | #### 代码规范原则
136 |
137 | * 在讲之前,我们先思考一个问题,代码规范的出现是为了什么?不就为了让我们更好地进行团队协作和项目维护吗?没错的,所以代码规范原则应该围绕这两个目标进行。
138 |
139 | * **特事特办**:代码规范文档只能解决 **99%** 场景下的问题,特殊情况应该要特殊处理,违背者需要给出**合理的解释**,建议在代码中直接**用注释注明**,这样可以**减少沟通成本**,否则在一般情况下应当要遵守代码规范文档上的约束。
140 |
141 | * **以人为本**:我们应该衡量不同写法带来的优点和缺点,然后根据当前项目的实际需求做出合适的选择或者变化。规则是人定的,不是**一成不变**的。在制定新的规则或者修改旧的规则之前应当先**参考和分析**谷歌或者知名公司的做法,然后与团队中的各个成员**沟通和协商好**。
142 |
143 | * **实事求事**:任何代码规范都应该追求在实际开发中发挥的作用或者效果,规则始终是规则,不能单纯为了制定规则而编写代码规范,而是更应该追求写法的实用性,实用性应该从**代码理解的难易程度**、**代码可维护性**、**代码可复用性**、**代码可扩展性**等方面因素综合考虑,其次是考虑**代码的视觉美观性**。
144 |
145 | #### 常规规范
146 |
147 | * 使用 **0px** 代替 **0dp**,这样就可以在获取时避免系统进行换算,提升代码的执行效率。
148 |
149 | * 字符串比较,应该用 `"xxx".equals(object)`,而不应该用 `object.equals("xxx")`,因为 **object** 对象可能为空,我们应该把不为空的条件放置在表达式的前面。
150 |
151 | * 字符串类型转换,应该用 `String.valueOf(Object object)` 来代替 `object.toString()`,因为 `object` 对象可能会为空, 直接调用 `toString` 方法可能会触发 `NullPointerException`,而 `valueOf` 方法内部有做判空处理。
152 |
153 | * **long** 类型的常量应该以大写英文 **L** 结尾,而不应该用小写英文 **l**,因为小写英文的 **l** 会和数字 **1** 容易造成一些混淆,例如 **1l** 会被看成 **11**,而使用 **1L** 就不会出现这种情况。
154 |
155 | * 尽量采用 **switch case** 来判断,如果不能实现则再考虑用 **if else**,因为在多条件下使用 **switch case** 语句判断会更加简洁。
156 |
157 | * 严禁用 **switch case** 语句来判断资源 id,因为 Gradle 在 5.0 之后的版本,资源 ID 将不会以常量的形式存在,而 **switch case** 语句只能判断常量,所以不能再继续使用 **switch case** 来判断资源 ID 了。
158 |
159 | * 不推荐用 **layout_marginLeft**,而应该用 **layout_marginStart**;不推荐用 **layout_marginRight**,而应该用 **layout_marginEnd**,原因有两个,一个是适配 Android 4.4 **反方向特性**(可在开发者选项中开启),第二个是 XML 布局中使用 **layout_marginLeft** 和 **layout_marginRight** 会有代码警告,**padding** 属性也是同理,这里不再赘述。另外有一点需要注意:严禁 **Left、Right** 属性和 **Start、End** 属性同时使用,两者只能二选一。
160 |
161 | * 如果在 **layout_marginStart** 和 **layout_marginEnd** 的值相同的情况下,请替换使用 **layout_marginHorizontal**,而 **layout_marginTop** 和 **layout_marginBottom** 也同理,请替换使用 **layout_marginVertical**,能用一句代码能做的事不要写两句,**padding** 属性也是同理,这里不再赘述。
162 |
163 | * **过期** 和 **高版本** 的 API 一定要做对应的兼容(包含 Java 代码和 XML 属性),如果不需要兼容处理的,需要对警告进行抑制。
164 |
165 | * 在能满足需求的情况下,尽量用 **invisible** 来代替 **gone**,因为 **gone** 会触发当前整个 View 树进行重新测量和绘制。
166 |
167 | * **api** 和 **implementation**,在能满足使用的情况下,优先选用 **implementation**,因为这样可以[减少一些编译时间](https://www.jianshu.com/p/8962d6ba936e)。
168 |
169 | * **ListView** 和 **RecyclerView** 都能实现需求的前提下,优先选用 **RecyclerView**,具体原因不解释,大家应该都懂。
170 |
171 | * **ScrollView** 和 **NestedScrollView** 都能实现需求的前提下,优先选用 **NestedScrollView**,是因为 **NestedScrollView** 和 **RecyclerView** 支持相互嵌套,而 **ScrollView** 是不支持嵌套滚动的。
172 |
173 | * 不能在项目中创建副本文件,例如创建 `HomeActivity2.java`、`home_activity_v2.xml` 类似的副本文件,因为这样不仅会增加项目的维护难度,同时对编译速度也会造成一定的影响,正确的做法应该是在原有的文件基础上面修改,如果出现需求变更的情况,请直接使用 **Git** 或者 **SVN** 进行版本回退。
174 |
175 | * 如果一个类不需要被继承,请直接用 **final** 进行修饰,如果一个字段在类初始化过程中已经赋值并且没有地方进行二次赋值,也应当用 **final** 修饰,如果一个字段不需要被外部访问,那么需要用 **private** 进行修饰。
176 |
177 | * 时间间隔的计算,对于前后时间的获取,不推荐使用 `System.currentTimeMillis()` 来获取,因为用户随时可能会调整手机的日期,这样会导致计算出来的时间间隔不准确,推荐使用 `SystemClock.uptimeMillis()` 来获取,此 API 用于获取本次已开机的毫秒数,用户就算调整了手机的日期也没有任何影响;值得一提的是,**Handler** 类中的 `postDelayed` 方法也是采用这种方式实现。
178 |
179 | * 每个小组成员应当安装[阿里巴巴代码约束插件](https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines),并及时地对插件所提示的**代码警告**进行处理或者抑制警告。
180 |
181 | * 应用图标应该放在 **mipmap** 目录下,其他图片资源应当放到 **drawable** 目录下,具体原因可以看[谷歌官方文档](https://developer.android.google.cn/guide/topics/resources/providing-resources)对这两个文件夹给出的介绍:
182 |
183 | | 目录 | 资源类型 |
184 | | :--------: | :-----------------------------------------------------------: |
185 | | **drawable** | 位图文件(`.png`、`.9.png`、`.jpg`、`.gif`)或编译为以下可绘制对象资源子类型的 XML 文件:位图文件九宫格(可调整大小的位图)状态列表形状动画可绘制对象其他可绘制对象请参阅 [Drawable 资源](https://developer.android.google.cn/guide/topics/resources/drawable-resource)。 |
186 | | **mipmap** | 适用于不同启动器图标密度的可绘制对象文件。如需了解有关使用 `mipmap` 文件夹管理启动器图标的详细信息,请参阅[管理项目概览](https://developer.android.google.cn/tools/projects#mipmap)。 |
187 |
188 | #### 后台接口规范
189 |
190 | * 后台返回的 **id 值**,不要使用 **int** 或者 **long** 类型来接收,而应该用 **string** 类型来接收,因为我们不需要对这个 **id 值**进行运算,所以我们不需要关心它是什么类型的。
191 |
192 | * 后台返回的**金额数值**应该使用 **String** 来接收,而不能用**浮点数**来接收,因为 **float** 或者 **double** 在数值比较大的情况下会容易丢失精度,并且还需要自己手动转换出想要保留的小数位,最好的方式是后台返回什么前端就展示什么,而到了运算的时候,则应该用 **BigDecimal** 类来进行转换和计算,当然金额在前端一般展示居多,运算的情况还算是比较少的。
193 |
194 | * 我们在定义后台返回的 Bean 类时,不应当将一些我们没有使用到的字段添加到代码中,因为这样会消耗性能,因为 Gson 是通过**反射**将后台字段赋值到 Java 字段中,所以我们应当避免一些不必要的字段解析,另外臃余的字段也会给我们排查问题造成一定的阻碍。
195 |
196 | * 如果后台给定的字段名不符合代码命名的时候,例如当遇到 `student_name` 这种命名时,我们应当使用 Gson 框架中的 **@SerializedName** 注解对字段进行映射。
197 |
198 | * 请求的接口参数和返回字段必须要写上注释,除此之外还应该备注对应的后台接口文档地址,以便我们后续能够更好地进行维护和迭代。
199 |
200 | * 后台返回的 Bean 类字段不能直接访问,而应该通过生成 **Get** 方法,然后使用这个 **Get** 方法来访问字段。
201 |
202 | * 接口请求成功的提示可以不显示,但请求失败的提示需要显示给到用户,否则会加大排查问题的难度,也极有可能会把问题掩盖掉,从而导致问题遗留到线上去。
203 |
204 | * 如果用的 Json 解析框架是 Gson,则建议进行容错处理,秉持不信任后台的原则,因为我们没有办法控制后台返回了什么数据结构,但是我们有办法保证应用不会为这个问题而导致崩溃。
205 |
206 | #### 变量命名规范
207 |
208 | * **严禁使用中文或者中文拼音**进行重命名
209 |
210 | * 遵循 **lowerCamelCase(驼峰式)命名风格**(单词最好控制在三个以内)
211 |
212 | * 变量应该以作用来命名,例如:
213 |
214 | ```java
215 | String name;
216 | TextView nameView;
217 | FrameLayout nameLayout;
218 | ```
219 |
220 | ```java
221 | // 命名规范附带技巧(当布局中同个类型的控件只有一个的时候,也可以这样命名)
222 | TextView textView;
223 | RecyclerView recyclerView;
224 | ```
225 |
226 | * 布尔值命名规范,无论是局部变量还是成员变量,都不应该携带 **is**,例如:
227 |
228 | ```java
229 | // 不规范写法示例
230 | boolean isDebug;
231 | ```
232 |
233 | ```java
234 | // 规范写法示例
235 | boolean debug;
236 | ```
237 |
238 | * 常量则需要用大写,并且用下划线代替驼峰,例如:
239 |
240 | ```java
241 | static final String REQUEST_INSTALL_PACKAGES;
242 | ```
243 |
244 | * 有细心的同学可能会发现一个问题,Android 源码中私有字段都是以 `m` 开头,而静态字段都是以 `s` 开头,其实这种是 AOSP 的源码规范,这样的写法也算规范的,大家可以自行抉择,但是建议组内统一。
245 |
246 | * 另外如果是在 Kotlin 类中,字段名则不能以 `m` 或者 `s` 开头,因为这样写是不规范的,因为作者翻阅了 AndroidX 的源码,发现里面写的 Kotlin 代码中的字段已经没有了 `m` 或者 `s` 开头的字段,但是 Java 代码中仍然有保留着这种写法。
247 |
248 | #### 包名命名规范
249 |
250 | * 不允许包名中携带**英文大写**
251 |
252 | * 包名应该以**简洁的方式**命名
253 |
254 | * 包名要按照**模块**或者**作用**来划分
255 |
256 | * 请不要在某一包名下放置**一些无关的类**
257 |
258 | #### 方法命名规范
259 |
260 | * initXX:初始化相关方法,使用 **init** 为前缀标识,如初始化布局 **initView**
261 |
262 | * isXX:方法返回值为 boolean 型的请使用 **is** 或 **check** 为前缀标识
263 |
264 | * getXX:返回某个值的方法,使用 **get** 为前缀标识,例如 **getName**
265 |
266 | * setXX:设置某个属性值,使用 **set** 为前缀标识,例如 **setName**
267 |
268 | * handleXX/processXX:对数据进行处理的方法,例如 **handleMessage**
269 |
270 | * displayXX/showXX:弹出提示框和提示信息,例如 **showDialog**
271 |
272 | * updateXX:更新某个东西,例如 **updateData**
273 |
274 | * saveXX:保存某个东西,例如 **saveData**
275 |
276 | * resetXX:重置某个东西,例如 **resetData**
277 |
278 | * clearXX:清除某个东西,例如 **clearData**
279 |
280 | * removeXX:移除数据或者视图等,例如 **removeView**
281 |
282 | * drawXX:绘制数据或效果相关的,使用 **draw** 前缀标识,例如 **drawText**
283 |
284 | #### 类文件命名规范
285 |
286 | * 业务模块:请以 **模块 + 类型** 来命名,例如:
287 |
288 | ```text
289 | HomeActivity.java
290 |
291 | SettingFragment.java
292 |
293 | HomeAdapter.java
294 |
295 | AddressDialog.java
296 | ```
297 |
298 | * 技术模块:请以类的 **作用** 来命名,例如:
299 |
300 | ```text
301 | CrashHandler.java
302 |
303 | GridSpaceDecoration.java
304 |
305 | PickerLayoutManager.java
306 | ```
307 |
308 | #### 接口文件命名规范
309 |
310 | * 如果是监听事件可以参考 **View** 的写法及命名:
311 |
312 | ```java
313 | public class View {
314 |
315 | private View.OnClickListener listener;
316 |
317 | public void setOnClickListener(OnClickListener listener) {
318 | this.listener = listener;
319 | }
320 |
321 | public interface OnClickListener {
322 |
323 | void onClick(View v);
324 | }
325 | }
326 | ```
327 |
328 | * 如果是回调事件可以参考 **Handler** 的写法及命名:
329 |
330 | ```java
331 | public class Handler {
332 |
333 | public interface Callback {
334 |
335 | boolean handleMessage(Message msg);
336 | }
337 | }
338 | ```
339 |
340 | * 至于接口写在内部还是外部,具体可以视实际情况而定,如果功能比较庞大,就可以考虑抽取成外部的,只作用在某个类上的,则就可以直接写成内部的。
341 |
342 | #### 代码嵌套规范
343 |
344 | * 代码嵌套很深一直以来是一个很头疼的问题,其实它也算一种代码不规范写法的表现,那么如何写代码才能降低代码逻辑嵌套呢?
345 |
346 | ```java
347 | // 不规范写法示例
348 | public void test(Object a, Object b, Object c) {
349 | if (a != null) {
350 | if (b != null) {
351 | if (c != null) {
352 | System.out.println("所有对象不为空");
353 | } else {
354 | System.out.println("对象 C 为空");
355 | }
356 | } else {
357 | System.out.println("对象 B 为空");
358 | }
359 | } else {
360 | System.out.println("对象 A 为空");
361 | }
362 | }
363 | ```
364 |
365 | ```java
366 | // 规范写法示例
367 | public void test(Object a, Object b, Object c) {
368 | if (a == null) {
369 | System.out.println("对象 A 为空");
370 | return;
371 | }
372 |
373 | if (b == null) {
374 | System.out.println("对象 B 为空");
375 | return;
376 | }
377 |
378 | if (c == null) {
379 | System.out.println("对象 C 为空");
380 | return;
381 | }
382 |
383 | System.out.println("所有对象不为空");
384 | }
385 | ```
386 |
387 | * 先让我们对比一下这两种写法,是不是觉得第一种写法可读性比较差?而第二种写法可读性比较强?
388 |
389 | * 我们应该遵循少写 `else` ,多用 `return` 语句的原则,这样就能降低代码之间的相互嵌套,提升代码的可读性。
390 |
391 | * 另外在不能用 `return` 的情况下,例如需要一直判断对象不能为空才能进行下一步操作,示例如下:
392 |
393 | ```java
394 | // 不规范写法示例
395 | public void test(Context context) {
396 | if (context instanceof Activity) {
397 | Window window = ((Activity) context).getWindow();
398 | if (window != null) {
399 | View decorView = window.getDecorView();
400 | if (decorView != null) {
401 | WindowInsets rootWindowInsets = decorView.getRootWindowInsets();
402 | if (rootWindowInsets != null) {
403 | DisplayCutout displayCutout = rootWindowInsets.getDisplayCutout();
404 | if (displayCutout != null) {
405 | // 安全区域距离屏幕左边的距离
406 | int safeInsetLeft = displayCutout.getSafeInsetLeft();
407 | // 安全区域距离屏幕顶部的距离
408 | int safeInsetTop = displayCutout.getSafeInsetTop();
409 | // 安全区域距离屏幕右部的距离
410 | int safeInsetRight = displayCutout.getSafeInsetRight();
411 | // 安全区域距离屏幕底部的距离
412 | int safeInsetBottom = displayCutout.getSafeInsetBottom();
413 | }
414 | }
415 | }
416 | }
417 | }
418 |
419 | ......
420 | }
421 | ```
422 |
423 | ```java
424 | // 规范写法示例
425 | public void test(Context context) {
426 | Window window = null;
427 | if (context instanceof Activity) {
428 | window = ((Activity) context).getWindow();
429 | }
430 | View decorView = null;
431 | if (window != null) {
432 | decorView = window.getDecorView();
433 | }
434 | WindowInsets rootWindowInsets = null;
435 | if (decorView != null) {
436 | rootWindowInsets = decorView.getRootWindowInsets();
437 | }
438 | DisplayCutout displayCutout = null;
439 | if (rootWindowInsets != null) {
440 | displayCutout = rootWindowInsets.getDisplayCutout();
441 | }
442 |
443 | if (displayCutout != null) {
444 | // 安全区域距离屏幕左边的距离
445 | int safeInsetLeft = displayCutout.getSafeInsetLeft();
446 | // 安全区域距离屏幕顶部的距离
447 | int safeInsetTop = displayCutout.getSafeInsetTop();
448 | // 安全区域距离屏幕右部的距离
449 | int safeInsetRight = displayCutout.getSafeInsetRight();
450 | // 安全区域距离屏幕底部的距离
451 | int safeInsetBottom = displayCutout.getSafeInsetBottom();
452 | }
453 |
454 | ......
455 | }
456 | ```
457 |
458 | * 这个时候大家可能有疑问了,循环语句不能用 `return` 语句怎么办?这个问题很简单,大家可以用 `continue` 或者 `break` 来代替,其实都是换汤不换药,这里不再赘述。
459 |
460 | * 另外不是说存在嵌套就一定不好,还有一种情况,减少代码嵌套时需要写很多重复代码,这种就需要大家根据实际情况做选择了。
461 |
462 | #### 接口实现规范
463 |
464 | * 一般情况下,我们会在类中这样实现接口,这样写的好处是,可以减少对象的创建,并且代码也比较美观。
465 |
466 | ```java
467 | public final class PasswordEditText extends EditText implements View.OnTouchListener, View.OnFocusChangeListener, TextWatcher {
468 |
469 | public PasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) {
470 | super(context, attrs, defStyleAttr);
471 | setOnTouchListener(this);
472 | setOnFocusChangeListener(this);
473 | addTextChangedListener(this);
474 | }
475 |
476 | @Override
477 | public void onFocusChange(View view, boolean hasFocus) {
478 | ......
479 | }
480 |
481 | @Override
482 | public boolean onTouch(View view, MotionEvent event) {
483 | ......
484 | }
485 |
486 | @Override
487 | public void onTextChanged(CharSequence s, int start, int before, int count) {
488 | ......
489 | }
490 |
491 | @Override
492 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
493 | ......
494 | }
495 |
496 | @Override
497 | public void afterTextChanged(Editable s) {
498 | ......
499 | }
500 | }
501 | ```
502 |
503 | * 但是有两个美中不足的地方,就是在实现的接口过多时,我们很难分辨是哪个方法是哪个接口的,这个时候可以使用注释的方式来解决这个问题,加上 **@link** 还可以帮助我们快速定位接口类在项目中所在的位置;另外一个是 **implements** 修饰符换行的问题,合理的换行会使代码更加简单直观。
504 |
505 | ```java
506 | public final class PasswordEditText extends EditText
507 | implements View.OnTouchListener,
508 | View.OnFocusChangeListener, TextWatcher {
509 |
510 | public PasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) {
511 | super(context, attrs, defStyleAttr);
512 | setOnTouchListener(this);
513 | setOnFocusChangeListener(this);
514 | addTextChangedListener(this);
515 | }
516 |
517 | /**
518 | * {@link OnFocusChangeListener}
519 | */
520 |
521 | @Override
522 | public void onFocusChange(View view, boolean hasFocus) {
523 | ......
524 | }
525 |
526 | /**
527 | * {@link OnTouchListener}
528 | */
529 |
530 | @Override
531 | public boolean onTouch(View view, MotionEvent event) {
532 | ......
533 | }
534 |
535 | /**
536 | * {@link TextWatcher}
537 | */
538 |
539 | @Override
540 | public void onTextChanged(CharSequence s, int start, int before, int count) {
541 | ......
542 | }
543 |
544 | @Override
545 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
546 | ......
547 | }
548 |
549 | @Override
550 | public void afterTextChanged(Editable s) {
551 | ......
552 | }
553 | }
554 | ```
555 |
556 | #### 异常捕获规范
557 |
558 | * 请不要使用此方式捕获异常,因为这种方式会把问题给隐藏掉,从而会加大后续排查问题的难度。
559 |
560 | ```java
561 | try {
562 | Xxx.xxx();
563 | } catch (Exception e) {}
564 | ```
565 |
566 | * 如需捕获异常,请用以下方式进行捕获,列出具体的异常类型,并在代码中输出对应的堆栈信息。
567 |
568 | ```java
569 | // 捕获这个异常,避免程序崩溃
570 | try {
571 | // 目前发现在 Android 7.1 主线程被阻塞之后弹吐司会导致崩溃,可使用 Thread.sleep(5000) 进行复现
572 | // 查看源码得知 Google 已经在 Android 8.0 已经修复了此问题
573 | // 主线程阻塞之后 Toast 也会被阻塞,Toast 因为显示超时导致 Window Token 失效
574 | mHandler.handleMessage(msg);
575 | } catch (WindowManager.BadTokenException | IllegalStateException e) {
576 | // android.view.WindowManager$BadTokenException:Unable to add window -- token android.os.BinderProxy is not valid; is your activity running?
577 | // java.lang.IllegalStateException:View android.widget.TextView has already been added to the window manager.
578 | e.printStackTrace();
579 | // 又或者上报到 Bugly 错误分析中
580 | // CrashReport.postCatchedException(e);
581 | }
582 | ```
583 |
584 | * 如果这个异常不是通过方法 throws 关键字抛出,则需要在 try 块中说明崩溃的缘由,并注明抛出的异常信息。
585 |
586 | * 还有一个问题,有异常就一定要 `try catch` ?,这种想法其实是错的,例如我们项目用 Glide 加载图片会抛出以下异常:
587 |
588 | ```java
589 | Caused by: java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
590 | at com.bumptech.glide.manager.RequestManagerRetriever.assertNotDestroyed(RequestManagerRetriever.java:348)
591 | at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:148)
592 | at com.bumptech.glide.Glide.with(Glide.java:826)
593 | ```
594 |
595 | * 这是因为 Activity 的销毁了而去加载图片导致的(场景:异步执行图片加载),大多人的解决方式可能是:
596 |
597 | ```java
598 | try {
599 | // Activity 销毁后执行加载图片会触发 crash
600 | Glide.with(this)
601 | .load(url)
602 | .into(mImageView);
603 | } catch (IllegalArgumentException e) {
604 | // java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
605 | e.printStackTrace();
606 | }
607 | ```
608 |
609 | * 虽然这种方式可以解决 **crash** 的问题,但是显得**不够严谨**,Glide 抛异常给外层,其实无非就想告诉调用者,调用的时机错了,正确的处理方式不是直接捕获这个异常,而是应该在外层做好逻辑判断,避免会进入出现 **crash** 的代码,正确的处理示例如下:
610 |
611 | ```java
612 | if (activity.isFinishing() || activity.isDestroyed()) {
613 | // Glide:You cannot start a load for a destroyed activity
614 | return;
615 | }
616 |
617 | Glide.with(this)
618 | .load(url)
619 | .into(mImageView);
620 | ```
621 |
622 | * 所以尽量不要通过捕获的方式来处理异常,除非外层真的判断不了,否则应该通过一些逻辑判断来避免进入一些会 **crash** 的代码。
623 |
624 | #### 参数传递规范
625 |
626 | * 如果跳转的 Activity 需要传递参数,应该在目标的 Activity 中定义静态的 **start** 又或者 **newIntent** 方法。
627 |
628 | ```java
629 | public final class WebActivity extends Activity {
630 |
631 | private static final String INTENT_KEY_URL = "url";
632 |
633 | public static void start(Context context, String url) {
634 | Intent intent = new Intent(context, WebActivity.class);
635 | intent.putExtra(INTENT_KEY_URL, url);
636 | context.startActivity(intent);
637 | }
638 | }
639 | ```
640 |
641 | ```java
642 | public final class WebActivity extends Activity {
643 |
644 | private static final String INTENT_KEY_URL = "url";
645 |
646 | public static Intent newIntent(Context context, String url) {
647 | Intent intent = new Intent(context, WebActivity.class);
648 | intent.putExtra(INTENT_KEY_URL, url);
649 | return intent;
650 | }
651 | }
652 | ```
653 |
654 | * 如果创建的 Fragment 需要传递参数,应该在目标的 Fragment 中定义静态的 **newInstance** 方法
655 |
656 | ```java
657 | public final class WebFragment extends Fragment {
658 |
659 | private static final String INTENT_KEY_URL = "url";
660 |
661 | public static WebFragment newInstance(String url) {
662 | WebFragment fragment = new WebFragment();
663 | Bundle bundle = new Bundle();
664 | bundle.putString(INTENT_KEY_URL, url);
665 | fragment.setArguments(bundle);
666 | return fragment;
667 | }
668 | }
669 | ```
670 |
671 | * 如果跳转的 Activity 或者创建的 Fragment 不需要传任何参数,可以不需要定义这些静态方法。
672 |
673 | * 另外如果一个界面需要传递的参数过多(一般 5 个以上),建议用一个对象对这些参数进行封装,然后实现 Serializable 或者 Parcelable 接口进行传递,具体写法示例:
674 |
675 | ```java
676 | public final class VideoPlayActivity extends Activity {
677 |
678 | private static final String INTENT_KEY_PARAMETERS = "parameters";
679 |
680 | /**
681 | * 播放参数构建
682 | */
683 | public static final class Builder implements Parcelable {
684 |
685 | /** 视频源 */
686 | private String videoSource;
687 | /** 视频标题 */
688 | private String videoTitle;
689 | /** 播放进度 */
690 | private int playProgress;
691 | /** 手势开关 */
692 | private boolean gestureEnabled = true;
693 | /** 循环播放 */
694 | private boolean loopPlay = false;
695 | /** 自动播放 */
696 | private boolean autoPlay = true;
697 | /** 播放完关闭 */
698 | private boolean autoOver = true;
699 |
700 | public Builder() {}
701 |
702 | public Builder setVideoSource(File file) {
703 | this.videoSource = file.getPath();
704 | if (this.videoTitle == null) {
705 | this.videoTitle = file.getName();
706 | }
707 | return this;
708 | }
709 |
710 | public Builder setVideoSource(String url) {
711 | this.videoSource = url;
712 | return this;
713 | }
714 |
715 | public Builder setVideoTitle(String title) {
716 | this.videoTitle = title;
717 | return this;
718 | }
719 |
720 | public Builder setPlayProgress(int progress) {
721 | this.playProgress = progress;
722 | return this;
723 | }
724 |
725 | public Builder setGestureEnabled(boolean enabled) {
726 | this.gestureEnabled = enabled;
727 | return this;
728 | }
729 |
730 | public Builder setLoopPlay(boolean enabled) {
731 | this.loopPlay = enabled;
732 | return this;
733 | }
734 |
735 | public Builder setAutoPlay(boolean enabled) {
736 | this.autoPlay = enabled;
737 | return this;
738 | }
739 |
740 | public Builder setAutoOver(boolean enabled) {
741 | this.autoOver = enabled;
742 | return this;
743 | }
744 |
745 | public void start(Context context) {
746 | Intent intent = new Intent(context, VideoPlayActivity.class);
747 | intent.putExtra(INTENT_KEY_PARAMETERS, this);
748 | if (!(context instanceof Activity)) {
749 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
750 | }
751 | context.startActivity(intent);
752 | }
753 | }
754 | }
755 | ```
756 |
757 | ```java
758 | new VideoPlayActivity.Builder()
759 | .setVideoTitle("速度与激情特别行动")
760 | .setVideoSource("http://xxxxx.mp4")
761 | .start(getAttachActivity());
762 | ```
763 |
764 | #### 代码美观性要求
765 |
766 | * if 语句后面应该加上大括号,而不应该将判断和处理的逻辑在同一行做处理,例如:
767 |
768 | ```java
769 | // 不规范写法示例
770 | if (AppConfig.isDebug()) return;
771 | ```
772 |
773 | ```java
774 | // 规范写法示例
775 | if (AppConfig.isDebug()) {
776 | return;
777 | }
778 | ```
779 |
780 | * 大括号应当统一放在表达式后面,而不应该换行处理,例如:
781 |
782 | ```java
783 | // 不规范写法示例
784 | if (AppConfig.isDebug())
785 | {
786 | ......
787 | }
788 | ```
789 |
790 | ```java
791 | // 规范写法示例
792 | if (AppConfig.isDebug()) {
793 | ......
794 | }
795 | ```
796 |
797 | * 代码之间应当有适当的空格,空格不用多也不能少,恰到好处即可,例如:
798 |
799 | ```java
800 | // 不规范写法示例
801 | public static boolean isAppInstalled(Context context ,String packageName ){
802 | try {
803 | context.getPackageManager() .getApplicationInfo(packageName,0);
804 | return true;
805 | }catch( PackageManager.NameNotFoundException e ){
806 | e.printStackTrace();
807 | return false ;
808 | }
809 | }
810 | ```
811 |
812 | ```java
813 | // 规范写法示例
814 | public static boolean isAppInstalled(Context context, String packageName) {
815 | try {
816 | context.getPackageManager().getApplicationInfo(packageName, 0);
817 | return true;
818 | } catch (PackageManager.NameNotFoundException e) {
819 | e.printStackTrace();
820 | return false;
821 | }
822 | }
823 | ```
824 |
825 | * 适当换行有助于提升代码的可读性,在单行代码较长的情况下可以考虑适当换行,例如:
826 |
827 | ```java
828 | // 不规范写法示例
829 | ScaleAnimation animation = new ScaleAnimation(1.0f, 1.1f, 1.0f, 1.1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
830 | textView.startAnimation(animation);
831 | ```
832 |
833 | ```java
834 | // 规范写法示例
835 | ScaleAnimation animation = new ScaleAnimation(1.0f, 1.1f, 1.0f, 1.1f,
836 | Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
837 | textView.startAnimation(animation);
838 | ```
839 |
840 | * 链式写法不能只用一行代码,而是应当遵守一句 API 换一行的策略,例如:
841 |
842 | ```java
843 | // 不规范写法示例
844 | GlideApp.with(this).load(url).circleCrop().into(imageView);
845 | ```
846 |
847 | ```java
848 | // 规范写法示例
849 | GlideApp.with(this)
850 | .load(url)
851 | .circleCrop()
852 | .into(imageView);
853 | ```
854 |
855 | * 方法参数的排序,上下文参数应当统一放在最前面,而回调监听应当统一放在最后面,例如:
856 |
857 | ```java
858 | public void openSystemFileChooser(Activity activity, FileChooserParams params, ValueCallback callback) {
859 | ......
860 | }
861 | ```
862 |
863 | * 一个类里面的内容,应该按照以下规则来排序,从上往下排序:
864 |
865 | 1. 常量
866 |
867 | 2. 静态变量
868 |
869 | 3. 静态方法
870 |
871 | 4. 类字段
872 |
873 | 5. 构造函数
874 |
875 | 6. 重载方法
876 |
877 | 7. 普通方法
878 |
879 | 8. 内部类
880 |
881 | 9. 接口类
882 |
883 | * 另外不同变量和方法的排序,应当根据重要程度、API 类型、执行顺序这几点来摆放,例如:
884 |
885 | ```java
886 | // 变量排序示例
887 | public class BaseDialog {
888 |
889 | public static class Builder>
890 |
891 | /** 宽度和高度 */
892 | private int width = WindowManager.LayoutParams.WRAP_CONTENT;
893 | private int height = WindowManager.LayoutParams.WRAP_CONTENT;
894 |
895 | /** 是否能够被取消 */
896 | private boolean cancelable = true;
897 | /** 点击空白是否能够取消 前提是这个对话框可以被取消 */
898 | private boolean canceledOnTouchOutside = true;
899 |
900 | /** 背景遮盖层开关 */
901 | private boolean backgroundDimEnabled = true;
902 | /** 背景遮盖层透明度 */
903 | private float backgroundDimAmount = 0.5f;
904 |
905 | /** Dialog 创建监听 */
906 | private BaseDialog.OnCreateListener createListener;
907 | /** Dialog 显示监听 */
908 | private final List showListeners = new ArrayList<>();
909 | /** Dialog 取消监听 */
910 | private final List cancelListeners = new ArrayList<>();
911 | /** Dialog 销毁监听 */
912 | private final List dismissListeners = new ArrayList<>();
913 | /** Dialog 按键监听 */
914 | private BaseDialog.OnKeyListener keyListener;
915 | }
916 | }
917 | ```
918 |
919 | ```java
920 | // 方法排序示例
921 | public class BaseDialog {
922 |
923 | public static class Builder>
924 |
925 | /**
926 | * 设置宽度
927 | */
928 | public B setWidth(int width) {
929 | ......
930 | }
931 |
932 | /**
933 | * 设置高度
934 | */
935 | public B setHeight(int height) {
936 | ......
937 | }
938 |
939 | /**
940 | * 是否可以取消
941 | */
942 | public B setCancelable(boolean cancelable) {
943 | ......
944 | }
945 |
946 | /**
947 | * 是否可以通过点击空白区域取消
948 | */
949 | public B setCanceledOnTouchOutside(boolean cancel) {
950 | ......
951 | }
952 |
953 | /**
954 | * 设置背景遮盖层开关
955 | */
956 | public B setBackgroundDimEnabled(boolean enabled) {
957 | ......
958 | }
959 |
960 | /**
961 | * 设置背景遮盖层的透明度(前提条件是背景遮盖层开关必须是为开启状态)
962 | */
963 | public B setBackgroundDimAmount(float dimAmount) {
964 | ......
965 | }
966 |
967 | /**
968 | * 设置创建监听
969 | */
970 | public B setOnCreateListener(BaseDialog.OnCreateListener listener) {
971 | ......
972 | }
973 |
974 | /**
975 | * 添加显示监听
976 | */
977 | public B addOnShowListener(BaseDialog.OnShowListener listener) {
978 | ......
979 | }
980 |
981 | /**
982 | * 添加取消监听
983 | */
984 | public B addOnCancelListener(BaseDialog.OnCancelListener listener) {
985 | ......
986 | }
987 |
988 | /**
989 | * 添加销毁监听
990 | */
991 | public B addOnDismissListener(BaseDialog.OnDismissListener listener) {
992 | ......
993 | }
994 |
995 | /**
996 | * 设置按键监听
997 | */
998 | public B setOnKeyListener(BaseDialog.OnKeyListener listener) {
999 | ......
1000 | }
1001 | }
1002 | }
1003 | ```
1004 |
1005 | * 代码美观性虽然不会干扰到业务的正常进行,但是对一个程序员来讲,是代码品质的一种追求,同时也是工匠精神的体现。
1006 |
1007 | #### 第三方框架使用规范
1008 |
1009 | * 集成一些第三方框架或者 SDK,必须注明框架的作用及出处,以便出现问题时能够快速核查和反馈。
1010 |
1011 | ```groovy
1012 | // 权限请求框架:https://github.com/getActivity/XXPermissions
1013 | implementation 'com.github.getActivity:XXPermissions:16.6'
1014 | ```
1015 |
1016 | * 尽量不要选择功能两套相同的框架,应当引用最合适的一套框架进行开发。
1017 |
1018 | * 使用第三方库必须要依赖指定的版本号,而不能使用 `latest.release` 或者 `+` 来指定依赖库最新的版本号。
1019 |
1020 | * 使用第三方开源库出现问题或者 Bug 时应及时通知到开源库的作者,如果没有及时回复就根据实际情况对问题进行修复。
1021 |
1022 | * 尽量避免 Copy 第三方库的技术代码到项目中,特别是在放置到项目业务模块中,因为这样会增加项目的复杂度,从而降低可维护性。
1023 |
1024 | * 如果出现问题不能找到开源库的作者,如果需要修改,应当将这些代码抽取到单独的 Module 中。
1025 |
1026 | * 能用框架就用成熟框架,尽量不要自己编写或者修改框架,如果有需要,要对这块进行严格测试。
1027 |
1028 | #### 多模块规范
1029 |
1030 | * 模块命名规范:应该以简单明了的方式来命名
1031 |
1032 | ```text
1033 | app
1034 | base
1035 | widget
1036 | umeng
1037 | course
1038 | socket
1039 | live
1040 | shop
1041 | ```
1042 |
1043 | * 模块混淆配置:请不要使用 `proguardFiles` 语句,而是应该使用 `consumerProguardFiles` 语句,因为 `consumerProguardFiles` 语句会将混淆规则和资源代码一同打包到 **aar** 包中,这样做的好处在于:在项目编译时会将 aar 包中的混淆规则合并到主模块中。
1044 |
1045 | ```groovy
1046 | android {
1047 |
1048 | defaultConfig {
1049 | // 模块混淆配置
1050 | consumerProguardFiles 'proguard-xxx.pro'
1051 | }
1052 | }
1053 | ```
1054 |
1055 | * 资源前缀限制:我们应该在模块中加入此限制,这样我们在模块中添加资源时,编译器如果发现资源名称前缀不符合规范,则会出现代码警告。这样做的好处在于,以某一名称作为前缀,可以有效避免在编译时引发的一些资源合并冲突。
1056 |
1057 | ```groovy
1058 | android {
1059 | // 资源前缀限制
1060 | resourcePrefix "xxx_"
1061 | }
1062 | ```
1063 |
1064 | * 框架版本管理:我们应该统一抽取框架的版本到 `config.gradle` 文件中:
1065 |
1066 | ```groovy
1067 | ext {
1068 |
1069 | android = [compileSdkVersion : 28,
1070 | minSdkVersion : 19,
1071 | targetSdkVersion : 28,
1072 | versionCode : 40102,
1073 | versionName : "4.1.2",
1074 | ]
1075 | dependencies = [
1076 | "appcompat" : "androidx.appcompat:appcompat:1.2.0",
1077 | "material" : "com.google.android.material:material:1.2.0",
1078 | ]
1079 | }
1080 | ```
1081 |
1082 | * 然后在每个模块下这样定义,这样做的好处是可以做到版本号的统一管理。
1083 |
1084 | ```groovy
1085 | apply from : '../config.gradle'
1086 |
1087 | android {
1088 | compileSdkVersion rootProject.ext.android["compileSdkVersion"]
1089 |
1090 | defaultConfig {
1091 | minSdkVersion rootProject.ext.android["minSdkVersion"]
1092 | targetSdkVersion rootProject.ext.android["targetSdkVersion"]
1093 | }
1094 | }
1095 | dependencies {
1096 | implementation rootProject.ext.dependencies["appcompat"]
1097 | implementation rootProject.ext.dependencies["material"]
1098 | }
1099 | ```
1100 |
1101 | * 除此之外还有另外一种写法,我们可以把 `config.gradle` 修改成这样:
1102 |
1103 | ```groovy
1104 | android {
1105 | compileSdkVersion 28
1106 |
1107 | defaultConfig {
1108 | minSdkVersion 19
1109 | targetSdkVersion 28
1110 | versionName '4.1.2'
1111 | versionCode 40102
1112 | }
1113 | }
1114 |
1115 | dependencies {
1116 | implementation 'androidx.appcompat:appcompat:1.2.0'
1117 | implementation 'com.google.android.material:material:1.2.1'
1118 | }
1119 | ```
1120 |
1121 | * 然后在每个模块上添加一句引用即可,相比上一种写方法,这种方式更强大,因为它不仅可以配置版本号,还支持统一其他的配置项。
1122 |
1123 | ```groovy
1124 | apply from : '../config.gradle'
1125 | ```
1126 |
1127 | * 具体要用哪一种,可以根据实际情况而定,如果项目采用的是组件化,则可以考虑使用第一种方式,如果项目采用的是模块化,则可以考虑使用第二种方式,当然两种一起结合用也是可以的。
1128 |
1129 | #### 代码注释规范
1130 |
1131 | * 类注释规范:**author** 是创建者(必填项)、**time** 是创建时间(必填项)、**desc** 是类的描述(必填项),**doc** 是文档地址(非必填),**github** 是开源地址(如果项目是开源的则必填,否则不填)
1132 |
1133 | ```java
1134 | /**
1135 | * author : Android 轮子哥
1136 | * github : https://github.com/getActivity/XXPermissions
1137 | * time : 2018/06/15
1138 | * desc : 权限请求实体类
1139 | * doc : https://developer.android.google.cn/reference/android/Manifest.permission?hl=zh_cn
1140 | * https://developer.android.google.cn/guide/topics/permissions/overview?hl=zh-cn#normal-dangerous
1141 | */
1142 | public final class Permission {
1143 | ....
1144 | }
1145 | ```
1146 |
1147 | * 方法注释规范:方法注释可根据实际情况而定
1148 |
1149 | ```java
1150 | /**
1151 | * 设置请求的对象
1152 | *
1153 | * @param activity 当前 Activity,可以传入栈顶的 Activity
1154 | */
1155 | public static XXPermissions with(FragmentActivity activity) {
1156 | return ....;
1157 | }
1158 | ```
1159 |
1160 | * 字段注释规范:根据字段的作用而定
1161 |
1162 | ```java
1163 | /** 请求的权限组 */
1164 | private static final String REQUEST_PERMISSIONS = "request_permissions";
1165 |
1166 | /** 权限回调对象 */
1167 | private OnPermissionCallback callBack;
1168 | ```
1169 |
1170 | * 变量注释规范(如果 API 是比较常见并且容易理解可以不用写,如果是复杂并且羞涩难懂则需要写上)
1171 |
1172 | ```java
1173 | // 设置保留实例,不会因为屏幕方向或配置变化而重新创建
1174 | fragment.setRetainInstance(true);
1175 | ```
1176 |
1177 | * 注释什么情况下要写?什么情况下不用写?这个问题我很有感触,代码注释写多了不好,显得太啰嗦,也会增加工作量,写少了也不好,又怕别人看不懂,也害怕给自己后面留坑。我个人的建议是尽量用规范的命名来减少不必要的注释,很多时候我们只需要换位思考一下,忘记这段代码是自己写的,再问一下自己能不能一下子读懂,如果可以的话,注释就可以不用写,否则注释还是要考虑写上。
1178 |
1179 | #### 代码硬编码规范
1180 |
1181 | * 请尽量避免使用硬编码,例如系统的一些常量值,不能直接写死,而是应该通过代码引用,这样有助于代码阅读和理解,例如:
1182 |
1183 | ```java
1184 | // 不规范写法示例
1185 | if (view.getVisibility() != 0) {
1186 | return;
1187 | }
1188 |
1189 | Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
1190 | startActivity(intent);
1191 | ```
1192 |
1193 | ```java
1194 | // 规范写法示例
1195 | if (view.getVisibility() != View.VISIBLE) {
1196 | return;
1197 | }
1198 |
1199 | Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
1200 | startActivity(intent);
1201 | ```
1202 |
1203 | * 在项目开发中,被多次使用到的数值或者字符串也应该提取成常量来供外部引用,例如:
1204 |
1205 | ```java
1206 | public final class UserInfoManager {
1207 |
1208 | /** 学生 */
1209 | public static final int TYPE_STUDENT = 0;
1210 |
1211 | /** 老师 */
1212 | public static final int TYPE_TEACHER = 1;
1213 |
1214 | /** 家长 */
1215 | public static final int TYPE_PATRIARCH = 2;
1216 | }
1217 | ```
1218 |
1219 | * 但并不代表所有的数值都需要常量化,有一些数值常量化的意义并不大,例如:
1220 |
1221 | ```java
1222 | ValueAnimator animator = ValueAnimator.ofInt(0, 100);
1223 | animator.setDuration(500);
1224 | animator.start();
1225 | ```
1226 |
1227 | * 所以衡量一个数值或者字符串是否进行常量化的标准有两点:
1228 |
1229 | * 这个数值或者字符串是否会被多次使用
1230 |
1231 | * 这个数值或者字符串是否具有一定的含义
1232 |
1233 | #### 布局文件命名规范
1234 |
1235 | * 以 **模块 + 类型** 来命名,例如:
1236 |
1237 | ```text
1238 | home_activity.xml
1239 |
1240 | setting_fragment.xml
1241 |
1242 | menu_item.xml
1243 |
1244 | address_dialog.xml
1245 | ```
1246 |
1247 | * 这样写的好处在于,由于 res 文件夹下是没有层级概念的
1248 |
1249 | * 通过前缀的命名可以帮助我们更好定位到同一模块下的资源
1250 |
1251 | * 例如分享对话框中,有对话框 Root 布局和 Item 布局
1252 |
1253 | ```text
1254 | share_dialog.xml(Root 布局)
1255 |
1256 | share_item.xml(Item 布局)
1257 | ```
1258 |
1259 | #### 资源文件命名规范
1260 |
1261 | * 如果是业务模块下的资源,以 **模块 + 类型** 来命名,例如分享对话框的资源:
1262 |
1263 | ```text
1264 | share_link_ic.png(复制链接)
1265 |
1266 | share_moment_ic.png(分享到朋友圈)
1267 |
1268 | share_qq_ic.png(分享到 QQ 好友)
1269 |
1270 | share_qzone_ic.png(分享到 QQ 空间)
1271 |
1272 | share_wechat_ic.png(分享到微信好友)
1273 | ```
1274 |
1275 | * 如果和业务模块不相干的资源,以 **作用 + 类型** 来命名,例如通用的控件样式资源:
1276 |
1277 | ```text
1278 | button_rect_selector.xml(通用直角按钮样式)
1279 |
1280 | button_round_selector.xml(通用圆角按钮样式)
1281 | ```
1282 |
1283 | * 这种资源有一个共同特点,它不属于哪个模块,但是在不同模块都有用到,所以不能用业务的模块名作为文件名前缀,最后附上常见类型名称对应表:
1284 |
1285 | | 名称 | 类型 |
1286 | | :-----: | :----: |
1287 | | ic | 图标 |
1288 | | bg | 背景 |
1289 | | selector | 选择器 |
1290 |
1291 | #### String ID 命名规范
1292 |
1293 | * 请以 **模块 + 功能** 来命名,例如:
1294 |
1295 | ```xml
1296 |
1297 | 首页
1298 | 发现
1299 | 消息
1300 | 我的
1301 | 再按一次退出
1302 |
1303 |
1304 | 注册
1305 | 请输入手机号
1306 | 请输入密码
1307 | 忘记密码?
1308 | 登录
1309 | 其他登录方式
1310 |
1311 |
1312 | 注册
1313 | 手机号仅用于登录和保护账号安全
1314 | 登录
1315 | 设置密码
1316 | 再次输入密码
1317 | 两次密码输入不一致,请重新输入
1318 |
1319 |
1320 | 设置
1321 | 语言切换
1322 | 简体中文
1323 | 繁体中文
1324 | ```
1325 |
1326 | * 另外有一类 String 被多个模块所引用,需要以 **common + 作用** 来命名,例如:
1327 |
1328 | ```xml
1329 | 加载中…
1330 |
1331 | 确定
1332 | 取消
1333 |
1334 | 年
1335 | 月
1336 | 日
1337 |
1338 | 时
1339 | 分
1340 | 秒
1341 | ```
1342 |
1343 | #### Color ID 命名规范
1344 |
1345 | * 请以 **模块 + 作用 + color** 来命名,例如:
1346 |
1347 | ```xml
1348 | #FFBBBBBB
1349 | #FF33B5E5
1350 | #FF99CC00
1351 | #FFFFBB33
1352 | #FFFF4444
1353 | #FFFFFFFF
1354 | ```
1355 |
1356 | * 另外有一类 Color 被多个模块所引用,需要以 **common + 作用 + color** 来命名,例如:
1357 |
1358 | ```xml
1359 |
1360 | @color/white
1361 | @color/black
1362 | #5A8DDF
1363 | #F4F4F4
1364 | #333333
1365 | @color/panda
1366 |
1367 |
1368 | #AA5A8DDF
1369 |
1370 | #BBBBBB
1371 |
1372 | #ECECEC
1373 | ```
1374 |
1375 | * 还有一类 Color 是行业通用的色值,需要以 **简单直接的方式** 来命名,例如:
1376 |
1377 | ```xml
1378 |
1379 | #00000000
1380 |
1381 | #FFFFFFFF
1382 |
1383 | #FF000000
1384 |
1385 | #FF808080
1386 |
1387 | #FFFF0000
1388 |
1389 | #FFFFD700
1390 |
1391 | #FFFFFF00
1392 |
1393 | #FF008000
1394 |
1395 | #FF0000FF
1396 |
1397 | #FF800080
1398 |
1399 | #FFFFC0CB
1400 |
1401 | #FFFFA500
1402 | ```
1403 |
1404 | * 在实际开发中,我们常常会遇到类似下面这种命名方式:
1405 |
1406 | ```xml
1407 | #FF35BF30
1408 | ```
1409 |
1410 | * 其实这种命名方式是不规范的,因为它对 **Color ID** 的名称定义比较模糊,会容易给别人造成误导;举个例子:假设项目中有 **200** 个地方引用了这个 `color_FF35BF30` 色值,其中有 **150** 地方是你自己引用的,另外 **50** 个地方是别人引用的,但是别人不知道你那个色值是干什么的,看到你有写就直接引用了,突然有一天产品经理心情不好要改这个色值,那么你要从 **200** 地方区分 **150** 个需要修改的地方和 **50** 个不需要修改的地方。
1411 |
1412 | #### Anim ID 命名规范
1413 |
1414 | * 应用到某个模块 **View**,例如:
1415 |
1416 | ```text
1417 | login_left_balloon_view.xml
1418 | login_right_balloon_view.xml
1419 | ```
1420 |
1421 | * 应用到全局 **Activity**,例如:
1422 |
1423 | ```text
1424 | left_in_activity.xml
1425 | left_out_activity.xml
1426 | ```
1427 |
1428 | * 应用到全局 **Dialog**,例如:
1429 |
1430 | ```text
1431 | bottom_in_dialog.xml
1432 | bottom_out_dialog.xml
1433 | ```
1434 |
1435 | #### View ID 命名规范
1436 |
1437 | * 应该以 **控件的缩写 + 模块名 + 作用** 来命名,例如
1438 |
1439 | ```text
1440 | @+id/R.id.rg_login_type
1441 |
1442 | @+id/R.id.et_login_phone
1443 |
1444 | @+id/R.id.et_login_sms
1445 |
1446 | @+id/R.id.et_login_password
1447 |
1448 | @+id/R.id.btn_login_commit
1449 | ```
1450 |
1451 | * View 和 Layout 控件缩写表,这里列举最常见的几个
1452 |
1453 | | 名称 | 缩写 |
1454 | | :------------: | :----: |
1455 | | TextView | tv |
1456 | | EditText | et |
1457 | | Button | btn |
1458 | | ImageView | iv |
1459 | | ImageButton | ib |
1460 | | ListView | lv |
1461 | | RecyclerView | rv |
1462 | | RadioButton | rb |
1463 | | RadioGroup | rg |
1464 | | ProgressBar | pb |
1465 | | CheckBox | cb |
1466 | | TableLayout | tl |
1467 | | ScrollView | sv |
1468 | | LinearLayout | ll |
1469 | | RelativeLayout | rl |
1470 | | FrameLayout | fl |
1471 |
1472 | #### Style 命名规范
1473 |
1474 | * 如果只是主题相关的样式,以 **Theme** 命名结尾,控件样式则以 **Style** 命名结尾,命名要求尽量简洁,并且需要有代码注释,示例如下:
1475 |
1476 | ```xml
1477 |
1478 |
1481 |
1482 |
1483 |
1486 |
1487 |
1488 |
1491 | ```
1492 |
1493 |
1494 | ```xml
1495 |
1496 |
1499 |
1500 |
1501 |
1504 |
1505 |
1506 |
1509 |
1510 |
1511 |
1514 | ```
1515 |
1516 | #### XML 编码规范
1517 |
1518 | * 不推荐用 **dp** 作为字体单位,虽然在大部分手机上面 **dp** 和 **sp** 计算是差不多的,但是会有一部分老年用户群,例如咱们的长辈,他们通常会把手机显示的字体大小调大,这样他们才不需要带眼镜看手机,如果我们用 **dp** 作为字体单位,无论手机怎么调整字体大小,应用的字体大小都不会有任何的变化,所以这种操作显然是非常不人性化的。
1519 |
1520 | ```xml
1521 |
1522 |
1526 | ```
1527 |
1528 | ```xml
1529 |
1530 |
1534 | ```
1535 |
1536 | * 不能根据设计图给定的宽高把 **TextView** 或者 **Button** 的宽高定死,而是通过 `wrap_content` 和 `padding` 的方式来调整 View 的宽高,因为在不同手机上面字体大小不一致,在字体显示比较小的手机上面会显示正常,但是在字体显示比较大的平板上面文字上半部分极有可能会出现被裁剪的情况,所以我们不能把宽高定死,而是通过 `padding` 来调整到控件的大小。不过需要注意的是,[TextView 有自带的文字间距](https://blog.csdn.net/ccpat/article/details/45226951),我们在拿设计图给定的 `padding`值时,需要拿设计图给定的值适当减去这一部分值(一般大概是在 **2~3dp**)。
1537 |
1538 | ```xml
1539 |
1540 |
1543 | ```
1544 |
1545 | ```xml
1546 |
1547 |
1554 | ```
1555 |
1556 | * **ImageView** 的宽高任一项定义成 `match_parent` 时,另外一项不能写死大小,而是应该使用 `wrap_content`,否则很可能会因为比例不对导致图片变形,另外还需要使用 `android:adjustViewBounds="true"` 属性,否则 `ImageView` 无法根据图片的宽高来调整自己的宽高。
1557 |
1558 | ```xml
1559 |
1560 |
1564 | ```
1565 |
1566 | ```xml
1567 |
1568 |
1573 | ```
1574 |
1575 | * XML 节点编写应该规范,在没有子节点的情况下,应当以 `/>` 节点结尾,如果有则以 `` 节点结尾
1576 |
1577 | ```xml
1578 |
1579 |
1582 |
1583 |
1584 |
1585 |
1588 |
1589 | ```
1590 |
1591 | ```xml
1592 |
1593 |
1596 |
1597 |
1598 |
1601 | ```
1602 |
1603 | #### 预览属性约定
1604 |
1605 | * 应该在布局文件根布局中定义 `tools:context` 属性,以便在布局文件中快速定位到对应的类
1606 |
1607 | ```xml
1608 |
1614 |
1615 |
1616 | ```
1617 |
1618 |
1619 | ```text
1620 | tools:context=".ui.activity.HomeActivity"
1621 |
1622 | tools:context=".ui.fragment.SettingFragment"
1623 |
1624 | tools:context=".ui.adapter.HomeAdapter"
1625 |
1626 | tools:context=".ui.dialog.PersonDataDialog"
1627 | ```
1628 |
1629 | * 此外,tools 属性还有各种各样的用途,例如 **RecyclerView** 的 **tools** 属性
1630 |
1631 | ```xml
1632 |
1643 | ```
1644 |
1645 | * 这种命名方式不止可以应用于 **RecyclerView**,还可以应用于其他 **View** 的属性,比如常用的 **TextView** 和 **ImageView**
1646 |
1647 | ```xml
1648 |
1652 |
1653 |
1658 | ```
1659 |
1660 | * 如果某个 **TextView** 显示的字符串是一成不变的,那么可以直接定义在布局文件中,如果是动态变化的,那么应该使用 `tools:text` 预览属性,而不应该使用 `android:text`,其他布局属性也同理。
1661 |
1662 | #### 资源硬编码规范
1663 |
1664 | * String 硬编码规范:如果项目已经适配了多语种,则严禁写死在 Java 代码或者布局文件中,如果没有这块需求的话,也可以将 String 资源定义在 `string.xml` 文件,此项不强制要求,大家根据实际情况而定。
1665 |
1666 | * Color 硬编码规范:在没有使用夜间模式的情况下,允许大部分 Color 值直接定义在布局文件中,但是如果某个色值引用得比较多(例如主题强调色、默认背景色等),需要抽取到 `color.xml` 文件中。
1667 |
1668 | * Dimens 硬编码规范:允许写死在 Java 代码或者布局文件中,但是如果使用了[通配符方案](https://github.com/wildma/ScreenAdaptation)对屏幕进行适配,那么则不能直接写死。
1669 |
1670 | * Style 样式规范:对于一些常用并且样式比较统一的控件,例如 **Button**、**EditText** 等,我们对这些控件的样式进行抽取到 `style.xml` 文件中来,避免属性重复定义。
1671 |
1672 | #### 多线程规范
1673 |
1674 | * 不要在主线程中执行耗时操作。这会导致 UI 卡顿,降低用户体验。建议使用 AsyncTask、Handler、Thread 等方式来实现异步操作。
1675 |
1676 | * 多线程代码必须保证线程安全。需要对共享资源进行同步操作,避免并发访问产生数据竞争问题。常用的同步方式有 synchronized、Lock、ReentrantLock 等。
1677 |
1678 | * 尽量避免死锁问题。不同线程之间存在依赖关系时,需要注意锁的获取顺序,防止出现死锁情况。
1679 |
1680 | * 使用线程池来管理线程数量。过多的线程会占用系统资源,影响应用性能;过少的线程则无法充分利用 CPU,导致任务延迟,合理设置线程池大小可以提高应用的效率和稳定性。
1681 |
1682 | #### 数据库操作规范
1683 |
1684 | * 避免频繁地打开和关闭数据库连接,尽可能复用数据库连接对象。
1685 |
1686 | * 使用事务来保证数据的完整性和一致性。对于批量数据操作或者需要执行多个 SQL 语句的场景,建议使用事务来优化性能。
1687 |
1688 | * 对于查询操作,尽量使用索引来提高查询效率,同时需要注意索引的创建和更新对数据库性能的影响。
1689 |
1690 | * 对于数据存储时涉及到密码、账号等敏感信息,应该使用加密算法进行加密保护。
1691 |
1692 | * 使用 ContentProvider 进行跨应用程序数据共享,并且在使用 ContentProvider 时,需要定义好 URI 和 MIME 类型,避免出现安全漏洞。
1693 |
1694 | #### 版本名和版本码规范
1695 |
1696 | * 版本名应该由三段整数组成
1697 |
1698 | * 第一段:代表大版本号,如果出现 UI 大改版,或者项目出现大重构时 +1
1699 |
1700 | * 第二段:代表需求版本号,一般双周发一次版,每个迭代周期 +1
1701 |
1702 | * 第三段:代表小版本号,发版之后出现 Bug,需要发版修复时 +1
1703 |
1704 | ```text
1705 | versionName '4.8.0'
1706 | ```
1707 |
1708 | * 另外版本码应当和版本名保持一定的关联性,例如:
1709 |
1710 | ```text
1711 | versionName '4.12.1'
1712 | versionCode 41201
1713 | ```
1714 |
1715 | * 这样的好处在于:版本名越高,版本码也会变大,不仅能方便记忆,还能帮助我们更好地管理和升级版本,在一定程度上能避免**高版本名低版本码**的 apk 能被**低版本名高版本码**的 apk 覆盖安装的情况。
1716 |
1717 | #### Git 版本管理规范
1718 |
1719 | * 提交规范:在提交代码时请进行 CodeReview(可以是自己也可以是别人),并且确保代码是自测通过的才能进行提交。另外有一点需要注意:提交之前不能先 Pull 代码,这样方式可行但是不规范。
1720 |
1721 | * 多分支规范:在多人协作开发中,我们应该用一个分支代表一个需求,这样做的好处是,在开发需求的过程中,一旦需求被砍,需求分支只要不合入主分支中就不会有太大的影响。另外一个需要注意的点就是分支合并的时机,时机应当在测试完毕后合入,由于分支合并有先后顺序,所以可能会导致代码在没有合并之前没有问题,在合并后出现了问题,所以我们应该约定一个封板时间(一般为上线或者灰度前两三天),在此期间对整个迭代的需求进行回归测试,不允许存在 Bug 之外的修改,例如需求变动修改,若非紧急变动,请勿轻易修改,一旦修改了,则应当将上线或者灰度的时间进行顺延,这样可以有效降低线上事故发生的概率。
1722 |
1723 | * 分支管理规范:分支一多会产生一些问题,那就是怎么管理这些分支,这个问题其实要从分支名称下手,由于分支是没有层级概念的,但是在一些版本管理软件上面(例如 `Sourcetree`),可以通过给分支的名称加 `/` 来建立层级,所以我们在给分支命名的时候,要遵循这种格式的命名,那么问题来了如何建立分支的层级?
1724 |
1725 | * 如果是 `需求主分支`,请以 `版本名` + `/` + `feature-branch` 的格式来命名,例如 `v5.2.0/feature-branch`
1726 |
1727 | * 如果是 `需求功能点分支`,请以 `版本名` + `/` + `feature` + `需求名称` 的格式来命名,例如 `v5.2.0/feature-push-message`
1728 |
1729 | * 如果是 `Bug 修复分支`,请以 `版本名` + `/` + `bugfix` + `Bug 名称` 的格式来命名,例如 `v5.2.0/bugfix-launch-crash`
1730 |
1731 | * 它们之间的合并顺序应当为:`需求功能点分支` 或者 `Bug 修复分支` 自测没有问题了合并到 `需求主分支`,下一个版本的 `需求主分支` 应该基于上一个版本的 `需求主分支` 的代码开分支,还有在代码灰度完成,正式上线之后,还应当给某个提交点打版本标签(俗称打版本 tag),标记这个版本的代码在此处提交点发版,方便后续追溯问题。
1732 |
1733 | * 这个时候大家可能有疑问了,上个版本有 Bug,紧急发了修复版本 `5.1.1`,但是 `5.2.0`(基于 `5.1.0`)已经开始迭代了,那么 `5.1.1` 应该在什么时机合入?答:在发完 `5.1.1` 版本后应该将代码及时合入 `5.2.0` 分支,避免后续出现遗留。
1734 |
1735 | * 分支依赖冲突处理:在多分支开发中,总是避免不了一种情况,那就是需求之间存在依赖关系的问题,面对这种问题最好的处理方式是:
1736 |
1737 | * 如果功能 A 依赖功能 B 的代码,而开发是同步进行的,则在分支 A 上面开发完功能 A 后,再合并一下分支 B 的功能 B 的代码,将分支 A 的功能补充完整再进行提测。
1738 |
1739 | * 如果功能 A 和功能 B 的功能之间是强依赖关系,那么比较优的解决方案是用同一个分支开发功能 A 和功能 B,当然这只是建议,最终还是要根据实际情况来判断要不要那么做。
1740 |
1741 | #### 致谢
1742 |
1743 | * [阿里巴巴 Android 开发手册.pdf](阿里巴巴Android开发手册.pdf)
1744 |
1745 | * [阿里巴巴 Java 开发手册.pdf](阿里巴巴Java开发手册.pdf)
1746 |
1747 | * [谷歌 Java 风格指南](https://google.github.io/styleguide/javaguide.html)
1748 |
1749 | * [谷歌 AOSP 代码样式指南](http://source.android.com/source/code-style.html)
1750 |
1751 | * [谷歌 Kotlin 代码样式指南](https://developer.android.com/kotlin/style-guide?hl=zh-cn)
1752 |
1753 | #### 作者其他开源项目
1754 |
1755 | * 安卓技术中台:[AndroidProject](https://github.com/getActivity/AndroidProject)  
1756 |
1757 | * 安卓技术中台 Kt 版:[AndroidProject-Kotlin](https://github.com/getActivity/AndroidProject-Kotlin)  
1758 |
1759 | * 权限框架:[XXPermissions](https://github.com/getActivity/XXPermissions)  
1760 |
1761 | * 吐司框架:[Toaster](https://github.com/getActivity/Toaster)  
1762 |
1763 | * 网络框架:[EasyHttp](https://github.com/getActivity/EasyHttp)  
1764 |
1765 | * 标题栏框架:[TitleBar](https://github.com/getActivity/TitleBar)  
1766 |
1767 | * 悬浮窗框架:[EasyWindow](https://github.com/getActivity/EasyWindow)  
1768 |
1769 | * ShapeView 框架:[ShapeView](https://github.com/getActivity/ShapeView)  
1770 |
1771 | * ShapeDrawable 框架:[ShapeDrawable](https://github.com/getActivity/ShapeDrawable)  
1772 |
1773 | * 语种切换框架:[MultiLanguages](https://github.com/getActivity/MultiLanguages)  
1774 |
1775 | * Gson 解析容错:[GsonFactory](https://github.com/getActivity/GsonFactory)  
1776 |
1777 | * 日志查看框架:[Logcat](https://github.com/getActivity/Logcat)  
1778 |
1779 | * 嵌套滚动布局框架:[NestedScrollLayout](https://github.com/getActivity/NestedScrollLayout)  
1780 |
1781 | * Android 版本适配:[AndroidVersionAdapter](https://github.com/getActivity/AndroidVersionAdapter)  
1782 |
1783 | * Android 资源大汇总:[AndroidIndex](https://github.com/getActivity/AndroidIndex)  
1784 |
1785 | * Android 开源排行榜:[AndroidGithubBoss](https://github.com/getActivity/AndroidGithubBoss)  
1786 |
1787 | * Studio 精品插件:[StudioPlugins](https://github.com/getActivity/StudioPlugins)  
1788 |
1789 | * 表情包大集合:[EmojiPackage](https://github.com/getActivity/EmojiPackage)  
1790 |
1791 | * 省市区 Json 数据:[ProvinceJson](https://github.com/getActivity/ProvinceJson)  
1792 |
1793 | * Markdown 语法文档:[MarkdownDoc](https://github.com/getActivity/MarkdownDoc)  
1794 |
1795 | #### 微信公众号:Android轮子哥
1796 |
1797 | 
1798 |
1799 | #### Android 技术 Q 群:10047167
1800 |
1801 | #### 如果您觉得我的开源库帮你节省了大量的开发时间,请扫描下方的二维码随意打赏,要是能打赏个 10.24 :monkey_face:就太:thumbsup:了。您的支持将鼓励我继续创作:octocat:([点击查看捐赠列表](https://github.com/getActivity/Donate))
1802 |
1803 |  
1804 |
1805 | ## License
1806 |
1807 | ```text
1808 | Copyright 2021 Huang JinQun
1809 |
1810 | Licensed under the Apache License, Version 2.0 (the "License");
1811 | you may not use this file except in compliance with the License.
1812 | You may obtain a copy of the License at
1813 |
1814 | http://www.apache.org/licenses/LICENSE-2.0
1815 |
1816 | Unless required by applicable law or agreed to in writing, software
1817 | distributed under the License is distributed on an "AS IS" BASIS,
1818 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1819 | See the License for the specific language governing permissions and
1820 | limitations under the License.
1821 | ```
--------------------------------------------------------------------------------
/阿里巴巴Android开发手册.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/AndroidCodeStandard/28496b8840f376da9d2564887f4988f3fef9a60c/阿里巴巴Android开发手册.pdf
--------------------------------------------------------------------------------
/阿里巴巴Java开发手册.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/AndroidCodeStandard/28496b8840f376da9d2564887f4988f3fef9a60c/阿里巴巴Java开发手册.pdf
--------------------------------------------------------------------------------