Android 開(kāi)發(fā)規(guī)范(個(gè)人版)

前言

做Android開(kāi)發(fā)這么多年,見(jiàn)過(guò)很多人寫(xiě)的代碼(開(kāi)源代碼除外),其中有的寫(xiě)的代碼很簡(jiǎn)潔、很漂亮,讓人看起來(lái)很舒服;有的寫(xiě)的那是一塌糊涂,根本就沒(méi)有心思再往下看。最近就是在做一個(gè)項(xiàng)目,由于之前的代碼很復(fù)雜,需要對(duì)整個(gè)軟件進(jìn)行重構(gòu),我是邊重構(gòu)邊吐槽。說(shuō)多了都是淚??!所以就準(zhǔn)備寫(xiě)一下這篇文章。另外說(shuō)一點(diǎn),本規(guī)范不是標(biāo)準(zhǔn),只是我自己在開(kāi)發(fā)中所遵循的。所以大家看看就行了。

一、命名規(guī)范

1、包的命名:

包的名字都是由小寫(xiě)單詞組成。

1.1、 項(xiàng)目包名(APP的唯一ID)

一般的項(xiàng)目都是 :com.公司名.項(xiàng)目名

1.2、功能模塊包名

這個(gè)包是項(xiàng)目下的功能模塊分包,主要有兩個(gè)命名方式:

  • 按照文件的類(lèi)型命名:
    例如:activity/、adapter/、fragment/ 等。
  • 按照功能模塊命名:
    例如:login/、register/、setting/ 等。

2、類(lèi)名

遵循兩點(diǎn)

  • Java類(lèi)名遵循大駝峰命名規(guī)范。即:以大寫(xiě)字母開(kāi)頭、由多個(gè)單詞組成時(shí),每一個(gè)單詞的首字母都大寫(xiě)。
  • 以功能模塊結(jié)尾。例如:LoginActivity、FriendListAdapter、UserModel等。

3、變量

標(biāo)準(zhǔn)的駝峰命名法。

4、靜態(tài)變量、常量、枚舉類(lèi)型

全部大寫(xiě),由多個(gè)單詞組成的時(shí)候,每個(gè)單詞之間以“_”連接。

5、方法名

  • 遵循駝峰命名
  • 根據(jù)方法實(shí)現(xiàn)功能來(lái)命名
  • 初始化一般以init開(kāi)頭
  • 帶有返回值的以get開(kāi)頭
  • 設(shè)置數(shù)據(jù)的以set開(kāi)頭
    例如:initView()、userLogin()、private UserModel getUserInfo()、setUserFriendList(List<FriendMode> friendList)等。

6、其他

如果代碼中有需要實(shí)現(xiàn)的功能,但是當(dāng)前沒(méi)有實(shí)現(xiàn)的代碼為了標(biāo)記可以打上 //TODO標(biāo)記

private void initView() {
   //TODO
}

二、布局文件優(yōu)化

1、多用<include />、<merge />標(biāo)簽

Android方法建議是一個(gè)布局文件最多嵌套三層,這個(gè)大家應(yīng)該都知道吧,所以要多用<include />、<merge />標(biāo)簽,這樣的好處有兩點(diǎn):

  • 減少層次的嵌套
  • 增強(qiáng)布局的重復(fù)利用性。

2、多用style屬性

這個(gè)不用多說(shuō)了吧,可以減少不少的代碼量。

3、文字顯示用"@string/xxx"

這個(gè)在做軟件的國(guó)際化的時(shí)候,是必須的。就算是不做國(guó)際化在需要修改某一個(gè)文字時(shí)也會(huì)很方便。

4、屏幕適配

做Android的應(yīng)該很關(guān)心這一點(diǎn)吧。關(guān)于這一點(diǎn)應(yīng)該網(wǎng)上有很多的教程告訴我們要怎么去做。我這里主要說(shuō)我常用的方式。
先分析頁(yè)面的結(jié)構(gòu),最外層采用什么樣的layout,如果是LinearLayout 多采用android:layout_weight屬性,如果是RelativeLayout就會(huì)多采用android:layout_centerxxx和android:layout_alignParentxxx屬性

三、代碼風(fēng)格

1、條件判斷

1.1、能用Switch 代替的就用Switch。說(shuō)一下我覺(jué)得用Switch的優(yōu)點(diǎn):
  • 有很多else if的時(shí)候,用switch case比較清晰。
  • 有很多else if的時(shí)候,用switch 的計(jì)算量少。
1.2、多判斷不成立的條件

拿一個(gè)登錄的功能做一個(gè)對(duì)比:
之前的代碼

