Android規範學習 之 開發規範

摘要

  • 1 前言

  • 2 AS 規範

  • 3 命名規範

  • 4 代碼樣式規範

  • 5 資源文件規範

  • 6 版本統一規範

  • 7 第三方庫規範

  • 8 註釋規範

  • 9 測試規範

  • 10 其他的一些規範

1 前言

爲了有利於項目維護、增強代碼可讀性、提升 Code Review 效率以及規範團隊安卓開發,故提出以下安卓開發規範,該規範結合本人多年的開發經驗並吸取多家之精華,可謂是本人的嘔心瀝血之作,稱其爲當前最完善的安卓開發規範一點也不爲過,如有更好建議,歡迎到 GitHub 提 issue,原文地址:Android 開發規範(完結版)。相關 Demo,可以查看我的 Android 開發工具類集合項目:Android 開發人員不得不收集的代碼。後續可能會根據該規範出一個 CheckStyle 插件來檢查是否規範,當然也支持在 CI 上運行。

2 AS 規範

工欲善其事,必先利其器。

  1. 儘量使用最新的穩定版的 IDE 進行開發;

  2. 編碼格式統一爲 UTF-8

  3. 編輯完 .java、.xml 等文件後一定要 格式化,格式化,格式化(如果團隊有公共的樣式包,那就遵循它,否則統一使用 AS 默認模板即可);

  4. 刪除多餘的 import,減少警告出現,可利用 AS 的 Optimize Imports(Settings -> Keymap -> Optimize Imports)快捷鍵;

  5. Android 開發者工具可以參考這裏:Android 開發者工具

3 命名規範

代碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。正確的英文拼寫和語法可以讓閱讀者易於理解,避免歧義。

注意:即使純拼音命名方式也要避免採用。但 alibabataobaoyoukuhangzhou 等國際通用的名稱,可視同英文。

3.1 包名

包名全部小寫,連續的單詞只是簡單地連接起來,不使用下劃線,採用反域名命名規則,全部使用小寫字母。一級包名是頂級域名,通常爲 comedugovnetorg 等,二級包名爲公司名,三級包名根據應用進行命名,後面就是對包名的劃分了,關於包名的劃分,推薦採用 PBF(按功能分包 Package By Feature),一開始我們採用的也是 PBL(按層分包 Package By Layer),很坑爹。PBF 可能不是很好區分在哪個功能中,不過也比 PBL 要好找很多,且 PBF 與 PBL 相比較有如下優勢:

  • package 內高內聚,package 間低耦合

    哪塊要添新功能,只改某一個 package 下的東西。

    PBL 降低了代碼耦合,但帶來了 package 耦合,要添新功能,需要改 model、dbHelper、view、service 等等,需要改動好幾個 package 下的代碼,改動的地方越多,越容易產生新問題,不是嗎?

    PBF 的話 featureA 相關的所有東西都在 featureA 包,feature 內高內聚、高度模塊化,不同 feature 之間低耦合,相關的東西都放在一起,還好找。

  • package 有私有作用域(package-private scope)

    你負責開發這塊功能,這個目錄下所有東西都是你的。

    PBL 的方式是把所有工具方法都放在 util 包下,小張開發新功能時候發現需要一個 xxUtil,但它又不是通用的,那應該放在哪裏?沒辦法,按照分層原則,我們還得放在 util 包下,好像不太合適,但放在其它包更不合適,功能越來越多,util 類也越定義越多。後來小李負責開發一塊功能時發現需要一個 xxUtil,同樣不通用,去 util 包一看,怎麼已經有了,而且還沒法複用,只好放棄 xx 這個名字,改爲 xxxUtil……,因爲 PBL 的 package 沒有私有作用域,每一個包都是 public(跨包方法調用是很平常的事情,每一個包對其它包來說都是可訪問的);如果是 PBF,小張的 xxUtil 自然放在 featureA 下,小李的 xxUtil 在 featureB 下,如果覺得 util 好像是通用的,就去 util 包看看要不要把工具方法添進 xxUtil, class 命名衝突沒有了。

    PBF 的 package 有私有作用域,featureA 不應該訪問 featureB 下的任何東西(如果非訪問不可,那就說明接口定義有問題)。

  • 很容易刪除功能

    統計發現新功能沒人用,這個版本那塊功能得去掉。

    如果是 PBL,得從功能入口到整個業務流程把受到牽連的所有能刪的代碼和 class 都揪出來刪掉,一不小心就完蛋。

    如果是 PBF,好說,先刪掉對應包,再刪掉功能入口(刪掉包後入口肯定報錯了),完事。

  • 高度抽象

    解決問題的一般方法是從抽象到具體,PBF 包名是對功能模塊的抽象,包內的 class 是實現細節,符合從抽象到具體,而 PBL 弄反了。

    PBF 從確定 AppName 開始,根據功能模塊劃分 package,再考慮每塊的具體實現細節,而 PBL 從一開始就要考慮要不要 dao 層,要不要 com 層等等。

  • 只通過 class 來分離邏輯代碼

    PBL 既分離 class 又分離 package,而 PBF 只通過 class 來分離邏輯代碼。

    沒有必要通過 package 分離,因爲 PBL 中也可能出現尷尬的情況:

    ├── service 
           ├── MainService.java

    按照 PBL, service 包下的所有東西都是 Service,應該不需要 Service 後綴,但實際上通常爲了方便,直接 import service 包,Service 後綴是爲了避免引入的 class 和當前包下的 class 命名衝突,當然,不用後綴也可以,得寫清楚包路徑,比如 new com.domain.service.MainService(),麻煩;而 PBF 就很方便,無需 import,直接 new MainService() 即可。

  • package 的大小有意義了

    PBL 中包的大小無限增長是合理的,因爲功能越添越多,而 PBF 中包太大(包裏 class 太多)表示這塊需要重構(劃分子包)。

