工欲善其事,必先利其器。为了有利于项目维护、增强代码可读性、提升 Code Review 效率以及规范团队安卓开发,特此整理了一篇Android基本开发规范,期望能帮助团队成长。
网络上关于Android开发规范的资料很多,这里只选取了个人认为比较重要的部分,更详细的文档我会在文末添加链接,有兴趣的可以进一步学习
.java
、.xml
等文件后一定要 格式化,格式化,格式化(如果团队有公共的样式包,那就遵循它,否则统一使用 AS 默认模板即可);import
,减少警告出现,可利用 AS 的Optimize Imports
(Settings -> Keymap -> Optimize Imports)快捷键代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。正确的英文拼写和语法可以让阅读者易于理解,避免歧义。
注意:即使纯拼音命名方式也要避免采用。但 alibaba
、taobao
、youku
、hangzhou
等国际通用的名称,可视同英文。
包名全部小写,连续的单词只是简单地连接起来,不使用下划线,采用反域名命名规则,全部使用小写字母。一级包名是顶级域名,通常为 com
、edu
、gov
、net
、org
等,二级包名为公司名,三级包名根据应用进行命名,后面就是对包名的划分了,关于包名的划分,推荐采用 PBF(按功能分包 Package By Feature).
com
└── domain
└── app
├── App.java 定义 Application 类
├── Config.java 定义配置数据(常量)
├── base 基础组件
├── custom_view 自定义视图
├── data 数据处理
│ ├── DataManager.java 数据管理器,
│ ├── local 来源于本地的数据,比如 SP,Database,File
│ ├── model 定义 model(数据结构以及 getter/setter、compareTo、equals 等等,不含复杂操作)
│ └── remote 来源于远端的数据
├── feature 功能
│ ├── feature0 功能 0
│ │ ├── feature0Activity.java
│ │ ├── feature0Fragment.java
│ │ ├── xxAdapter.java
│ │ └── ... 其他 class
│ └── ...其他功能
├── injection 依赖注入
├── util 工具类
└── widget 小部件
类名都以 UpperCamelCase(大驼峰)
风格编写。
例如:
Activity 类 |
Activity 为后缀标识 |
欢迎页面类 WelcomeActivity |
---|---|---|
Adapter 类 |
Adapter 为后缀标识 |
NewsAdapter |
自定义的共享基础类 | 以 Base 开头 |
BaseActivity , BaseFragment |
测试类的命名以它要测试的类的名称开始,以 Test 结束。例如:HashTest
或 HashIntegrationTest
。
接口(interface
):命名规则与类一样采用大驼峰命名法,多以 able
或ible
结尾,如 interface Runnable
、interface Accessible
。
方法名都以 lowerCamelCase
风格编写。
initXX() |
初始化相关方法,使用 init 为前缀标识,如初始化布局 initView() |
---|---|
isXX() , checkXX() |
方法返回值为 boolean 型的请使用 is/check 为前缀标识 |
handleXX() , processXX() |
对数据进行处理的方法 |
常量名命名模式为 CONSTANT_CASE
,全部字母大写,用下划线分隔单词。
非常量字段名以 lowerCamelCase
风格的基础上改造为如下风格:基本结构为 scope{Type0}VariableName{Type1}
、type0VariableName{Type1}
、variableName{Type1}
。
说明:{}
中的内容为可选。
非公有,非静态字段命名以 m
开头。
静态字段命名以 s
开头。
其他字段以小写字母开头。
public class MainActivity extends AppCompatActivity {
public int publicField;
private static String sString;
int mPackagePrivate;
private int mPrivate;
protected int mProtected;
}
考虑到 Android 众多的UI
控件,为避免控件和普通成员变量混淆以及更好地表达意思,所有用来表示控件的成员变量统一加上控件缩写作为前缀。
例如:tvAvatar
、rvBooks
、flContainer
对于表示集合或者数组的非常量字段名,我们可以添加后缀来增强字段的可读性,比如:
集合添加如下后缀:List、Map、Set。
数组添加如下后缀:Arr。
例如:mIvAvatarList
、userArr
、firstNameSet
。
不明确数据类型的也可以使用复数形式来表示,如mBooks
;
Android SDK 中的很多类都用到了键值对函数,比如SharedPreferences
、Bundle
、Intent
,所以,即便是一个小应用,我们最终也不得不编写大量的字符串常量。
当时用到这些类的时候,我们 必须 将它们的键定义为 static final
字段,并遵循以下指示作为前缀。
SharedPreferences | PREF_ |
---|---|
Bundle | BUNDLE_ |
Fragment Arguments | ARGUMENT_ |
Intent Extra | EXTRA_ |
Intent Action | ACTION_ |
// 注意:字段的值与名称相同以避免重复问题
static final String PREF_EMAIL = "PREF_EMAIL";
static final String BUNDLE_AGE = "BUNDLE_AGE";
static final String ARGUMENT_USER_ID = "ARGUMENT_USER_ID";
// 与意图相关的项使用完整的包名作为值的前缀
static final String EXTRA_SURNAME = "com.myapp.extras.EXTRA_SURNAME";
static final String ACTION_OPEN_USER = "com.myapp.action.ACTION_OPEN_USER";
当 Activity
或 Fragment
传递数据通过 Intent
或 Bundle
时,不同值的键须遵循上一条所提及到的。
当 Activity
或 Fragment
启动需要传递参数时,那么它需要提供一个 public static
的函数来帮助启动或创建它。
这方面,AS 已帮你写好了相关的 Live Templates,启动相关 Activity
的只需要在其内部输入 starter
即可生成它的启动器,如下所示:
public static void start(Context context, User user) {
Intent starter = new Intent(context, MainActivity.class);
starter.putParcelableExtra(EXTRA_USER, user);
context.startActivity(starter);
}
同理,启动相关 Fragment
在其内部输入 newInstance
即可,如下所示:
public static MainFragment newInstance(User user) {
Bundle args = new Bundle();
args.putParcelable(ARGUMENT_USER, user);
MainFragment fragment = new MainFragment();
fragment.setArguments(args);
return fragment;
}
onSaveInstanceState()
方法不是 Activity 生命周期方法,它是用来在 Activity 被意外销毁时保存 UI 状态的,只能用于保存临时性数据,例如UI控件的属性等,不能跟数据的持久化存储混为一谈。持久化存储应该在 Activity#onPause()/onStop()中实行.Service
#onStartCommand()
/onBind()
方法中执行耗时操作,如果有需求,应改用 IntentService
或采用其他异步机制完成.BroadcastReceiver
#onReceive()
中执行耗时操作,如果有耗时工作,应该创建 IntentService
完成,而不应该在 BroadcastReceiver
内创建子线程去做.Intent
广播敏感信息,信息可能被其他注册了对应BroadcastReceiver
的 App 接收,如果广播仅限于应用内,则可以使用 LocalBroadcastManager
#sendBroadcast()
实现,避免敏感信息外泄和 Intent
拦截的风险.onDestroy()
内执行释放资源的工作,例如一些工作线程的销毁和停止,因为 onDestroy()
执行的时机可能较晚。可根据实际需要,在 Activity#onPause()
/onStop()
中结合 isFinishing()
的判断来执行.BroadCastReceiver
时,registerReceiver()
和 unregisterReceiver()
要成对(生命周期对应)出现.Activity 的生命周期不对应,可能出现多次 onResume
造成 receiver
注册多个,但最终只注销一个,其余 receiver
产生内存泄漏。DialogFragment
,而非 Dialog
/AlertDialog
,这样便于随Activity生命周期管理对话框/弹出浮层的生命周期.View
和父 View
设置同样背景进而造成页面过度绘制,无需显示的布局及时隐藏.layout
刷新:1) 设置固定的 View 大小的宽高,如倒计时组件等;
2) 调用 View 的 layout 方法修改位置,如弹幕组件等;
3) 通过修改 Canvas 位置并且调用 invalidate(int l, int t, int r, int b)等方式限定刷新区域;
4) 通过设置一个是否允许requestLayout的变量,然后重写控件的requestlayout、 onSizeChanged
方法,判断控件的大小没有改变的情况下,当进入 requestLayout 的时候,直接返回而不调用 super
的 requestLayout 方法。
PopupWindow
和 Dialog
,推荐在 Activity#onAttachedToWindow()
/Activity#onWindowFocusChanged()
之后创建对话框.AnimationDrawable
,它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错.ScrollView
包裹ListView
/GridView
/ExpandableListVIew
;这样会把 ListView
的所有 Item
加载到内存中,会消耗巨大的内存和 cpu 资源,推荐使用 NestedScrollView
.Intent
在 Android 基础组件之间传递大数据(binder transaction
缓存为 1MB),可能导致 OOM,***TransactionTooLargeException***等.AsyncTask
,ThreadPoolExecutor
,Rxjava
,kotlin 协程
,或其他形式自定义线程池,不能直接使用Executors
),不允许在应用中自行显式创建线程.ThreadPoolExecutor
设置线程存活时间(setKeepAliveTime
),确保空闲时线程能被释放.SharedPreferences
共享数据,虽然可以(MODE_MULTI_PROCESS),但官方已***不推荐***。android.os.Environment#getExternalStorageDirectory()
android.os.Environment#getExternalStoragePublicDirectory()
android.content.Context#getFilesDir()
android.content.Context#getCacheDir
// 读/写检查
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
// 只读检查
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
SharedPreference
中只能存储简单数据类型(int、boolean、String 等),复杂数据类型建议使用文件、数据库等其他方式存储.SharedPreference
提交数据时,尽量使用 Editor#apply()
,而非 Editor#commit()
。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使用 Editor#commit()
,commit
会直接读写磁盘,频繁操作性能不好.Bitmap
、Drawable
与动画ListView
,ViewPager
,RecyclerView
,GirdView
等组件中使用图片时,应做好图片的缓存,避免始终持有图片导致内存溢出,也避免重复创建图片,引起性能问题。建议 使 用 Fresco
、Glide
等图片库.onPause()
或 #onStop()
回调中,关闭当前正在执行的的动画.RGB_565
代替 RGB_888
,在不怎么降低视觉效果的前提下,减少内存占用.(Glide).Bitmap
(BitmapDrawable
)的使用,尽量使用纯色(ColorDrawable
)、渐变色(GradientDrawable
)、StateSelector
(StateListDrawable
)等与 Shape 结合的形式构建绘图.gif
图片,注意限制每个页面允许同时播放的 gif
图片,以及单个 gif
图片的大小.资源文件命名为全部小写,采用下划线命名法。
anim
/ 和 animator
/)安卓主要包含属性动画和视图动画,其视图动画包括补间动画和逐帧动画。属性动画文件需要放在 res/animator/
目录下,视图动画文件需放在 res/anim/
目录下。
命名规则:{模块名_}逻辑名称
。
说明:{}
中的内容为可选,逻辑名称
可由多个单词加下划线组成。
例如:refresh_progress.xml
、market_cart_add.xml
、market_cart_remove.xml
。
如果是普通的补间动画或者属性动画,可采用:动画类型_方向
的命名方式。
fade_in |
淡入 |
---|---|
fade_out |
淡出 |
slide_in_from_top |
从头部滑动进入 |
res/drawable/
目录下放的是位图文件(.png
、.9.png
、.jpg
、.gif
)或编译为可绘制对象资源子类型的 XML 文件,而 res/mipmap/
目录下放的是不同密度的启动图标,所以 res/mipmap/
只用于存放启动图标,其余图片资源文件都应该放到 res/drawable/
目录下。
命名规则:类型{_模块名}_逻辑名称
、类型{_模块名}_颜色
。
说明:{}
中的内容为可选;类型
可以是可绘制对象资源类型,也可以是控件类型;最后可加后缀 _small
表示小图,_big
表示大图。
btn_main_about.png |
主页关于按键 类型_模块名_逻辑名称 |
---|---|
btn_red.png |
红色按键 类型_颜色 |
divider_white.png |
白色分割线 类型_颜色 |
如果有多种形态,如按钮选择器:sel_btn_xx.xml
,采用如下命名:
sel_btn_xx |
作用在 btn_xx 上的 selector |
---|---|
btn_xx_normal |
默认状态效果 |
btn_xx_pressed |
state_pressed 点击效果 |
layout
/)命名规则:类型_模块名
、类型{_模块名}_逻辑名称
。
说明:{}
中的内容为可选。
activity_main.xml |
主窗体 类型_模块名 |
---|---|
fragment_music.xml |
音乐片段 类型_模块名 |
dialog_loading.xml |
加载对话框 类型_逻辑名称 |
menu
/)菜单相关的资源文件应放在该目录下。
命名规则:{模块名_}逻辑名称
说明:{}
中的内容为可选。
例如:main_drawer.xml
、navigation.xml
。
values/
资源文件下的文件都以 s
结尾,如 attrs.xml
、colors.xml
、dimens.xml
,起作用的不是文件名称,而是
标签下的各种标签,
比如 决定样式,
决定颜色,所以,可以把一个大的 xml
文件分割成多个小的文件,
比如可以有多个 style
文件,如 styles.xml
、styles_home.xml
、styles_item_details.xml
、styles_forms.xml
。
的 name
命名使用下划线命名法,在你的 colors.xml
文件中应该只是映射颜色的名称一个 ARGB 值,而没有其它的。不要使用它为不同的按钮来定义 ARGB 值。
<resources>
<color name="white">#FFFFFFcolor>
<color name="gray_light">#DBDBDBcolor>
<color name="gray">#939393color>
<color name="gray_dark">#5F5F5Fcolor>
<color name="black">#323232color>
<color name="green">#27D34Dcolor>
<color name="blue">#2A91BDcolor>
<color name="orange">#FF9D2Fcolor>
<color name="red">#FF432Fcolor>
resources>
的 name
命名使用下划线命名法,采用以下规则:{模块名_}逻辑名称
,这样方便同一个界面的所有 string
都放到一起,方便查找。
main_menu_about |
主菜单按键文字 |
---|---|
friend_title |
好友模块标题栏 |
login_check_email |
登录验证 |
命名规则:view缩写{_模块名}_逻辑名
,例如: btn_main_search
、btn_back
。
1.新建项目以最新的Android SDK为准,及时适配最新SDK,并遵循最新的Android开发规范
2.及时掌握最新的Android开发组件,使用官方建议的方式开发高质量的app,如Jetpack
组件
Android 包命名规范
Android 开发最佳实践
Android 编码规范
Google Java 编程风格指南
《阿里巴巴Android开发手册》v1.0.1更新,优化部分内容和示例代码