大家有哪些好的 Android 開發習慣?

本人剛開始學習android開發,經過一段時間的學習感覺一些開發過程中的習慣也十分的重要,比如文件命名,代碼的備份,注釋等。由於沒有人帶,自己在這方面有很多壞的習慣。各位大神有沒有什麼經驗來分享下吧


受一個朋友邀請來回答這問題,以上各位都回答得很不錯,不過對於一個Android開發新手來說,能有更詳細的答案那可能會更有幫助。以下內容是來自eoe的一個網友的回答,但現在已經找不回那個帖子,文字已經珍藏起來。感謝原著作者的。

一、Android編碼規範
1.java代碼中不出現中文,最多注釋中可以出現中文;
2.局部變數命名、靜態成員變數命名:只能包含字母,單詞首字母出第一個都為大寫,其他字母都為小寫;
3.常量命名:只能包含字母和_,字母全部大寫,單詞之間用_隔開;
4.layout中的id命名:命名模式為:view縮寫_模塊名稱_view的邏輯名稱
view的縮寫詳情如下
LinearLayout:ll
RelativeLayout:rl
TextView:tv
ImageView:iv
ImageButton:ib
Button:btn
5.activity中的view變數命名
命名模式為:view縮寫+邏輯名稱
建議:如果layout文件很複雜,建議將layout分成多個模塊,每個模塊定義一個moduleViewHolder,其成員變數包含所屬view
6.strings.xml中的id命名:


命名模式:activity名稱_功能模塊名稱_邏輯名稱 activity名稱_邏輯名稱 common_邏輯名稱
strings.xml中,使用activity名稱注釋,將文件內容區分開來
7.drawable中的圖片命名
命名模式:activity名稱_邏輯名稱/common_邏輯名稱
7.styles.xml:將layout中不斷重現的style提煉出通用的style通用組件,放到styles.xml中;
8.使用layer-list和selector
9.圖片盡量分拆成多個可重用的圖片
10.服務端可以實現的,就不要放在客戶端
11.引用第三方庫要慎重,避免應用大容量的第三方庫,導致客戶端包非常大
12.處理應用全局異常和錯誤,將錯誤以郵件的形式發送給服務端
13.圖片的.9處理
14.使用靜態變數方式實現界面間共享要慎重
15.Log(系統名稱 模塊名稱 介面名稱,詳細描述)
16.單元測試(邏輯測試、界面測試)
17.不要重用父類的handler,對應一個類的handler也不應該讓其子類用到,否則會導致message.what衝突
18.activity中在一個View.OnClickListener中處理所有的邏輯
19.strings.xml中使用%1$s實現字元串的通配
20.如果多個Activity中包含共同的UI處理,那麼可以提煉一個CommonActivity,把通用部分叫由它來處理,其他activity只要繼承它即可
21.使用button+activitgroup實現tab效果時,使用Button.setSelected(true),確保按鈕處於選擇狀態,並使activitygroup的當前activity與該button對應
22.如果所開發的為通用組件,為避免衝突,將drawable/layout/menu/values目錄下的文件名增加前綴
23.數據一定要效驗,例如
字元型轉數字型,如果轉換失敗一定要有預設值;
服務端響應數據是否有效判斷;

二、Android性能優化
1.http用gzip壓縮,設置連接超時時間和響應超時時間
http請求按照業務需求,分為是否可以緩存和不可緩存,那麼在無網路的環境中,仍然通過緩存的httpresponse瀏覽部分數據,實現離線閱讀。
2.listview 性能優化
1).復用convertView
在getItemView中,判斷convertView是否為空,如果不為空,可復用。如果couvertview中的view需要添加listerner,代碼一定要在if(convertView==null){}之外。
2).非同步載入圖片
item中如果包含有webimage,那麼最好非同步載入
3).快速滑動時不顯示圖片
當快速滑動列表時(SCROLL_STATE_FLING),item中的圖片或獲取需要消耗資源的view,可以不顯示出來;而處於其他兩種狀態(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),則將那些view顯示出來
3.使用線程池,分為核心線程池和普通線程池,下載圖片等耗時任務放置在普通線程池,避免耗時任務阻塞線程池後,導致所有非同步任務都必須等待
4.非同步任務,分為核心任務和普通任務,只有核心任務中出現的系統級錯誤才會報錯,非同步任務的ui操作需要判斷原activity是否處於激活狀態
5.盡量避免static成員變數引用資源耗費過多的實例,比如Context
6.使用WeakReference代替強引用,弱引用可以讓您保持對對象的引用,同時允許GC在必要時釋放對象,回收內存。對於那些創建便宜但耗費大量內存的對象,即希望保持該對象,又要在應用程序需要時使用,同時希望GC必要時回收時,可以考慮使用弱引用。
7.超級大胖子Bitmap
及時的銷毀(Activity的onDestroy時,將bitmap回收)
設置一定的採樣率
巧妙的運用軟引用
drawable對應resid的資源,bitmap對應其他資源8.保證Cursor 佔用的內存被及時的釋放掉,而不是等待GC來處理。並且 Android明顯是傾向於編程者手動的將Cursor close掉
9.線程也是造成內存泄露的一個重要的源頭。線程產生內存泄露的主要原因在於線程生命周期的不可控
10.如果ImageView的圖片是來自網路,進行非同步載入
11.應用開發中自定義View的時候,交互部分,千萬不要寫成線程不斷刷新界面顯示,而是根據TouchListener事件主動觸發界面的更新


三、Android UI優化
1.layout組件化,盡量使用merge及include復用
2.使用styles,復用樣式定義
3.軟鍵盤的彈出控制,不要讓其覆蓋輸入框
4.數字、字母和漢字混排佔位問題:將數字和字母全形化。由於現在大多數情況下我們的輸入都是半形,所以 字母和數字的佔位無法確定,但是一旦全形化之後,數字、字母的佔位就和一個漢字的佔位相同了,這樣就可以避免由於佔位導致的排版問題。
5.英文文檔排版:textview自動換行時要保持單詞的完整性,解決方案是計算字元串長度,然後手動設定每一行顯示多少個字母並加上『n『
6.複雜布局使用RelativeLayout
7.自適應屏幕,使用dp替代pix
8.使用android:layout_weight或者TableLayout製作等分布局
9.使用animation-list製作動畫效果


四、其他的一些Android開發建議

1.跟上時代的步伐,把Eclipse換成Android Studio,把SVN換成Git,這當然要適合項目開發屬性的需要,Git學習中文網站:https://git-scm.com/book/zh/v2

2.勤做總結,推薦使用印象筆記,把一些懂的經驗總結起來,把還不懂的文章掛裡面,有時間就慢慢消化;

3.定期code review,不斷迭代,你總會發現一些不合理的代碼,或者需要優化的地方。

4.關注一些知名的技術大V或網站,裡面許多東西值得你去消化,推薦:Android 開源項目集合,http://tech.meituan.com/,stormzhang,但總歸來說,去Android官網或者參考一份Java API文檔雖說枯燥,但熟悉之後,你會有更大的進步。

5.如想更深入了解可閱讀珍藏許久的文章:Android應用程序開發以及背後的設計思想深度剖析

6.如果你公司沒有強大的測試團隊,發布應用前最好把應用放到測試平台去測測,比如雲測之類的;

7.取應用包名的時候切忌取太容易重複的,如果同款手機已經有該包名,那麼會因為簽名不同而導致安裝不上,這也怪中國安卓市場太多,無法像Google Play那樣進行包名審核。


我覺得首先是命名規範。命名規範這種東西每個人都有自己的風格,Google 也有自己的一套規範(多看看 Android 系統源碼就明白了)。好的規範可以有效地提高代碼的可讀性,對於將來接手代碼的小夥伴也是一件幸事。題主可以自行 Google 一下 Java (Android)命名規範,會由不少的博客介紹。

其次是注釋。嚴格來說這個應該屬於命名規範的範疇。注釋一方面是幫助自己記憶 ,另一方面是團隊協作中的一個規範,特別是對於開發 API 的小夥伴來說,總不能天天被人跟在屁股後面問你這個介面是什麼作用,你這參數是什麼意思?好的注釋配合好的命名規範,可以省去很多溝通上的成本。 注釋至少要有如下幾方面的內容:

  1. 該介面(或類)的作用。注意寫的應該是作用,而不是你做了什麼;
  2. 參數列表的各個參數說明;
  3. 返回值的說明;
  4. 如果有異常拋出,對拋出異常的說明;
  5. 如果注釋是在類上的,總得留個聯繫方式吧,免得以後出了問題都找不到原作者。當然了,如果類設計的過於讓人蛋疼,我也可以聯繫到作者,約出來,打一頓的嘛。
  6. 其他你認為有必要解釋。

再者是版本控制。就算是自己一個人寫代碼,版本控制也是有必要的。Git 也好,SVN 也好,都是有幫助的。版本控制一方面是對自己代碼的一個備份,另一方面,如果想回滾到歷史版本也是極有幫助的。所以最好能夠熟悉一下 Git 或者 SVN 的使用。

第四是名詞表。這個應該屬於肥肥自創。Android 是圍繞四大組件特別是 Activity 和 Service 進行開發的,但是如果項目龐大,有多個 Activity 存在,那麼記住每一個 Activity 的類名是很難得,但是記住每一個 Activity 的功能卻相對容易。故而肥肥在帶項目的過程中自創了名詞表這一東東。記錄了每一個模塊、組件、甚至是每一個 Activity 的官方統一名稱(比如,功能是作品列表的 Activity,名稱就叫作品列表頁,對應的類是 WorksListActivity),在溝通過程中,大家(包括測試人員等項目相關人員)統一說「作品列表頁」。當時的初衷是解決測試團隊的Bug 描述過於模糊(如果有多個列表頁,測試人員往往會寫列表頁****問題)。

第五是不要重複製造輪子。這個適用於代碼層面以及業務層面。

第六是千萬不要餓著肚子寫代碼。先挖這個坑,吃了東西再找時間繼續寫。
=========================剛才公司小夥伴在互換禮物,來晚了===================
第七這一點應該不算是習慣,但是卻需要每一個 Android 程序猿需要慎重再者慎重的地方---內存管理。Android 雖然延續了 Java 的垃圾回收機制,但是並不意味 Android 應用程序就不會出現內存問題。在 Android 中引起內存開銷過大的往往是 BitMap 對象。BitMap.java實際上是 Skia 引擎中對圖片處理部分的 Java 層代碼而已(真正工作的是 C++層代碼,通過 JNI 封裝,最後提供 Java 層的介面),那麼你創建 BitMap 對象實際上是創建了兩部分內存,一部分是 Java 層的,就是 BitMap對象,Java 的垃圾回收會在合適的時機回收這一部分內存。另一部分是 C++層面的,也就是通過 JNI 調用 C++層的代碼分配的那一部分內存。Java 的垃圾回收是不會回收這一部分內存的,所以如果不手動釋放的話就容易引起內存問題。