if (TextUtils.isEmpty(useName)) {
  //如果用戶(hù)名為空
   showMessage("賬號(hào)為空,請(qǐng)輸入登錄賬號(hào)");
} else {
  //如果用戶(hù)名不為空,判斷密碼
  if (TextUtils.isEmpty(pwd)) {
    //如果密碼為空
   showMessage("密碼為空,請(qǐng)輸入密碼");
  }else{
    //如果密碼不為空,登錄
    userLogin();
  }
}

看到這里朋友不要笑,也不要問(wèn)誰(shuí)會(huì)這樣寫(xiě)啊,因?yàn)槲业幕卮鹗牵艺娴囊?jiàn)過(guò)有人這樣寫(xiě)。代碼的邏輯很清晰,但是if..else..的嵌套太多了。而且還加深了代碼的層次。
我們?cè)倏纯锤倪^(guò)之后的代碼:

if (TextUtils.isEmpty(useName)) {
   showMessage("賬號(hào)為空,請(qǐng)輸入登錄賬號(hào)");
   return;
}
if (TextUtils.isEmpty(useName)) {
   showMessage("密碼為空,請(qǐng)輸入密碼");
   return;
}
userLogin();

相比之下,如何?是不是更簡(jiǎn)潔一些?

1.3、判斷為對(duì)象為空和字符串為空字符的寫(xiě)法
1.3.1、判斷一個(gè)對(duì)象是否為null

我見(jiàn)過(guò)很多都是這樣寫(xiě)的

if(null == obj){
  return;
}

關(guān)于這種寫(xiě)法,我不想多說(shuō)什么,但是我不習(xí)慣這種寫(xiě)法,所以我們先看一下Android系統(tǒng)是怎么判斷一個(gè)字符是否為空或者是空字符的寫(xiě)法:

public class TextUtils {
...
  /** * Returns true if the string is null or 0-length.
   * @param str the string to be examined
   * @return true if str is null or zero length
   */
  public static boolean isEmpty(@Nullable CharSequence str) {
      if (str == null || str.length() == 0)
          return true;
      else
          return false;
  }
....
}

這個(gè)是在android.text包下面的一個(gè)工具類(lèi)。借鑒官方的例子,我判斷對(duì)象是否為空一般寫(xiě)法是:

if(obj == null){
  return;
}
1.3.2、判斷字符是否為空字符

我見(jiàn)過(guò)最多的寫(xiě)法是

if(str.equals(""))
  return;
}

可以說(shuō)我之前帶過(guò)的工程師都是這么寫(xiě)的,看到這種寫(xiě)法我都會(huì)讓他們改。原因很簡(jiǎn)單,如果當(dāng)str為null的時(shí)候,這段代碼是會(huì)NullPointerException的,我的寫(xiě)法如下,可以規(guī)避NullPointerException。

if("".equals(str)){
  return;
}

2、final 、static、static final 以及 final static 的區(qū)別

為什么要寫(xiě)這個(gè)呢,感覺(jué)和主題無(wú)關(guān)啊?是啊,本來(lái)我也是不想寫(xiě)這個(gè)的,想起之前有人問(wèn)我:
什么是靜態(tài)變量,什么是常量,靜態(tài)變量和常量之間誰(shuí)更省內(nèi)存?
那這個(gè)問(wèn)題就牽涉到了軟件的內(nèi)存優(yōu)化的問(wèn)題,所以還是提一下吧。

說(shuō)一下個(gè)人理解:

final
  • 修飾類(lèi)時(shí),表示此類(lèi)為最終類(lèi),不能被繼承
  • 修飾方法(不包括構(gòu)造方法)時(shí),此方法不可以被子類(lèi)覆蓋。
  • 修飾變量時(shí),就變成了常量(不可變量),一般會(huì)伴隨static一起使用
static
  • 修飾變量,即為靜態(tài)變量,靜態(tài)變量在內(nèi)存中只有一個(gè)拷貝(省內(nèi)存),JVM只為靜態(tài)分配一次內(nèi)存,在加載類(lèi)的過(guò)程中完成靜態(tài)變量的內(nèi)存分配,可用類(lèi)名直接訪(fǎng)問(wèn),當(dāng)然也可以通過(guò)對(duì)象來(lái)訪(fǎng)問(wèn)(不推薦)。(ps:這就是為什么final的常量會(huì)伴隨static一起使用的原因)
  • static代碼塊是類(lèi)加載時(shí),初始化自動(dòng)執(zhí)行的。如果static代碼塊有多個(gè),JVM將按照它們?cè)陬?lèi)中出現(xiàn)的先后順序依次執(zhí)行它們,每個(gè)代碼塊只會(huì)被執(zhí)行一次。