如要知道更多好處,可以查看這篇博文:Package by features, not layers,當然,我們大谷歌也有相應的 Sample:todo-mvp,其結構如下所示,很值得學習。

com 
└── example
   └── android
       └── architecture
           └── blueprints
               └── todoapp
                   ├── BasePresenter.java
                   ├── BaseView.java
                   ├── addedittask
                   │   ├── AddEditTaskActivity.java
                   │   ├── AddEditTaskContract.java
                   │   ├── AddEditTaskFragment.java
                   │   └── AddEditTaskPresenter.java
                   ├── data
                   │   ├── Task.java
                   │   └── source
                   │       ├── TasksDataSource.java
                   │       ├── TasksRepository.java
                   │       ├── local
                   │       │   ├── TasksDbHelper.java
                   │       │   ├── TasksLocalDataSource.java
                   │       │   └── TasksPersistenceContract.java
                   │       └── remote
                   │           └── TasksRemoteDataSource.java
                   ├── statistics
                   │   ├── StatisticsActivity.java
                   │   ├── StatisticsContract.java
                   │   ├── StatisticsFragment.java
                   │   └── StatisticsPresenter.java
                   ├── taskdetail
                   │   ├── TaskDetailActivity.java
                   │   ├── TaskDetailContract.java
                   │   ├── TaskDetailFragment.java
                   │   └── TaskDetailPresenter.java
                   ├── tasks
                   │   ├── ScrollChildSwipeRefreshLayout.java
                   │   ├── TasksActivity.java
                   │   ├── TasksContract.java
                   │   ├── TasksFilterType.java
                   │   ├── TasksFragment.java
                   │   └── TasksPresenter.java
                   └── util
                       ├── ActivityUtils.java
                       ├── EspressoIdlingResource.java
                       └── SimpleCountingIdlingResource.java

參考以上的代碼結構,按功能分包具體可以這樣做:

com 
└── domain
   └── app
       ├── App.java 定義 Application 類
       ├── Config.java 定義配置數據(常量)
       ├── base 基礎組件
       ├── custom_view 自定義視圖
       ├── data 數據處理
       │   ├── DataManager.java 數據管理器,
       │   ├── local 來源於本地的數據,比如 SP,Database,File
       │   ├── model 定義 model(數據結構以及 getter/setter、compareTo、equals 等等,不含複雜操作)
       │   └── remote 來源於遠端的數據
       ├── feature 功能
       │   ├── feature0 功能 0
       │   │   ├── feature0Activity.java
       │   │   ├── feature0Fragment.java
       │   │   ├── xxAdapter.java
       │   │   └── ... 其他 class
       │   └── ...其他功能
       ├── injection 依賴注入
       ├── util 工具類
       └── widget 小部件

3.2 類名

類名都以 UpperCamelCase 風格編寫。

類名通常是名詞或名詞短語,接口名稱有時可能是形容詞或形容詞短語。現在還沒有特定的規則或行之有效的約定來命名註解類型。

名詞,採用大駝峯命名法,儘量避免縮寫,除非該縮寫是衆所周知的, 比如 HTML、URL,如果類名稱中包含單詞縮寫,則單詞縮寫的每個字母均應大寫。

描述例如
ActivityActivity 爲後綴標識歡迎頁面類 WelcomeActivity
AdapterAdapter 爲後綴標識新聞詳情適配器 NewsDetailAdapter
解析類Parser 爲後綴標識首頁解析類 HomePosterParser
工具方法類UtilsManager 爲後綴標識線程池管理類:ThreadPoolManager
日誌工具類:LogUtilsLogger 也可)
打印工具類:PrinterUtils
數據庫類DBHelper 後綴標識新聞數據庫:NewsDBHelper
ServiceService 爲後綴標識時間服務 TimeService
BroadcastReceiverReceiver 爲後綴標識推送接收 JPushReceiver
ContentProviderProvider 爲後綴標識ShareProvider
自定義的共享基礎類Base 開頭BaseActivity, BaseFragment

測試類的命名以它要測試的類的名稱開始,以 Test 結束。例如:HashTestHashIntegrationTest

接口(interface):命名規則與類一樣採用大駝峯命名法,多以 able 或 ible 結尾,如 interface Runnableinterface Accessible

注意:如果項目採用 MVP,所有 Model、View、Presenter 的接口都以 I 爲前綴,不加後綴,其他的接口採用上述命名規則。

3.3 方法名

方法名都以 lowerCamelCase 風格編寫。

方法名通常是動詞或動詞短語。

