我的Android開發(fā)實(shí)戰(zhàn)經(jīng)驗(yàn)總結(jié)
以前一直想寫一篇總結(jié) Android 開發(fā)經(jīng)驗(yàn)的文章,估計當(dāng)時的我還達(dá)不到某種水平,所以思路跟不上,下筆又捉襟見肘。近日,思路較為明朗,于是重新操起鍵盤開始碼字一番。先聲明一下哈,本人不是大廠的程序猿。去年畢業(yè)前,就一直在當(dāng)前創(chuàng)業(yè)小團(tuán)隊從事自己熱愛的打碼事業(yè)至今。下面總結(jié)是建立在我當(dāng)前的技術(shù)水平和認(rèn)知上寫的,如有不同看法歡迎留下評論互相交流。
1.理解抽象,封裝變化
目前 Android 平臺上絕大部分開發(fā)都是用著 Java ,而跟 Java 這樣一門面向?qū)ο蟮恼Z言打交道,不免要觸碰到 抽象 和 封裝 的概念。我身邊接觸過的一些開發(fā)者,有一部分還對這些概念停留在寫一個抽象類、接口、或者一個方法(或抽象方法)。至于為什么,我不大清楚是他們表達(dá)不出來,還是不理解。下面我也不高談闊論,直接舉例子來解釋我所理解的抽象。
//Activity 間使用 Intent 傳遞數(shù)據(jù)的兩種寫法 下面均是偽代碼形式,請忽略一些細(xì)節(jié)
//寫法一
//SrcActivity 傳遞數(shù)據(jù)給 DestActivity
Intent intent = new Intent(this,DestActivity.class);
intent.putExtra("param", "clock");
SrcActivity.startActivity(intent);
//DestActivity 獲取 SrcActivity 傳遞過來的數(shù)據(jù)
String param = getIntent.getStringExtra("param");
//寫法二
//SrcActivity 傳遞數(shù)據(jù)給 DestActivity
Intent intent = new Intent(this,DestActivity.class);
intent.putExtra(DestActivity.EXTRA_PARAM, "clock");
SrcActivity.startActivity(intent);
//DestActivity 獲取 SrcActivity 傳遞過來的數(shù)據(jù)
public final static String EXTRA_PARAM = "param";
String param = getIntent.getStringExtra(EXTRA_PARAM);
寫法一,存在的問題是,如果 SrcActivity 和 DestActivity 哪個把 “param” 打錯成 “para” 或者 “paran” ,傳遞的數(shù)據(jù)都無法成功接收到。而寫法二則不會出現(xiàn)此類問題,因?yàn)閮蓚€ Activity 之間傳遞數(shù)據(jù)只需要知道 EXTRA_PARAM 變量即可,至于 EXTRA_PARAM 變量到底是 “param” 、 “para” 、”paran” 這一點(diǎn)并不需要關(guān)心,這就是一種對可能發(fā)生變化的地方進(jìn)行抽象封裝的體現(xiàn),它所帶來的好處就是降低手抖出錯的概率,同時方便我們進(jìn)行修改。
基于抽象和封裝,Java 本身很多 API 在設(shè)計上就有這樣的體現(xiàn),如 Collections 中的很多排序方法:
Collections中的排序API
這些方法都是基于 List 這個抽象的列表接口進(jìn)行排序,至于這是一個用什么樣的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn) List(ArrayList 還是 LinkedList),排序方法本身并不關(guān)心。看,是不是體現(xiàn)了 JDK 的設(shè)計人員的一種抽象編程的思維,因?yàn)?List 的具體實(shí)現(xiàn)可能有千萬種,如果每一類 List 都要寫一套排序方法,估計要哭瞎了。
小結(jié):把容易出現(xiàn)變化的部分進(jìn)行抽象,就是對變化的一種封裝。
2.選好”車輪”
一個項目的開發(fā),我們不可能一切從0做起,如果真是這樣,那同樣要哭瞎。因此,善于借用已經(jīng)做好的 “車輪” 非常重要,如:
- 網(wǎng)絡(luò)訪問框架: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
- 數(shù)據(jù)統(tǒng)計:友盟統(tǒng)計,百度統(tǒng)計…
- 奔潰搜集:騰訊bugly、bugtags…
- 云存儲:七牛…
- 即使通訊:環(huán)信、融云、阿里百川…
- 推送:小米推送、騰訊推送、百度推送…
- 安全加固:360加固寶、愛加密…
一般情況下,我在選擇是否引入一些開源框架主要基于以下幾個因素:
- 借助搜索引擎,如果網(wǎng)上有一大波資料,說明使用的人多,出了問題好找解決方案;當(dāng)然,如果普遍出現(xiàn)差評,就可以直接Pass掉了
- 看框架的作者或團(tuán)隊,如 JakeWharton大神、Facebook團(tuán)隊等。大神和大公司出品的框架質(zhì)量相對較高,可保證后續(xù)的維護(hù)和bug修復(fù),不容易爛尾;
- 關(guān)注開源項目的 commit密度,issue的提交、回復(fù)、關(guān)閉數(shù)量,watch數(shù),start數(shù),fork數(shù)等。像那種個基本不怎么提交代碼、提issue又不怎么回復(fù)和修復(fù)的項目,最好就pass掉;
針對不開源SDK的選擇,也主要基于以下幾點(diǎn)去考慮:
- 借助搜索引擎,查明口碑;
- 很多第三方SDK的官網(wǎng)首頁都會告訴你,多少應(yīng)用已經(jīng)接入了此SDK,如果你看到有不少知名應(yīng)用在上面,那這個SDK可以考慮嘗試一下了。諸如,友盟官網(wǎng):
接入友盟的App
- 查看SDK使用文檔、它們的開發(fā)者社區(qū)、聯(lián)系客服。好的SDK,使用文檔肯定會詳細(xì)指引你。出了問題,上開發(fā)者社區(qū)提問,他們的開發(fā)工程師也會社區(qū)上回答。實(shí)在不行只能聯(lián)系客服,如果客服的態(tài)度都讓你不爽,那就可以考慮換別家的SDK了。
小結(jié):選好 “車輪” ,事半功倍
3.抽象依賴第三方框架
為什么要抽象依賴于第三方框架呢?這里和第1點(diǎn)是互相照應(yīng)的,就是降低我們對具體某個框架的依賴性,從而方便我們快速切換到不同的框架去。說到這里,你可能覺得很抽象,那我直接舉一個加載圖片的例子好了。
假設(shè)你當(dāng)前為項目引入一個加載圖片的框架 —— Android-Universal-Image-Loader,最簡單的做法就是加入相應(yīng)的依賴包后,在任何需要加載圖片的地方寫上下面這樣的代碼段。
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
}
});
這種做法最簡單粗暴,但是帶來的問題也最嚴(yán)重的。如果我有幾十上百個地方都這么寫,而在某一天,我聽說Facebook出了個神器 Fresco,想要換掉 Android-Universal-Image-Loader ,你就會發(fā)現(xiàn)你需要喪心病狂的去改動幾十上百個地方的代碼,不僅工作量大,而且還容易出錯。造成這樣的原因,就在于項目和加載圖片的框架之間形成了強(qiáng)耦合,而實(shí)際上,項目本身不應(yīng)該知道我具體用了哪個加載圖片的框架。
正確的方式,應(yīng)該是對框架做一個抽象的封裝,以應(yīng)對未來發(fā)生的變化,我直接舉自己的開源項目 AndroidAlbum 中的一種封裝做法好了。
AndroidAlbum
大致代碼如下:
//1、聲明 ImageLoaderWrapper 接口,定義一些抽象的加載接口方法
public interface ImageLoaderWrapper {
/** * 顯示 圖片 * * @param imageView 顯示圖片的ImageView * @param imageFile 圖片文件 * @param option 顯示參數(shù)設(shè)置 */
public void displayImage(ImageView imageView, File imageFile, DisplayOption option);
/** * 顯示圖片 * * @param imageView 顯示圖片的ImageView * @param imageUrl 圖片資源的URL * @param option 顯示參數(shù)設(shè)置 */
public void displayImage(ImageView imageView, String imageUrl, DisplayOption option);
/** * 圖片加載參數(shù) */
public static class DisplayOption {
/** * 加載中的資源id */
public int loadingResId;
/** * 加載失敗的資源id */
public int loadErrorResId;
}
}
// 2、將 UniversalAndroidImageLoader 封裝成繼承 ImageLoaderWrapper 接口的 UniversalAndroidImageLoader ,
//這里代碼有點(diǎn)長,感興趣可以查看項目源碼中的實(shí)現(xiàn) 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、在所有需要加載圖片的地方作如下的調(diào)用
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);
這樣一來,切換框架所帶來的代價就會變得很小,這就是不直接依賴于框架所帶來的好處。當(dāng)然,以上只是我比較簡單的封裝,你也可以進(jìn)行更加細(xì)致的處理。
小結(jié):預(yù)留變更,不強(qiáng)耦合于第三方框架
4.從 MVC 到 MVP
說實(shí)話,在沒接觸 MVP 的架構(gòu)之前,一直都是使用 MVC 的模式進(jìn)行開發(fā)。而隨著項目越來越大,Activity或者 Fragment里面代碼越來越臃腫,看的時候想吐,改的時候想屎…這里撇開其他各種各樣的架構(gòu)不談,只對比MVC 和 MVP 。
- View:布局的xml文件
- Controller:Activity、Fragment、Dialog等
- Model:相關(guān)的業(yè)務(wù)操作處理數(shù)據(jù)(如對數(shù)據(jù)庫的操作、對網(wǎng)絡(luò)等的操作都應(yīng)該在Model層里)
你會發(fā)現(xiàn),如果 View 層只包含了xml文件,那我們 Android 項目中對 View 層可做操作的程度并不大,頂多就是用include復(fù)用一下布局。而 Activity 等簡直就是一個奇葩,它雖然歸屬于 Controller 層,但實(shí)際上也干著 View 層的活(View 的初始化和相關(guān)操作都是在Activity中)。就是這種既是 View 又是 Controller 的結(jié)構(gòu),違背了單一責(zé)任原則,也使得 Activity 等出現(xiàn)了上述的臃腫問題。
- View:Activity、Fragment、Dialog、Adapter等,該層不包含任何業(yè)務(wù)邏輯
- Presenter:中介,View 與 Model 不發(fā)生聯(lián)系,都通過 Presenter 傳遞
- Model:相關(guān)的業(yè)務(wù)操作處理數(shù)據(jù)(如對數(shù)據(jù)庫的操作、對網(wǎng)絡(luò)等的操作都應(yīng)該在Model層里)
相比 MVC,MVP在層次劃分上更加清晰了,不會出現(xiàn)一人身兼二職的情況(有些單元測試的童鞋,會發(fā)現(xiàn)單元測試用例更好寫了)。在此處你可以看到 View 和 Model 之間是互不知道對方存在的,這樣應(yīng)對變更的好處更大,很多時候都是 View 層的變化,而 Model 層發(fā)生的變化會相對較少,遵循 MVP 的結(jié)構(gòu)開發(fā)后,改起來代碼來也沒那么蛋疼。
這里也有地方需要注意,因?yàn)榇罅康慕换ゲ僮骷性?Presenter 層中,所以需要把握好 Presenter 的粒度,一個 Activity 可以持有多個 View 和 Presenter,這樣也就可以避開一個碩大的 View 和 Presenter 的問題了。
推薦兩個不錯的 MVP 架構(gòu)的項目給大家,還不明白的童鞋,可以自行體會一下其設(shè)計思想:
https://github.com/pedrovgs/EffectiveAndroidUI
https://github.com/antoniolg/androidmvp
小結(jié):去加以實(shí)踐的理解 MVP 吧
5.歸檔代碼
把一些常用的工具類或業(yè)務(wù)流程代碼進(jìn)行歸類整理,加入自己的代碼庫(還沒有自己個人代碼倉庫的童鞋可以考慮建一個了)。如加解密、拍照、裁剪圖片、獲取系統(tǒng)所有圖片的路徑、自定義的控件或動畫以及其其他他一些常用的工具類等。歸檔有助于提高你的開發(fā)效率,在遇到新項目的時候隨手即可引入使用。如果你想要更好的維護(hù)自己的代碼庫,不妨在不泄露公司機(jī)密的前提下,把這個私人代碼庫加上詳細(xì)文檔給開源出去。 這樣能夠吸引更多開發(fā)者來使用這些代碼,也可以獲得相應(yīng)的bug反饋,以便于著手定位修復(fù)問題,增強(qiáng)這個倉庫代碼的穩(wěn)定性。
小結(jié):合理歸檔代碼,可以的話,加以開源維護(hù)
6.性能優(yōu)化
關(guān)于性能優(yōu)化的問題,大體都還是關(guān)注那幾個方面:內(nèi)存、CPU、耗電、卡頓、渲染、進(jìn)程存活率等。對于這些地方的性能優(yōu)化思路和分析方法,網(wǎng)絡(luò)上已經(jīng)有很多答案了,此處不做贅述。我只想說以下幾點(diǎn):
- 不要過早的做性能優(yōu)化,app先求能用再求好用。在需求都還沒完成的時候把大量時間花在優(yōu)化上是本末倒置的;
- 優(yōu)化要用實(shí)際數(shù)據(jù)說話,借助測試工具進(jìn)行檢測(如:網(wǎng)易的Emmagee、騰訊的GT和APT,科大訊飛的iTest,Google的Battery Historian)。畢竟老板問你比以前耗電降低多少,總不能回答降低了一些吧???
- 任何不以減低性能損耗來做?;畹氖侄危际撬A髅?。
小結(jié):合理優(yōu)化,數(shù)據(jù)量化
7.實(shí)踐新技術(shù)
Rxjava、React Native、Kotlin…開始興起后,身邊有很多開發(fā)者會跟風(fēng)直上。學(xué)習(xí)新技術(shù)的精神是非常值得鼓勵的,但沒有經(jīng)過一段時間實(shí)踐觀察,就擅自把新技術(shù)引入到商業(yè)項目中,則有失妥當(dāng)。對于大公司的團(tuán)隊來說,會有專門團(tuán)隊或項目去研究這些新興技術(shù),以確定是否在自己的產(chǎn)品線開發(fā)中引入。但作為小公司,是不是就意味著沒有實(shí)踐嘗試新技術(shù)的機(jī)會呢?并不是!個人有以下幾點(diǎn)建議:
- 借助搜索引擎??创隧椉夹g(shù)坑多不多,口碑不錯但是坑多的話,則說明當(dāng)前技術(shù)不成熟,可以耐心等待更新;
- 考慮學(xué)習(xí)成本。學(xué)習(xí)成本太大且不容易招到懂這方面的開發(fā)者的情況下,建議不要引入該技術(shù);
- 高仿一個項目并開源。如果你想引入 React Native 做商業(yè)開發(fā),最好先高仿實(shí)現(xiàn)一個應(yīng)用然后將其開源。這樣一些對 RN 感興趣的開發(fā)者會運(yùn)行你的代碼并反饋 bug 給你,有助于你知道一些新技術(shù)的坑,并尋找相應(yīng)的解決方案,最終確定是否引入該技術(shù);
- 降低入門門檻。實(shí)踐新技術(shù)的過程盡量加以詳細(xì)的文檔記錄,這會有助于降低項目組其他同事對新技術(shù)的入門門檻,可以的話,也將學(xué)習(xí)文檔開源,獲得更多開發(fā)者對此份文檔的反饋,也可糾正一些文檔中的錯誤;
- 結(jié)合實(shí)際業(yè)務(wù)。所有新技術(shù)的引入都要考慮是否符合當(dāng)下的業(yè)務(wù)需求,我聽過有些程序猿想引入新技術(shù)的原因是因?yàn)橛X得這種技術(shù)很酷,網(wǎng)上說很好用,很啥啥啥…自己完全沒弄過就人云亦云。有時候好無語,感覺在會用一些技術(shù)就像在炫技一樣;
小結(jié):空談?wù)`國,實(shí)干興邦
8.UML
UML,馴服代碼和了解項目結(jié)構(gòu)的利器,本人也在學(xué)習(xí)和體驗(yàn)其好處的路途上。不管遇到大小項目,有了它,可以更好的理清一些脈絡(luò)結(jié)構(gòu)。對付舊的龐大項目代碼,或者有志閱讀某些開源項目代碼的開發(fā)者,絕對是居家必備。
小結(jié):工欲善其事,必先利其器
9.自造”車輪”
前面 2 提到,項目不可能從0開始,是需要引入很多第三方框架的。這里并不與 2 互相違背,而是建議有想提高技術(shù)逼格的開發(fā)者,可以在空暇時間去編碼實(shí)現(xiàn)一個框架。如果你對網(wǎng)絡(luò)訪問、圖片加載方面很有研究見解,不妨把這些腦海里的思想落實(shí)成具體的代碼。也許你會發(fā)現(xiàn),你動手去實(shí)踐的時候,考慮的東西會多得多,自己最終得到的也會更多。(特別建議那些看過很多開源代碼,又至今未自己動手自擼一發(fā)的)
小結(jié):不要停留在 api 調(diào)用的層面
10.擴(kuò)大技術(shù)圈
有空又經(jīng)濟(jì)能力承受得起的時候,不妨去參加一些自己感興趣的技術(shù)交流會。很多都有大牛上臺演講,聽聽人家的解決方案,拓寬一下自己看問題的思路,也可以多參加一些含金量高的線上活動。我有挺多開發(fā)者朋友,就是參加活動的時候認(rèn)識的,有時候遇到一些技術(shù)問題,還會互相探討交換一下解決思路。挺贊的!
小結(jié):拓寬技術(shù)視野
11.寫博客總結(jié)
這個可能沒什么好說的,大家看了標(biāo)題就懂了。它最大的好處在于:
- 系統(tǒng)化記錄自己的解決方案;
- 方便日后自己回顧;
- 有問題也會有讀者評論反饋,促進(jìn)技術(shù)交流;
- 增強(qiáng)自己書面表達(dá)能力;
小結(jié):認(rèn)真總結(jié),不斷完善
12.找個對象
程序猿不要老是對著電腦,趕緊找個對象提升一下幸福感。據(jù)說幸福感高的程序猿,編碼效率高,出bug幾率小…
總結(jié):做個面向?qū)ο蟮?span id="k6zqhab033oa" class="wp_keywordlink">程序員
大概就想到這些了,以后要是再有想寫的,另開新篇。絮絮叨叨寫了這么多,最關(guān)鍵的還是自己要落實(shí),千萬不要聽說過太多道理,卻依然過不好這一生哈!