第八是千萬不要阻塞用戶主線程。用戶主線程就是 UI 線程,主要負責 UI 的繪製(除 SurfaceView 外,其他 View 對象都是需要在 UI 線程中進程操作的)。為了保證 App 的交互儘可能的流程,請不要在 UI 線程中進行耗時操作(文件讀寫、Http 請求(4.0之前可以在主線程中發起)等)。否則會引起兩種可能的問題:第一是造成用戶交互極度不流暢,第二容易觸發 ANR 的超時機制(UI 線程5秒,廣播10秒)。

第九是嚴格把控生命周期(Activity、Service、ContentProvider 等)。在每一個生命周期事件中,明確應該做什麼不應該做什麼是很有必要的,不然也會容易造成各種莫名其妙的問題(比如 onCreate 中使用了 onResume 中才初始化的對象)。

第十是在使用 XML 文件進行 UI 布局時,應該盡量減少 Layout 的嵌套層級。Layout 的過度嵌套會造成渲染時資源開銷過大的問題。

寫到這裡突然不想寫了。由於右手受傷的緣故,今天高強度的鍵盤敲擊已經造成右手非常疲憊。等有時間的時候再寫吧。

==============2014年12月26日=============
第一次在知乎上的得到這麼多贊(竟然有24了),受寵若驚,決定繼續補充前面的各種廢話。

第十一應該是資源文件的使用,資源文件包括圖片、字元串、尺寸、顏色等等。在使用尺寸資源的時候應該盡量使用像素無關的單位,比如 dp 和 sp。而字元串資源(比如 Button 上顯示的名稱)也應該儘可能的抽離出來,使用 res/value 下的xml 文件進行維護。一方面方便日後管理,另一方面方便國際化。

第十二多線程以及線程池的使用。前面說過應該盡量避免在主線程中執行耗時操作,那麼多線程就變得很有必要。對於 Java 來說,線程的創建與銷毀是非常佔用資源的,線程的濫用(隨手 new Thread 等)會造成 App 整體性能的下降。Java 提供了Executors的線程池方案,而 Android 自身也提供了AsyncTask 這樣的非同步任務方案(實際上也是線程池)。

第十三 Java 的許可權控制機制。Java提供了public, private, protected 三個訪問許可權修飾詞,提供了以下四種訪問許可權控制機制:

  1. 包訪問許可權;
  2. Public訪問許可權;
  3. Private訪問許可權;
  4. Protected訪問許可權;

訪問許可權的合理使用可以有效地隱藏實現,避免將不必要的數據或介面暴露出來。

第十四 final 和 static 關鍵字的合理使用。很多人覺得這是很基礎的東西,但是 final 和 static 關鍵字的合理使用能夠有效提升代碼的執行效率,而不合理使用則後患無窮。

第十五 Android 設備的內存資源是極度珍貴的,合理的使用、回收內存也是一種好的編程習慣。Java 對象的引用類型會影響到垃圾回收對象的時機。Java 有強引用、 軟引用、 弱引用、虛引用,以及 Android 增加的 Lru 內存管理。建議題主了解一下這四種引用類型的特點以及 Lru 內存管理的具體實現。

第十六 介面和抽象類。這是老生常談的話題了,但卻是永恆的話題。介面和抽象類的合理使用,可以增加代碼的可維護性和擴展性。介面和抽象類也是各種設計模式的基石。

第十七 軟體設計的六大設計原則,即

  1. 針對介面編程,不針對實現編程
  2. 單一職責原則
  3. 開放封閉原則
  4. 里氏代換原則
  5. 迪米特法則
  6. 合成聚合復用原則

第十八 統一項目的編碼格式(推薦使用 UTF-8)。如果多人協作,這種舉措顯得尤為重要。由此引申出來的另外一個規範就是,規範統一命名風格,即團隊中使用相同的命名風格。(感謝@徐強 的提醒。更新於2014年12月30日17:22:20)

第十九 TextView(往往 TextView 派生子類同樣適用)調用 setText 方法設置一個 int 型的數據,千萬要將該值轉為 String,否則在某些設備中它會默認去查詢 R 文件中定義的資源

第二十 使用友盟分享 SDK,需要執行分享的 Activity 請不要為該 Activity 設置android:process屬性。比如你的 App 運行在 com.codingfish.test 進程,需要產生分享動作的Activity 設置 android:proces=":com.codingfish.hello" ,那麼新浪微博就會出現你設置的分享內容沒有顯示的問題。該 Bug 已經提交給友盟的技術人員,但是 N 久沒有得到修復。

第二十一 上線之前一定要使用正式簽名打包。某朋友公司 Android 的應用上架之前,負責打包上線的童鞋(新人,老人已離職,只有這一個Android)沒有簽名的概念,直接將 Debug 簽名的 Apk 投放到渠道了,到現在還有一批設備沒有替換回來。

第二十二 在 Activity 中儘可能少的創建 Handler 對象,創建一個主線程 Handler,一個後台 HandlerThread 就可以了。

第二十三 使用線程的地方盡量不要 new Thread,而是使用 AsyncThread 。

第二十四 onCreate(Bundle savedInstanceState) 切記將super.onCreate(savedInstanceState);放在一切業務的前面。

第二十五 創建了四大組件一定記得要在 AndroidManifest 文件中聲明(當然 BroadcastReceiver 可以動態註冊)。

=============2015年01月09日補充==============

剛才 Google 了一下,有一篇文章介紹的不錯,肥肥做了一次伸手黨,直接複製過來了。
原文鏈接: Android生存指南之:開發中的注意事項_Android_腳本之家

第二十六 為Activity聲明系統配置變更事件 系統配置變更事件是指轉屏,區域語言發生變化,屏幕尺寸發生變化等等,如果Activity沒有聲明處理這些事件,發生事件時,系統會把Activity殺掉然後重啟,並嘗試恢復狀態,Activity有機會通過onSaveInstanceState()保存一些基本數據到Bundle中,然後此Bundle會在Activity的onCreate()中傳遞過去。雖然這貌似正常,但是這會引發問題,因為很多其他的東西比如Dialog等是要依賴於具體Activity實例的。所以這種系統默認行為通常都不是我們想要的。
為了避免這些系統默認行為,就需要為Activity聲明這些配置,如下二個是每個Activity必須聲明的:
&
幾乎所有的Activity都要聲明如上,為什麼Android不把它們變成Default的呢?

第二十七 盡量使用Android的API 這好像是廢話,在Android上面開發不用Android API用什麼?因為Android幾乎支持Java SE所有的API,所以有很多地方Android API與Java SE的API會有重複的地方,比如說對於文件的操作最好使用Android裡面Context封裝的API,而不要直接使用File對象:
Context.openFileOutput(String); // no File file = new File(String)
原因就是API裡面會考慮到Android平台本身的特性;再如,少用Thread,而多使用AsyncTask等。

第二十八 要考慮到Activity和進程被殺掉的情況 如了通常情況退出Activity外,還有Activity因其他原因被殺的情況,比如系統內存過低,系統配置變更,有異常等等,要考慮和測試這種情況,特別是Activity處理重要的數據時,做好的數據的保存。

第二十九 小心多語言 有些語言真的很啰嗦,中文或英文很簡短就能表達的事情到了其他語言就變的死長死長的,所以如果是wrap_content就可能把其他控制擠出可視範圍; 如果是指定長度就可能顯示不全。也要注意特殊語言比如那些從右向左讀的語言。

第三十 不要用四大組件去實現介面 一是組件的對象都比較大,實現介面比較浪費,而且讓代碼更不易讀和理解; 另外更重要的是導致多方引用,可能會引發內存泄露。

第三十二 用getApplication()來取Context當參數 對於需要使用Context對象作為參數的函數,要使用getApplication()獲取Context對象當參數,而不要使用this,除非你需要特定的組件實例!getApplication()返回的Context是屬於Application的,它會在整個應用的生命周期內存在,遠大於某個組件的生命周期,所以即使某個引用長期持有Context對象也不會引發內存泄露。

第三十三 主線程只做UI控制和Frameworks回調相關的事。附屬線程只做費時的後台操作。交互只通過Handler。這樣就可以避免大量的線程問題。

第三十四 Frameworks的回調不要做太多事情僅做必要的初始化,其他不是很重要的事情可以放到其他線程中去做,或者用Handler Schedule到稍後再做。

第三十五 要考慮多解析度 至少為hdpi, mdpi, ldpi準備圖片和布局。元素的單位也儘可能的使用dip而不要用px。

第三十六 利用Android手機的硬鍵 幾乎所有的Android手機都有BACK和MENU,它們的作用是返回和彈出菜單,所以就不要再在UI中設計返回按扭和菜單按扭。很多優秀的應用如隨手記和微信都有返回鍵,他們之所以有是因為他們都是從iOS上移植過來的,為了保存體驗的一致,所以也有了返回和菜單。但這不夠Android化,一個純正的Android是沒有必須重複硬鍵的功能的。
PS:多看看官方的 APIDEMO,多讀一下Android 上的內容。肥肥上面的各種廢話,題主基本都能在開發者官網找到。

第三十七 最好的一個習慣,放到最後壓軸吧。善用 Google 和知乎。

PS: 鑒於 Android 官網和 Google 不能打開,肥肥還是順便分享一個 hosts 文件吧,至於怎麼用,不多說了吧? hosts_免費高速下載
===================上面的 hosts 已經廢棄,下面的是最新的host 託管地址============

https://coding.net/u/levi/p/imouto-host/git 感謝這位前輩。


  1. 寫一個抽象的BaseActivity.java,將初始化抽象為setContentView()、findViews()、getData()、showContent()這四個方法,所有的Activity都繼承它:

public abstract class BaseActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
}

public void init(){
setContentView();
findViews();
getData();
showContent();
}

public abstract void setContentView();
public abstract void findViews();
public abstract void getData();
public abstract void showContent();
}

2. 方法返回值不返回null,返回空值,比如:

public static List& getFolderFiles( String folderPath ){
List& fileList = new ArrayList&( );
if( TextUtils.isEmpty( folderPath ) ){
return fileList;
}

File file = new File( folderPath );
if( file.isDirectory( ) ){
File[] files = file.listFiles( );
if( null != files ){
fileList = new ArrayList&( );
for( File subFile : files ){
fileList.add( subFile.getPath( ) );
}
}
}

return fileList;
}

3. 在方法頂部做入參判斷,入參異常直接返回,比如:

public static List& getAssertsFiles( Context context ){
List& assertsFileList = new ArrayList&( );
if( null == context ){
return assertsFileList;
}

AssetManager assetManager = context.getAssets();
String[] files = null;
try {
files = assetManager.list("");
assertsFileList = Arrays.asList( files );
} catch (IOException e) {
e.printStackTrace( );
}

return assertsFileList;
}

4. 使用List、Map而不是ArrayList、HashMap聲明成員變數、作為返回值、作為入參等等,盡量用抽象而不是具體實現。

5. 在任何時候不使用類的finalize方法釋放資源;

6. 在finally中關閉文件流、cursor等等;