方法說明
initXX()初始化相關方法,使用 init 爲前綴標識,如初始化佈局 initView()
isXX(), checkXX()方法返回值爲 boolean 型的請使用 is/check 爲前綴標識
getXX()返回某個值的方法,使用 get 爲前綴標識
setXX()設置某個屬性值
handleXX(), processXX()對數據進行處理的方法
displayXX(), showXX()彈出提示框和提示信息,使用 display/show 爲前綴標識
updateXX()更新數據
saveXX(), insertXX()保存或插入數據
resetXX()重置數據
clearXX()清除數據
removeXX(), deleteXX()移除數據或者視圖等,如 removeView()
drawXX()繪製數據或效果相關的,使用 draw 前綴標識

3.4 常量名

常量名命名模式爲 CONSTANT_CASE,全部字母大寫,用下劃線分隔單詞。那到底什麼算是一個常量?

每個常量都是一個 static final 字段,但不是所有 static final 字段都是常量。在決定一個字段是否是一個常量時,得考慮它是否真的感覺像是一個常量。例如,如果觀測任何一個該實例的狀態是可變的,則它幾乎肯定不會是一個常量。只是永遠不打算改變的對象一般是不夠的,它要真的一直不變才能將它示爲常量。

// Constantsstatic final int NUMBER = 5;static final ImmutableListNAMES = ImmutableList.of("Ed", "Ann");static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutablestatic final SomeMutableType[] EMPTY_ARRAY = {};enum SomeEnum { ENUM_CONSTANT }// Not constantsstatic String nonFinal = "non-final";final String nonStatic = "non-static";static final SetmutableCollection = new HashSet();static final ImmutableSetmutableElements = ImmutableSet.of(mutable);static final Logger logger = Logger.getLogger(MyClass.getName());static final String[] nonEmptyArray = {"these", "can", "change"};

3.5 非常量字段名

非常量字段名以 lowerCamelCase 風格的基礎上改造爲如下風格:基本結構爲 scope{Type0}VariableName{Type1}type0VariableName{Type1}variableName{Type1}

說明:{} 中的內容爲可選。

注意:所有的 VO(值對象)統一採用標準的 lowerCamelCase 風格編寫,所有的 DTO(數據傳輸對象)就按照接口文檔中定義的字段名編寫。

3.5.1 scope(範圍)

非公有,非靜態字段命名以 m 開頭。

靜態字段命名以 s 開頭。

其他字段以小寫字母開頭。

例如:

public class MyClass {    public int publicField;    private static MyClass sSingleton;    int mPackagePrivate;    private int mPrivate;    protected int mProtected; 
}

使用 1 個字符前綴來表示作用範圍,1 個字符的前綴必須小寫,前綴後面是由表意性強的一個單詞或多個單詞組成的名字,而且每個單詞的首寫字母大寫,其它字母小寫,這樣保證了對變量名能夠進行正確的斷句。

3.5.2 Type0(控件類型)

考慮到 Android 衆多的 UI 控件,爲避免控件和普通成員變量混淆以及更好地表達意思,所有用來表示控件的成員變量統一加上控件縮寫作爲前綴(具體見附錄UI 控件縮寫表)。

例如:mIvAvatarrvBooksflContainer

3.5.3 VariableName(變量名)

變量名中可能會出現量詞,我們需要創建統一的量詞,它們更容易理解,也更容易搜索。

例如:mFirstBookmPreBookcurBook

量詞列表量詞後綴說明
First一組變量中的第一個
Last一組變量中的最後一個
Next一組變量中的下一個
Pre一組變量中的上一個
Cur一組變量中的當前變量
3.5.4 Type1(數據類型)

對於表示集合或者數組的非常量字段名,我們可以添加後綴來增強字段的可讀性,比如:

集合添加如下後綴:List、Map、Set。

數組添加如下後綴:Arr。

例如:mIvAvatarListuserArrfirstNameSet

注意:如果數據類型不確定的話,比如表示的是很多書,那麼使用其複數形式來表示也可,例如 mBooks

3.6 參數名

參數名以 lowerCamelCase 風格編寫,參數應該避免用單個字符命名。

3.7 局部變量名

局部變量名以 lowerCamelCase 風格編寫,比起其它類型的名稱,局部變量名可以有更爲寬鬆的縮寫。

雖然縮寫更寬鬆,但還是要避免用單字符進行命名,除了臨時變量和循環變量。

即使局部變量是 final 和不可改變的,也不應該把它示爲常量,自然也不能用常量的規則去命名它。

3.8 臨時變量

臨時變量通常被取名爲 ijkmn,它們一般用於整型;cde,它們一般用於字符型。 如:for (int i = 0; i < len; i++)

3.9 類型變量名

類型變量可用以下兩種風格之一進行命名:

  1. 單個的大寫字母,後面可以跟一個數字(如:E, T, X, T2)。

  2. 以類命名方式(參考3.2 類名),後面加個大寫的 T(如:RequestT, FooBarT)。

更多還可參考:阿里巴巴 Java 開發手冊

4 代碼樣式規範

4.1 使用標準大括號樣式

左大括號不單獨佔一行,與其前面的代碼位於同一行:

class MyClass {    int func() {        if (something) {            // ... 
       } else if (somethingElse) {            // ...
       } else {            // ...
       }
   }
}

