本文总结了在项目中做的一次CodeReview实例,考虑到安全因素,重要的代码已改名、混淆或删除。
文本重在记录是如何怎样做的。希望对你有所帮助。
CodeReiview 代码范围:以时间线范围为准:2019.5.7-2019.6.8
目的:代码、规范、总结+实例、工具推荐等等。
CodeReiview 工具使用:
执行Android studio –> Analyze –> Inspect code操作之后,所有的lint警告列表就会出来。
于是得到六大类Android Lint
开发人员A:
activity_car_index.xml
第245行
<LinearLayout
android:id="@+id/llVehicleRecordCount"
android:layout_width="match_parent"
android:baselineAligned="false"
android:layout_height="49dp"
android:background="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent">
优化提示:Set ‘android:baselineAligned=“false”’ on this element for better performance
有两点好处:
扩展:LinearLayout中的baselineAligned属性
shortToast()
; TextView
,距离中使用了硬编码;activity_car_index.xml
中, 用了ConstraintLayout
后,其他子控件都要相应约束。如在CarFragment中第181行
tvName.text = "$name · $shopName"
改成用placeholders //String.format(R.string.xx)
删除多余的属性声明
<ImageView
android:id="@+id/ivBack"
android:layout_width="59dp"
android:layout_height="43dp"
android:layout_gravity="start|center_vertical"
android:paddingStart="15dp"
android:paddingLeft="15dp"
android:paddingTop="14dp"
android:paddingEnd="25dp"
android:paddingRight="25dp"
android:paddingBottom="14dp"
android:src="@drawable/ic_common_back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
属性中的right和End、left和Start,建议只保留一个最新的api接口
登录启动页面的图片可以压缩后再使用,不影响分辨率,安装包减少(4M左右)。
在线压缩:https://tinypng.com
压缩后图片减少了80%
使用场景:
当你需要在2个UI控件添加间距的时候,你可能会添加padding
或margin
。有时最终的layout文件是非常混乱,可读性非常差。
当你需要解决问题时,你突然意识到这里有一个5dp的paddingTop
,那里有一个2dp的marginBottom
,还有一个4dp的paddingBottom
在第三个控件上然后你很难弄明白到底是哪个控件导致的问题。
还有我发现有些人在2个控件之间添加LinearLayou
t或View
来解决这个问题,看起来是一个很简单解决方案但是对App的性能有很大的影响。
原因:
Space is a lightweight View subclass that may be used to create gaps between components in general purpose layouts.
如果你看过Space的源码实现会发现Space继承View但是没有绘制任何东西在canvas。
同时你也会发现在约束布局中:androidx.constraintlayout.widget.Guideline
中的draw中也没有没有执行任何方法。
/**
* Draw nothing.
*
* @param canvas an unused parameter.
*/
@Override
public void draw(Canvas canvas) {
}
Space控件
如果给条目中间添加间距
space
替代<Space
android:layout_width="match_parent"
android:layout_height="50dp" />
编译警告:
Possible overdraw: Root element paints background ‘@color/bgGray’ with a theme that also paints a background (inferred theme is ‘@style/AppTheme’)
解决方案:尽量把background部分的颜色放子布局中,或者自定义主题,将背景色设置进主题,在运用主题。
在系统主题中(common/styles.xml)修改windowBackground
:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
--TODO:移除主题背景色,减少一次界面过度绘制-->
- "android:windowBackground">@null
style>
移除后,系统默认主题会变成黑色,此时需要在各个界面上检测是否没有设置颜色。
在项目中需要优化的地方有:
1.过度绘制(在反馈、关于、修改密码、设置、提交认证等)37个界面。
开发人员B:
git 作为版本管理工具,本身就是来管理不同版本的代码,如果不再使用,就删除代码,如果需要恢复,从 git 记录里也很方面找到。
如:MainActivity 49 - 53 行
能放在自己组件的东西尽量放在自己的组件,不要往公用组件堆积东西。
CarEntity 新增了一个 isSelected 字段,建议在车辆组件新增一个类,继承自 CarEntity ,将 isSelected 放在该类中,可以参考 CarItemEntity 类。在实际场景中,由于使用了 CarAdapter ,建议直接将 isSelected 放在 CarItemEntity 而不是 CarEntity 中。
小结:通用和组件,可总结成规范,使用继承,类似于使用装饰者模式,添加新的类再继承通过的类。
common 组件 dimens.xml 的 dp_0_5 作为 0.5dp 的感觉可能会引起歧义,和 5dp 的命名很像,建议使用 dp_0p5等不一样的命名。
car 组件 styles.xml RightTopPopAnim 没有使用组件前缀。(需求不明确而保留动画,可优化)
car 组件 styles.xml pop_anim 没有使用组件前缀。
CarAdapter isSlideMode 的意义太大了,建议修改成 isShowDeleteButton
小结: 代码规范统一后即可优化,命名规范尽量明确化。
背景重复设置
存在无用布局嵌套
子元素 很少的时候,或者就只有一个,建议使用 FrameLayout ,而不是 ConstraintLayout 或 LinearLayout。
优先级:FrameLayout ->LinearLayout->ConstraintLayout ->RelativeLayout
car_activity_recycling_station.xml
7 和 21 行设置了重复的背景,考虑到绘制优化,最好只是设置在需要的 view 上,这里是 RecyclerView,不要设置在整个布局上
一层 LinearLayout 就够了,不必使用两层
完整代码如下:
<LinearLayout 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"
android:orientation="vertical">
<cc.xx.common.widget.TitleLayout
android:id="@id/titleLayout"
android:layout_width="match_parent"
android:layout_height="@dimen/titleHeight"
app:title_layout_rightText="@string/vehicle_edit"
app:title_layout_titleText="@string/vehicle_recycling_station_title" />
<cc.xx.common.widget.layoutstatus.LayoutStatusView
android:id="@+id/statusLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/common_bg_gray_light"
android:paddingStart="@dimen/dp_15"
android:paddingTop="@dimen/dp_15"
android:paddingEnd="@dimen/dp_15"
tools:listitem="@layout/item_vehicle_recycling" />
cc.xx.common.widget.layoutstatus.LayoutStatusView>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/clBottom"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_55"
android:background="@color/common_bg_white"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
tools:visibility="visible">
<ImageView
android:id="@+id/ivSelectedAll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_20"
android:layout_marginTop="@dimen/dp_16"
android:background="@drawable/vehicle_rb_normal"
android:contentDescription="@string/app_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<cc.xx.lib.widget.CustomTextView
android:id="@+id/tvSelectedAll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_8"
android:layout_marginTop="@dimen/dp_16"
android:text="@string/vehicle_selected_all"
android:textColor="@color/common_font_black"
android:textSize="@dimen/font_16"
app:layout_constraintStart_toEndOf="@id/ivSelectedAll"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnTotalDelete"
android:layout_width="@dimen/dp_120"
android:layout_height="@dimen/dp_55"
android:background="@color/common_bg_orange_red"
android:text="@string/vehicle_total_delete"
android:textColor="@color/common_font_white"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<Button
android:id="@+id/btnRecovery"
android:layout_width="@dimen/dp_120"
android:layout_height="@dimen/dp_55"
android:background="@color/green07"
android:text="@string/vehicle_recovery"
android:textColor="@color/common_font_white"
app:layout_constraintEnd_toStartOf="@id/btnTotalDelete"
app:layout_constraintTop_toTopOf="parent" />
androidx.constraintlayout.widget.ConstraintLayout>
LinearLayout>
layout_car_recovery_delete.xml
建议使用 FrameLayout 而 不是用 LinearLayout 作为外层 Parent。
background 应该设置在 ConstraintLayout 上 而不是外层的 Parent。
item_car_recycling.xml
不需要外面的LinearLayout,建议移除。
LinearLayout 和 里面的 ConstraintLayout 都设置了背景,而且 ConstraintLayout 的背景把 LinearLayout 背景的圆角效果给遮住了。
最终建议:移除LinearLayout,将 ConstraintLayout 的背景由 @android:color/white 改为 @drawable/common_bg_rect_white_radius_8。
(dialog中为了计算高度可能会用到最外层的LinrearLaytoutk 或FrameLayout,RecyclerView中的item可不用最外层的)
item_car.xml
第 8 行,android:orientation="vertical"
无用,建议删除。
VehicleDeleteDialog
private fun initList() {
list.clear()
val reasonEntity1 = VehicleReasonEntity()
val reasonEntity2 = CarReasonEntity()
val reasonEntity3 = CarReasonEntity()
reasonEntity1.reasonContent =getString(R.string.vehicele_reason_one)
reasonEntity2.reasonContent = getString(R.string.vehicele_reason_two)
reasonEntity3.reasonContent = getString(R.string.vehicele_reason_three)
list.add(reasonEntity1)
list.add(reasonEntity2)
list.add(reasonEntity3)
adapter.setNewData(list)
}
可以修改 CarReasonEntity 代码:
class CarReasonEntity(
// 删除原因
var reasonContent: String = ""
) {
//是否选中
var isSelected = false
}
initList 方法修改为:
private fun initList() {
list.clear()
list.add(CarReasonEntity(getString(R.string.vehicele_reason_one))
list.add(VehicleRCartity(getString(R.string.vehicle_reason_two))
list.add(VehicleReasonEnty(getString(R.string.vehicle_reason_three))
adapter.setNewData(list)
}
或者在将初始化的数据放到 Adapter类中的init中
class HomeAdapter : MyBaseAdapter<CarReasonEntity>(R.layout.item_home) {
//放到这里
init {
list.clear()
list.add(VehiCaronEntity(getString(R.string.vehicle_reson_one))
list.add(VehicleReasoCar(getString(R.string.vehicle_reason_three))
list.add(VehicleReasonEntity(getString(R.string.vehicle_reason_three))
setNewData(list)
}
override fun convert(helper: BaseViewHolder, item: HomeBusinessEntity) {
}
}
CarRecyclingAdapter setEditMode 和 selectedAll 调用 notifyDataSetChanged 应该放在 if 里,有满足条件再执行。
bg_common_rect_gray_radius_4.xml 和 bg_common_rect_red_radius_4.xml 放在了 drawable-xhdpi,建议放在 drawable。(图片放的位置不能粗心)
CarRecyclingStationActivity 第 79 行 判断建议增加一个变量来判断,而不是比较字符串。(开发习惯统一)
CarDeleteDialog 第 222 和 223 行,如果确认为必须代码,建议将其移动到基础库 InputUtil.hideSoftInput 方法中。(焦点获取处理)
1.注解放在了方法上,对于具体的注解,放在具体的语句上是否更合适?
(消除警告,这个需求不明确,可重新优化)
2.ImageView 的 contentDescription 使用 app_name 是否合适?
(这个也是为了消除警告,Google的人性化设计,可统一优化成“默认为空或其他”)
3.将部分对列表UI的操作放到适配器中,这样减少的Activity中的代码,是否需要提取到规范
(可提取到规范)
4.考虑App崩溃恢复后,保留之前用户数据录入数据的情况
ConfirmCarInfoActivity 参考 不保留历史活动
(产品需求明确或怎么体验更好)
5.类中方法顺序:个人觉得,采用总、分的方式比较方便。
(可优化,可总结成代码规范)
6.字符串资源文件占位符,不需要写数字 + $ 符号。
明确代码的职责范围,比如说设置背景的位置,命名,范围刚好满足功能就好。
缺失一个较为详细的命名规范:布局文件,资源名等
缺失组件和通用资源和代码的较为明确的界限划分
CodeReview后,发现开发人员比较好的实现方式和思路,可以总结成开发规范,最好能加上实例,这样方便记录和开发人员的规范统一,做到代码的实现看上去是一个人写的。
以这次为例子:在之前的Android开发规范之上,可以持续完善到开发规范文档中。
渲染原理:渲染大概分为"layout",“measure”"draw"这三个阶段
使用原则:减少布局层级、减少过度绘制、布局复用
使用建议:
使用合适的布局
三种常见的ViewGroup的绘制速度:FrameLayout
> LinerLayout
> RelativeLayout
。
ConstraintLayout
是一个更高性能的消灭布局层级的神器
使用布局优先级:FrameLayout>ConstraintLayout>LinearLayout>RelativeLayout
,结合效率和需求实现。
尽量减少使用wrap_content
,推荐使用mathch_parent
或固定尺寸配合gravity="center"
因为 在测量过程中,match_parent
和固定宽高度对应EXACTLY
,而wrap_content
对应AT_MOST
,这两者对比AT_MOST
耗时较多。
在需要的地方添加渲染背景,外层不渲染,在内层需要的地方渲染。
文本控件,需要考虑文本过长时的省略策略
切图至少提供两套,xhdpi
和xxhdpi
消除布局警告,同时删除控件中的无用属性
使用部分示例:
(1)RecycleView中item 一般用ConstraintLayout
或直接使用控件来布局,以业务需求为准。
(2)简单布局一般用FrameLayout
来布局,同时结合include、merge来使用。
布局文件都要有根节点,但android中的布局嵌套过多会造成性能问题,于是在使用include嵌套的时候我们可以使用merge作为根节点,这样可以减少布局嵌套,提高显示速率。
(3)对于只有在某些条件下才展示出来的组件,建议使用viewStub
包裹起来,include 某布局如果其根布局和引入他的父布局一致,建议使用merge包裹起来,如果你担心preview效果问题,这里完全没有必要,可以tools:showIn=""
属性,这样就可以正常展示preview了。
1.避免创建不必要的对象 不必要的对象应该避免创建。
如果有需要拼接的字符串,那么可以优先考虑使用StringBuffer
或者StringBuilder
来进行拼接,而不是加号连接符,因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低。
如字符串拼接使用
用StringBuffer
的效率高于 String直接“+”拼接
//good
//车牌号=省份+号码
tvPlateNumber.text = StringBuilder(sf).append(hphm)
//bad
tvPlateNumber.text = sf + hphm
2.尽可能地少创建临时对象,越少的对象意味着越少的GC操作。
3.onDraw
方法里面不要执行对象的创建
4.尽量使用基本数据类型替代封装数据类型,如int
比Integer
要更加有效。
view自定义控件异常销毁保存状态。在程序异常崩溃时,保存界面相关数据,如用户输入的数据,再次恢复时数据还原,增加用户体验。
示例:
@Override
protected Parcelable onSaveInstanceState() {
//异常情况保存重要信息。
//return super.onSaveInstanceState();
final Bundle bundle = new Bundle();
bundle.putInt("selectedPosition",selectedPosition);
bundle.putInt("flingSpeed",mFlingSpeed);
bundle.putInt("orientation",orientation);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle bundle = (Bundle) state;
selectedPosition = bundle.getInt("selectedPosition",selectedPosition);
mFlingSpeed = bundle.getInt("flingSpeed",mFlingSpeed);
orientation = bundle.getInt("orientation",orientation);
return;
}
super.onRestoreInstanceState(state);
}
或者在 OnCreate
和onSaveInstanceState
中读取和保存数据
参考资料:
1.代码Review
2.Lint常见的问题及解决方案
3.Android Studio 工具:Lint 代码扫描工具(含自定义lint)
4.怎样优化你的布局层级结构之RelativeLayout和LinearLayout及FrameLayout性能分析
5.Android性能优化之工具和优化点总结