记一次CodeReview实例

前言

本文总结了在项目中做的一次CodeReview实例,考虑到安全因素,重要的代码已改名、混淆或删除。

文本重在记录是如何怎样做的。希望对你有所帮助。

CodeReiview 代码范围:以时间线范围为准:2019.5.7-2019.6.8

目的:代码、规范、总结+实例、工具推荐等等。

CodeReiview 工具使用:

执行Android studio –> Analyze –> Inspect code操作之后,所有的lint警告列表就会出来。

于是得到六大类Android Lint

  1. Correctness 正确性
  2. Internationalization 国际化,如字符缺少翻译等问题。
  3. Performance 性能,例如在 onMeasure、onDraw 中执行 new,内存泄露,产生了冗余的资源,xml 结构冗余等。
  4. Security 安全性,例如没有使用 HTTPS 连接 Gradle,AndroidManifest 中的权限问题等。
  5. Usability 易用性,例如缺少某些倍数的切图,重复图标等。
  6. Accessibility 无障碍例如 ImageView 缺少contentDescription 描述,String 编码字符串等问题。

在实际项目中的Code Review情况

开发人员A:

1.性能布局优化

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

有两点好处:

  • 控件对齐:android:baselineAligned(基线对齐)设置为true时同时设置了layout_weight属性控件的对齐方式会根据控件内部的内容对齐,当设置为false时会根据控件的上方对齐。
  • 加速绘制:如果LinearLayout被用于嵌套的layout空间计算,它的android:baselineAligned属性应该设置成false,以加速layout计算。

扩展:LinearLayout中的baselineAligned属性

2.消除硬编码、警告等

  • 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接口

3.图片压缩

登录启动页面的图片可以压缩后再使用,不影响分辨率,安装包减少(4M左右)。

在线压缩:https://tinypng.com

压缩后图片减少了80%

VWbkuV.md.png

4.用Space替换View 来进一步减少过度绘制(讨论)

使用场景:

当你需要在2个UI控件添加间距的时候,你可能会添加paddingmargin。有时最终的layout文件是非常混乱,可读性非常差。

当你需要解决问题时,你突然意识到这里有一个5dp的paddingTop,那里有一个2dp的marginBottom,还有一个4dp的paddingBottom在第三个控件上然后你很难弄明白到底是哪个控件导致的问题。

还有我发现有些人在2个控件之间添加LinearLayout或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控件
如果给条目中间添加间距

  • 添加view :增加了view 增加了控件 ,影响性能
  • 使用layout_marginTop:使用过多的margin 影响代码的可读性
    可用轻量级的space替代
<Space
       android:layout_width="match_parent"
       android:layout_height="50dp" />

5.移除系统的主题颜色,在各个界面中添加绘制,减少一次过度绘制。

编译警告:

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:移除主题背景色,减少一次界面过度绘制-->
        <!--<item name="android:windowBackground">@android:color/white</item>-->
        <item name="android:windowBackground">@null</item>
        <!--或者-->
        <!--<item name="android:windowBackground">@android:color/transparent</item>-->
    </style>

移除后,系统默认主题会变成黑色,此时需要在各个界面上检测是否没有设置颜色。

在项目中需要优化的地方有:

1.过度绘制(在反馈、关于、修改密码、设置、提交认证等)37个界面。

开发人员B:

6.存在代码注释

git 作为版本管理工具,本身就是来管理不同版本的代码,如果不再使用,就删除代码,如果需要恢复,从 git 记录里也很方面找到。

如:MainActivity 49 - 53 行

7.通用组件冗余

能放在自己组件的东西尽量放在自己的组件,不要往公用组件堆积东西。

CarEntity 新增了一个 isSelected 字段,建议在车辆组件新增一个类,继承自 CarEntity ,将 isSelected 放在该类中,可以参考 CarItemEntity 类。在实际场景中,由于使用了 CarAdapter ,建议直接将 isSelected 放在 CarItemEntity 而不是 CarEntity 中。

小结:通用和组件,可总结成规范,使用继承,类似于使用装饰者模式,添加新的类再继承通过的类。

8.命名

common 组件 dimens.xml 的 dp_0_5 作为 0.5dp 的感觉可能会引起歧义,和 5dp 的命名很像,建议使用 dp_0p5等不一样的命名。

car 组件 styles.xml RightTopPopAnim 没有使用组件前缀。(需求不明确而保留动画,可优化)

car 组件 styles.xml pop_anim 没有使用组件前缀。

CarAdapter isSlideMode 的意义太大了,建议修改成 isShowDeleteButton

小结: 代码规范统一后即可优化,命名规范尽量明确化。

9.布局优化:

  • 背景重复设置

  • 存在无用布局嵌套

  • 子元素 很少的时候,或者就只有一个,建议使用 FrameLayout ,而不是 ConstraintLayout 或 LinearLayout。

    优先级:FrameLayout ->LinearLayout->ConstraintLayout ->RelativeLayout

vehicle_activity_recycling_station.xml

  • 7 和 21 行设置了重复的背景,考虑到绘制优化,最好只是设置在需要的 view 上,这里是 RecyclerView,不要设置在整个布局上

  • 一层 LinearLayout 就够了,不必使用两层

  • 完整代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <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_vehicle_recovery_delete.xml

建议使用 FrameLayout 而 不是用 LinearLayout 作为外层 Parent。

background 应该设置在 ConstraintLayout 上 而不是外层的 Parent。

item_vehicle_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_vehicle.xml

第 8 行,android:orientation="vertical" 无用,建议删除。

10.重复代码太多

VehicleDeleteDialog