我們需要在條件語句周圍添加大括號。例外情況:如果整個條件語句(條件和主體)適合放在同一行,那麼您可以(但不是必須)將其全部放在一行上。例如,我們接受以下樣式:

if (condition) { 
   body();
}

同樣也接受以下樣式:

if (condition) body();

但不接受以下樣式:

if (condition) 
   body();  // bad!

4.2 編寫簡短方法

在可行的情況下,儘量編寫短小精煉的方法。我們瞭解,有些情況下較長的方法是恰當的,因此對方法的代碼長度沒有做出硬性限制。如果某個方法的代碼超出 40 行,請考慮是否可以在不破壞程序結構的前提下對其拆解。

4.3 類成員的順序

這並沒有唯一的正確解決方案,但如果都使用一致的順序將會提高代碼的可讀性,推薦使用如下排序:

  1. 常量

  2. 字段

  3. 構造函數

  4. 重寫函數和回調

  5. 公有函數

  6. 私有函數

  7. 內部類或接口

例如:

public class MainActivity extends Activity {    private static final String TAG = MainActivity.class.getSimpleName();    private String mTitle;    private TextView mTextViewTitle;    @Override 
   public void onCreate() {
       ...
   }    public void setTitle(String title) {
    mTitle = title;
   }    private void setUpView() {
       ...
   }    static class AnInnerClass {

   }
}

如果類繼承於 Android 組件(例如 ActivityFragment),那麼把重寫函數按照他們的生命週期進行排序是一個非常好的習慣,例如,Activity 實現了 onCreate()onDestroy()onPause()onResume(),它的正確排序如下所示:

public class MainActivity extends Activity {    //Order matches Activity lifecycle 
   @Override
   public void onCreate() {}    @Override
   public void onResume() {}    @Override
   public void onPause() {}    @Override
   public void onDestroy() {}
}

4.4 函數參數的排序

在 Android 開發過程中,Context 在函數參數中是再常見不過的了,我們最好把 Context 作爲其第一個參數。

正相反,我們把回調接口應該作爲其最後一個參數。

例如:

// Context always goes firstpublic User loadUser(Context context, int userId);// Callbacks always go lastpublic void loadUserAsync(Context context, int userId, UserCallback callback);

4.5 字符串常量的命名和值

Android SDK 中的很多類都用到了鍵值對函數,比如SharedPreferencesBundleIntent,所以,即便是一個小應用,我們最終也不得不編寫大量的字符串常量。

當時用到這些類的時候,我們 必須 將它們的鍵定義爲 static final 字段,並遵循以下指示作爲前綴。

字段名前綴
SharedPreferencesPREF_
BundleBUNDLE_
Fragment ArgumentsARGUMENT_
Intent ExtraEXTRA_
Intent ActionACTION_

說明:雖然 Fragment.getArguments() 得到的也是 Bundle ,但因爲這是 Bundle 的常用用法,所以特意爲此定義一個不同的前綴。

例如:

// 注意:字段的值與名稱相同以避免重複問題static final String PREF_EMAIL = "PREF_EMAIL";static final String BUNDLE_AGE = "BUNDLE_AGE";static final String ARGUMENT_USER_ID = "ARGUMENT_USER_ID";// 與意圖相關的項使用完整的包名作爲值的前綴static final String EXTRA_SURNAME = "com.myapp.extras.EXTRA_SURNAME";static final String ACTION_OPEN_USER = "com.myapp.action.ACTION_OPEN_USER";

4.6 Activities 和 Fragments 的傳參

ActivityFragment 傳遞數據通過 IntentBundle 時,不同值的鍵須遵循上一條所提及到的。

ActivityFragment 啓動需要傳遞參數時,那麼它需要提供一個 public static 的函數來幫助啓動或創建它。

這方面,AS 已幫你寫好了相關的 Live Templates,啓動相關 Activity 的只需要在其內部輸入 starter 即可生成它的啓動器,如下所示:

public static void start(Context context, User user) { 
     Intent starter = new Intent(context, MainActivity.class);
     starter.putParcelableExtra(EXTRA_USER, user);
     context.startActivity(starter);
}

同理,啓動相關 Fragment 在其內部輸入 newInstance 即可,如下所示:

public static MainFragment newInstance(User user) { 
     Bundle args = new Bundle();
     args.putParcelable(ARGUMENT_USER, user);
     MainFragment fragment = new MainFragment();
     fragment.setArguments(args);      return fragment;
}

注意:這些函數需要放在 onCreate() 之前的類的頂部;如果我們使用了這種方式,那麼 extrasarguments 的鍵應該是 private 的,因爲它們不再需要暴露給其他類來使用。

4.7 行長限制

代碼中每一行文本的長度都應該不超過 100 個字符。雖然關於此規則存在很多爭論,但最終決定仍是以 100 個字符爲上限,如果行長超過了 100(AS 窗口右側的豎線就是設置的行寬末尾 ),我們通常有兩種方法來縮減行長。

  • 提取一個局部變量或方法(最好)。

  • 使用換行符將一行換成多行。

不過存在以下例外情況:

  • 如果備註行包含長度超過 100 個字符的示例命令或文字網址,那麼爲了便於剪切和粘貼,該行可以超過 100 個字符。

  • 導入語句行可以超出此限制,因爲用戶很少會看到它們(這也簡化了工具編寫流程)。