7. 封裝一個DebugUtils類來管理程序中所有的列印:

public class DebugUtils{
public static final String TAG = "Debug";

private DebugUtils( ){

}

public static void println( String printInfo ){
if( Debug.DEBUG_MODE null != printInfo ){
System.out.println( printInfo );
}
}

public static void print( String printInfo ){
if( Debug.DEBUG_MODE null != printInfo ){
System.out.print( printInfo );
}
}

public static void printLogI( String logInfo ){
printLogI( TAG, logInfo );
}

public static void printLogI( String tag, String logInfo ){
if( Debug.DEBUG_MODE null != tag null != logInfo ){
Log.i( tag, logInfo );
}
}

public static void printLogE( String logInfo ){
printLogE( TAG, logInfo );
}

public static void printLogE( String tag, String logInfo ){
if( Debug.DEBUG_MODE null != tag null != logInfo ){
Log.e( tag, logInfo );
}
}

public static void printLogW( String logInfo ){
printLogW( TAG, logInfo );
}

public static void printLogW( String tag, String logInfo ){
if( Debug.DEBUG_MODE null != tag null != logInfo ){
Log.w( tag, logInfo );
}
}

public static void printLogD( String logInfo ){
printLogD( TAG, logInfo );
}

public static void printLogD( String tag, String logInfo ){
if( Debug.DEBUG_MODE null != tag null != logInfo ){
Log.d( tag, logInfo );
}
}

public static void printLogV( String logInfo ){
printLogV( TAG, logInfo );
}

public static void printLogV( String tag, String logInfo ){
if( Debug.DEBUG_MODE null != tag || null != logInfo ){
Log.v( tag, logInfo );
}
}

public static void printLogWtf( String logInfo ){
printLogWtf( TAG, logInfo );
}

public static void printLogWtf( String tag, String logInfo ){
if( Debug.DEBUG_MODE null != tag null != logInfo ){
Log.wtf( tag, logInfo );
}
}

public static void showToast( Context context, String toastInfo ){
if( null != context null != toastInfo ){
Toast.makeText( context, toastInfo, Toast.LENGTH_LONG ).show( );
}
}

public static void showToast( Context context, String toastInfo, int timeLen ){
if( null != context null != toastInfo ( timeLen &> 0 ) ){
Toast.makeText( context, toastInfo, timeLen ).show( );
}
}

public static void printBaseInfo( ){
if( Debug.DEBUG_MODE ){
StringBuffer strBuffer = new StringBuffer( );
StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );

strBuffer.append( "; class:" ).append( stackTrace[ 1 ].getClassName( ) )
.append( "; method:" ).append( stackTrace[ 1 ].getMethodName( ) )
.append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) )
.append( "; fileName:" ).append( stackTrace[ 1 ].getFileName( ) );

println( strBuffer.toString( ) );
}
}

public static void printFileNameAndLinerNumber( ){
if( Debug.DEBUG_MODE ){
StringBuffer strBuffer = new StringBuffer( );
StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );

strBuffer.append( "; fileName:" ).append( stackTrace[ 1 ].getFileName( ) )
.append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) );

println( strBuffer.toString( ) );
}
}

public static int printLineNumber( ){
if( Debug.DEBUG_MODE ){
StringBuffer strBuffer = new StringBuffer( );
StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );

strBuffer.append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) );

println( strBuffer.toString( ) );
return stackTrace[ 1 ].getLineNumber( );
}else{
return 0;
}
}

public static void printMethod( ){
if( Debug.DEBUG_MODE ){
StringBuffer strBuffer = new StringBuffer( );
StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );

strBuffer.append( "; number:" ).append( stackTrace[ 1 ].getMethodName( ) );

println( strBuffer.toString( ) );
}
}

public static void printFileNameAndLinerNumber( String printInfo ){
if( null == printInfo || !Debug.DEBUG_MODE ){
return;
}
StringBuffer strBuffer = new StringBuffer( );
StackTraceElement[ ] stackTrace = new Throwable( ).getStackTrace( );

strBuffer.append( "; fileName:" ).append( stackTrace[ 1 ].getFileName( ) )
.append( "; number:" ).append( stackTrace[ 1 ].getLineNumber( ) ).append( "
" )
.append( ( null != printInfo ) ? printInfo : "" );

println( strBuffer.toString( ) );
}

public static void showStrictMode( ) {
if (DebugUtils.Debug.DEBUG_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects().detectLeakedClosableObjects().penaltyLog().penaltyDeath().build());
}
}

public static void d(String tag, String msg){
if(DebugUtils.Debug.DEBUG_MODE){
Log.d(tag, msg);
}
}

public class Debug{
public static final boolean DEBUG_MODE = true;
}
}

8. 靜態類的構造方法私有化,具體實例參見7;

9. 沒有必要用硬體加速的頁面在AndroidMainfest.xml中將其關掉

android:hardwareAccelerated="false"

10. 在沒有使用到多點觸控的頁面,通過在主題中設置下列兩個屬性將多點觸控關掉:

&false&
&false&

11. 定義一個防止視圖重複點擊的靜態類,在需要做防止重複點擊的地方加上該判斷:

public class BtnClickUtils {
private static long mLastClickTime = 0;

private BtnClickUtils( ){

}

public static boolean isFastDoubleClick() {
long time = System.currentTimeMillis();
long timeD = time - mLastClickTime;
if ( 0 &< timeD timeD &< 500) { return true; } mLastClickTime = time; return false; } }

12. 在布局中使用LinearLayout的android:weight屬性時,將view的android:layout_width屬性設置為0dp;

13. 涉及到APP的核心數據全部加密處理,在使用的時候解密;

14. 如果你的IDE是Eclipse,用好這幾個快捷鍵,爽歪歪滴:

  • CTRL + SHIFT + O
  • ALT + SHIFT + M
  • ALT + SHIFT + S
  • CTRL + O
  • CTRL + /
  • ALT + /
  • CTRL + D
  • CTRL + K
  • ALT + → 和 ALT + 左箭頭
  • CTRL + SHIFT + X 和 CTRL + SHIFT + Y
  • F5、F6、F7、F8
  • F11
  • CTRL + M

還有很多很多,先就寫在這裡吧,希望對題主有幫助。
再補一刀:如果題主有代碼潔癖,很注重代碼質量,可以詳盡閱讀SonarQube關於JAVA編碼的規則:Sonar@OSC - 代碼質量分析平台,如果條件允許,自己配置一下,每周通過Sonar運行一下自己的代碼,以提高代碼質量。

一些開發中的細節問題可以參見我的博客:首頁 | 張明雲的博客


說說應用層的開發:
1,寫代碼前先在本子上畫好大概的模塊圖,類圖,能和同事討論下最好。
2,使用開源代碼務必搞懂主要實現方式,以便填坑。
3,應用內的資源要統一管理,如線程,緩存,網路,資料庫,配置,設備事件監聽等等,以便性能分析和維護。
4,合理使用設計模式,做好UI之間,和業務以及數據層的解耦非常必要,以便應付產品汪(褒義詞)的各種變化。
5,管理好各種對象的生命周期,誰說Java不泄漏內存?來我泄漏給你看看。
6,不要總是TODO,因為翔留下就留下了,還得寫新的翔,等後來的童鞋來收拾,被人罵罵也不好。
7,沒事多看下Github,Android程序基本可以用一個個開源庫搭積木搞出來,當然業務除外。也可以看看其他平台的庫和各種框架,不要把自己局限在Android上。
8,關於注釋,盡量寫未來自己還看得懂的注釋,曾經閱讀同事的代碼,懂了之後留下一句注釋,半年後再次閱讀,感嘆道,辛虧老子留了條注釋,不然又看不懂了。
9,代碼風格團隊要統一
10,提高基本素質,做程序員,而不是Android程序員。

Google下,有Github上的Android常用庫和框架這樣的文章。

手機碼字,見諒


上邊說的好多都是個人習慣而已,說不上好和不好(比如抽象出基類,寫一些抽象方法什麼的。。。雖然大部分情況下適用,但是很有一些情況這種做法是十分不靈活的)。你去看GitHub上邊的優秀開源項目,除了變數命名清晰外,也抽象不出什麼統一規範(當然,寫ListView善用viewholder做緩存這種人盡皆知的就不說了)

拋開代碼,要說有什麼好習慣,我覺得有一點就是——一定全部用xml一點一點寫靜態界面,前邊可視化部分只用來預覽,千萬別往裡邊拖控制項。養成良好習慣,從初學做起。

//分割線
啊對了,安利一下,如果要是在意代碼風格以及維護的話,《重構》這本書很不錯。雖然是講重構的,但是如果在初期就注意書中說的一些點的話,是能編寫出質量非常高,可維護性非常高的代碼的。當然,書中關於局部變數的控制的苛刻程度太高了,到了令人髮指的地步。總之,還是很有裨益的。


推薦一篇來自阿里技術協會(ATA)的文章——《 Android性能與優化》:本文比較全面的描述了一般項目在Android開發中如何提高性能,如何著手調優項目,如果你目前正在優化你的Android項目,恭喜你,這正是你要的,也十分歡迎拍磚代。

作者:龔振傑

I. 編碼習慣

盡量避免分配內存(創建對象)

  • 如果一個方法返回一個String,並且這個方法的返回值始終都是被用來append到一個StringBuffer上,就改為傳入StringBuffer直接append上去,避免創建一個短生命周期的臨時對象;
  • 如果使用的字元串是截取自某一個字元串,就直接從那個字元串上面substring,不要拷貝一份,因為通過substring雖然創建了新的String對象,但是共享了裡面的char數組中的char對象,減少了這塊對象的創建;
  • 盡量使用多個一維數組,其性能高於多維數組;int數組性能遠大於Integer數組性能;

儘可能static方法

如果你確定不需要訪問類成員,讓方法static,這樣調用時可以提升15%~20%的速度,因為不需要切換對象狀態。

儘可能使用常量而非變數*

如果某個參數是常量,別忘了使用static final,這樣可以讓Class首次初始化時,不需要調用&來創建static方法,而是在編譯時就直接將常量替換代碼中使用的位置。

從性能層面出發,儘可能直接訪問變數而非方法*

Android開發中,類內盡量避免通過get/set訪問成員變數,雖然這在語言的開發中是一個好的習慣,但是Android虛擬機中,對方法的調用開銷遠大於對變數的直接訪問。在沒有JIT的情況下,直接的變數訪問比調用方法快3倍,在JIT下,直接的變數訪問更是比調用方法快7倍!

對被內部類調用的方法/變數改為包可見

當內部類需要訪問外部類的私有方法/變數時,考慮將這些外部類的私有方法/變數改用包可見的方式。首先在編寫代碼的時候,通過內部類訪問外部類的私有方法/變數是合法的,但是在編譯的時候為了滿足這個會將需要被內部類訪問的私有方法/變數封裝一層包可見的方法,實現讓內部類訪問這些私有的方法/變數,根據前面我們有提到說方法的調用開銷大於變數的調用,因此這樣使得性能變差,所以我們在編碼的時候可以考慮直接將需要被內部類調用的外部類私有方法/變數,改為包可見。