private fun initList() {
        list.clear()
        val reasonEntity1 = VehicleReasonEntity()
        val reasonEntity2 = CarReasonEntity()
        val reasonEntity3 = CarReasonEntity()

        reasonEntity1.reasonContent ="原因1" 
        reasonEntity2.reasonContent = "原因2" 
        reasonEntity3.reasonContent = "原因3" 

        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(ResourcesUtil.getString(R.string.vehicele_reason_one))
 list.add(VehicleRCartity(ResourcesUtil.getString(R.string.vehicle_reason_two))
 list.add(VehicleReasonEntCarourcesUtil.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(ResourcesUtil.getString(R.string.vehicle_reson_one))
    list.add(VehicleReasoCar(ResourcesUtil.getString(R.string.vehicle_reason_three           list.add(VehicleReasonEntity(ResourcesUtil.getString(R.string.vehicle_reason_three))                                                                                          setNewData(list)
    }

    override fun convert(helper: BaseViewHolder, item: HomeBusinessEntity) {
    }
  }

11.其他

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 方法中。(焦点获取处理)

12.不明确

1.注解放在了方法上,对于具体的注解,放在具体的语句上是否更合适?

  • AccountActivity 71行
  • LoginActivity 46 行

(消除警告,这个需求不明确,可重新优化)

2.ImageView 的 contentDescription 使用 app_name 是否合适?

(这个也是为了消除警告,Google的人性化设计,可统一优化成“默认为空或其他”)

3.将部分对列表UI的操作放到适配器中,这样减少的Activity中的代码,是否需要提取到规范

  • CarRecyclingAdapter 的方法 setEditMode 和 selectedAll

可提取到规范

4.考虑App崩溃恢复后,保留之前用户数据录入数据的情况

  • CarDeleteDialog、CarRecyclingStationActivity
ConfirmCarInfoActivity 参考 不保留历史活动

(产品需求明确或怎么体验更好)

5.类中方法顺序:个人觉得,采用总、分的方式比较方便。

  • CarRecyclingStationActivity 55 行 方法 initRecyclerView 建议 放在方法 loadData之后

(可优化,可总结成代码规范)

6.字符串资源文件占位符,不需要写数字 + $ 符号,

总结

  • 明确代码的职责范围,比如说设置背景的位置,命名,范围刚好满足功能就好。

  • 缺失一个较为详细的命名规范:布局文件,资源名等

  • 缺失组件和通用资源和代码的较为明确的界限划分

CodeReview后,发现开发人员比较好的实现方式和思路,可以总结成开发规范,最好能加上实例,这样方便记录和开发人员的规范统一,做到代码的实现看上去是一个人写的。

以这次为例子:在之前的Android开发规范之上,可以持续完善到开发规范文档中。

开发规范总结完善及应用示例总结(持续完善中):

1.替换Serializable使用Parcelable

2.布局优化总结

渲染原理:渲染大概分为"layout",“measure”"draw"这三个阶段

使用原则:减少布局层级、减少过度绘制、布局复用

使用建议:

  1. 使用合适的布局

    三种常见的ViewGroup的绘制速度:FrameLayout> LinerLayout> RelativeLayout

    ConstraintLayout是一个更高性能的消灭布局层级的神器

    使用布局优先级:FrameLayout>ConstraintLayout>LinearLayout>RelativeLayout,结合效率和需求实现。

  2. 尽量减少使用wrap_content,推荐使用mathch_parent或固定尺寸配合gravity="center"

    因为 在测量过程中,match_parent和固定宽高度对应EXACTLY,而wrap_content对应AT_MOST,这两者对比AT_MOST耗时较多。

  3. 在需要的地方添加渲染背景,外层不渲染,在内层需要的地方渲染。

  4. 文本控件,需要考虑文本过长时的省略策略

  5. 切图至少提供两套,xhdpixxhdpi

  6. 消除布局警告,同时删除控件中的无用属性

使用部分示例:

(1)RecycleView中item 一般用ConstraintLayout或直接使用控件来布局,以业务需求为准。

(2)简单布局一般用FrameLayout来布局,同时结合include、merge来使用。

布局文件都要有根节点,但android中的布局嵌套过多会造成性能问题,于是在使用include嵌套的时候我们可以使用merge作为根节点,这样可以减少布局嵌套,提高显示速率。

(3)对于只有在某些条件下才展示出来的组件,建议使用viewStub包裹起来,include 某布局如果其根布局和引入他的父布局一致,建议使用merge包裹起来,如果你担心preview效果问题,这里完全没有必要,可以tools:showIn=""属性,这样就可以正常展示preview了。

3.内存优化

1.避免创建不必要的对象 不必要的对象应该避免创建。

如果有需要拼接的字符串,那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号连接符,因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低。

如字符串拼接使用

StringBuffer的效率高于 String直接“+”拼接

//good
//车牌号=省份+号码
tvPlateNumber.text = StringBuilder(sf).append(hphm)
//bad
tvPlateNumber.text = sf + hphm

2.尽可能地少创建临时对象,越少的对象意味着越少的GC操作。

3.onDraw方法里面不要执行对象的创建

4.尽量使用基本数据类型替代封装数据类型,如intInteger要更加有效。

4.View异常优化

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);
}

或者在 OnCreateonSaveInstanceState中读取和保存数据

参考资料:

1.代码Review

2.Lint常见的问题及解决方案

3.Android Studio 工具:Lint 代码扫描工具(含自定义lint)

4.怎样优化你的布局层级结构之RelativeLayout和LinearLayout及FrameLayout性能分析

5.Android性能优化之工具和优化点总结

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章