4.7.1 換行策略

這沒有一個準確的解決方案來決定如何換行,通常不同的解決方案都是有效的,但是有一些規則可以應用於常見的情況。

4.7.1.1 操作符的換行

除賦值操作符之外,我們把換行符放在操作符之前,例如:

int longName = anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne 
       + theFinalOne;

賦值操作符的換行我們放在其後,例如:

int longName = 
       anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne + theFinalOne;
4.7.1.2 函數鏈的換行

當同一行中調用多個函數時(比如使用構建器時),對每個函數的調用應該在新的一行中,我們把換行符插入在 . 之前。

例如:

Picasso.with(context).load("https://blankj.com/images/avatar.jpg").into(ivAvatar);

我們應該使用如下規則:

Picasso.with(context) 
       .load("https://blankj.com/images/avatar.jpg")
       .into(ivAvatar);
4.7.1.3 多參數的換行

當一個方法有很多參數或者參數很長的時候,我們應該在每個 , 後面進行換行。

比如:

loadPicture(context, "https://blankj.com/images/avatar.jpg", ivAvatar, "Avatar of the user", clickListener);

我們應該使用如下規則:

loadPicture(context,        "https://blankj.com/images/avatar.jpg", 
       ivAvatar,        "Avatar of the user",
       clickListener);
4.7.1.4 RxJava 鏈式的換行

RxJava 的每個操作符都需要換新行,並且把換行符插入在 . 之前。

例如:

public Observable<Location> syncLocations() {    return mDatabaseHelper.getAllLocations() 
           .concatMap(new Func1<Location, Observable<? extends Location>>() {                @Override
                public Observable<? extends Location> call(Location location) {                     return mRetrofitService.getLocation(location.id);
                }
           })
           .retry(new Func2<Integer, Throwable, Boolean>() {                 @Override
                public Boolean call(Integer numRetries, Throwable throwable) {                     return throwable instanceof RetrofitError;
                }
           });
}

5 資源文件規範

資源文件命名爲全部小寫,採用下劃線命名法。

如果想對資源文件進行分包可以參考我這篇文章:Android Studio 下對資源進行分包

5.1 動畫資源文件(anim/ 和 animator/)

安卓主要包含屬性動畫和視圖動畫,其視圖動畫包括補間動畫和逐幀動畫。屬性動畫文件需要放在 res/animator/ 目錄下,視圖動畫文件需放在 res/anim/ 目錄下。

命名規則:{模塊名}邏輯名稱

說明:{} 中的內容爲可選,邏輯名稱 可由多個單詞加下劃線組成。

例如:refresh_progress.xmlmarket_cart_add.xmlmarket_cart_remove.xml

如果是普通的補間動畫或者屬性動畫,可採用:動畫類型方向 的命名方式。

例如:

名稱說明
fade_in淡入
fade_out淡出
push_down_in從下方推入
push_down_out從下方推出
push_left推向左方
slide_in_from_top從頭部滑動進入
zoom_enter變形進入
slide_in滑動進入
shrink_to_middle中間縮小

5.2 顏色資源文件(color/)

專門存放顏色相關的資源文件。

命名規則:類型邏輯名稱

例如:sel_btn_font.xml

顏色資源也可以放於 res/drawable/ 目錄,引用時則用 @drawable 來引用,但不推薦這麼做,最好還是把兩者分開。

5.3 圖片資源文件(drawable/ 和 mipmap/)

res/drawable/ 目錄下放的是位圖文件(.png、.9.png、.jpg、.gif)或編譯爲可繪製對象資源子類型的 XML 文件,而 res/mipmap/ 目錄下放的是不同密度的啓動圖標,所以 res/mipmap/ 只用於存放啓動圖標,其餘圖片資源文件都應該放到 res/drawable/ 目錄下。

命名規則:類型{模塊名}邏輯名稱類型{模塊名}顏色

說明:{} 中的內容爲可選;類型 可以是可繪製對象資源類型,也可以是控件類型(具體見附錄UI 控件縮寫表);最後可加後綴 _small 表示小圖,_big 表示大圖。

例如:

名稱說明
btn_main_about.png主頁關於按鍵 類型模塊名邏輯名稱
btn_back.png返回按鍵 類型邏輯名稱
divider_maket_white.png商城白色分割線 類型模塊名顏色
ic_edit.png編輯圖標 類型邏輯名稱
bg_main.png主頁背景 類型邏輯名稱
btn_red.png紅色按鍵 類型顏色
btn_red_big.png紅色大按鍵 類型顏色
ic_head_small.png小頭像圖標 類型邏輯名稱
bg_input.png輸入框背景 類型邏輯名稱
divider_white.png白色分割線 類型顏色
bg_main_head.png主頁頭部背景 類型模塊名邏輯名稱
def_search_cell.png搜索頁面默認單元圖片 類型模塊名邏輯名稱
ic_more_help.png更多幫助圖標 類型邏輯名稱
divider_list_line.png列表分割線 類型邏輯名稱
sel_search_ok.xml搜索界面確認選擇器 類型模塊名邏輯名稱
shape_music_ring.xml音樂界面環形形狀 類型模塊名邏輯名稱

