Android 開發規範(完結版)

blankj發表於2017-12-04

logo

摘要

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,如果類名稱中包含單詞縮寫,則單詞縮寫的每個字母均應大寫。

描述 例如
Activity Activity 為字尾標識 歡迎頁面類 WelcomeActivity
Adapter Adapter 為字尾標識 新聞詳情介面卡 NewsDetailAdapter
解析類 Parser 為字尾標識 首頁解析類 HomePosterParser
工具方法類 UtilsManager 為字尾標識 執行緒池管理類:ThreadPoolManager
日誌工具類:LogUtilsLogger 也可)
列印工具類:PrinterUtils
資料庫類 DBHelper 字尾標識 新聞資料庫:NewsDBHelper
Service Service 為字尾標識 時間服務 TimeService
BroadcastReceiver Receiver 為字尾標識 推送接收 JPushReceiver
ContentProvider Provider 為字尾標識 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 欄位都是常量。在決定一個欄位是否是一個常量時,得考慮它是否真的感覺像是一個常量。例如,如果觀測任何一個該例項的狀態是可變的,則它幾乎肯定不會是一個常量。只是永遠不打算改變的物件一般是不夠的,它要真的一直不變才能將它示為常量。

// Constants
static final int NUMBER = 5;
static final ImmutableListNAMES = ImmutableList.of("Ed", "Ann");
static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutable
static final SomeMutableType[] EMPTY_ARRAY = {};
enum SomeEnum { ENUM_CONSTANT }

// Not constants
static 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 first
public User loadUser(Context context, int userId);

// Callbacks always go last
public void loadUserAsync(Context context, int userId, UserCallback callback);
複製程式碼

4.5 字串常量的命名和值

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

當時用到這些類的時候,我們 必須 將它們的鍵定義為 static final 欄位,並遵循以下指示作為字首。

欄位名字首
SharedPreferences PREF_
Bundle BUNDLE_
Fragment Arguments ARGUMENT_
Intent Extra EXTRA_
Intent Action ACTION_

說明:雖然 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 資原始檔規範

資原始檔命名為全部小寫,採用下劃線命名法。

如果是元件化開發,我們可以在元件和公共模組間建立一個 ui 模組來專門存放資原始檔,然後讓每個元件都依賴 ui 模組。這樣做的好處是如果老專案要實現元件化的話,只需把資原始檔都放入 ui 模組即可,如果想對資原始檔進行分包,可以參考我這篇文章:Android Studio 下對資源進行分包;還避免了多個模組間資源不能複用的問題。

如果是三方庫開發,其使用到的資原始檔及相關的 name 都應該使用庫名作為字首,這樣做可以避免三方庫資源和實際應用資源重名的衝突。

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_pressed state_pressed 點選效果
btn_xx_focused state_focused 聚焦效果
btn_xx_disabled state_enabled 不可用效果
btn_xx_checked state_checked 選中效果
btn_xx_selected state_selected 選中效果
btn_xx_hovered state_hovered 懸停效果
btn_xx_checkable state_checkable 可選效果
btn_xx_activated state_activated 啟用效果
btn_xx_window_focused state_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 可使用如下優秀輪子:

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. 最後不要忘了記憶體洩漏的檢測;


最後囉嗦幾句:

  • 好的命名規則能夠提高程式碼質量,使得新人加入專案的時候降低理解程式碼的難度;
  • 規矩終究是死的,適合團隊的才是最好的;
  • 命名規範需要團隊一起齊心協力來維護執行,在團隊生活裡,誰都不可能獨善其身;
  • 一開始可能會有些不習慣,持之以恆,總會成功的。

附錄

UI 控制元件縮寫表

名稱 縮寫
Button btn
CheckBox cb
EditText et
FrameLayout fl
GridView gv
ImageButton ib
ImageView iv
LinearLayout ll
ListView lv
ProgressBar pb
RadioButtion rb
RecyclerView rv
RelativeLayout rl
ScrollView sv
SeekBar sb
Spinner spn
TextView tv
ToggleButton tb
VideoView vv
WebView wv

常見的英文單詞縮寫表

名稱 縮寫
average avg
background bg(主要用於佈局和子佈局的背景)
buffer buf
control ctrl
current cur
default def
delete del
document doc
error err
escape esc
icon ic(主要用在 App 的圖示)
increment inc
information info
initial init
image img
Internationalization I18N
length len
library lib
message msg
password pwd
position pos
previous pre
selector sel(主要用於某一 view 多種狀態,不僅包括 ListView 中的 selector,還包括按鈕的 selector)
server srv
string str
temporary tmp
window win

程式中使用單詞縮寫原則:不要用縮寫,除非該縮寫是約定俗成的。

參考

Android 包命名規範

Android 開發最佳實踐

Android 編碼規範

阿里巴巴 Java 開發手冊

Project and code style guidelines

Google Java 程式設計風格指南

小細節,大用途,35 個 Java 程式碼效能優化總結!

版本日誌

  • 17/12/08: 新增元件化和三方庫開發資原始檔規範;
  • 17/12/05: 新增 logo;
  • 17/12/04: 完善按功能分包,修復 typo,定該版為完結版;
  • 17/12/03: 完善程式碼樣式規範和測試規範;
  • 17/12/02: 新增程式碼樣式規範;
  • 17/12/01: 對資原始檔規範進行重構;
  • 17/11/29: 格式化中英混排;
  • 17/03/14: 包名劃分為按功能劃分;
  • 17/03/13: 新增其他註釋;
  • 17/03/08: 規範排版,修復 typo 及新增一些規範;
  • 17/03/07: 修訂目錄排版,完善某些細節;
  • 17/03/06: 釋出初版;

相關文章