海尔兄弟星球分享之规范篇

关于分支管理

git flow 图解

主分支

  • master 稳定分支,用于生产环境,与线上版本同步

  • dev 开发分支,用于测试环境,用于构建测试版本,来源于 master

流程分支

  • feature/xxx 功能分支,用于功能开发,来源于 dev,开发完成并自测通过后合并到 dev

  • hotfix/xxx 修复分支,线上功能修复,来源于 master,修复后双向mergedevmaster

  • release/xxx 发布分支,版本发布使用,来源于 dev 测试后的稳定代码,不应再发功能开发,发布后,要双向 mergedevmaster

命名规范

所有命名都应该以业务为优先,并且避免通用命名的方法。另外命名不宜过长,但要表明功能。

  • 关于关键字冲突
1
2
3
thiz - this
clazz - class
interfaze - interface
  • 方法名使用小驼峰命名,首词应为动词
1
2
public void doAnyThing() {}
public String getMyName() {}
  • 类名使用大驼峰命名
1
class MainActivity {}
  • 数据类属性使用小驼峰命名
1
2
3
class DataObj {
public String name;
}
  • 业务类非静态属性使用 m + 大驼峰 命名
1
2
3
class HomePresenter {
private boolean mIsTesting;
}
  • 业务类静态属性使用 s + 大驼峰 命名
1
2
3
class HomePresenter {
private static String sPoolStr;
}
  • 数据常量全部大些,下划线分隔
1
public static final int TYPE_CUTOFF = 1;
  • 控件命名规范
1
2
private TextView mNameTv;
private ImageView mAvatorIv;
  • layout 资源命名

使用 bussiness_xxx.xml 的命名格式,做业务区分

1
2
homepage_fragment_layout.xml
homepage_fragment_banner_item.xml
  • id 命名

使用 R.id.function_view 的命名格式,这个规则与前面控件命名规范保持一致。

1
2
R.id.content_tv
R.id.cover_iv
  • 禁止通用命名

禁止使用类似如下的通用字符串作为变量、方法、常量、资源、id 的名字

1
2
3
4
5
6
list
listener
view
a,b,c
x,y,z
...
  • 分包

本着业务优先的原则,分包方案应以业务模块为划分规则,而不应该以组件类型划分,如 activity/fragment/adapter 这种通用的包划分在项目业务膨胀后将会变得非常难管理。

按照业务划分,对以后的业务拆分和组件化也非常重要。

编码规范

  • 属性注释
1
public String name; // 姓名
  • 方法注释
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 公开方法注释
*
* @param name 姓名
*/
public void makeFullName(String name) {
}

// 私有方法注释
private void doSomeThingInternal() {

}
  • 常量提取,并提升到类的顶部
1
2
3
4
public class MainViewModel {
public static final int TYPE_TEST = 0;
public static final String KEY_NAME = "KEY_NAME";
}
  • 启动 Activity 和创建 Fragment 使用静工厂封装/不用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MainActivity extends MvvmActivity {
public static final String KEY_NAME = "KEY_NAME";
public static void startActivity(Context context, String name) {
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra(KEY_NAME, name);
context.startActivity(intent);
}
public static void startActivityForResult(Activity context, int reqCode) {
Intent intent = new Intent(context, MainActivity.class);
context.startActivityForResult(intent, reqCode);
}
}

// 创建 Fragment
public class TestFragment extends Fragment {
public static final String KEY_NAME = "KEY_NAME";
public static TestFragment newInstance(String name) {
Bundle args = new Bundle();
args.putString(KEY_NAME, name);
TestFragment fragment = new TestFragment();
fragment.setArguments(args);
return fragment;
}
}
  • xml 标签在行末闭合
1
2
3
4
5
6
7
8
9
10
// good
<ImageView
android:layout_width="43dp"
android:layout_height="43dp" />