如果有多種形態,如按鈕選擇器:sel_btn_xx.xml,採用如下命名:

名稱說明
sel_btn_xx作用在 btn_xx 上的 selector
btn_xx_normal默認狀態效果
btn_xx_pressedstate_pressed 點擊效果
btn_xx_focusedstate_focused 聚焦效果
btn_xx_disabledstate_enabled 不可用效果
btn_xx_checkedstate_checked 選中效果
btn_xx_selectedstate_selected 選中效果
btn_xx_hoveredstate_hovered 懸停效果
btn_xx_checkablestate_checkable 可選效果
btn_xx_activatedstate_activated 激活效果
btn_xx_window_focusedstate_window_focused 窗口聚焦效果

注意:使用 Android Studio 的插件 SelectorChapek 可以快速生成 selector,前提是命名要規範。

5.4 佈局資源文件(layout/)

命名規則:類型模塊名類型{模塊名}邏輯名稱

說明:{} 中的內容爲可選。

例如:

名稱說明
activity_main.xml主窗體 類型模塊名
activity_main_head.xml主窗體頭部 類型模塊名邏輯名稱
fragment_music.xml音樂片段 類型模塊名
fragment_music_player.xml音樂片段的播放器 類型模塊名邏輯名稱
dialog_loading.xml加載對話框 類型邏輯名稱
ppw_info.xml信息彈窗(PopupWindow) 類型邏輯名稱
item_main_song.xml主頁歌曲列表項 類型模塊名邏輯名稱

5.5 菜單資源文件(menu/)

菜單相關的資源文件應放在該目錄下。

命名規則:{模塊名_}邏輯名稱

說明:{} 中的內容爲可選。

例如:main_drawer.xmlnavigation.xml

5.6 values 資源文件(values/)

values/ 資源文件下的文件都以 s 結尾,如 attrs.xmlcolors.xmldimens.xml,起作用的不是文件名稱,而是 <resources> 標籤下的各種標籤,比如 <style> 決定樣式,<color> 決定顏色,所以,可以把一個大的 xml 文件分割成多個小的文件,比如可以有多個 style 文件,如 styles.xmlstyles_home.xmlstyles_item_details.xmlstyles_forms.xml

5.6.1 colors.xml

<color>name 命名使用下劃線命名法,在你的 colors.xml 文件中應該只是映射顏色的名稱一個 ARGB 值,而沒有其它的。不要使用它爲不同的按鈕來定義 ARGB 值。

例如,不要像下面這樣做:

  <resources> 
     <color name="button_foreground">#FFFFFF</color>
     <color name="button_background">#2A91BD</color>
     <color name="comment_background_inactive">#5F5F5F</color>
     <color name="comment_background_active">#939393</color>
     <color name="comment_foreground">#FFFFFF</color>
     <color name="comment_foreground_important">#FF9D2F</color>
     ...      <color name="comment_shadow">#323232</color>

使用這種格式,會非常容易重複定義 ARGB 值,而且如果應用要改變基色的話會非常困難。同時,這些定義是跟一些環境關聯起來的,如 button 或者 comment,應該放到一個按鈕風格中,而不是在 colors.xml 文件中。

相反,應該這樣做:

  <resources>

     <!-- grayscale -->
     <color name="white"     >#FFFFFF</color>
     <color name="gray_light">#DBDBDB</color>
     <color name="gray"      >#939393</color>
     <color name="gray_dark" >#5F5F5F</color>
     <color name="black"     >#323232</color>

     <!-- basic colors -->
     <color name="green">#27D34D</color>
     <color name="blue">#2A91BD</color>
     <color name="orange">#FF9D2F</color>
     <color name="red">#FF432F</color>

 </resources>

嚮應用設計者那裏要這個調色板,名稱不需要跟 "green""blue" 等等相同。"brand_primary""brand_secondary""brand_negative" 這樣的名字也是完全可以接受的。像這樣規範的顏色很容易修改或重構,會使應用一共使用了多少種不同的顏色變得非常清晰。通常一個具有審美價值的 UI 來說,減少使用顏色的種類是非常重要的。

注意:如果某些顏色和主題有關,那就單獨寫一個 colors_theme.xml

5.6.2 dimens.xml

像對待 colors.xml 一樣對待 dimens.xml 文件,與定義顏色調色板一樣,你同時也應該定義一個空隙間隔和字體大小的“調色板”。 一個好的例子,如下所示:

<resources>

   <!-- font sizes -->
   <dimen name="font_22">22sp</dimen>
   <dimen name="font_18">18sp</dimen>
   <dimen name="font_15">15sp</dimen>
   <dimen name="font_12">12sp</dimen>

   <!-- typical spacing between two views -->
   <dimen name="spacing_40">40dp</dimen>
   <dimen name="spacing_24">24dp</dimen>
   <dimen name="spacing_14">14dp</dimen>
   <dimen name="spacing_10">10dp</dimen>
   <dimen name="spacing_4">4dp</dimen>

   <!-- typical sizes of views -->
   <dimen name="button_height_60">60dp</dimen>
   <dimen name="button_height_40">40dp</dimen>
   <dimen name="button_height_32">32dp</dimen></resources>

