├── .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 |