少用float*

  • 盡量少使用float。在很多現代設備中,double的性能與float的性能幾乎沒有差別,但是從大小上面double是float的兩倍的大小。
  • 盡量考慮使用整型而非浮點數,在較好的Android設備中,浮點數比整型慢一倍。

使用乘法代替除法

盡量不要使用除法操作,有很多處理器有乘法器,但是沒有除法器,也就是說在這些設備中需要將除法分解為其他的計算方式速度會比較慢。

使用內部實現,而非上層實現

盡量使用系統sdk中提供的方法,而非自己去實現。如String.indexOf()相關的API,Dalvik將會替換為內部方法;System.arraycopy()方法在Nexus One手機上,會比我們上層寫的類似方法的執行速度快9倍。

謹慎編寫Native

Android JVM相關知識,可參看: ART、Dalvik Android JNI、NDK相關知識,可參看: NDK

寫native性能不一定更好,Native並不是用於使得性能更好,而是用於有些已經存在的庫是使用native語言實現的,我們需要引入Android,這時才使用。

  • 需要多出開銷在維持Java-native的通信,Java調用JNI的耗時較Java調用Java肯定更慢,雖然隨著JDK版本的升級,差距已經越來越小(JDK1.6版本是5倍Java調用Java方法的耗時
  • 在native中創建的資源由於在native heap上面,因此需要主動的釋放,但也因此對應用而言沒有OOM的問題,並且不需要考慮GC時鎖線程帶來的掉幀,如Facebook的Fresco就是將圖片緩存到Native Heap中
  • 需要對不同的處理器架構進行支持,存在明顯的兼容性問題需要解決
  • 如果是Dalvik,將省去了由JIT編譯期轉為本地代碼的這個步驟

一些重要的參數之類,也可以考慮放在Native層,保證安全性。參考: Android應用程序通用自動脫殼方法研究

權衡面向介面編程

在沒有JIT的設備中,面向介面編程的模式(如Map map),相比直接訪問對象類(如HashMap map),會慢6%,但是在存在JIT的設備中,兩者的速度差不多。但是內存佔用方面面向介面變成會消耗更多內存,因此如果你的面向介面編程不是十分的必要的情況下可以考慮不用。

重複訪問的變數,賦值為本地變數

在沒有JIT的設備中,訪問本地化變數相對與成員變數會快20%,但是在存在JIT的設備中,兩者速度差不多。

遍歷優化

盡量使用Iterable而不是通過長度判斷來進行遍歷。

// 這種性能是最差的,JIT也無法對其優化。
public void zero() {
int sum = 0;
for (int i = 0; i &< mArray.length; ++i) { sum += mArray[i].mSplat; } } // 相對zero()來說,這種寫法會更快些,在存在JIT的情況下速度幾乎和two()速度一樣快。 public void one() { int sum = 0; // 1) 通過本地化變數,減少查詢,在不存在JIT的手機下,優化較明顯。 Foo[] localArray = mArray; // 2) 獲取隊列長度,減少每次遍歷訪問變數的長度,有效優化。 int len = localArray.length; for (int i = 0; i &< len; ++i) { sum += localArray[i].mSplat; } } // 在無JIT的設備中,是最快的遍歷方式,在存在JIT的設備中,與one()差不多快。 public void two() { int sum = 0; for (Foo a : mArray) { sum += a.mSplat; } }

II. 資料庫相關

建多索引的原則: 哪個欄位可以最快的**減少查詢**結果,就把該欄位放在最前面

無法使用索引的情況

  • 操作符BETWEEN、LIKE、OR
  • 表達式
  • CASE WHEN

不推薦

  • 不要設計出索引是其他索引的前綴(沒有意義)
  • 更新時拒絕直接全量更新,要更新哪列就put哪列的數據
  • 如果最頻繁的是更新與插入,別建很多索引 (原本表就很小就也沒必要建)
  • 拒絕用大字元串創建索引
  • 避免建太多索引,查詢時可能就不會選擇最好的來執行

推薦

  • 多使用整型索引,效率遠高於字元串索引
  • 搜索時使用SQL參數("?", parameter)代替字元串拼接(底層有特殊優化與緩存)
  • 查詢需要多少就limit多少(如判斷是否含有啥,就limit 1就行了嘛)
  • 如果出現很寬的列(如blob類型),考慮放在單獨表中(在查詢或者更新其他列數據時防止不必要的大數據i/o影響性能)

III. 網路調優

更多網路優化,可移步微信Mars與其策略 當然無論是網速評估、心跳間隔、超時間隔,我認為這些在往常是基於特定環境下指定演算法,然後結合自己的經驗值給出的結果(如微信中的網速評估、超時間隔等),都能夠藉助AI整合原本的經驗數據,給出一個更優數據的可能性(如某環境下超時間隔為5s為最優值的可能性為80%),來替代人的經驗值。但是目前可預見的難點是在於如何去區分以及定義訓練的數據,如:網速評估,其實是根據不同的環境(2G、3G、LTE、4G、千兆4G、5G、Wifi、之類的),之前微信其實有自己的一個評估策略,但是如果要接入AI,是因為網速這個評估的結果一直不是一個準確值,之前只是根據我們自己的經驗給一個粗略的演算法;可能這塊要結合各類網路因素,參考RTT(這塊的計算演算法),輸入的因素越多,對應我們能夠確定的結果越少,應該訓練出來的模型能夠越有效,這樣可以結合AI給出的」經驗」,讓網速評估更準確些。這也是目前我在探究的,所以才有了前幾天寫的敲開TensorFlow的大門。

策略層面優化

1. 通過If-Modified-Since與Last-Modified

  • 第一次請求時,服務端在頭部通過Last-Modified帶下來最後一次修改的時間。
  • 之後的請求,在請求頭中通過If-Modified-Since帶上之前服務端返回的Last-Modified的值
  • 如果服務端判斷最後一次修改的時間距離目前數據沒有修改過,就直接返回304 NOT MODIFIED的狀態碼,此時客戶端直接呈現之前的數據,由於不需要帶下來重複的數據,減少用戶流量的同時也提高了響應速度。

2. 通過Etag與If-None-Match

  • 第一次請求時,服務端在頭部通過Etag帶下來請求數據的hash值
  • 之後的請求,在請求頭中通過If-None-Match帶上之前服務端返回的Etag的值
  • 如果服務端判斷文件沒有修改過,就直接返回304 NOT MODIFIED,此時客戶端直接呈現之前的數據,由於不需要帶下來重複的數據,減少用戶流量的同時也提高了響應速度。

如果你使用Okhttp3與Retrofit2,對於304 NOT MODIFIED的緩存便可以直接通過下面的代碼直接創建一個2M緩存文件來戶緩存這類數據。一旦是304 NOT MODIFIED, Retrofit2與Okhttp3將會偽裝一個與之前一樣的響應給上層,因此對上層是透明的。

private final static int CACHE_SIZE_BYTES = 1024 * 1024 * 2;
public static Retrofit getAdapter(Context context, String baseUrl) {
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.cache(
new Cache(context.getCacheDir(), CACHE_SIZE_BYTES));
Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
retrofitBuilder.baseUrl(baseUrl).client(client);
return retrofitBuilder.build();
}

數據結構層面

  • 如果是需要全量數據的,考慮使用Protobuffers (序列化反序列化性能高於json),並且考慮使用nano protocol buffer。
  • 如果傳輸回來的數據不需要全量讀取,考慮使用Flatbuffers (序列化反序列化幾乎不耗時,耗時是在讀取對象時(就這一部分如果需要優化,可以參看Flatbuffer Use Optimize

其他層面優化

  • 通過自實現DNS(如實現自己的HTTPDNS(用Okhttp3實現尤為簡單,因為Okhttp3支持定製DNS)),來降低沒必要的DNS更新(由於DNS常見策略是與文件大小以及TTL相關,如果我們分文件以及分域名協商TTL有效期,可能case by case有效這塊的刷新率),甚至防止DNS劫持
  • 圖片、JS、CSS等靜態資源,採用CDN(當然如果是使用7牛之類的服務就已經給你搭建布置好了)
  • 全局圖片處理採用漏斗模型全局管控,所請求的圖片大小最好依照業務大小提供/最大不超過屏幕解析度需要,如果請求原圖,也不要超過GL10.GL_MAX_TEXTURE_SIZE
  • 全局縮略圖直接採用webp,在儘可能不損失圖片質量的前提下,圖片大小與png比縮小30% ~ 70%
  • 如果列表裡的縮略圖伺服器處理好的小圖,考慮到減少下載時的RTT,可以考慮直接在列表數據請求中,直接以base64在列表數據中直接帶上圖片(但是需要注意的是通常base64後的圖片大小會大於原圖片大小,適當權衡)(國內還比較少,海外有些這種做法,好像web端比較常見)
  • 輪詢或者socket心跳採用系統AlarmManager提供的鬧鐘服務來做,保證在系統休眠的時候cpu可以得到休眠,在需要喚醒時可以喚醒(持有cpu喚醒鎖),這塊考慮到省點等問題可以參考這篇文章
  • 在一些非同步的任務時,可以考慮合併請求

IV. 多進程抉擇

360 17個進程: 360手機衛士 Android開發 InfoQ視頻 總結 ,但是考慮到多進程的消耗,我們更需要關注多個組件復用同一進程。 在沒有做任何操作的空進程而言,其大約需要額外暫用1.4MB的內存。

  • 充分獨立,解耦部分
  • 大內存(如臨時展示大量圖片的Activity)、無法解決的crash、內存泄漏等問題,考慮通過獨立進程解決
  • 獨立於UI進程,需要在後台長期存活的服務(參看Android中線程、進程與組件的關係)
  • 非己方第三方庫(無法保證穩定、性能等問題,並且獨立組件),可考慮獨立進程

最後,多進程存在的兩個問題: 1. 由於進程間通訊或者首次調起進程的消耗等,帶來的cpu、i/o等的資源競爭。2. 也許對於部分同事來說,會還有可讀性問題吧,畢竟多了層IPC繞了點。

V. UI層面

相關深入優化,可參看Android繪製布局相關 對於卡頓相關排查推薦參看: Android性能優化案例研究(上)與Android性能優化案例研究(下)

  • 可以考慮使用ConstraintLayout,有效減少了布局的層級,提高了性能
  • 減少不必要的不透明背景相互覆蓋,減少重繪,因為GPU不得不一遍又一遍的畫這些圖層
  • 保證UI線程一次完整的繪製(measure、layout、draw)不超過16ms(60Hz),否則就會出現掉幀,卡頓的現象
  • 在UI線程中頻繁的調度中,盡量少的對象創建,減少gc等。
  • 分步載入(減少任務顆粒)、預載入、非同步載入(區別出耗時任務,採用非同步載入)

VI. 內存

根據設備可用內存的不同,每個設備給應用限定的Heap大小是有限的,當達到對應限定值還申請空間時,就會收到OutOfMemoryError的異常。

1. 內存管理

Android根據不同的進程優先順序,對不同進程進行回收來滿足內存的供求,可以參照這篇文章: Android中線程、進程與組件的關係。 在後台進程的LRU隊列中,除了LRU為主要的規則以外,系統也會根據殺死一個後台進程所獲得的內存是否更多作為一定的參考依據,因此後台進程為了保活,盡量少的內存,儘可能的釋放內存也是十分必要的。

  • 儘可能的縮短Service的存活周期(可以考慮直接使用執行完任務直接關閉自己的IntentService),也就是說在Service沒有任何任務的時候,儘可能的將其關閉,以減少系統資源的浪費。
  • 可以通過系統服務ActivityManager中的getMemoryClass()獲知當前設備允許每個應用大概可以有多少兆的內存使用(如果在AndroidManifest設置了largeHeap=true,使用getLargeMemoryClass()獲知),並且讓應用中的內存始終低於這個值,避免OOM。
  • 相對於靜態常量而言,通常Enum枚舉需要大於兩倍的內存空間來存儲相同的數據。
  • Java中的每個class(或者匿名類)大約佔用500位元組。
  • 每個對象實例大約開銷12~16位元組的內存。

onTrimMemory()回調處理

監聽onTrimMemory()的回調,根據不同的內存等級,做相應的釋放以此讓系統資源更好的利用,以及自己的進程可以更好的保活。

當應用還在前台

  • TRIM_MEMORY_RUNNING_MODERATE: 當前應用還在運行不會被殺,但是設備可運行的內存較低,系統正在從後台進程的LRU列表中殺死進程其他進程。
  • TRIM_MEMORY_RUNNING_LOW: 當前應用還在運行不會被殺,但是設備可運行內存很低了,會直接影響當前應用的性能,當前應用也需要考慮釋放一些無用資源。
  • TRIM_MEMORY_RUNNING_CRITICAL: 當前應用還在運行中,但是系統已經殺死了後台進程LRU隊列中絕大多數的進程了,當前應用需要考慮釋放所有不重要的資源,否則很可能系統就會開始清理服務進程,可見進程等。也就說,如果內存依然不足以支撐,當前應用的服務也很有可能會被清理掉。

TRIM_MEMORY_UI_HIDDEN

當回調回來的時候,說明應用的UI對用戶不可見的,此時釋放UI使用的一些資源。這個不同於onStop(),onStop()的回調,有可能僅僅是當前應用中進入了另外一個Activity。

當應用處於後台

  • TRIM_MEMORY_BACKGROUND: 系統已經處於低可用內存的情況,並且當前進程處於後台進程LRU隊列隊頭附近,因此還是比較安全的,但是系統可能已經開始從LRU隊列中清理進程了,此時當前應用需要釋放部分資源,以保證盡量的保活。
  • TRIM_MEMORY_MODERATE: 系統處於低可用內存的情況,並且當前進程處於後台進程LRU隊列中間的位置,如果內存進一步緊缺,當前進程就有可能被清理掉,需要進一步釋放資源。
  • TRIM_MEMORY_COMPLETE: 系統處於低可用內存的情況,並且當前進程處於後天進程LRU隊列隊首的位置,如果內存進一步緊缺,下一個清理的就是當前進程,需要釋放儘可能的資源來保活當前進程。在API14之前,onLowMemory()就相當於這個級別的回調。

2. 避免內存泄漏相關

  • 無法解決的泄漏(如系統底層引起的)移至獨立進程(如2.x機器存在webview的內存泄漏)
  • 大圖片資源/全屏圖片資源,要不放在assets下,要不放在nodpi下,要不都帶,否則縮放會帶來額外耗時與內存問題
  • 4.x在AndroidManifest中配置largeHeap=true,一般dvm heep最大值可增大50%以上。但是沒有特殊明確的需要,儘可能的避免這樣設置,因為這樣一來很可能隱藏了消耗了完全沒有必要的內存的問題。
  • 在Activity#onDestory以後,遍歷所有View,幹掉所有View可能的引用(通常泄漏一個Activity,連帶泄漏其上的View,然後就泄漏了大於全屏圖片的內存)。
  • 萬金油: 靜態化內部類,使用WeakReference引用外部類,防止內部類長期存在,泄漏了外部類的問題。

3. 圖片

Android 2.3.x或更低版本的設備,是將所有的Bitmap對象存儲在native heap,因此我們很難通過工具去檢測其內存大小,在Android 3.0或更高版本的設備,已經調整為存儲到了每個應用自身的Dalvik heap中了。

  • 全局統一BitmapFactory#decode出口,捕獲此處decode oom,控制長寬(小於屏幕解析度大小 )
  • 如果採用RGB_8888 oom了,嘗試RGB_565(相比內存小一半以上(w*h*2(bytes)))
  • 如果還考慮2.x機器的話,設置BitmapFactory#options的InNativeAlloc參數為true,此時decode的內存不會上報到dvm中,便不會oom。
  • 建議採用lingochamp/QiniuImageLoader的方式,所有圖片的操作都放到雲端處理,本地默認使用Webp,並且獲取的每個位置的圖片,盡量通過精確的大小按需獲取,避免內存沒必要的消耗。

VII. 線程

  • 採用全局線程池管理體系,有效避免野線程。可參照 ThreadDebugger-demo/DemoThreadPoolCentral.java
  • 結合全局線程池管理體系,使用ThreadDebugger監控線程,避免線程泄漏的存在。

VIII. 編譯與發布

關於開發流程優化,可以參考這裡

  • 考慮採用DexGuard,或ProGuard結合相關資源混淆來提高安全與包大小,參考: DexGuard、Proguard、Multi-dex
  • 結合Gradle、Gitlab-CI 與Slack(Incoming WebHooks),快速實現,打相關git上打相關Tag,自動編相關包通知Slack。
  • 結合Gitlab-CI與Slack(Incoming WebHooks),快速實現,所有的push,Slack快速獲知。
  • 結合Gradle中Android提供的productFlavors參數,定義不同的variations,快速批量打渠道包
  • 迭代過程中,包定期做多緯度掃描,如包大小、位元組碼大小變化、紅線掃描、資源變化掃描、相同測試用例耗電量內存等等,更多的可以參考 360手機衛士 Android開發 InfoQ視頻 總結
  • 迭代過程中,對關鍵Activity以及Application對打開的耗時進行統計,觀察其變化,避免因為迭代導致某些頁面非預期的打開變慢。

IX. 工具

這塊的拓展閱讀,可以直接參考Android開發周邊

  • TraceView可以有效的更重一段時間內哪個方法最耗時,但是需要注意的是目前TraceView在錄製過中,會關閉JIT,因此也許有些JIT的優化在TraceView過程被忽略了。
  • Systrace可以有效的分析掉幀的原因。
  • HierarchyViewer可以有效的分析View層級以及布局每個節點measure、layout、draw的耗時。

X. 其他

  • 懶預載入,如簡單的ListView、RecyclerView等滑動列表控制項,停留在當前頁面的時候,可以考慮直接預載入下個頁面所需圖片
  • 智能預載入,通過權重等方式結合業務層面,分析出哪些更有可能被用戶瀏覽使用,然後再在某個可能的時刻進行預載入。如,進入朋友圈之前通過用戶行為,智能預載入部分原圖。
  • 做好有損體驗的準備,在一些無法避免的問題面前做好有損體驗(如,非UI進程crash,可以自己解決就不要讓用戶感知,或者UI進程crash了,做好場景恢復)
  • 做好各項有效監控:crash(注意還有JNI的)、anr(定期掃描文件)、掉幀(繪製監控、activity生命周期監控等)、異常狀態監控(本地Log根據需要不同級別打Log並選擇性上報監控)等
  • 文件存儲推薦放在/sdcard/Android/data/[package name]/里(在應用卸載時,會隨即刪除)(Context#getExternalFilesDir()),而非/sdcard/根目錄建文件夾(節操問題)
  • 通過gradle的shrinkResources與minifyEnabled參數可以簡單快速的在編包的時候自動刪除無用資源
  • 由於resources.arsc在api8以後,aapt中默認採用UTF-8編碼,導致資源中大都是中文的resources.arsc相比採用UTF-16編碼更大,此時,可以考慮aapt中指定使用UTF-16
  • 谷歌建議,大於10M的大型應用考慮安裝到SD卡上: App Install Location
  • 當然運維也是一方面: Optimize Your App
  • 在已知並且不需要棧數據的情況下,就沒有必要需要使用異常,或創建Throwable生成棧快照是一項耗時的工作。
  • 需要十分明確發布環境以及測試環境,明確僅僅為了方便測試的代碼以及工具在發布環境不會被帶上。
  • 國內環境的長連接抉擇: 根據各廠商設備在日活的排行,優先適配,而後再結合後台的工作量,評估是否自己做,客戶端做主要就考慮電量以及可靠性權衡。如果要接第三方的,一定要了解清楚,國內現在第三方的,依然不太有節操(甚至有些會把你加入某套餐,就是會各種喚起其他應用),如果要自己實現可以看看本文有提到的這篇文章
  • 控制合理載入資源的時間區間: 如由於圖片的載入通常都與頁面的生命周期有關係,在Android中可以考慮當從頁面A進入頁面B時,暫停所有頁面A的圖片載入,退出頁面B時,終止所有頁面B相關的圖片載入,回到頁面A時恢復頁面A的所有圖片載入(這些操作使用Picasso十分快速的實現,因此Picasso支持不同TAG的圖片載入暫停、恢復、取消)
  • 代碼質量: phabricator 的arc diff (盡量小顆粒度的arc diff 與update review),其實也可以看看Google是如何做的: 筆記-谷歌是如何做代碼審查的,還有一點的TODO要寫好deadline與master
  • 編包管理: Gitlab CI (結合Gitlab,功能夠用,方便)

關註:我是程序員 - 知乎專欄 獲取更多技術乾貨


工作時遇到問題都記下來,等到下班和周末自己研究透這些問題,然後寫成文章放在 技術小黑屋


  • 下載編譯aosp源碼,生成ipr文件,把aosp工程導入到android studio里。開發中只要碰到疑問,都去aosp中找到答案,會有很大收穫。
  • git clone android framework源碼,無聊時看git日誌,「欣賞」aosp的contributor們是怎麼踩坑的。

手機先佔個位置,有空回來寫。

2015-7-8

原諒我的不邀自答(我從未見過如此厚顏無恥之人(:з」∠))。
這個問題,一開始要我回答其實我拒絕的,總不能你說要乾貨我就給,然後去GOOGLE上隨便搜索一下空間博客,複製黏貼然後Duang~的一下,給算給你乾貨了。我得凌晨2點先用手機佔個坑,然後想想自己到底有什麼乾貨能寫出來,雖然並沒有什麼卵用


1、四大組件以及Fragment的生命周期

如果只要寫一個能用的APP的話,知道生命周期的流程就夠用了。不過我建議還是要詳細閱讀源碼(不一定要全部懂),知道Android系統後台管理組件的什麼周期的大概流程,理解Android系統組件生命周期設計的意圖,了解這些關鍵字:ActivityManager,FragmentManager,WindowManager。


2、 知道生命周期後

Android系統內存空間不夠後,會回收相應生命周期內的組件,所以你應該保證的你Activity在onStop或者OnDestroy後,沒有被其他對象引用著,不然會造成系統無法回收內存空間,這樣就造成內存泄露了,結果要麼是系統變卡,要麼直接拋OOM Error。


3、所以應該遵循組件的生命周期,保存和恢複數據(嗎)

Android組件的什麼周期應該是系統控制的,APP不能干預,APP要做的是在Activity銷毀時保存數據,創建或恢復時恢複數據。但是為了不讓Activity每次重新創建的時候都重新去拉取數據浪費流量,要在onsaveinstancestate的時候把數據保存, onrestoreinstancestate的時候把數據恢復嗎?不全是,用Intent傳遞數據時,容量是有限制的,應該盡量保存一些關鍵的參數用於恢複數據(例如Adapter的當前position,最好是基本的Parcelable類型),如果數據太大會拋TransactionTooLargeException,如果真的想保存大數據的話推薦用序列化對象保存。


4、不要輕易new Thread().start();

為了保證主線程(Activity Thread)的順暢,應該把一些耗時的工作都放到後台線程去,但是也不要輕易地創建線程,因為線程啟動後我們就很能控制它了,如果線程引用著Activity的實例的話那更糟糕,會容易造成Activity無法被回收,而Activity又引用著大量的資源(所以系統內存空間不夠用的時候最喜歡回收Activity),容易造成OOM,這也是一個項目越大越容易出現卡頓的問題之一。


5、應該控制好整個項目中Thread的數量

不要輕易new Thread().start();,採用類似Looper的循環隊列,需要後台工作的使用,用對應的Looper的Handler把任務提交到Looper的隊列里執行,我自己控制的線程數大概有:

  • ActivityThread
  • HttpThead
  • ImageLoader Thread
  • FileThread
  • SqliteThread
  • LogThread (其實可以用FileThread,但是我把LOG的文件保存給單獨拿出來了)
  • ……

每次要做長時間的任務時,都是把任務扔到現有的隊列中,而不是new Thread()


6、不推薦使用AsyncTask和Timer,用Handler代替

AsyncTask用來做非同步任務確實比自己寫Thread方便許多,問題是AsyncTask自己內部維護了一個線程池,線程的數目不好控制(可以自定義),而且AsyncTask同樣容易引用著Activity導致資源沒被回收,Timer同理。


趁著午休先寫這麼多吧,待續。


@肥肥魚的回答已經比較全且實用了,贊一個。

補充一點,一定要看android設計規範。

這個不僅涉及到了UI設計,在你開發過程中也會用到。比如,什麼位置下的什麼控制項該使用多少dp(dip)的寬高、margin及padding,Google都已經設定好了,你照做就行。(這些設定在給出的時候都有合理的解釋,比自己從視覺上去估計數值強多了)


上面的建議,都不符合中國國情.國內大公司寫app是這樣的:
1.申請所有許可權.
2.開機自啟
3.各種事件鬧鈴都搞了,喚醒app
4.一人啟動,召喚全家
5.不給退功能,後台開100個service
6.各種數據有用沒用,收集了再說.


個人感覺最多的幾點:
1.命名規範和變數聲明時機(包括成員變數的初始化時機)
2.子線程的使用一定要考慮同步問題
3.學會看log,習慣看log,學會打log方便調試
我遇到很多剛寫程序的人或者接手新項目,一出錯就大喊,我艹有bug,什麼情況?其實你先看下出錯堆棧,對於android(基本說的是java)大多都是空指針,很好解的,不會的直接複製異常第一句百度或者其他方式搜索,先了解這是什麼錯誤再研究為什麼會產生,然後想怎麼解決,到了解決的過程如果一點感覺沒有可以其他熟練的人交流,這時一般不會太反感,頂多懶得幫你看說不知道,你自己看。
4.多理解原理(包括流程,源代碼等)
剛開發,經常會有程序執行到哪了的疑惑,或者什麼時候幹什麼比較合適
android里各種事件基本都是回調(listener),廣播以及message來交互,在你熟悉應用基本組成後就開始慢慢看各種生命周期,各種回調的時機。
Activity(最常見了),Service,View(這個是最麻煩了),我也是2年菜鳥,加油吧!


我認為這個問題不太適合限縮在android 的這個概念,感覺提主問的問題也比較不像是開發android 需要注意的元件細節,而比較像是編程時要注意的事項,那某把這問題放大到任何一種編程, 這樣的話市面上就有很多大神有寫過,像是clean code , 編碼規範,
因此小弟分享之前讀完clean code 這本書後的一點心得,希望有幫助
網頁版:
Clean code: 無暇的程式碼 – 書摘心得(一)
Clean code: 無暇的程式碼 – 書摘心得(二)


wtf

『唯一有效的的『程式品質』度量單位: 每分鐘罵髒話的次數(wtf/minutes)』版權網址附上

這句話讓我想到同事改其他部門程式碼時的狀況,我想以此作為引言,已經相當足夠的表示出 寫出clean code 的重要性

(序)


認真一點的話就是:

『你有兩個原因來讀這本書:首先你是個程式設計師,接者,你想成為一個更好的設計師』

我自己加上去的:『剛好,好的設計師薪水好像還能看』

(p1)

書摘心得:


第一章:無瑕的程式碼

承接&<序&>的部份,為何我們需要寫出 clean code:


『在大部份的情況下,如果把自己的 coding 過程給錄下來的話,會發現以下行為

Heaton 打開程式碼

找到要修改的函式,開始想想要改啥

向上卷,檢查函數的初始狀況

向下卷回剛剛的函式,開始改一改

阿,又刪掉剛剛的程式碼,再輸入一次,再一次刪掉

發現跟其他函式有關係,卷到剛剛的函數,看了一下要怎某呼叫

回到剛剛的函式,重新修改,發現打錯,再刪掉,又增加』

(p.15)

前面這一段話完全可以用個迴圈給包起來,因為只會不斷地重複

以這樣的記錄來看,其實花費在閱讀程式跟撰寫程式的時間大概是10:1


所以說如果把技能點數點在閱讀程式的技能上,會比點在寫程式這技能

在開發上,效益好很多,所以一開始就把code 寫得能看,保持整潔,對於之後的開發絕對是10倍數的影響


第二章:有意義的命名

要有意義的區分


請問以下三個函數要做的事情你分的出來嗎?

1. getActitiveAccount();

2. getActitiveAccounts();

3. getActitiveAccountInfo();

請盡量避免這樣曖昧的命名

(p.23)


成員的字首:

2.1 全域變數:

很多情況下,會在全域變數的前面加上m 或者m_ 這一類的開頭,來幫助開發者更加容易的便是此變數,但實際上這並沒有太有意義,大部分的人還是只會閱讀m之後有意義的命名,更何況現在的ide 都已經進化到,可以主動把全域變數自動換色所以m 這樣的字首提示就變得只會干擾

(p27)


本人 m 的習慣用法到現在還是不太會改,這實在有點見仁見智


2.2 介面和實作:

介面的的宣告時,不少open source project 都會特地加上I 這樣的開頭,例如IImageBackView,藉此告知開發者這是一個介面,但這個I 同樣也只有讓人分心的功能,開發者根本不在意是不是介面,而是在意這ImageBackView 到底是怎樣的view,如果真的想要區分interface 還是implement ,那會比較建議在實作端改成ImageBackViewImpl。

(p28)


2.3 每種概念使用一種字詞:

在同一個專案時,儘可能的在某些關鍵字保持一致,例如在取得資料時函式命名fetchXXX , getXXX , retrieveXXX 這幾個開頭,要區分開來,或是統一隻用一種,維持一致性,讓開發者在search method 時可以更快地找出關鍵字。


同樣的用在class 命名,一個專案里同時有XXXController , XXXManager , XXXDriver 在,對於開發者也只會感到困惑,這幾種到底差在哪裡,如果沒有差到天翻地遠,統一起來只會有好處,這裡可以配合正在用的design pattern 習慣用名,幫助開發者進入狀況。

(p.30)

個人在home 開發時,我就常常同時使用 controller 和 manager 混用,後來覺得好像變得不好跟老大說明哪個東西放在哪個 class ,那乾脆通通用 manager ,這樣溝通時也方便多了。


第三章:函式

簡短,只做一件事


一段程式碼,假設做了三件事情,最好把這三件事情分開獨立成三個函式,然後給予有意義的命名,幫助開發者更快理解程式碼

以 home 的 BitmapVolleyManager 的部分程式碼來看(如下),以下 load bitmap 做了三件事情

1. set view loading url and check url is valid.

2. get bitmap from image cache.

3. assign bitmap worker task for this view and url

public synchronized void loadBitmap(String imageUri, ImageBackView imageView
, BitmapConfiguration configuration, boolean stackKeep, boolean clearFirst) {

imageView.setKey(imageUri);
if (clearFirst)
imageView.setBitmap(null);
if (!Utils.isValid(imageUri)) {
Log.d("BitmapVolleyManager", "loadBitmap, image uri should not be null, cancel loading");
return;
}

Bitmap bitmap = VolleyManager.getPageImageCache(mContext)
.getBitmap(ImageLoader.getCacheKey(imageUri, configuration.requestWidth
, configuration.requestHeight, configuration.mScaleType));
if (bitmap != null) {
imageView.setBitmap(bitmap);
return;
}
final BitmapWorkerTask task = new BitmapWorkerTask(imageView, imageUri, configuration);
if (stackKeep) {
synchronized (this) {
mStackLoadTask.push(task);
}
} else {
Log.d("BitmapVolleyManager", "Add a Bitmap task ,image uri:" + imageUri);
task.executeOnExecutor(getExecutorPool());
}
}
直接來看也並不難懂,但是更進一步來看或許

可以更加獨立成


public synchronized void loadBitmap(String imageUri, ImageBackView imageView
, BitmapConfiguration configuration, boolean stackKeep, boolean clearFirst) {

checkViewForURL(imageuri, imageview);
if (clearFirst)
imageView.setBitmap(null);

Bitmap bitmap = getBitmapFromCache(imageUri, configuration);
if (bitmap != null) {
imageView.setBitmap(bitmap);
return;
}
startNetworkBitmapLoadingTask(imageUri, imageView , configuration , stackkeep);
}
public boolean checkViewURLIsValid(String targetURL, ImageBackView imageView) {
imageView.setKey(imageUri);
if (!Utils.isValid(imageUri)) {
Log.d("BitmapVolleyManager", "loadBitmap, image uri should not be null, cancel loading");
return false;
}
} else {
return true;
}
}

public Bitmap getBitmapFromCache(String targetURL , BitmapConfiguration configuration) {
Bitmap bitmap = VolleyManager.getPageImageCache(mContext)
.getBitmap(ImageLoader.getCacheKey(imageUri, configuration.requestWidth
, configuration.requestHeight, configuration.mScaleType));
return bitmap;
}

public void startNetworkBitmapLoadingTask(String targetURL , ImageBackView imageView , BitmapConfiguration configuration
, boolean stackKeep) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView, targetURL, configuration);
if (stackKeep) {
synchronized (this) {
mStackLoadTask.push(task);
}
} else {
Log.d("BitmapVolleyManager", "Add a Bitmap task ,image uri:" + targetURL);
task.executeOnExecutor(getExecutorPool());
}
}

好吧,至少第一階段的原則已經修正完成了(雖然還是違反很多之後會談到的原則,提到時再改)

(p40.41)


3.1 switch 敘述:

要 switch 簡短,並不容易,因為switch 總是會違反『單一原則(single responsibility principle)』

和 『開放閉和原則(open closed principle)』,詳細程式碼的範例還是看書比較好。

簡單來說就是 switch 的用法往往綁定特殊值,例如 enum , 這樣在要擴增的時候,勢必要到處修改用到的地方。可是書中也提到這是必要之惡,如果可以的話,最好一大段程式碼,就只有一個switch 就好,剩下的用多型給處理掉。

(p.44)


3.2 函式的參數:

函式的最佳參數數量,最理想的是零個,其次是一個,接下來是兩個,好吧三個是極限,別再上去了,因為參數本身也是一種概念的傳達。

下面兩個函式哪個比較好理解

includeSetupPageInfo(newPageContent);

includeSetupPage();


基本上參數和函式名稱是屬於不同分區,理解上是會比較困難一些,無法一眼就快速釐清,參數和函式的關係,而且參數會強迫開發者去理解不必要的資訊,因為開發者會去擔心這參數到底會影響到啥,又該怎某處理,此外,參數越多,測試的複雜度更會指數型上升。

(p.47)


輸出型參數的設計更是一大難以理解點,大部分開發者對於函式的理解通常都是從參數輸入資料,然後再return 里得到輸出資料。一般情況下根本不會想到會用放在參數的資料做為傳遞的工具。

(本人這點深感痛苦,老是有人把 list 放進參數里,然後直接對 list 操作,一般我在 trace 都會感到困惑,想說啥時這list 被改過了)


List output = sortByTime(input);

底下的寫法真的很容易困惑

sortByTime(input);

(p.52)


3.2.1 單一的參數

這一小段,作者十分反對使用 flag 作為參數

主因為 boolean is true 會做第一種事,那 false 就會做第二種事

很明顯地違反單一原則,以上面的例子就是


public synchronized void loadBitmap(String imageUri, ImageBackView imageView

, BitmapConfiguration configuration, boolean stackKeep, boolean clearFirst)

這裡面的 stackKeep , clearFirst

這兩個參數分別是,讀取圖片任務,先放在stack 等待其他時間,在開始啟動任務和 先清除 ImageView 上面的圖片,在載入圖片

老實說只看這參數名稱幾乎不懂啥意義

(p.48)

(不過我個人並不是那某反對單一 boolean 參數的設計,因為任何彈性的設計再多形的基礎上

總會有個 method 是最大參數集合,如下,因此只要特地把參數還有函式功能的註解寫好就可以了,不過之後的章節也會提到作者並不喜歡註解)


/**

**/

public void loadBitmap(String imageUri, ImageBackView imageView) {

loadBitmap(imageUri, imageView, new BitmapConfiguration(DEFAULT_LEFT_ITEM_WIDTH, DEFAULT_LEFT_ITEM_HEIGHT), false, true);

}


public void loadBitmapStackKeep(String imageUri, ImageBackView imageView, BitmapConfiguration configuration) {
loadBitmap(imageUri, imageView, configuration, true, true);
}
public synchronized void loadBitmap(String imageUri, ImageBackView imageView
, BitmapConfiguration configuration, boolean stackKeep, boolean clearFirst) {

3.2.2 物件型態的參數

超過兩個三個的參數時,很可能需要將其中幾個組合成一個類別,看起來很像只是規避參數數量盡量少的原則

其實並不,這完全可以幫助開法者去理解參數的理念,以上面例子來看 就是 BitmapConfiguration 裡面包含寬高,縮放類型

或許可以再把 stack keep 和 clearfirst 加進去,不過個人覺得加進去的話就不能用BitmapConfiguration 而是使用

LoadConfiguration 會比較好,然後包含BitmapConfiguration and stackkeep and clearfirst.

(可是老樣子,我覺得這有點見仁見智)

book example:


Circle makeCircle(double x , double y , double radius);

Circle makeCircle(Point center , double radius);

(p.50)


3.3 使用例外處理代替回傳錯誤碼

使用try/catch,並在 catch 把後續處理完成,而不要用回傳錯誤碼的方式,這代表者事情往後面丟,形成更深層的巢狀結構,尤其是自定義的 enum error code.


3.4 把 try/catch 區塊提取出來

在 try/catch 區塊內最好就只有一行函式,然後把code寫在函式里

(p.54)


少用 break , continue,保持效率可以斟酌使用

還有絕對不要用 goto,這隻會讓看的人痛苦


while (condition1) {
xxx
if (condition2) {
break;
}
}

換成下面,這樣明顯好多了


while (condition1 !condition2) {
xxx
}

至於continue,怎某不考慮一下把條件式反轉一下


List goodNames = new ArrayList&<&>();
for (String name: names) {
if (name.contains("bad")) {
continue;
}
goodNames.add(name);
...
}

改寫一下


List goodNames = new ArrayList&<&>();
for (String name: names) {
if (!name.contains("bad")) {
goodNames.add(name);
...
}
}

上面兩段code , reference :

編程的智慧 - 簡書

(p.57)


第四章 註解

沒有任何東西可以比一段放對位置的註解還有用,但是放錯地方的註解,更可以弄亂模組化,更別說寫錯的註解,這根本是謠言產生器

(p.61)


4.1 註解沒辦法彌補糟糕的程式碼

如果程式碼已經髒亂到必須寫一大堆註解來搞清楚意圖,那請重寫你的程式碼,讓他更整潔,進而不需要那一堆難搞的註解

整潔的程式碼絕對好過一大堆的註解,最高境界就是不用註解

(p.63)


4.1.1 當然也是有必要或好的註解

1.法律形版權宣告


2.函式名稱多到不行,弄個有分字的註解比較好懂


3.解釋自己設計的意圖想法,例如函式的順序,或是特殊的spec request.


4.函式庫里的函式真的太詭異,但是我們不能去修改函式庫的函式名稱,只好自己在使用時,說清楚


5. side effect 的提示


6. TODO 的註解,這最高境界就是全部清掉,但是我們也知道這不太可能,使用TODO可以幫助開發者快速找到修改點


4.1.2 糟糕的註解

1.神秘註解,只有你看得懂的文字


2.寫太長註解,看你的註解可能能比看你程式碼還要浪費時間的註解, html 式的註解,解釋 rfc 文件編碼模式啊,理解起來又累又不需要


3.誤導形註解,這可能是歷史共業,拜託快刪掉


4.規定行註解,例如 java doc 的規定格式,每個都要寫真的蠢爆了


5.日誌形註解,老實說這在沒有 git 的時代還有理由,但現在用 commit message 去取代不是很好嗎?作者跟出處沒規定的話也是同樣


6.干擾形註解,就是廢話,寫錯地方啊,全域和區域的註解要分清楚


7.被註解起來的程式碼

這個我覺得要大力提倡,通常你註解起來的程式碼,其他人都會以為很重要,然後不敢去刪掉,其他都會以為留者必有原因,最後就變成類似黴菌的東西,實際上這在沒git 的時代或許可以幫助把code 保留著,但是現在有版本控制了,請直接刪掉。

(這個可以大力推廣,老實說註解起來到最後的下場都只是忘了刪,而且隨者開發,那些被註解的程式碼一點都不重要)


接下來我來表演一段糟糕的代碼,至於如何 refactor 請自己練習吧:


/

**
* changes (from 11-OCT-2009 ) author : heaton
*
* Add a CD with title , author , number , like celine.
* @param title The title of the CD
* @param author The author of the CD
* @param tracks The number of tracks on the CD
*/
public void addCD(String title , String author , int tracks) {
// new CD for add to total list.
CD cd = new CD();
cd.title = title;
cd.author = author;
// size mean the cd duration from start to end.
//cd.size = size;
cd.tracks = tracks;
cdList.add(cd);
}

上面的吐槽點真的太多了,請好好檢視現在手上的專案

(p.63-77)


第五章 編排

程式的編排很重要,維持一定的團隊規則進行編排絕對可以加速閱讀

更重要的是 只要確立好團隊規則後,自動化工具可以幾乎無痛導入,cp值極高

(P.86)


個人覺得這段還好,主要就是兩個重點

5.1水平距離:

5.1.1 :程式碼的任何一行不應該太長,最低最低的要求就是不得超出螢幕頁面寬度

,保持在視線範圍內,如果需要左右滑動,那肯定造成困擾

5.1.2:每行的程式碼,是情況保持空白行,喘口氣

(P.86)


5.2. 垂直距離:

5.2.1:變數宣告位置統一放置,java 習慣全域變數都放在文件最上端,而區域變數則盡量靠近使用位子

5.2.2:相依函式盡量附近,最好的情況就是,可以從開頭的函式順順地往下看,而不需要大範圍的捲動滑鼠

5.2.3:保持一定的空白行,人們喜歡有些留白的人生

(P.90)


這一段個人覺的,上面的水平和垂直距離的規則都可以靠IDE做到

但是共用的變數名,或是數值定義的位址要確不好執行

ex: 像是常見的 xxxSetting.java , xxxConstant.java , xxxDefine.java , 這一類的百家爭鳴

不值得鼓勵,勢必要有人統一規範並整理,放在差不多的路徑下,並用正確的命名幫助閱讀

這點手上的 Home 專案,也有同樣的問題


第六章 物件及資料結構

java 開發上我們常常讓物件的變數保持 private ,避免其他開發者突發奇想的想要改動其數值導致狀況大亂

但為何還是有那某多工程師會加上 setter and getter,搞得像是 public 一樣,大家想改就改

(p.105)

其實就是資料抽象化,我提供你的讀取資料的目的要求,但是手段由我控制,重setter and getter 來看

這樣的效果並不明顯,但同樣是不告訴你裡面詳細怎某運作的


6.1 物件與資料的反對稱性

首先先定義好物件與資料

物件將他們的資料再抽象層之後隱藏起來,然後將操作這些資料的函式暴露在外

資料則將資料暴露在外,而且也沒提供有意義的函式

注意這兩個定義,這是對立且也互補

(p.107)

看個例子:


public class Square {
public Point topLeft;
public double side;
}
public class Rectangle {
public Point topLeft;
public double height;
public double width;
}
public class Circle {
public Point center;
public double radius;
}

public class Geometry {
public final double PI = 3.14159;
public double area(Object Shape) throws NoSuchShapeException {
if (shape instanceof Square) {
Square s = (Square) shape;
return s.side * s.side;
} else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
return r.height * r.width
} else if (shape instanceof Circle) {
Circle c = (Circle) shape;
return PI * c.radius * c.radius;
}
throw new NoSuchShapeException();
}
}

老實說這樣的程式,忠實的物件導向程式設計師會覺得很詭異(我),這根本就是 procedural function


所以換個方式試試看(物件版)


interface Shape {
public double area();
}

public class Square implement Shape {
private Point topLeft;
private double side;
public double area() {
return side * side;
}
}
//....other class implement Shape

從上面兩個例子可以看出

如果要加入一個新的圖形類別時,會發生啥事?

第一段程式碼,我必須要修改Geometry 內的所有的相關函式才能處理這件事情

但是採用第二段程式碼的話,則根本不需要Geometry 這個類別

新增任何類別都不會影響到現有的函式。


那如果要加入一個新的函式像是 perimeter() 的話,會發生啥事?

第二段程式碼的所有 Shape 物件都必須跟者修改,

但第一段程式碼卻只要修改 Geometry 類別,增加一個函式即可

(p.108)


由此可以看出這兩種設計的對立以及互補面

再次推導出來:

『結構化程式碼容易添加新的函式,而不需要變動已有的資料結構

而物件導向的程式碼,容易添加新的類別,而不用變動已有的函式』

反過來說:

『結構化程式碼難以添加新的資料結構,因為必須改變所有的函式

物件導向程式碼難以添加新的函式,因為必須改變所有的類別』

(p.109)


本人儘管身為物件導向派的一份子,當然也知道全部都是物件,基本上是個神話

所以在適合的時候,要懂得用結構化的程式(需要常常增加減少函式的情況),幫助開發


所有的語法都是工具,挑最適合的就對了


6.2 Data Transfer Object

這種物件非常常見,基本上就是資料庫的原始資料 轉成 應用的程式內的物件

例如: greendao , json to object , gson 這類的關鍵字

俗稱bean

這其實很多資料內容都可以用公開變數,但是還是習慣寫成setter and getter

其實就是一種像物件導向開發者的妥協

(p.113)


讀到這段時,突然想到之前跟老大在討論(感覺跟這章節沒啥關係)

android 提供外部 api access content provider 的方法,用哪種比較好?

本人覺得 就走 content provider 的玩法加文件,提供 URI 然後用android 原生的 sqlite crud 操作

主要就是保持資料的彈性操作,原生的玩法到哪都不會有錯,這樣可以保持目前的程式碼,以最小的修改幅度

就可以完成這項任務

老大覺得 出專門的 lib 把所有的crud操作都規範起來,提供 example , 要求存取者造規則走

有點忘了為何,但主要是利用函式的操作,讓第三方必須認真的看懂才能去操作資料庫

確保安全,並保持程式的抽象化,以後隨便改都可以


不知道個位看官覺得優缺點是啥,或是可以補充沒注意到的點,歡迎討論


第七章 錯誤處理

定義最小的 handle scope 和正常的程式流程


7.1 try/catch 最好能定義在最精準的範圍:

一般情況下,不少人會貪圖方便,或只是想避開編譯器跳出的錯誤,進而把一大段程式碼用try/catch 包起來,更甚者只用Exception 這個巨大的錯誤目標,但這樣以後必定付出代價,最主要的就是無法在錯誤時,提供足夠精確的error message.


7.2 正常的程式流程

try/catch 的catch 只去處理錯誤的回復,確保程式不會卡在這裡,但是不要把商業邏輯坐在catch 處理,也就是把錯誤發生情況給常態化,這會造成後續其他開發者,無法區分商業邏輯和錯誤處理的差異,也就是不好讀,光是這樣就違反了clean code.


7.3 不要回傳 null

『許多語言(C,C++,Java,C#,……)的類型系統對於null的處理,其實是完全錯誤的。這個錯誤源自於Tony Hoare最早的設計,Hoare把這個錯誤稱為自己的「billion dollar mistake」,因為由於它所產生的財產和人力損失,遠遠超過十億美元。 』


有人回傳null 那就代表有人要處理null , 很好這時候個人確認一下手上專案的LPageLayout.java ,大概總共寫了約40個null 檢查,這完全就是給自己找麻煩的行為,這時候有人可能認為可以用NullPointerException 來省下大量的null check , 但這時候和第一項的『try/catch 最好能定義在最精準的範圍:』完全衝突,如果範圍大,確實可以省下大量的null check , 但是第一項的麻煩也會跟者產生。

因此最好的方法就是: 不要回傳 null

最常出現這種情況就是,List 的 null check ,


List personalList = loader.getPersionalData();
if (personalList != null) {
for (int i = 0; i &< personalList.size() ; i ++ ) { xxxxx; // 如果這時候牽扯到多執行緒那就更刺激了 } }

因此這種情況下,我們完全建議 loader.getPersionalData() 回傳一個 list size is 0 的 empty list,可以用


Collections.emptyList();

來產生一個空 list


7.4 不要傳遞 null

比回傳null 還要糟糕的就是傳遞null 參數到函式里,大部分函式的參數處理都不會預想到輸入參數是null 的情況(基本型別想到0的情況可能還有),大部分就直接爆個NullPointerException 給你,這造成要碼是你做null check ,不然就是函式特別對參數做null check (建議利用@NonNull),然後爆InvalidArgumentException,我想這劇情沒有好太多,一樣搞死大家.

因此同樣最好的方法就是 不要傳遞 null


-----第一節先到這邊----


Clean code: 無暇的程式碼的第一段心得先寫到這裡

總共有17章,繼續整理


我說一個,在logcat使用tag添加各個模塊的log filter,在查看某個具體問題或者需要了解程序流程的時候,選擇一個filter就可以。這種對於檢查數據流和後期維護特別有用,當然前提是每個模塊本身log的tag要有調理,如聯繫人的都統一用一個


上面總結的太好,我說點偏的吧,在項目中我認為開發人員需要的好習慣

1,計劃嚴格執行

2,永遠留有變化的餘地

3,遇到的問題不要藏起來


英語得先學好, 不然命名誰也看不懂

Android Open Source Code, 多看, 多讀
Downloading and Building

代碼規範上, 《重構遺留代碼》 不錯
一份能加單元測試的代碼, 才是好代碼。


  1. 版本控制
  2. 注釋
  3. 慎重引入第三方庫
  4. 所有圖片用tinypng處理一遍
  5. Android應用發布,用灰度測試的方式能發現很多潛藏bug
  6. 測試APK升級的情況會不會出現問題
  7. Lint工具檢查,去除不再使用的資源
  8. ...

命名跟著系統源碼命就行,比如前面帶小寫m的mContext,mTitle等等,剛學的先想著怎麼去實現,實現完了覺得代碼不好看就重構一下,不貪多,貪精。
自己平時也做過一些小項目吧,然後找找開源的有沒有類似的,看看別人代碼怎麼寫的,學習學習。


看了樓上幾位分享關於Android一些具體的問題技巧,我這裡就不多說了
來分享一下其他的東西.不僅僅是Andriod,其他的程序員同樣適用.
①一定要寫筆記.你可以不寫博客,但是一定要有筆記.
好記性不如爛筆頭.當然對於程序員來說,一個電子的筆記遠遠好與紙質的筆記
②寫博客.博客是附帶品.但是你只有去寫博客了,才知道別人的博客是怎麼寫出來了
調查發現,寫博客的程序員工資要搞那麼5000+.
博客也是你和別人交流技術的東西.別人可以從你文章中學習東西,你可以與別人交流.
③盡量閱讀英文文檔,而不是有問題就百度.有問題問Strack Overflow
④不要等別人告訴你這東西怎麼用,那東西怎麼學,只有自己去提前研究了,你才能更勝一籌.
⑤保持不斷學習


在任何情況下,從您的應用代碼中提取 UI 字元串並將其存放在外部文件中都是個好辦法。Android 在每個 Android 項目中都提供一個資源目錄,從而簡化了這一過程。

如果您是使用 Android SDK 工具創建的項目(請閱讀創建 Android 項目),工具會在項目的頂層創建一個 res/ 目錄。此 res/ 目錄中包含用於存放各類資源的子目錄。此外,還包含幾個默認文件(如 res/values/strings.xml),用於存放您的字元串值。

創建語言區域目錄和字元串文件

如需添加對更多語言的支持,請在 內創建額外的 valuesres/ 目錄,並在目錄名稱末尾加上連字元和 ISO 語言代碼。例如,values-es/ 目錄包含的簡單資源用於語言代碼為「es」的語言區域。Android 根據運行時設備的語言區域設置載入相應的資源。如需了解詳細信息,請參閱提供備用資源 。

一旦您決定了為哪些語言提供支持,便可創建資源子目錄和字元串資源文件

例如:

MyProject/
res/
values/
strings.xml
values-es/
strings.xml
values-fr/
strings.xml

將各個語言區域的字元串值添加到相應文件中。

在運行時,Android 系統會根據當前為用戶設備設置的語言區域使用相應的字元串資源集。

例如,以下是一些面向不同語言的不同字元串資源文件。

英語(默認語言區域),/values/strings.xml:

&
&
&My Application&
&Hello World!&
&

西班牙語,/values-es/strings.xml:

&
&
&Mi Aplicación&
&Hola Mundo!&
&

法語,/values-fr/strings.xml:

&
&
&Mon Application&
&Bonjour le monde !&
&

註:您可以在任何資源類型上使用語言區域限定符(或任何配置限定符),例如,您可以提供本地化版本的可繪製點陣圖。

使用字元串資源

您可以使用由 元素的 name 屬性定義的資源名稱在您的源代碼和其他 XML 文件中引用您的字元串資源。

在您的源代碼中,可以使用語法 R.string. 引用字元串資源。有許多方法都接受以這種方式引用的字元串資源。

例如:

// Get a string resource from your app"s Resources
String hello = getResources().getString(R.string.hello_world);

// Or supply a string resource to a method that requires a string
TextView textView = new TextView(this);
textView.setText(R.string.hello_world);

在其他 XML 文件中,只要 XML 屬性接受字元串值,您就可以使用語法 @string/ 引用字元串資源。

例如:

&

我的微信二維碼如下
http://weixin.qq.com/r/W4317brEb_cQrf6O99hJ (二維碼自動識別)

歡迎關注《IT面試題匯總》微信訂閱號。
http://weixin.qq.com/r/dztQSJTE9cmKrdAr925l (二維碼自動識別)


推薦閱讀:

如何評價索尼 Xperia Z2 手機?
對於手機遊戲公司來說,內購機制比付費下載更賺錢嗎?為什麼?
魅族是如何掉隊的?
如何評價華為 Ascend P6 ?
哪個 App 讓你覺得「原來手機還可以這麼用」?原因是什麼?

TAG:程序員 | 軟體開發 | 編程 | Android 開發 | Android |