佈局時在寫 marginspaddings 時,你應該使用 spacing_xx 尺寸格式來佈局,而不是像對待 string 字符串一樣直接寫值,像這樣規範的尺寸很容易修改或重構,會使應用所有用到的尺寸一目瞭然。 這樣寫會非常有感覺,會使組織和改變風格或佈局非常容易。

5.6.3 strings.xml

<string>name 命名使用下劃線命名法,採用以下規則:{模塊名_}邏輯名稱,這樣方便同一個界面的所有 string 都放到一起,方便查找。

名稱說明
main_menu_about主菜單按鍵文字
friend_title好友模塊標題欄
friend_dialog_del好友刪除提示
login_check_email登錄驗證
dialog_title彈出框標題
button_ok確認鍵
loading加載文字
5.6.4 styles.xml

<style>name 命名使用大駝峯命名法,幾乎每個項目都需要適當的使用 styles.xml 文件,因爲對於一個視圖來說,有一個重複的外觀是很常見的,將所有的外觀細節屬性(colorspaddingfont)放在 styles.xml 文件中。 在應用中對於大多數文本內容,最起碼你應該有一個通用的 styles.xml 文件,例如:

<style name="ContentText"> 
   <item name="android:textSize">@dimen/font_normal</item>
   <item name="android:textColor">@color/basic_black</item>
</style>

應用到 TextView 中:

<TextView 
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@string/price"
   style="@style/ContentText"
   />

或許你需要爲按鈕控件做同樣的事情,不要停止在那裏,將一組相關的和重複 android:xxxx 的屬性放到一個通用的 <style> 中。

5.7 id 命名

命名規則:view縮寫{模塊名}邏輯名,例如: btn_main_searchbtn_back

如果在項目中有用黃油刀的話,使用 AS 的插件:ButterKnife Zelezny,可以非常方便幫助你生成註解;沒用黃油刀的話可以使用 Android Code Generator 插件。

6 版本統一規範

Android 開發存在着衆多版本的不同,比如 compileSdkVersionminSdkVersiontargetSdkVersion 以及項目中依賴第三方庫的版本,不同的 module 及不同的開發人員都有不同的版本,所以需要一個統一版本規範的文件。

具體可以參考我寫的這篇博文:Android 開發之版本統一規範

如果是開發多個系統級別的應用,當多個應用同時用到相同的 so 庫時,一定要確保 so 庫的版本一致,否則可能會引發應用崩潰。

7 第三方庫規範

別再閉門造車了,用用最新最火的技術吧,安利一波:Android 流行框架查速表,順便帶上自己的乾貨:Android 開發人員不得不收集的代碼

希望 Team 能用時下較新的技術,對開源庫的選取,一般都需要選擇比較穩定的版本,作者在維護的項目,要考慮作者對 issue 的解決,以及開發者的知名度等各方面。選取之後,一定的封裝是必要的。

個人推薦 Team 可使用如下優秀輪子:

  • Retrofit

  • RxAndroid

  • OkHttp

  • Glide/Fresco

  • Gson/Fastjson

  • EventBus/AndroidEventBus

  • GreenDao

  • Dagger2(選用)

  • Tinker(選用)

8 註釋規範

爲了減少他人閱讀你代碼的痛苦值,請在關鍵地方做好註釋。

8.1 類註釋

每個類完成後應該有作者姓名和聯繫方式的註釋,對自己的代碼負責。

/** 
* <pre>
*     author : Blankj
*     e-mail : xxx@xx
*     time   : 2017/03/07
*     desc   : xxxx 描述
*     version: 1.0
* </pre>
*/
public class WelcomeActivity {
   ...
}

具體可以在 AS 中自己配製,進入 Settings -> Editor -> File and Code Templates -> Includes -> File Header,輸入

/** 
* <pre>
*     author : ${USER}
*     e-mail : xxx@xx
*     time   : ${YEAR}/${MONTH}/${DAY}
*     desc   :
*     version: 1.0
* </pre>
/

這樣便可在每次新建類的時候自動加上該頭註釋。

8.2 方法註釋

每一個成員方法(包括自定義成員方法、覆蓋方法、屬性方法)的方法頭都必須做方法頭註釋,在方法前一行輸入 /* + 回車 或者設置 Fix doc comment(Settings -> Keymap -> Fix doc comment)快捷鍵,AS 便會幫你生成模板,我們只需要補全參數即可,如下所示。

/** 
* bitmap 轉 byteArr
*
* @param bitmap bitmap 對象
* @param format 格式
* @return 字節數組
*/
public static byte[] bitmap2Bytes(Bitmap bitmap, CompressFormat format) {    if (bitmap == null) return null;
   ByteArrayOutputStream baos = new ByteArrayOutputStream();
   bitmap.compress(format, 100, baos);    return baos.toByteArray();
}

8.3 塊註釋

塊註釋與其周圍的代碼在同一縮進級別。它們可以是 /* ... / 風格,也可以是 // ... 風格(//後最好帶一個空格)。對於多行的 / ... / 註釋,後續行必須從 開始, 並且與前一行的 對齊。以下示例註釋都是 OK 的。

/ 
* This is
* okay.
/
// And so// is this./ Or you can
* even do this. /

註釋不要封閉在由星號或其它字符繪製的框架裏。

Tip:在寫多行註釋時,如果你希望在必要時能重新換行(即註釋像段落風格一樣),那麼使用 / ... */