static{
.....
}
  • static方法可以直接通過(guò)類(lèi)名調(diào)用,static方法只能訪(fǎng)問(wèn)static的變量和方法

上面首先解答了什么是常量、什么是靜態(tài)變量的問(wèn)題,那么再看一下誰(shuí)更省內(nèi)存:

一個(gè)完整的Java程序運(yùn)行過(guò)程會(huì)涉及以下內(nèi)存區(qū)域:
寄存器: JVM內(nèi)部虛擬寄存器,存取速度非???,程序不可控制。
棧: 保存局部變量的值,包括:a.用來(lái)保存基本數(shù)據(jù)類(lèi)型的值;b.保存類(lèi)的 實(shí)例 ,即堆區(qū) 對(duì)象 的引用(指針)。也可以用來(lái)保存加載方法時(shí)的幀。
堆: 用來(lái)存放動(dòng)態(tài)產(chǎn)生的數(shù)據(jù),比如new出來(lái)的 對(duì)象 。注意創(chuàng)建出來(lái)的對(duì)象只包含屬于各自的成員變量,并不包括成員方法。因?yàn)橥粋€(gè)類(lèi)的對(duì)象擁有各自的成員變量,存儲(chǔ)在各自的堆中,但是他們共享該類(lèi)的方法,并不是每創(chuàng)建一個(gè)對(duì)象就把成員方法復(fù)制一次。
常量池: JVM為每個(gè)已加載的類(lèi)型維護(hù)一個(gè)常量池,常量池就是這個(gè)類(lèi)型用到的常量的一個(gè)有序集合。包括直接常量(基本類(lèi)型,String)和對(duì)其他類(lèi)型、方法、字段的 符號(hào)引用(1) 。池中的數(shù)據(jù)和數(shù)組一樣通過(guò)索引訪(fǎng)問(wèn)。由于常量池包含了一個(gè)類(lèi)型所有的對(duì)其他類(lèi)型、方法、字段的符號(hào)引用,所以常量池在Java的動(dòng)態(tài)鏈接中起了核心作用。 常量池存在于堆中 。
代碼段: 用來(lái)存放從硬盤(pán)上讀取的源程序代碼。
全局?jǐn)?shù)據(jù)段: 用來(lái)存放static定義的靜態(tài)成員或全局變量。分配該區(qū)時(shí)內(nèi)存全部清0,結(jié)果變量的初始化為0。

下圖表示內(nèi)存分配圖:

具體參考:Java中的內(nèi)存處理機(jī)制和final、static、final static總結(jié)

static屬性在類(lèi)加載,也就是第一次用到這個(gè)類(lèi)的時(shí)候初始化,對(duì)于后來(lái)的實(shí)例的創(chuàng)建,不再次進(jìn)行初始化。

常量在代碼執(zhí)行之前綁定到內(nèi)存單元,并且在整個(gè)程序執(zhí)行過(guò)程中都指向相同的內(nèi)存單元.
所以,在程序的啟動(dòng)時(shí),常量要相對(duì)與靜態(tài)變量占內(nèi)存,而在軟件運(yùn)行過(guò)程中,他們是一樣的。但是對(duì)于小運(yùn)行內(nèi)存的手機(jī)來(lái)說(shuō),大量的static 和final 是要盡量要避免的。

3、視圖與邏輯處理分開(kāi)

這一點(diǎn)很重要。這也是我寫(xiě)這篇文章的最主要目的,我說(shuō)過(guò),最近是在對(duì)一個(gè)項(xiàng)目進(jìn)行重構(gòu)。其中,有一個(gè)自定義的 View,里面充斥著大量的邏輯代碼還有網(wǎng)絡(luò)請(qǐng)求等等??吹竭@種代碼,我是徹底無(wú)語(yǔ)了。

以下為個(gè)人理解:

View本身只是一個(gè)控件,可以提供接口或者方法讓外部改變其內(nèi)容或者形狀,并且需要把自己的觸摸事件返回給調(diào)用者。需要做什么完全交由調(diào)用者自己去實(shí)現(xiàn)。

所以,一個(gè)視圖,僅僅是做顯示的,怎么能進(jìn)行邏輯代碼的處理?不管從任何角度來(lái)說(shuō),都不應(yīng)該這樣做。

4、分模塊,各司其職

在進(jìn)行代碼重構(gòu)的時(shí)候,抽時(shí)間學(xué)習(xí)了下MVP(Model View Presenter)模式。這里先說(shuō)一下Android中的MVC。在說(shuō)之前先問(wèn)大家一個(gè)問(wèn)題:
在你們做過(guò)的項(xiàng)目中,哪些是M,哪些是V,哪些又是C?