// not good
<ImageView
android:layout_width="43dp"
android:layout_height="43dp"
/>
  • xml 中使用 left/right 的地方,使用 start/end 消除警告
1
2
3
4
5
6
7
8
<ImageView
android:layout_gravity="end"
android:layout_marginEnd="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginStart="10dp"
android:layout_width="43dp"
android:layout_height="43dp"/>
  • 拒绝硬编码 UI

主要体现在如果是一个列表结构,那么不应该在 xml 挨个罗列,应该在代码中使用循环的形式,动态 inflateaddView,这样会具有更好的扩展性。

1
2
3
4
5
6
7
LinearLayout container = helper.getView(R.id.container);
container.removeAllViews();
List<HomepageModel.CategoryItem> topCategories = item.topCategories;
for (HomepageModel.CategoryItem topCategory : topCategories) {
View inflate = LayoutInflater.from(mContext).inflate(R.layout.homepage_category_item, null, false);
container.addView(inflate);
}
  • 使用 tools 命名空间,不要在 xml 中编码非真实数据
1
2
3
4
<TextView
tools:text="兔小贝儿歌"/>
<ImageView
tools:src="@drawable/icon_test"/>
  • 尽量使用 ConstraintLayout,尽量不要使用 weight

使用 ConstraintLayout 减少布局层级,使用 weight 会导致 measure 多次,应尽量不要使用 weight

  • 代码格式化

提交代码之前应该先对代码进行格式化,包括 java 代码和 xml 代码。

使用相同的格式化规则能尽量减少代码的冲突。

  • 每行代码不要过长

代码语句不应超过屏幕可观察范围太多,语句太长阅读起来麻烦,而且增加了理解的难度,应该使用中间变量做语句拆分。

  • 内部类必须使用静态关键字声明

禁止使用非静态内部类,尤其是在 Activity 使用 Hander/AsyncTask 等,静态内部类与宿主使用虚引用交互。

1
2
3
4
5
6
public class MainActivity {

static class MyAdapter extends BaseAdapter {

}
}
  • 生成 Bean

尽量不要使用 GsonFormat,如果用了最后一定要检查生成的结果,不应该出现 类型为 Object 的属性。

使用范型组织 Bean 避免重复声明没有继承关系的 Bean

  • 属性声明

不要在类文件中随意位置声明属性,所有属性声明应该提升到文件顶部,在静态常量声明之下。

属性应该尽量避免在声明时初始化,初始化代码放在初始化方法中。

  • 避免使用魔法数字和字符串
1
2
3
4
5
6
7
// good
Map<String, Object> map = new HashMap<>();
map.put(KEY_OPEN_ID,openId);

// not good
Map<String, Object> map = new HashMap<>();
map.put("openId",openId);
  • Handler 的使用

使用 Handler 必须使用静态内部类,并且在页面销毁时调用

1
handler.removeCallbacksAndMessages(null);

这对避免内存泄露至关重要

  • 传递 Context

在任何传递 Context 都要非常慎重,尤其是单例等伪静态实现的情况。

如果不保证持有 Context 的对象可以被及时销毁,则应该使用 WeakRef 来持有上下文的引用。

  • switch

switch 语法必须添加 default 分支。

所有的分支必须以 returnbreak 语句结束。

  • if

if 分支遵循 fast stop 的原则,需要返回的地方优先结束流程。

只有一行的 if 语句,也需要使用 {}

1
2
3
4
5
6
7
8
9
10
public void test(int num) {
// fast stop
if (num < 0) {
return;
}
// 只有一行也需要使用 {} 分离代码结构
if (num > 100) {
show('> 100');
}
}
  • 及时回收资源

这部分不再赘述,在编写一个页面的代码时,要在销毁页面的时候检查所有占用资源的对象手动回收资源。

关于内存的优化参考这篇文章

  • todo

测试代码使用 todo 标记,发版本时会检查禁止 todo 代码的出现。

  • toast