8.4 其他一些註釋

AS 已幫你集成了一些註釋模板,我們只需要直接使用即可,在代碼中輸入 todofixme 等這些註釋模板,回車後便會出現如下注釋。

// TODO: 17/3/14 需要實現,但目前還未實現的功能的說明// FIXME: 17/3/14 需要修正,甚至代碼是錯誤的,不能工作,需要修復的說明

9 測試規範

業務開發完成之後,開發人員做單元測試,單元測試完成之後,保證單元測試全部通過,同時單元測試代碼覆蓋率達到一定程度(這個需要開發和測試約定,理論上越高越好),開發提測。

9.1 單元測試

測試類的名稱應該是所測試類的名稱加 Test,我們創建 DatabaseHelper 的測試類,其名應該叫 DatabaseHelperTest

測試函數被 @Test 所註解,函數名通常以被測試的方法爲前綴,然後跟隨是前提條件和預期的結果。

  • 模板:void methodName前提條件和預期結果()

  • 例子:void signInWithEmptyEmailFails()

注意:如果函數足夠清晰,那麼前提條件和預期的結果是可以省略的。

有時一個類可能包含大量的方法,同時需要對每個方法進行幾次測試。在這種情況下,建議將測試類分成多個類。例如,如果 DataManager 包含很多方法,我們可能要把它分成 DataManagerSignInTestDataManagerLoadUsersTest 等等。

9.2 Espresso 測試

每個 Espresso 測試通常是針對 Activity,所以其測試名就是其被測的 Activity 的名稱加 Test,比如 SignInActivityTest

10 其他的一些規範

  1. 合理佈局,有效運用 <merge><ViewStub><include> 標籤;

  2. ActivityFragment 裏面有許多重複的操作以及操作步驟,所以我們都需要提供一個 BaseActivityBaseFragment,讓所有的 ActivityFragment 都繼承這個基類。

  3. 方法基本上都按照調用的先後順序在各自區塊中排列;

  4. 相關功能作爲小區塊放在一起(或者封裝掉);

  5. 當一個類有多個構造函數,或是多個同名函數,這些函數應該按順序出現在一起,中間不要放進其它函數;

  6. 數據提供統一的入口。無論是在 MVP、MVC 還是 MVVM 中,提供一個統一的數據入口,都可以讓代碼變得更加易於維護。比如可使用一個 DataManager,把 httppreferenceeventpostdatabase 都放在 DataManager 裏面進行操作,我們只需要與 DataManager打交道;

  7. 多用組合,少用繼承;

  8. 提取方法,去除重複代碼。對於必要的工具類抽取也很重要,這在以後的項目中是可以重用的。

  9. 可引入 Dagger2 減少模塊之間的耦合性。Dagger2 是一個依賴注入框架,使用代碼自動生成創建依賴關係需要的代碼。減少很多模板化的代碼,更易於測試,降低耦合,創建可複用可互換的模塊;

  10. 項目引入 RxAndroid 響應式編程,可以極大的減少邏輯代碼;

  11. 通過引入事件總線,如:EventBusAndroidEventBusRxBus,它允許我們在 DataLayer 中發送事件,以便 ViewLayer 中的多個組件都能夠訂閱到這些事件,減少回調;

  12. 儘可能使用局部變量;

  13. 及時關閉流;

  14. 儘量減少對變量的重複計算;

    如下面的操作:

    for (int i = 0; i < list.size(); i++) { 
         ...
    }

    建議替換爲:

    for (int i = 0, len = list.size(); i < len; i++) { 
         ...
    }
  15. 儘量採用懶加載的策略,即在需要的時候才創建;

    例如:

    String str = "aaa";if (i == 1) { 
         list.add(str);
    }

    建議替換爲:

    if (i == 1) { 
         String str = "aaa";
         list.add(str);
    }
  16. 不要在循環中使用 try…catch…,應該把其放在最外層;

  17. 使用帶緩衝的輸入輸出流進行 IO 操作;

  18. 儘量使用 HashMapArrayListStringBuilder,除非線程安全需要,否則不推薦使用 HashTableVectorStringBuffer,後三者由於使用同步機制而導致了性能開銷;

  19. 儘量在合適的場合使用單例;

    使用單例可以減輕加載的負擔、縮短加載的時間、提高加載的效率,但並不是所有地方都適用於單例,簡單來說,單例主要適用於以下三個方面:

    1. 控制資源的使用,通過線程同步來控制資源的併發訪問。

    2. 控制實例的產生,以達到節約資源的目的。

    3. 控制數據的共享,在不建立直接關聯的條件下,讓多個不相關的進程或線程之間實現通信。

  20. 把一個基本數據類型轉爲字符串,基本數據類型.toString() 是最快的方式,String.valueOf(數據) 次之,數據 + "" 最慢;

  21. 使用 AS 自帶的 Lint 來優化代碼結構(什麼,你不會?右鍵 module、目錄或者文件,選擇 Analyze -> Inspect Code);

  22. 最後不要忘了內存泄漏的檢測;


本文轉載自:https://juejin.im/post/5a24ec145188254dd9363b81

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