
文章目錄
- 1. 了解抽象,封裝變化
- 2. 選好"車輪"
- 3. 抽象依賴第三方架構
- 4. 從 MVC 到 MVP
- 5. 歸檔代碼
- 6. 性能優化
- 7. 實踐新技術
- 8. UML
- 9. 自造"車輪"
- 10. 擴大技術圈
- 11. 寫部落格總結
1. 了解抽象,封裝變化
目前 Android 平台上絕大部分開發都是用着 Java ,而跟 Java 這樣一門面向對象的語言打交道,不免要觸碰到 抽象 和 封裝 的概念。我身邊接觸過的一些開發者,有一部分還對這些概念停留在寫一個抽象類、接口、或者一個方法(或抽象方法)。至于為什麼,我不大清楚是他們表達不出來,還是不了解。下面我也不高談闊論,直接舉例子來解釋我所了解的抽象。
//Activity 間使用 Intent 傳遞資料的兩種寫法 下面均是僞代碼形式,請忽略一些細節
//寫法一
//SrcActivity 傳遞資料給 DestActivity
Intent intent = new Intent(this,DestActivity.class);
intent.putExtra("param", "clock");
SrcActivity.startActivity(intent);
//DestActivity 擷取 SrcActivity 傳遞過來的資料
String param = getIntent.getStringExtra("param");
//寫法二
//SrcActivity 傳遞資料給 DestActivity
Intent intent = new Intent(this,DestActivity.class);
intent.putExtra(DestActivity.EXTRA_PARAM, "clock");
SrcActivity.startActivity(intent);
//DestActivity 擷取 SrcActivity 傳遞過來的資料
public final static String EXTRA_PARAM = "param";
String param = getIntent.getStringExtra(EXTRA_PARAM);
寫法一,存在的問題是,如果 SrcActivity 和 DestActivity 哪個把 “param” 打錯成 “para” 或者 “paran” ,傳遞的資料都無法成功接收到。而寫法二則不會出現此類問題,因為兩個 Activity 之間傳遞資料隻需要知道 EXTRA_PARAM 變量即可,至于 EXTRA_PARAM 變量到底是 “param” 、 “para” 、“paran” 這一點并不需要關心,這就是一種對可能發生變化的地方進行抽象封裝的展現,它所帶來的好處就是降低手抖出錯的機率,同時友善我們進行修改。
基于抽象和封裝,Java 本身很多 API 在設計上就有這樣的展現,如 Collections 中的很多排序方法:
這些方法都是基于 List 這個抽象的清單接口進行排序,至于這是一個用什麼樣的資料結構實作 List(ArrayList 還是 LinkedList),排序方法本身并不關心。看,是不是展現了 JDK 的設計人員的一種抽象程式設計的思維,因為 List 的具體實作可能有千萬種,如果每一類 List 都要寫一套排序方法,估計要哭瞎了。
小結:把容易出現變化的部分進行抽象,就是對變化的一種封裝。
2. 選好"車輪"
一個項目的開發,我們不可能一切從0做起,如果真是這樣,那同樣要哭瞎。是以,善于借用已經做好的 “車輪” 非常重要,如:
- 網絡通路架構:okhttp、retrofit、android-async-http、volley
- 圖檔加載架構:Android-Universal-Image-Loader、Glide、Fresco、Picasso
- 緩存架構:DiskLruCache、 Robospice
- Json 解析架構:Gson、Fastjson、Jackson
- 事件總線:EventBus、Otto
- ORM架構:GreenDAO、Litepal
還有其他各種各樣開源的自定義控件、動畫等。除了以上提到的開源架構,也包括一些不開源的SDK
- 資料統計:友盟統計,百度統計…
- 奔潰搜集:騰訊bugly、bugtags…
- 雲存儲:七牛…
- 即使通訊:環信、融雲、阿裡百川…
- 推送:小米推送、騰訊推送、百度推送…
- 安全加強:360加強寶、愛加密…
一般情況下,我在選擇是否引入一些開源架構主要基于以下幾個因素:
- 借助搜尋引擎,如果網上有一大波資料,說明使用的人多,出了問題好找解決方案;當然,如果普遍出現差評,就可以直接Pass掉了
- 看架構的作者或團隊,如 JakeWharton大神、Facebook團隊等。大神和大公司出品的架構品質相對較高,可保證後續的維護和bug修複,不容易爛尾;
- 關注開源項目的 commit密度,issue的送出、回複、關閉數量,watch數,start數,fork數等。像那種個基本不怎麼送出代碼、提issue又不怎麼回複和修複的項目,最好就pass掉;
針對不開源SDK的選擇,也主要基于以下幾點去考慮:
- 借助搜尋引擎,查明口碑;
- 很多第三方SDK的官網首頁都會告訴你,多少應用已經接入了此SDK,如果你看到有不少知名應用在上面,那這個SDK可以考慮嘗試一下了。諸如,友盟官網:
- 檢視SDK使用文檔、它們的開發者社群、聯系客服。好的SDK,使用文檔肯定會詳細指引你。出了問題,上開發者社群提問,他們的開發工程師也會社群上回答。實在不行隻能聯系客服,如果客服的态度都讓你不爽,那就可以考慮換别家的SDK了。
小結:選好 “車輪” ,事半功倍
3. 抽象依賴第三方架構
為什麼要抽象依賴于第三方架構呢?這裡和第1點是互相照應的,就是降低我們對具體某個架構的依賴性,進而友善我們快速切換到不同的架構去。說到這裡,你可能覺得很抽象,那我直接舉一個加載圖檔的例子好了。
假設你目前為項目引入一個加載圖檔的架構 —— Android-Universal-Image-Loader,最簡單的做法就是加入相應的依賴包後,在任何需要加載圖檔的地方寫上下面這樣的代碼段。
ImageLoader imageLoader = ImageLoader.getInstance(); // Get singleton instance
// Load image, decode it to Bitmap and display Bitmap in ImageView (or any other view
// which implements ImageAware interface)
imageLoader.displayImage(imageUri, imageView);
// Load image, decode it to Bitmap and return Bitmap to callback
imageLoader.loadImage(imageUri, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
// Do whatever you want with Bitmap
}
});
這種做法最簡單粗暴,但是帶來的問題也最嚴重的。如果我有幾十上百個地方都這麼寫,而在某一天,我聽說Facebook出了個神器 Fresco,想要換掉 Android-Universal-Image-Loader ,你就會發現你需要喪心病狂的去改動幾十上百個地方的代碼,不僅工作量大,而且還容易出錯。造成這樣的原因,就在于項目和加載圖檔的架構之間形成了強耦合,而實際上,項目本身不應該知道我具體用了哪個加載圖檔的架構。
正确的方式,應該是對架構做一個抽象的封裝,以應對未來發生的變化,我直接舉自己的開源項目 AndroidAlbum 中的一種封裝做法好了。
大緻代碼如下:
//1、聲明 ImageLoaderWrapper 接口,定義一些抽象的加載接口方法
public interface ImageLoaderWrapper {
/**
* 顯示 圖檔
*
* @param imageView 顯示圖檔的ImageView
* @param imageFile 圖檔檔案
* @param option 顯示參數設定
*/
public void displayImage(ImageView imageView, File imageFile, DisplayOption option);
/**
* 顯示圖檔
*
* @param imageView 顯示圖檔的ImageView
* @param imageUrl 圖檔資源的URL
* @param option 顯示參數設定
*/
public void displayImage(ImageView imageView, String imageUrl, DisplayOption option);
/**
* 圖檔加載參數
*/
public static class DisplayOption {
/**
* 加載中的資源id
*/
public int loadingResId;
/**
* 加載失敗的資源id
*/
public int loadErrorResId;
}
}
// 2、将 UniversalAndroidImageLoader 封裝成繼承 ImageLoaderWrapper 接口的 UniversalAndroidImageLoader ,
//這裡代碼有點長,感興趣可以檢視項目源碼中的實作 https://github.com/D-clock/AndroidAlbum
// 3、做一個ImageLoaderFactory
public class ImageLoaderFactory {
private static ImageLoaderWrapper sInstance;
private ImageLoaderFactory() {
}
/**
* 擷取圖檔加載器
*
* @return
*/
public static ImageLoaderWrapper getLoader() {
if (sInstance == null) {
synchronized (ImageLoaderFactory.class) {
if (sInstance == null) {
sInstance = new UniversalAndroidImageLoader();//<link>https://github.com/nostra13/Android-Universal-Image-Loader</link>
}
}
}
return sInstance;
}
}
//4、在所有需要加載圖檔的地方作如下的調用
ImageLoaderWrapper loaderWrapper = ImageLoaderFactory.getLoader();
ImageLoaderWrapper.DisplayOption displayOption = new ImageLoaderWrapper.DisplayOption();
displayOption.loadingResId = R.mipmap.img_default;
displayOption.loadErrorResId = R.mipmap.img_error;
loaderWrapper.displayImage(imagview, url, displayOption);
這樣一來,切換架構所帶來的代價就會變得很小,這就是不直接依賴于架構所帶來的好處。當然,以上隻是我比較簡單的封裝,你也可以進行更加細緻的處理。
小結:預留變更,不強耦合于第三方架構
4. 從 MVC 到 MVP
說實話,在沒接觸 MVP 的架構之前,一直都是使用 MVC 的模式進行開發。而随着項目越來越大,Activity或者 Fragment裡面代碼越來越臃腫,看的時候想吐,改的時候想屎…這裡撇開其他各種各樣的架構不談,隻對比MVC 和 MVP 。
- View:布局的xml檔案
- Controller:Activity、Fragment、Dialog等
- Model:相關的業務操作處理資料(如對資料庫的操作、對網絡等的操作都應該在Model層裡)
你會發現,如果 View 層隻包含了xml檔案,那我們 Android 項目中對 View 層可做操作的程度并不大,頂多就是用include複用一下布局。而 Activity 等簡直就是一個奇葩,它雖然歸屬于 Controller 層,但實際上也幹着 View 層的活(View 的初始化和相關操作都是在Activity中)。就是這種既是 View 又是 Controller 的結構,違背了單一責任原則,也使得 Activity 等出現了上述的臃腫問題。
- View:Activity、Fragment、Dialog、Adapter等,該層不包含任何業務邏輯
- Presenter:中介,View 與 Model 不發生聯系,都通過 Presenter 傳遞
- Model:相關的業務操作處理資料(如對資料庫的操作、對網絡等的操作都應該在Model層裡)
相比 MVC,MVP在層次劃分上更加清晰了,不會出現一人身兼二職的情況(有些單元測試的童鞋,會發現單元測試用例更好寫了)。在此處你可以看到 View 和 Model 之間是互不知道對方存在的,這樣應對變更的好處更大,很多時候都是 View 層的變化,而 Model 層發生的變化會相對較少,遵循 MVP 的結構開發後,改起來代碼來也沒那麼蛋疼。
這裡也有地方需要注意,因為大量的互動操作集中在 Presenter 層中,是以需要把握好 Presenter 的粒度,一個 Activity 可以持有多個 View 和 Presenter,這樣也就可以避開一個碩大的 View 和 Presenter 的問題了。
推薦兩個不錯的 MVP 架構的項目給大家,還不明白的童鞋,可以自行體會一下其設計思想:
https://github.com/pedrovgs/EffectiveAndroidUIhttps://github.com/antoniolg/androidmvp
小結:去加以實踐的了解 MVP 吧
5. 歸檔代碼
把一些常用的工具類或業務流程代碼進行歸類整理,加入自己的代碼庫(還沒有自己個人代碼倉庫的童鞋可以考慮建一個了)。如加解密、拍照、裁剪圖檔、擷取系統所有圖檔的路徑、自定義的控件或動畫以及其其他他一些常用的工具類等。歸檔有助于提高你的開發效率,在遇到新項目的時候随手即可引入使用。如果你想要更好的維護自己的代碼庫,不妨在不洩露公司機密的前提下,把這個私人代碼庫加上詳細文檔給開源出去。 這樣能夠吸引更多開發者來使用這些代碼,也可以獲得相應的bug回報,以便于着手定位修複問題,增強這個倉庫代碼的穩定性。
小結:合理歸檔代碼,可以的話,加以開源維護
6. 性能優化
關于性能優化的問題,大體都還是關注那幾個方面:記憶體、CPU、耗電、卡頓、渲染、程序存活率等。對于這些地方的性能優化思路和分析方法,網絡上已經有很多答案了,此處不做贅述。我隻想說以下幾點:
不要過早的做性能優化,app先求能用再求好用。在需求都還沒完成的時候把大量時間花在優化上是本末倒置的;
優化要用實際資料說話,借助測試工具進行檢測(如:網易的Emmagee、騰訊的GT和APT,科大訊飛的iTest,Google的Battery Historian)。畢竟老闆問你比以前耗電降低多少,總不能回答降低了一些吧???
任何不以減低性能損耗來做保活的手段,都是耍流氓。
小結:合理優化,資料量化
7. 實踐新技術
Rxjava、React Native、Kotlin…開始興起後,身邊有很多開發者會跟風直上。學習新技術的精神是非常值得鼓勵的,但沒有經過一段時間實踐觀察,就擅自把新技術引入到商業項目中,則有失妥當。對于大公司的團隊來說,會有專門團隊或項目去研究這些新興技術,以确定是否在自己的産品線開發中引入。但作為小公司,是不是就意味着沒有實踐嘗試新技術的機會呢?并不是!個人有以下幾點建議:
借助搜尋引擎。看此項技術坑多不多,口碑不錯但是坑多的話,則說明目前技術不成熟,可以耐心等待更新;
考慮學習成本。學習成本太大且不容易招到懂這方面的開發者的情況下,建議不要引入該技術;
高仿一個項目并開源。如果你想引入 React Native 做商業開發,最好先高仿實作一個應用然後将其開源。這樣一些對 RN 感興趣的開發者會運作你的代碼并回報 bug 給你,有助于你知道一些新技術的坑,并尋找相應的解決方案,最終确定是否引入該技術;
降低入門門檻。實踐新技術的過程盡量加以詳細的文檔記錄,這會有助于降低項目組其他同僚對新技術的入門門檻,可以的話,也将學習文檔開源,獲得更多開發者對此份文檔的回報,也可糾正一些文檔中的錯誤;
結合實際業務。所有新技術的引入都要考慮是否符合當下的業務需求,我聽過有些程式猿想引入新技術的原因是因為覺得這種技術很酷,網上說很好用,很啥啥啥…自己完全沒弄過就人雲亦雲。有時候好無語,感覺在會用一些技術就像在炫技一樣;
小結:空談誤國,實幹興邦
8. UML
UML,馴服代碼和了解項目結構的利器,本人也在學習和體驗其好處的路途上。不管遇到大小項目,有了它,可以更好的理清一些脈絡結構。對付舊的龐大項目代碼,或者有志閱讀某些開源項目代碼的開發者,絕對是居家必備。
小結:工欲善其事,必先利其器
9. 自造"車輪"
前面 2 提到,項目不可能從0開始,是需要引入很多第三方架構的。這裡并不與 2 互相違背,而是建議有想提高技術逼格的開發者,可以在空暇時間去編碼實作一個架構。如果你對網絡通路、圖檔加載方面很有研究見解,不妨把這些腦海裡的思想落實成具體的代碼。也許你會發現,你動手去實踐的時候,考慮的東西會多得多,自己最終得到的也會更多。(特别建議那些看過很多開源代碼,又至今未自己動手自撸一發的)
小結:不要停留在 api 調用的層面
10. 擴大技術圈
有空又經濟能力承受得起的時候,不妨去參加一些自己感興趣的技術交流會。很多都有大牛上台演講,聽聽人家的解決方案,拓寬一下自己看問題的思路,也可以多參加一些含金量高的線上活動。我有挺多開發者朋友,就是參加活動的時候認識的,有時候遇到一些技術問題,還會互相探讨交換一下解決思路。挺贊的!
小結:拓寬技術視野
11. 寫部落格總結
- 系統化記錄自己的解決方案;
- 友善日後自己回顧;
- 有問題也會有讀者評論回報,促進技術交流;
- 增強自己書面表達能力;