先不要往下看,先仔細(xì)想一想。

相信會(huì)有很多人包括之前的我在內(nèi),都會(huì)認(rèn)為M就是寫(xiě)的一些實(shí)體類(lèi),V就是寫(xiě)的布局文件,C就是寫(xiě)在Activity里面的邏輯代碼。而且有的時(shí)候V和C還是混合的。
但是后來(lái)發(fā)現(xiàn)不是這樣的。

MVC for Android
在Android開(kāi)發(fā)中,比較流行的開(kāi)發(fā)框架模式采用的是MVC框架模式,采用MVC模式的好處是便于UI界面部分的顯示和業(yè)務(wù)邏輯,數(shù)據(jù)處理分開(kāi)。那么Android項(xiàng)目中哪些代碼來(lái)充當(dāng)M,V,C角色呢?
M層:適合做一些業(yè)務(wù)邏輯處理,比如數(shù)據(jù)庫(kù)存取操作,網(wǎng)絡(luò)操作,復(fù)雜的算法,耗時(shí)的任務(wù)等都在model層處理。
V層:應(yīng)用層中處理數(shù)據(jù)顯示的部分,XML布局可以視為V層,顯示Model層的數(shù)據(jù)結(jié)果。
C層:在Android中,Activity處理用戶(hù)交互問(wèn)題,因此可以認(rèn)為Activity是控制器,Activity讀取V視圖層的數(shù)據(jù)(eg.讀取當(dāng)前EditText控件的數(shù)據(jù)),控制用戶(hù)輸入(eg.EditText控件數(shù)據(jù)的輸入),并向Model發(fā)送數(shù)據(jù)請(qǐng)求(eg.發(fā)起網(wǎng)絡(luò)請(qǐng)求等)。

具體可參考鏈接:框架模式 MVC 在Android中的使用
同樣的問(wèn)題,如果采用MVP模式,在你們做過(guò)的項(xiàng)目中,哪些是M,哪些是V,哪些又是P?

View層負(fù)責(zé)處理用戶(hù)事件和視圖部分的展示。在Android中,它可能是Activity或者Fragment類(lèi)。
Model層負(fù)責(zé)訪(fǎng)問(wèn)數(shù)據(jù)。數(shù)據(jù)可以是遠(yuǎn)端的Server API,本地?cái)?shù)據(jù)庫(kù)或者SharedPreference等。
Presenter層是連接(或適配)View和Model的橋梁。

具體參考鏈接:Android開(kāi)發(fā)中的MVP架構(gòu)詳解

本文的重點(diǎn)不是研究什么是MVP和什么是MVC,只是想說(shuō),不管是在代碼重構(gòu)的時(shí)候還是說(shuō)開(kāi)發(fā)新項(xiàng)目的時(shí)候,都應(yīng)該采取一種開(kāi)發(fā)模式,一個(gè)是方便代碼的維護(hù),另一個(gè)就是方便別人閱讀。
就拿之前重構(gòu)的那個(gè)自定義View類(lèi)來(lái)說(shuō),不出bug還好一點(diǎn),一旦出現(xiàn)什么問(wèn)題,那是牽一發(fā)而動(dòng)全身??!代價(jià)很大的。

先寫(xiě)這么多吧,如果想到什么以后再補(bǔ)充。
以上都是個(gè)人觀(guān)點(diǎn),不當(dāng)之處,敬請(qǐng)諒解。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,001評(píng)論 25 709
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線(xiàn)程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,662評(píng)論 18 399
  • 擰開(kāi)散熱器該發(fā)現(xiàn)總有一些油漬飄浮在水面上,而且發(fā)現(xiàn)換機(jī)油時(shí)有水分 原因分析: 發(fā)動(dòng)機(jī)有兩大循環(huán):一個(gè)是冷卻液循環(huán),...
    宏宇_8a57閱讀 266評(píng)論 0 1
  • 逛著逛著到了一間只有零興幾人的大自習(xí)室沒(méi)有空調(diào)大冬天的這樣的自習(xí)室是不受待見(jiàn)的人是趨利避害的動(dòng)物這一點(diǎn)放到進(jìn)化論中...
    長(zhǎng)馬閱讀 453評(píng)論 0 8
  • 單調(diào)的工作,兩點(diǎn)一線(xiàn)的生活,都市的快節(jié)奏讓人喘不過(guò)氣,回到家里,馬上就想窩在沙發(fā)休息一下,選款好沙發(fā),對(duì)于整個(gè)家裝...
    d3b89f968221閱讀 238評(píng)論 0 0

友情鏈接更多精彩內(nèi)容