toast 统一使用 HToast 封装后的,不要直接使用 ToastUtils 不方便切换。

海尔星球项目相关

  • DTO

我们会对数据传输的类进行封装,这种类成为 DTODTO 类根据使用场景不同继承 BaseXXXDTO,不要声明多余的 DTO 类,后期不好维护,如果需要继承实现应该放在 dtos 文件夹下。

1
2
3
Flowable<BaseDTO<VideoEntity>> testBaseDTO();
Flowable<BaseListDTO<VideoEntity>> testBaseListDTO();
Flowable<BasePagedDTO<VideoEntity>> testBasePagedDTO();
  • 通信

通信建议依赖 EventBus 实现,所有事件继承 BaseEvent,事件要合理分类,不要声明过多的 Event,所有的Event` 类应该在各自业务逻辑包下面。

1
2
3
4
5
6
public class LikeTagChooseEvent extends BaseEvent {
public static final String CHOOSE_TAG = "CHOOSE_TAG";
public LikeTagChooseEvent(String msg) {
super(msg);
}
}
  • 数据类

数据类放在指定业务文件夹下面的 beans 包下面,并以 XXXBean 命名,统一路径和命名规则,可以简化混淆成本。

  • 模块包结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- home
- beans
- TestBean
- dtos
- SearchResultDTO
- event
- TestEvent
- tabs // 注意这边可能是别的,但不应该是 fragments 这种
- HomeFragment
- AnimationFragment
- TestFragment
- SearchActivity.java // View
- SearchPresenter.java // Presenter
- SearchModel.java // Model
- SearchService.java // Service
  • 路由管理

更好的页面管理方案应该是基于路由的,也就是 ActivityActivityActivityFragment 之间应该是松耦合,他们之间不应该有互相的引用

为了后面更好的向路由方案迁移,启动 Activity 和创建 Fragment 统一由 Router 管理

其他类调用 Router 类,启动 Activity 和创建 Fragment,同时所有页面传输的 KEY 也在 Router 类里面注册,尽量复用。

  • 网络请求
  1. 请求的 Service 集成在每个模块内部,比如首页的 5 个请求,会在 HomeApiService 里面,除非公共的请求,否则不要使用 CommonService,避免造成 CommonService 臃肿和频繁的代码冲突。

  2. 请求方法的命名,如果是 get 请求,方法命名为 getXXX(), post 请求,方法命名为 postXXX(),以此类推,方便看到请求就知道是什么请求方式。

  3. 网络请求代码的编写

发起请求使用 Api.use(SomeService.class).xxx,它是一个独立的网络请求管理者,你可以在任何位置使用它。

Model 层发起网络请求,并做数据处理,增强 Model 层的功能性,数据的处理使用 compose() 方法,在 ApiTransformers 中提供了大多数数据转换的方法,如果不能满足需求那么应该使用 map 函数再扩展,理论上不应在 Presenter 在做数据的转换和处理操作。

1
2
3
4
public Observable<HomeTopStaticDTO> getIndexUnion() {
return Api.use(HomeApiService.class).getIndexUnion()
.compose(ApiTransformers.composeBaseDTO(true));
}

Presenter 层接受转换后的数据,强制 使用 ApiObserver 的静态工厂创建观察者,不要自己创建,统一观察者方便做一些公共的事情,比如加载动画,统一错误处理等等,这至关重要。

创建观察者时,需要传入 View 层的标签,使请求可以绑定到生命周期,自动取消请求,建议使用 lambda 表达式的形式,它让我们更关注业务实现,而不是接口的名字。

1
2
3
4
5
mModel.getIndexUnion().subscribe(ApiObserver.make(mView.viewTag(), data -> {
// 成功代码在这里处理
},error -> {
// 错误统一在这里处理,不实现可以不处理。
}));

综上,请大家遵循网络请求的模版代码,统一网络请求的格式。

------ 本文结束 🎉🎉 谢谢观看  ------