記一次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性能優化之工具和優化點總結

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