码云地址:Gitee
做开源几年了,被很多人夸过,你的代码写得比较规范,甚至有人质疑自己代码的写法,但是迟迟没有出一个代码规范,说来惭愧,只是因为我早几年写的代码还不够规范,不敢出来误导大家,而代码规范是后续才慢慢养成的,在这个过程中,我不仅参考了大公司出的代码规范文档,也研究了很多关于谷歌源码的编码规范,同时我也在无时不刻在思考,如何能写出让别人更好理解的代码,自打入行以来,我就在一直在这个问题上面探索。
为什么要做成一个开源项目?因为项目会长期更新,大家如果对里面一些规范表示不能理解的或者感觉写得不太对的,又或者有什么想要补充的,随时欢迎你通过 issue 反馈给我,大家的建议很重要,是我做好这件事的关键,我会认真对待和思考提出的每一个建议。同时我也相信一份好的代码规范经得住大众的反复推敲和不断实践,在这里也欢迎你提出自己的想法和建议。
这份代码规范文档开始编写的时间是 2020 年 7 月,中间不断地修改和补充,于 2021 年 1 月发布定稿,同年 2 月,纠正和补充了一些代码规范,从 0 到 1,从不成熟到成熟,整个过程耗时 200 多天。原因是我积累了很多开发的好习惯,但是一直没有任何记录,在短时间将这些东西全部输出几乎不可能,所以我前面花了很多时间回忆和总结,这段时间内我做的最多的一件事是,代码写着写着就去写代码规范文档,直到后面发布初稿时,我又投入了大把的时间和精力来做这件事。大家心中可能都有一个疑问,我为什么不用网上写的,直接照抄照搬,又或者直接采用阿里的,这样不是简单轻松多了?关于这个问题,可以跟大家谈谈我的想法,我看过很多关于 Android 代码规范文档,但是我感觉存在一些问题,可以跟大家分享一下:
说服力低:是很多代码规范文档只告诉你应该这样写,但是基本没有人提及这样写的好处,那样写不好的地方。他们只会告诉你规则,但是从不告诉你前因后果,这样写出来的代码规范难以服众。
不够全面:例如后台接口规范、接口实现规范、异常捕获规范、第三方框架使用规范、代码硬编码规范、资源硬编码规范、多模块规范;这些是我们开发中所避不开的东西,但是现在网上的代码规范文档却很少有人提及。
不够细致:我举个最简单的例子,很多代码规范文档都会说 String ID 以 模块 + 作用 来命名,但是没有人提及过在其他情况下的处理,例如:确定取消
这种字符串,虽然在很多模块中使用到了,但是它却不属于任何模块的,这个时候该怎么命名?这个虽然是小细节,但足以看出文档在细节处理上不够完美和严谨。
2021 年 2 月 23 日:补充代码规范原则、补充参数传递规范
2021 年 2 月 21 日:
补充多模块规范
补充代码硬编码规范
补充资源硬编码规范
补充 Color ID 命名规范
补充写代码规范文档的原因
纠正公开成员变量命名方式
添加谷歌代码样式指南链接
2021 年 2 月 11 日:补充异常捕获规范标准
2021 年 2 月 9 日:补充字符串 equals 使用规范
2021 年 2 月 7 日:补充做成开源项目的原因
2021 年 2 月 6 日:补充注释规范和标准
2021 年 2 月 6 日:纠正后台返回金额的处理方式
2021 年 2 月 5 日:新增文本间距的问题阐述
2021 年 2 月 5 日:修改全局变量为成员变量
2021 年 2 月 5 日:优化描述的语言艺术
2021 年 2 月 5 日:添加码云地址
2021 年 2 月 5 日:第一次提交代码
代码规范是我们每个程序员要做的事,假设我们按照自己的喜好来写代码,那么很可能出现的问题就是我看不懂你的代码或者你看不懂我的代码,这样会给后续维护形成巨大的障碍。这个时候问题来了,如何让代码不分你我,或许只需要一套规则,你和我都认可并且遵守的代码规范守则。
那么你的疑问可能又来了,怎么样才能算好的代码规范,答案只有一个,真正好的代码规范就是别人的代码你一眼就能看懂,更不需要反复去看。之所以这样并不是因为看的人 Review 代码的能力有多强,而是写代码的人愿意遵守规则,他知道自己想这么写,但是知道你会看不懂,所以换了一种方式来写,这种方式就是代码规范。
代码规范:一个好的代码规范可以帮助我们快速了解和熟悉相关的业务,降低后续维护的成本(二次开发或者排查问题)。
代码不规范:代码不规范会导致项目可读性变差,具体表现为难分辨和难理解,在无形之中加大项目后续维护的成本。
经验总结:编码不规范,同行泪两行
在讲之前,我们先想一个问题,代码规范的出现是为了什么?不就为了让我们更好地进行团队协作和项目维护吗?没错的,所以代码规范原则应该围绕这两点进行。
特事特办:代码规范文档只能解决 99.99% 场景下的问题,特殊情况应该要特殊处理,违背者需要给出合理的解释,建议在代码中直接用注释注明,这样可以减少沟通成本,否则在一般情况下应当要遵守代码规范文档上的约束。
以人为本:我们应该衡量不同写法带来的优点和缺点,然后根据当前项目的实际需求做出合适的选择或者变化。规则是人定的,不是一成不变的。在制定新的规则或者修改旧的规则之前应当先参考和分析谷歌或者知名公司的做法,然后与团队中的各个成员沟通和协商好。
实事求事:任何代码规范都应该追求在实际开发中发挥的作用或者效果,规则始终是规则,不能单纯为了制定规则而编写代码规范,而是更应该追求写法的实用性,实用性应该从代码理解的难易程度、代码可维护性、代码可复用性、代码可扩展性等方面因素综合考虑,其次是考虑代码的视觉美观性。
使用 0px 代替 0dp,这样就可以在获取时避免系统进行换算,提升代码的执行效率。
字符串比较,应该用 "xxx".equals(object)
,而不应该用 object.equals("xxx")
,因为 object 对象可能为空,我们应该把不为空的条件放置在表达式的前面。
尽量采用 switch case 来判断,如果不能实现则再考虑用 if else,因为在多条件下使用 switch case 语句判断会更加简洁。
不推荐用 layout_marginLeft,而应该用 layout_marginStart;不推荐用 layout_marginRight,而应该用 layout_marginEnd,原因有两个,一个是适配 Android 4.4 反方向特性(可在开发者选项中开启),第二个是 XML 布局中使用 layout_marginLeft 和 layout_marginRight 会有代码警告,padding 属性也是同理,这里不再赘述。
如果在 layout_marginStart 和 layout_marginEnd 的值相同的情况下,请替换使用 layout_marginHorizontal,而 layout_marginTop 和 layout_marginBottom 也同理,请替换使用 layout_marginVertical,能用一句代码能做的事不要写两句,padding 属性也是同理,这里不再赘述。
过期 和 高版本 的 API 一定要做对应的兼容(包含 Java 代码和 XML 属性),如果不需要兼容处理的,需要对警告进行抑制。
在能满足需求的情况下,尽量用 invisible 来代替 gone,因为 gone 会触发当前整个 View 树进行重新测量和绘制。
api 和 implementation,在能满足使用的情况下,优先选用 implementation,因为这样可以减少一些编译时间。
ListView 和 RecyclerView 都能实现需求的前提下,优先选用 RecyclerView,具体原因不解释,大家应该都懂。
ScrollView 和 NestedScrollView 都能实现需求的前提下,优先选用 NestedScrollView,是因为 NestedScrollView 和 RecyclerView 支持相互嵌套,而 ScrollView 是不支持嵌套滚动的。
不能在项目中创建副本文件,例如创建 HomeActivity2.java
、home_activity_v2.xml
类似的副本文件,因为这样不仅会增加项目的维护难度,同时对编译速度也会造成一定的影响,正确的做法应该是在原有的文件基础上面修改,如果出现需求变更的情况,请直接使用 Git 或者 SVN 进行版本回退。
如果一个类不需要被继承,请直接用 final 进行修饰,如果一个字段在类初始化过程中已经赋值并且没有地方进行二次赋值,也应当用 final 修饰,如果一个字段不需要被外部访问,那么需要用 private 进行修饰。
每个小组成员应当安装阿里巴巴代码约束插件,并及时地对插件所提示的代码警告进行处理或者抑制警告。
应用图标应该放在 mipmap 目录下,其他图片资源应当放到 drawable 目录下,具体原因可以看谷歌官方文档对这两个文件夹给出的介绍:
目录 | 资源类型 |
---|---|
drawable |
位图文件(.png 、.9.png 、.jpg 、.gif )或编译为以下可绘制对象资源子类型的 XML 文件:位图文件九宫格(可调整大小的位图)状态列表形状动画可绘制对象其他可绘制对象请参阅 Drawable 资源。 |
mipmap |
适用于不同启动器图标密度的可绘制对象文件。如需了解有关使用 mipmap 文件夹管理启动器图标的详细信息,请参阅管理项目概览。 |
后台返回的 id 值,不要使用 int 或者 long 类型来接收,而应该用 string 类型来接收,因为我们不需要对这个 id 值进行运算,所以我们不需要关心它是什么类型的。
后台返回的金额数值应该使用 String 来接收,而不能用浮点数来接收,因为 float 或者 double 在数值比较大的时候会容易丢失精度,并且还需要自己手动转换出想要保留的小数位,最好的方式是后台返回什么前端就展示什么,而到了运算的时候,则应该用 BigDecimal 类来进行转换和计算,当然金额在前端一般展示居多,运算的情况还算是比较少的。
我们在定义后台返回的 Bean 类时,不应当将一些我们没有使用到的字段添加到代码中,因为这样会消耗性能,因为 Gson 是通过反射将后台字段赋值到 Java 字段中,所以我们应当避免一些不必要的字段解析,另外臃余的字段也会给我们排查问题造成一定的阻碍。
如果后台给定的字段名不符合代码命名的时候,例如当遇到 student_name
这种命名时,我们应当使用 Gson 框架中的 @SerializedName 注解进行重命名。
请求的接口参数和返回字段必须要写上注释,除此之外还应该备注对应的后台接口文档地址,以便我们后续能够更好地进行维护和迭代。
后台返回的 Bean 类字段不能直接访问,而应该通过生成 Get 方法,然后使用这个 Get 方法来访问字段。
接口请求成功的提示可以不显示,但请求失败的提示需要显示给到用户,否则会加大排查问题的难度,也极有可能会把问题掩盖掉,从而导致问题遗留到线上去。
严禁使用中文或者中文拼音进行重命名
使用驼峰式命名风格(单词最好控制在三个以内)
局部变量或者公开的成员变量应该以作用来命名,例如:
public String name;
public TextView nameView;
public FrameLayout nameLayout;
// 命名规范附带技巧(当布局中同个类型的控件只有一个的时候,也可以这样命名)
public TextView textView;
public RecyclerView recyclerView;
private String mName;
private TextView mNameView;
private FrameLayout mNameLayout;
// 命名规范附带技巧(当布局中同个类型的控件只有一个的时候,也可以这样命名)
private TextView mTextView;
private RecyclerView mRecyclerView;
// 不规范写法示例
private boolean mIsDebug;
boolean isDebug;
// 规范写法示例
private boolean mDebug;
boolean debug;
static Handler sHandler;
static final String REQUEST_INSTALL_PACKAGES;
有细心的同学可能会发现一个问题,为什么我们最常用的 Glide 和 OkHttp 的源码中,非公开的成员变量为什么没有用小 m
开头?但是谷歌的 SDK 源码和 Support 库就有呢?那究竟是用还是不用呢?这个问题其实很好回答,我们可以先从体量上分析,首先谷歌的开发人员和项目数量肯定是最多的,那么谷歌在这块的探索和研究肯定是多于 Glie 和 OkHttp 的,其次是 Glide 和 OkHttp 的源码都有一个特点,很多类都维持在 1k 行代码左右,而谷歌源码很多类都接近 10k 行代码,例如 Activity 的源码在 API 30 上面有 8.8k 行代码,所以谷歌在这块略胜一筹,如果非要二选一,我选择谷歌的代码风格,并不是说 Glide 和 OkHttp 命名风格不好,是因为或许在未来的某一天,可能会有新的图片请求框架和网络请求框架来代替 Glide 和 OkHttp,但是基本不可能会出现有代替 Android SDK 或者 Support 库的一天。
最后让我们静静地欣赏一下 Activity 类中成员变量的命名:
public class Activity {
public static final int RESULT_CANCELED = 0;
public static final int RESULT_OK = -1;
private Instrumentation mInstrumentation;
private IBinder mToken;
private IBinder mAssistToken;
private Application mApplication;
/*package*/ Intent mIntent;
/*package*/ String mReferrer;
private ComponentName mComponent;
/*package*/ ActivityInfo mActivityInfo;
/*package*/ ActivityThread mMainThread;
Activity mParent;
boolean mCalled;
/*package*/ boolean mResumed;
/*package*/ boolean mStopped;
boolean mFinished;
boolean mStartedActivity;
private boolean mDestroyed;
private boolean mDoReportFullyDrawn = true;
private boolean mRestoredFromBundle;
private final ArrayList<Application.ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
new ArrayList<Application.ActivityLifecycleCallbacks>();
private Window mWindow;
private WindowManager mWindowManager;
private CharSequence mTitle;
private int mTitleColor = 0;
final Handler mHandler = new Handler();
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
}
不允许包名中携带英文大写
包名应该以简洁的方式命名
包名要按照模块或者作用来划分
请不要在某一包名下放置一些无关的类
initXX:初始化相关方法,使用 init 为前缀标识,如初始化布局 initView
isXX:方法返回值为 boolean 型的请使用 is 或 check 为前缀标识
getXX:返回某个值的方法,使用 get 为前缀标识,例如 getName
setXX:设置某个属性值,使用 set 为前缀标识,例如 setName
handleXX/processXX:对数据进行处理的方法,例如 handleMessage
displayXX/showXX:弹出提示框和提示信息,例如 showDialog
updateXX:更新某个东西,例如 updateData
saveXX:保存某个东西,例如 saveData
resetXX:重置某个东西,例如 resetData
clearXX:清除某个东西,例如 clearData
removeXX:移除数据或者视图等,例如 removeView
drawXX:绘制数据或效果相关的,使用 draw 前缀标识,例如 drawText
HomeActivity.java
SettingFragment.java
HomeAdapter.java
AddressDialog.java
CrashHandler.java
GridSpaceDecoration.java
PickerLayoutManager.java
public class View {
private View.OnClickListener mListener;
public void setOnClickListener(OnClickListener listener) {
mListener = listener;
}
public interface OnClickListener {
void onClick(View v);
}
}
public class Handler {
public interface Callback {
boolean handleMessage(Message msg);
}
}
public final class PasswordEditText extends EditText implements View.OnTouchListener, View.OnFocusChangeListener, TextWatcher {
public PasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnTouchListener(this);
setOnFocusChangeListener(this);
addTextChangedListener(this);
}
@Override
public void onFocusChange(View view, boolean hasFocus) {
......
}
@Override
public boolean onTouch(View view, MotionEvent event) {
......
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
......
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
......
}
@Override
public void afterTextChanged(Editable s) {
......
}
}
public final class PasswordEditText extends EditText
implements View.OnTouchListener,
View.OnFocusChangeListener, TextWatcher {
public PasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnTouchListener(this);
setOnFocusChangeListener(this);
addTextChangedListener(this);
}
/**
* {@link OnFocusChangeListener}
*/
@Override
public void onFocusChange(View view, boolean hasFocus) {
......
}
/**
* {@link OnTouchListener}
*/
@Override
public boolean onTouch(View view, MotionEvent event) {
......
}
/**
* {@link TextWatcher}
*/
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
......
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
......
}
@Override
public void afterTextChanged(Editable s) {
......
}
}
try {
Xxx.xxx();
} catch (Exception e) {}
// 捕获这个异常,避免程序崩溃
try {
// 目前发现在 Android 7.1 主线程被阻塞之后弹吐司会导致崩溃,可使用 Thread.sleep(5000) 进行复现
// 查看源码得知 Google 已经在 Android 8.0 已经修复了此问题
// 主线程阻塞之后 Toast 也会被阻塞,Toast 因为显示超时导致 Window Token 失效
mHandler.handleMessage(msg);
} catch (WindowManager.BadTokenException | IllegalStateException e) {
// android.view.WindowManager$BadTokenException:Unable to add window -- token android.os.BinderProxy is not valid; is your activity running?
// java.lang.IllegalStateException:View android.widget.TextView has already been added to the window manager.
e.printStackTrace();
}
如果这个异常不是通过方法 throws 关键字抛出,则需要在 try 块中说明崩溃的缘由,并注明抛出的异常信息。
还有一个问题,有异常就一定要 try catch
?,这种想法其实是错的,例如我们项目用 Glide 加载图片会抛出以下异常:
Caused by: java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
at com.bumptech.glide.manager.RequestManagerRetriever.assertNotDestroyed(RequestManagerRetriever.java:348)
at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:148)
at com.bumptech.glide.Glide.with(Glide.java:826)
try {
// Activity 销毁后执行加载图片会触发 crash
Glide.with(this)
.load(url)
.into(mImageView);
} catch (IllegalArgumentException e) {
// java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
e.printStackTrace();
}
if (isFinishing() || isDestroyed()) {
// Glide:You cannot start a load for a destroyed activity
return;
}
Glide.with(this)
.load(url)
.into(mImageView);
public class IntentKey {
/** id */
public static final String ID = "id";
/** token */
public static final String TOKEN = "token";
/** 订单 */
public static final String ORDER = "order";
/** 余额 */
public static final String BALANCE = "balance";
/** 时间 */
public static final String TIME = "time";
/** URL */
public static final String URL = "url";
/** 路径 */
public static final String PATH = "path";
/** 其他 */
public static final String OTHER = "other";
.....
}
public final class WebActivity extends Activity {
public static void start(Context context, String url) {
Intent intent = new Intent(context, WebActivity.class);
intent.putExtra(IntentKey.URL, url);
context.startActivity(intent);
}
}
public final class WebActivity extends Activity {
public static Intent newIntent(Context context, String url) {
Intent intent = new Intent(context, WebActivity.class);
intent.putExtra(IntentKey.URL, url);
return intent;
}
}
public final class WebFragment extends Fragment {
public static WebFragment newInstance(String url) {
WebFragment fragment = new WebFragment();
Bundle bundle = new Bundle();
bundle.putString(IntentKey.URL, url);
fragment.setArguments(bundle);
return fragment;
}
}
如果跳转的 Activity 或者创建的 Fragment 不需要传任何参数,可以不需要定义这些静态方法。
另外如果一个界面需要传递的参数过多(5 个以上),建议用一个对象对这些参数进行封装,然后实现 Serializable 或者 Parcelable 接口进行传递,具体写法示例:
public final class VideoPlayActivity extends Activity {
/**
* 播放参数构建
*/
public static final class Builder implements Parcelable {
/** 视频源 */
private String mVideoSource;
/** 视频标题 */
private String mVideoTitle;
/** 播放进度 */
private int mPlayProgress;
/** 手势开关 */
private boolean mGestureEnabled = true;
/** 循环播放 */
private boolean mLoopPlay = false;
/** 自动播放 */
private boolean mAutoPlay = true;
/** 播放完关闭 */
private boolean mAutoOver = true;
public Builder() {}
public Builder setVideoSource(File file) {
mVideoSource = file.getPath();
if (mVideoTitle == null) {
mVideoTitle = file.getName();
}
return this;
}
public Builder setVideoSource(String url) {
mVideoSource = url;
return this;
}
private String getVideoSource() {
return mVideoSource;
}
public Builder setVideoTitle(String title) {
mVideoTitle = title;
return this;
}
private String getVideoTitle() {
return mVideoTitle;
}
public Builder setPlayProgress(int progress) {
mPlayProgress = progress;
return this;
}
private int getPlayProgress() {
return mPlayProgress;
}
public Builder setGestureEnabled(boolean enabled) {
mGestureEnabled = enabled;
return this;
}
private boolean isGestureEnabled() {
return mGestureEnabled;
}
public Builder setLoopPlay(boolean enabled) {
mLoopPlay = enabled;
return this;
}
private boolean isLoopPlay() {
return mLoopPlay;
}
public Builder setAutoPlay(boolean enabled) {
mAutoPlay = enabled;
return this;
}
public boolean isAutoPlay() {
return mAutoPlay;
}
public Builder setAutoOver(boolean enabled) {
mAutoOver = enabled;
return this;
}
private boolean isAutoOver() {
return mAutoOver;
}
public void start(Context context) {
Intent intent = new Intent(context, VideoPlayActivity.class);
intent.putExtra(IntentKey.VIDEO, this);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
}
}
}
new VideoPlayActivity.Builder()
.setVideoTitle("速度与激情特别行动")
.setVideoSource("http://xxxxx.mp4")
.start(getAttachActivity());
// 权限请求框架:https://github.com/getActivity/XXPermissions
implementation 'com.hjq:xxpermissions:9.8'
尽量不要选择功能两套相同的框架,应当引用最合适的一套框架进行开发。
使用第三方库必须要依赖指定的版本号,而不能使用 + 号来指定依赖库最新的版本号。
使用第三方开源库出现问题或者 Bug 时应及时通知到开源库的作者,如果没有及时回复就根据实际情况对问题进行修复。
尽量避免 Copy 第三方库的技术代码到项目中,特别是在放置到项目业务模块中,因为这样会增加项目的复杂度,从而降低可维护性。
如果出现问题不能找到开源库的作者,如果需要修改,应当将这些代码抽取到单独的 Module 中。
能用框架就用成熟框架,尽量不要自己编写或者修改框架,如果有需要,要对这块进行严格测试。
app
base
widget
umeng
course
socket
live
shop
proguardFiles
语句,而是应该使用 consumerProguardFiles
语句,因为 consumerProguardFiles
语句会将混淆规则和资源代码一同合并到 aar 包中,这样做的好处在于:在项目编译时会将 aar 包中的混淆规则合并到主模块中。android {
defaultConfig {
// 模块混淆配置
consumerProguardFiles 'proguard-xxx.pro'
}
}
android {
// 资源前缀限制
resourcePrefix "xxx_"
}
config.gradle
文件中:ext {
android = [compileSdkVersion : 28,
minSdkVersion : 19,
targetSdkVersion : 28,
versionCode : 40102,
versionName : "4.1.2",
]
dependencies = [
"appcompat" : "androidx.appcompat:appcompat:1.2.0",
"material" : "com.google.android.material:material:1.2.0",
]
}
apply from : '../config.gradle'
android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
defaultConfig {
minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
}
}
dependencies {
implementation rootProject.ext.dependencies["appcompat"]
implementation rootProject.ext.dependencies["material"]
}
config.gradle
修改成这样:android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
versionName '4.1.2'
versionCode 40102
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
}
apply from : '../config.gradle'
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/XXPermissions
* time : 2018/06/15
* desc : 权限请求实体类
* doc : https://developer.android.google.cn/reference/android/Manifest.permission?hl=zh_cn
* https://developer.android.google.cn/guide/topics/permissions/overview?hl=zh-cn#normal-dangerous
*/
public final class Permission {
....
}
/**
* 设置请求的对象
*
* @param activity 当前 Activity,可以传入栈顶的 Activity
*/
public static XXPermissions with(FragmentActivity activity) {
return ....;
}
/** 请求的权限组 */
private static final String REQUEST_PERMISSIONS = "request_permissions";
/** 权限回调对象 */
private OnPermissionCallback mCallBack;
// 设置保留实例,不会因为屏幕方向或配置变化而重新创建
fragment.setRetainInstance(true);
// 不规范写法示例
if (view.getVisibility() != 0) {
return;
}
Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
startActivity(intent);
// 规范写法示例
if (view.getVisibility() != View.VISIBLE) {
return;
}
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
startActivity(intent);
public final class UserInfoManager {
/** 学生 */
public static final int TYPE_STUDENT = 0;
/** 老师 */
public static final int TYPE_TEACHER = 1;
/** 家长 */
public static final int TYPE_PATRIARCH = 2;
}
ValueAnimator animator = ValueAnimator.ofInt(0, 100);
animator.setDuration(500);
animator.start();
所以衡量一个数值或者字符串是否进行常量化的标准有两点:
这个数值或者字符串是否会被多次使用
这个数值或者字符串是否具有一定的含义
home_activity.xml
setting_fragment.xml
menu_item.xml
address_dialog.xml
这样写的好处在于,由于 res 文件夹下是没有层级概念的
通过前缀的命名可以帮助我们更好定位到同一模块下的资源
例如分享对话框中,有对话框 Root 布局和 Item 布局
share_dialog.xml(Root 布局)
share_item.xml(Item 布局)
share_link_ic.png(复制链接)
share_moment_ic.png(分享到朋友圈)
share_qq_ic.png(分享到 QQ 好友)
share_qzone_ic.png(分享到 QQ 空间)
share_wechat_ic.png(分享到微信好友)
button_rect_selector.xml(通用直角按钮样式)
button_round_selector.xml(通用圆角按钮样式)
名称 | 类型 |
---|---|
ic | 图标 |
bg | 背景 |
selector | 选择器 |
<!-- 主界面 -->
<string name="home_nav_index">首页</string>
<string name="home_nav_found">发现</string>
<string name="home_nav_message">消息</string>
<string name="home_nav_me">我的</string>
<string name="home_exit_hint">再按一次退出</string>
<!-- 登录界面 -->
<string name="login_register">注册</string>
<string name="login_phone_hint">请输入手机号</string>
<string name="login_password_hint">请输入密码</string>
<string name="login_forget">忘记密码?</string>
<string name="login_text">登录</string>
<string name="login_other">其他登录方式</string>
<!-- 注册界面 -->
<string name="register_title">注册</string>
<string name="register_hint">手机号仅用于登录和保护账号安全</string>
<string name="register_login">登录</string>
<string name="register_password_hint1">设置密码</string>
<string name="register_password_hint2">再次输入密码</string>
<string name="register_password_input_error">两次密码输入不一致,请重新输入</string>
<!-- 设置界面 -->
<string name="setting_title">设置</string>
<string name="setting_language_switchover">语言切换</string>
<string name="setting_language_simple">简体中文</string>
<string name="setting_language_complex">繁体中文</string>
<string name="common_loading">加载中…</string>
<string name="common_confirm">确定</string>
<string name="common_cancel">取消</string>
<string name="common_year">年</string>
<string name="common_month">月</string>
<string name="common_day">日</string>
<string name="common_hour">时</string>
<string name="common_minute">分</string>
<string name="common_second">秒</string>
<color name="logcat_level_verbose_color">#FFBBBBBB</color>
<color name="logcat_level_debug_color">#FF33B5E5</color>
<color name="logcat_level_info_color">#FF99CC00</color>
<color name="logcat_level_warn_color">#FFFFBB33</color>
<color name="logcat_level_error_color">#FFFF4444</color>
<color name="logcat_level_other_color">#FFFFFFFF</color>
<!-- App 样式中引用的颜色 -->
<color name="common_primary_color">@color/white</color>
<color name="common_primary_dark_color">@color/black</color>
<color name="common_accent_color">#5A8DDF</color>
<color name="common_window_background_color">#F4F4F4</color>
<color name="common_text_color">#333333</color>
<color name="common_text_hint_color">@color/panda</color>
<!-- 按钮按压时的颜色 -->
<color name="common_button_pressed_color">#AA5A8DDF</color>
<!-- 按钮禁用时的颜色 -->
<color name="common_button_disable_color">#BBBBBB</color>
<!-- 分割线的颜色 -->
<color name="common_line_color">#ECECEC</color>
<!-- 透明色 -->
<color name="transparent">#00000000</color>
<!-- 白色 -->
<color name="white">#FFFFFFFF</color>
<!-- 黑色 -->
<color name="black">#FF000000</color>
<!-- 灰色 -->
<color name="gray">#FF808080</color>
<!-- 红色 -->
<color name="red">#FFFF0000</color>
<!-- 金色 -->
<color name="gold">#FFFFD700</color>
<!-- 黄色 -->
<color name="yellow">#FFFFFF00</color>
<!-- 绿色 -->
<color name="green">#FF008000</color>
<!-- 蓝色 -->
<color name="blue">#FF0000FF</color>
<!-- 紫色 -->
<color name="purple">#FF800080</color>
<!-- 粉色 -->
<color name="pink">#FFFFC0CB</color>
<!-- 橙色 -->
<color name="orange">#FFFFA500</color>
<name="color_FF35BF30">#FF35BF30</color>
color_FF35BF30
色值,其中有 150 地方是你自己引用的,另外 50 个地方是别人引用的,但是别人不知道你那个色值是干什么的,看到你有写就直接引用了,突然有一天产品经理心情不好要改这个色值,那么你要从 200 地方区分 150 个需要修改的地方和 50 个不需要修改的地方。login_left_balloon_view.xml
login_right_balloon_view.xml
left_in_activity.xml
left_out_activity.xml
bottom_in_dialog.xml
bottom_out_dialog.xml
@+id/R.id.rg_login_type
@+id/R.id.et_login_phone
@+id/R.id.et_login_sms
@+id/R.id.et_login_password
@+id/R.id.btn_login_commit
名称 | 缩写 |
---|---|
TextView | tv |
EditText | et |
Button | btn |
ImageView | iv |
ImageButton | ib |
ListView | lv |
RecyclerView | rv |
RadioButton | rb |
RadioGroup | rg |
ProgressBar | pb |
CheckBox | cb |
TableLayout | tl |
ScrollView | sv |
LinearLayout | ll |
RelativeLayout | rl |
FrameLayout | fl |
<!-- 应用主题样式 -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
.....
</style>
<!-- 全屏主题样式 -->
<style name="FullScreenTheme" parent="AppTheme">
.....
</style>
<!-- 闪屏页主题样式 -->
<style name="SplashTheme" parent="FullScreenTheme">
.....
</style>
<!-- 默认圆角按钮样式 -->
<style name="ButtonStyle" parent="Widget.AppCompat.Button.Borderless">
.....
</style>
<!-- 不带圆角按钮样式 -->
<style name="RectButtonStyle" parent="ButtonStyle">
.....
</style>
<!-- 默认文本框样式 -->
<style name="EditTextStyle">
.....
</style>
<!-- 验证码按钮样式 -->
<style name="CountdownViewStyle">
.....
</style>
<!-- 不规范写法示例 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18dp" />
<!-- 规范写法示例 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp" />
wrap_content
和 padding
的方式来调整 View 的宽高,因为在不同手机上面字体大小不一致,在字体显示比较小的手机上面会显示正常,但是在字体显示比较大的平板上面文字上半部分极有可能会出现被裁剪的情况,所以我们不能把宽高定死,而是通过 padding
来调整到控件的大小。不过需要注意的是,TextView 有自带的文字间距,我们在拿设计图给定的 padding
值时,需要拿设计图给定的值适当减去这一部分值(一般大概是在 2~3dp)。<!-- 不规范写法示例 -->
<Button
android:layout_width="180dp"
android:layout_height="60dp" />
<!-- 规范写法示例 -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="80dp"
android:paddingTop="20dp"
android:paddingEnd="80dp"
android:paddingBottom="20dp" />
match_parent
时,另外一项不能写死大小,而是应该使用 wrap_content
,否则很可能会因为比例不对导致图片变形,另外还需要使用 android:adjustViewBounds="true"
属性,否则 ImageView
无法根据图片的宽高来调整自己的宽高。<!-- 不规范写法示例 -->
<ImageView
android:layout_width="match_parent"
android:layout_height="300dp"
android:src="@drawable/example_bg" />
<!-- 规范写法示例 -->
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="@drawable/example_bg" />
/>
节点结尾,如果有则以 </xxx.xxx.xxx>
节点结尾<!-- 不规范写法示例 -->
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
<!-- 不规范写法示例 -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent">
</TextView>
<!-- 规范写法示例 -->
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- 规范写法示例 -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent" />
tools:context
属性,以便在布局文件中快速定位到对应的类<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.activity.HomeActivity">
</FrameLayout>
tools:context=".ui.activity.HomeActivity"
tools:context=".ui.fragment.SettingFragment"
tools:context=".ui.adapter.HomeAdapter"
tools:context=".ui.dialog.PersonDataDialog"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_pay_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never"
tools:listitem="@layout/item_dialog_pay_password"
tools:itemCount="9"
tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
tools:spanCount="3" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="学生姓名" />
<ImageView
android:id="@+id/iv_home_course_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:src="@drawable/bg_home_placeholder" />
tools:text
预览属性,而不应该使用 android:text
,其他布局属性也同理。String 硬编码规范:如果项目已经适配了多语种,则严禁写死在 Java 代码或者布局文件中,如果没有这块需求的话,也建议将 String 资源定义在 string.xml
文件,此项不强制要求,大家根据实际情况而定。
Color 硬编码规范:在没有使用夜间模式的情况下,允许大部分 Color 值直接定义在布局文件中,但是如果某个色值引用得比较多(例如主题强调色、默认背景色等),需要抽取到 color.xml
文件中。
Dimens 硬编码规范:允许写死在 Java 代码或者布局文件中,但是如果使用了通配符方案对屏幕进行适配,那么则不能直接写死。
Style 样式规范:对于一些常用并且样式比较统一的控件,例如 Button、EditText 等,我们对这些控件的样式进行抽取到 style.xml
文件中来,避免属性重复定义。
安卓技术中台:AndroidProject
网络框架:EasyHttp
吐司框架:ToastUtils
权限框架:XXPermissions
标题栏框架:TitleBar
悬浮窗框架:XToast
国际化框架:MultiLanguages
Gson 解析容错:GsonFactory
日志查看框架:Logcat
Copyright 2021 Huang JinQun
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。