[譯]Android原生開發的現狀,截止到2019年12月

NO IMAGE
[譯]Android原生開發的現狀,截止到2019年12月

Android原生開發的生態一直在不斷地發展變化,過去5年從事android開發的經歷讓我深刻的體會到了這一點。每隔2到3年,谷歌就會發布一些的新的開發指導建議、libraries、frameworks,我花了很多時間來認真審查這些變化並從中找出可能存在的問題。我相信許多Android開發者都有我這樣類似的經歷。

然而,2019年絕對是Android原生開發生態發生劇變的一年。在這一年裡,Android sdk添加了許多新的內容、重寫和移除了一些舊的內容,官方的開發者指南也進行了大幅度的更新。想要對Android開發有一種完整而又詳細的認識實在是太難了。

於是我寫下了這篇文章,我試圖去總結Android生態系統中所發生的事情,並對原生開發的未來做出一些預測。接下來我會把我的觀點分成不同的章節來進行具體的闡述,文章的最後我會分享一些極具爭議的觀點。

我希望這篇文章會對你們有所幫助,但是請記住,文章中肯定遺漏了許多重要的內容,而且其中的許多觀點都是我個人的偏見。

AndroidX

AndroidX的預覽版是在一年半前發佈的。大約一年前它變得穩定了,與此同時Google也停止了對舊版Support Library的進一步開發。 當我寫下這句話的那一刻,我想起了我之前在StackOverflow上提出的一個問題:Why does AOSP add new APIs to support libraries without adding them to SDK?,當時我是一個android開發新手,我想知道Support Library背後的動機,Googl為什麼不直接把Support Library放到android sdk裡呢?

不過使用“穩定”一詞來描述AndroidX似乎有點諷刺,因為關於AndroidX的任何信息似乎都不是穩定的。 Google不斷的在往AndroidX裡添加新的庫和框架。 命名空間和許多舊的API(目前還不到一年)正在以非常快的速度發展。

到目前為止,我已經將我的兩個應用遷移到了AndroidX。 一切都很順利,但我也不覺得有多驚喜。 Jetifier是一種將Support Library上的依賴項重定向到其AndroidX對等項的工具,轉換效果令人驚訝。 但是即使應用程序不大,也並不是“一鍵式遷移”。

我還參與了一個尚未遷移到AndroidX的項目,沒有任何問題。在某些情況下,似乎我完全不需要AndroidX。

總而言之,我想說的是:如果是開啟新的Android項目,那麼肯定是應該使用AndroidX;對於現有的項目,我也建議您做好遷移到AndroidX的計劃,即使您現在看不到明顯的好處。 無論如何,您很可能都需要遷移,因此最好按自己的計劃進行遷移,而不是以後當你需要一些新的AndroidX庫時進行緊急的遷移

Jetpack

在討論完AndroidX之後,就不得不提到Jetpack了。 據我所知,Jetpack最初只是“architecture components”的工具集合,但是後來擴展為包含了AndroidX的大多數(甚至所有)API的工具集合。 因此,到目前為止,我還沒有看到AndroidX和Jetpack之間有任何有意義的區別,市場營銷和公關宣傳除外。

當您訪問Jetpack的官方網站時,它看起來不像是技術文檔,更像是早期SaaS初創公司的主頁。

看看這些“感言”:

[譯]Android原生開發的現狀,截止到2019年12月

再看看下面這些app:

[譯]Android原生開發的現狀,截止到2019年12月

如果Jetpack申請2020年獨立IPO,我不會感到驚訝,因為他們是如此的專注於營銷和公關。

不過說真的,這種向自己的生態系統中的開發人員“銷售”api的做法存在一些深層次的問題。比如,為什麼有人真的想在搜索中宣傳ViewModel?

[譯]Android原生開發的現狀,截止到2019年12月

總而言之,由於Jetpack的大部分內容都是來源於AndroidX,所以我之前寫的有關AndroidX的內容在很大程度上也適用於Jetpack。

下面,我將分別討論其中一些具體的API。

Background Work

當應用程序不在前臺時讓應用也能執行操作是Android開發中最常見的場景之一。 在引入doze模式、SyncAdapter、GCMNetworkManager、FirebaseJobDispatcher、JobScheduler以及最近的WorkManager之前,您可以通過啟動服務(而不是綁定服務)來實現。 這些都是Google自己的API,不過也有很多第三方解決方案,比如Android-Job。

但是,Google最近宣佈,他們將通過WorkManager API來統一後臺任務的調度。 聽起來很不錯,但是由於某種原因,當我聽到這樣的聲音時,我總有種似曾相識的感覺……

無論最終能否統一,WorkManager都無法解決在執行後臺任務過程中存在的一個最嚴重的問題:可靠性。 我在這裡不做解釋,但是請記住,如果您需要在應用程序中執行後臺任務,請先閱讀dontkillmyapp.com上的所有信息。 此外,請在Google的issue tracker中閱讀並加註星標。

Android系統的後臺任務執行與調度是一團糟,碎片化使得它非常細微且不可靠。

過去,我一直主張儘可能的將數據同步等類似的工作放在後臺來執行,我可能是SyncAdapter的最後一批粉絲。 但是今天,鑑於可靠性問題,我主張相反的做法:儘可能避免在後臺執行操作。 如果您的PM堅持使用此功能,請向他們展示以上鍊接,並向他們解釋,後臺任務需要花費數百小時的時間來實現,而且帶來的麻煩多於收益。

有些時候執行後臺任務是不可避免的,但是在大多數情況下,您可以不這樣做。即使以給用戶帶來一些不便為代價,它也可能是最佳選擇。

Databases

毫無疑問,Room在眾多SQLite的ORM框架中佔據著主導地位。從2.2.0開始,Room支持增量註解處理。不過請記住,您的應用架構不應過於關心使用了哪種ORM框架。因此,作為architecture components的一員,Room只是市場術語,而不是技術角色。

Android開發中ORM框架的主要競爭者是SQLDelight。 這個庫比Room還要老,但是據我瞭解,在過去的一年左右的時間裡,它已經被重寫了很多。 不幸的是,它現在只針對Kotlin。 另一方面,SQLDelight支持Kotlin跨平臺。 因此,隨著Kotlin使用率的增加,我預計SQLDelight的使用率也會隨之增加。

順便說一下,AndroidX中有對原生SQLite的使用說明,我還不知道該怎麼使用。但是如果您想在應用中使用原生SQLite,那麼你或許需要去認真的研究下這個主題。

此外還有許多針對Android的非關係型的數據庫,例如Realm,Parse,Firebase,ObjectBox等(其中有些仍在使用SQLite)。如果我沒記錯的話,它們中的大多數(甚至全部)都具有自動數據同步功能。 一段時間以來,這些解決方案比較流行,但據我所知,它們已經不復存在了。但是我並不會馬上認為非關係型的數據庫不再重要了。

去年,我編寫了一個非常複雜的集成了Parse Server的Android應用。 我使用了Android版本的的Parse SDK,體驗都非常好。如果您的公司已經僱用了許多後端開發人員,或者您需要實現許多服務器端邏輯,這可能不是最佳解決方案,但是對於僅在後端執行CRUD操作的初創企業和個人來說,這可能會是一種好的選擇。

但是我必須提醒的一點是:如果您要採用數據庫即服務的解決方案(例如Firebase),那麼請務必瞭解其長期的成本和影響。

External Storage

關於外部存儲的開發,這裡有許多有“意思”的事情。

如果您應用的target sdk版本等於或者大於29,那麼你的應用將無法再正常訪問手機外部存儲上的文件,除了少數幾種明顯的情況。 相反,您需要使用SAF框架(據說),該框架允許用戶進行更精細的訪問管理。不幸的是,SAF的工作方式與之前完全不同,因此某些應用程序可能需要進行重大重構。

Google希望從Android 10開始對所有的應用程序都實行這一要求,但它引起了開發者社區的強烈抗議,於是他們決定推遲此功能。 因此,即使您的應用設置target sdk版本為 29,它仍可以在“舊版”模式下工作。 但是,無論目標API級別是多少,下一版的Android系統都將對所有應用的存儲訪問範圍做更加嚴格的限制。

到目前為止,我還沒有使用SAF框架,但是從我在互聯網上閱讀的許多討論中看來,這可能是一項艱鉅的任務。因此,如果您的應用程序還在以“舊版”模式使用外部存儲,那麼最好立即開始進行重構和測試。

Shared Preferences

幾周前,AndroidX系列中添加了一個新框架。 它的commit message是這麼說的:

New library meant to replace SharedPreferences. The name is not final, this is just for implementation review and to support the design doc (feel free to request the design doc privately)[…]

目前我們無需擔心,但從長遠來看,似乎SharedPreferences會被重寫,我們需要使用這種新的方法。

SharedPreferences和這個新框架之間的主要區別在於,默認情況下後者是異步的。 換句話說,您需要實現一個回調以獲取特定鍵的值,該回調將在以後的某個時間收到通知。

如果您對這種異步通知的機制感到好奇,則可以閱讀StackOverflow上的這個答案。 Reddit用戶Tolriq在這裡分享了他們遇到此bug的概率。 在他們的應用中,這個bug會影響1 / 10,000 / SESSIONS_PER_USER_PER_MONTH的用戶。 對於一般的應用程序,這可能微不足道。但是在需要高可靠性的情況下,這可能會引起嚴重的後果。 例如,在裝有Android Auto的汽車中,應用程序掛起和隨後的崩潰會分散駕駛員的注意力,這可能會導致非常不幸的後果。

Dependency Injection

在依賴注入方面,最大的變化就是Dagger-Android的棄用。 這裡我想解釋兩點: 首先,我說的棄用並不是指“正式”棄用,因為它尚未正式棄用。 其次,Dagger-Android並不是整個Dagger2框架,而只是相對較新的功能。 我在這個主題上寫了一篇非常詳盡的文章,所以我在這裡不再重複。

至於其他依賴注入框架,我不認為它們是Dagger的真正競爭者。 例如,Koin也許不錯,但我認為它不會吸引很多人。 實際上,我相信它僅由於兩個主要原因而得到了初步採用。 第一個是Dagger的糟糕文檔,Koin在這方面要比Dagger領先N光年。 第二個原因是Koin是用Kotlin編寫的,它藉著kotlin發展的浪潮開始興起。 到目前為止,這波浪潮已經幾乎消逝。

我認為可能會發生的情況是,純依賴注入的框架(又稱為手動依賴注入)會逐漸出現。

現在,谷歌聲稱“隨著應用程序的不斷增大,手動依賴項注入成本呈指數增長”。 我認為,這僅表明他們既不瞭解“指數”的含義,也沒做過任何實際的“測量”。 此聲明是完全錯誤的,我希望Google不要以這種方式來誤導社區裡的開發者了。

事實上,純依賴注入在後端開發中非常普遍(尤其是在開發微服務的時候,您不想在其中添加對每個服務的框架的依賴),反射也是後端開發中的一個有效的選項。 因此,如果要使用依賴注入框架,他們通常不需要解析編譯時代碼。

但是,Android開發的情況有所不同。由於我們不能使用反射式DI框架,所以我們使用了Dagger。事實上,我們可以使用反射式DI框架,並且對於大多數項目來說都可以,但是卻存在性能問題。我並不是說使用反射式DI框架是安全的,但它絕對不是一種非黑即白的方案。無論如何,Dagger已經是在Android開發中使用依賴注入的事實上的標準,我們都使用它。但使用Dagger的代價也很明顯:

  • 1)應用的代碼越多,在構建過程中運行註解處理所花費的時間就越多。
  • 2)應用參與的開發人員越多,他們需要執行的構建次數就越多。
  • 3)所有開發人員都需要學習Dagger ,這需要很多時間。

換句話說,雖然Dagger確實允許您編寫更少的代碼,但由於它會影響構建時間和所需的培訓時間,因此在大型項目上它會花費更多的時間。

在大型項目中,構建時間慢才是真正的問題,併成為主要的生產力瓶頸。 因此,儘管Dagger確實提供了非常出色的功能來簡化DI(當然,一旦您知道如何使用它),但我相信我們對純依賴注入會產生越來越多的興趣。

DataBinding

開發人員採用DataBidning的主要原因之一是不再需要調用findViewById()了。 老實說,findViewById確實很冗餘,我也不介意擺脫它們。 但是,在我看來,調用findViewById()帶來的小麻煩並不能證明使用DataBinding是合理的。 好消息是,很快我們將能夠使用另一個新功能ViewBinding來刪除這些findViewById()的調用。

實際上,我從來都不相信DataBinding。對於它(應該)解決的問題,我感覺太複雜了。 此外,DataBinding允許開發人員將邏輯放入XML佈局中。 經驗豐富的開發人員是不會使用這種方法的,因為這增加了項目維護的難度。這是DataBinding框架的另一個缺點。

早在2016年11月,當DataBinding正處於大肆宣傳的頂峰時,我在StackOverflow上的一個答案中做出了以下預測:

However, there is one prediction I can make with a high degree of confidence: Usage of the Data Binding library will not become an industry standard. I’m confident to say that because the Data Binding library (in its current implementation) provides short-term productivity gains and some kind of architectural guideline, but it will make the code non-maintainable in the long run. Once long-term effects of this library will surface – it will be abandoned.

現在,關於DataBinding的使用率,我沒有任何統計數據,但是很明顯,它並沒有成為行業標準。 我自己還從未見過使用DataBinding的專業項目,也很少見到在其應用中使用DataBinding的開發人員。據我估計,一旦ViewBinding成熟並被廣泛採用,DataBinding將會更加流行,併成為“傳統”框架。

Preserving State on Configuration Changes

自從引入ViewModel之後,在Android應用中對於配置更改的處理就變得一團糟。 我知道我的這種說法太苛刻了點,但實際上,這是我可以描述的最溫和的表達方式。

對我來說幸運的是,Gabor Varadi(又名Zhuinden)已經在Reddit上的這篇文章中對這一問題進行了總結,所以我不需要自己做。他的結論就是:不推薦使用onRetainCustomNonConfigurationInstance(),而推薦使用ViewModel。有趣的是,在該帖子的結尾,Gabor做了一些頗具嘲諷味道的預測:

[譯]Android原生開發的現狀,截止到2019年12月

你發現什麼了嗎? Retained Fragments現在已經被棄用了!

我認為,棄用Retained Fragments實際上是一個好主意。 Fragment的生命週期裡具有onAttach()和onDetach()這兩個方法的唯一原因就是為了支持Retained Fragments的使用。 通過棄用Retained Fragments,這些方法也可以棄用,並且可以簡化Fragment的生命週期。 如果您使用我的方法來處理Fragment的生命週期,那麼這種棄用就不會讓您感到困擾,因為我長期以來一直建議您避免Retained Fragments,忽略onAttach()和onDetach()方法。

儘管有充分的理由要棄用Retained Fragments,但棄用onRetainCustomNonConfigurationInstance()卻是胡說八道。 這不是我說的,而是Jake Wharton說的(您可以在前面提到的Gabor在Reddit上的帖子下閱讀他的原話)。

為什麼要做這些變動呢?我只能看到一種解釋:Google決定不管其它技術優勢如何,都強制將所有Android項目遷移到ViewModel。 他們願意棄用所有現有的替代方案以實現其目標,即使這些替代方案實際上優於ViewModel本身。

聽起來有點陰謀吧? 我同意。 但是,幸運的是,我們可以對此理論有一個簡單的檢驗。

雖然我不喜歡Preserving State on Configuration Changes,但它不會以任何方式影響我,因為我沒有使用它。 實際上,絕大部分應用程序都不需要它。 它們也不需要ViewModel。正確處理Configuration Changes的方式就是在onSaveInstanceState(Bundle)回調方法裡增加處理邏輯。 這是一種更簡單,更好的方法,因為它還可以處理保存和恢復流程(也稱為進程終止)。 因此,只要我能以這種方式保存狀態,就可以了。 儘管Google進行了大量的營銷和公關工作,但許多經驗豐富的開發人員都意識到ViewModel太複雜了,並且有更好的方法來保留配置更改的狀態。

因此,如果Google確實有別有用心,並且想迫使所有項目都使用ViewModel,那麼他們還需要棄用onSaveInstanceState(Bundle)。 我知道這聽起來很瘋狂,但這實際上是件好事,因為如果這種瘋狂的預測成真,您就會知道基礎理論是正確的。

但是,鑑於Android的內存管理機制,Google不能僅在不提供可靠替代方案的情況下就棄用onSaveInstanceState(Bundle)。 “幸運”的是,這些變化已經應用在ViewModel的保存狀態模塊上了

我想在一兩年內我們就會知道這種做法是否有任何優點。‘

總而言之,正如我在本節開頭所說的那樣,自ViewModel發佈以來,Android中的Configuration Changes就成了屎。 兩年多以前,當我撰寫題為“Android ViewModel Architecture Component Considered Harmful”的文章時,我預測ViewModels將是一種浪費。我的所有預測都是真實的,但不幸的是,事實證明真相比這還糟。

Concurrency

在併發這方面,最大的變化就是AsyncTask的棄用。 我已經寫了一篇有關此主題的非常詳細的文章,並提出了具體建議,因此在此不再贅述。

接下來我說的話可能會使部分讀者感到不滿。拜託,別太把這事當真。

Android開發中另一個流行的多線程框架RxJava很快就會成為“過去式”。 從下面這幅StackOverflow趨勢圖可以明顯看出:

[譯]Android原生開發的現狀,截止到2019年12月

許多開發者會質疑我的觀點,稱該數據不具有代表性,並且還有其他方法的可以解釋該圖。他們可能是正確的,因為我自己也不是數據科學家。但是,在此圖中,我看不到任何其他關於峰值的解釋,而且RxJava的曲線與AsyncTask的曲線具有相同的斜率。

因此,如果您尚未花時間在學習RxJava上並且您的項目沒有使用它,那麼我建議您避免使用它。實際上,這一直是我的建議,今天它也得到了數據的支持。

如果您的項目已經使用了Rx,也請不要驚慌,您無需立即重構任何東西。但是,請記住,今後找到具有Rx經驗的開發人員將越來越困難。 因此,在項目中廣泛使用Rx可能需要新的開發人員投入更多的時間。最終,廣泛使用Rx的項目將被視為“not cool”(例如今天使用AsyncTask和Loaders的項目)。

我知道我的這些觀點對於許多開發人員來說很不友好。他們花了數週時間來學習RxJava,甚至說服了同事在項目中使用RxJava,現在我卻說它會成為“過去式”。我只想說我只是分析實際情況並根據我所看到的做出預測,我可能是錯的,也可能是對的。

在Kotlin語言中,我們可以使用協程。我最近使用協程實現了一些複雜的用例,發現此框架非常的細微和複雜,並且相對不成熟,我甚至發現了一個bug。

有一種流行的說法是,協程使得併發處理更簡單。我從來不這樣認為,因為我知道併發是非常複雜的,但是在我有了一些實踐經驗之後,我可以自信地說,協程並沒有想像中的那麼美好。在我看來,協程實際上增加了程序複雜性,所以我建議你們小心地使用它們。

另一方面,協程似乎將成為Kotlin語言裡處理併發操作的默認方式。因此,我認為如果您編寫Kotlin代碼,您需要投入時間並學會使用它們。

據我所知,目前還有一個流式框架,它在協程之上添加了流處理操作符。幾個月前才穩定下來,所以我現在還不能說什麼。

Kotlin

現在讓我們來討論一下Kotlin。根據以往的經驗,我知道這是一個非常敏感的話題,而且不管我描述的多麼客觀,最終都會遭受一些開發者的攻擊。然而,我認為在總結原生Android開發現狀的時候跳過Kotlin是極不誠實的。因此,我再次請你不要把我說的話當真。

你所需要知道的一個重要的事實是:在Android開發中使用Kotlin會嚴重增加你的構建時間。

在這篇文章中,您會瞭解到我在使用Kotlin進行開發時對構建時間所進行的統計測試的結果。clean build 增加了18%的構建時間,incremental build 增加了8%的構建時間。

Uber與JetBrains也聯合發表了他們自己的研究結果,他們的結果更為負面。如果您不在應用程序中使用註解處理器,那麼引入Kotlin可能會使您的構建時間增加四倍!如果您使用了註解處理器,那麼引入Kotlin會使您的構建時間增加50%-100%。

Uber的研究結果與將OkHttp遷移到Kotlin版本後構建時間增加了4倍的結果是一致的。

如果您對這些數字感到驚訝,您不用擔心-這不是您的錯,而且您並不孤單。儘管這個事實極為重要,但它並未得到廣泛討論,並且我覺得Google也試圖迴避這個事實。我曾與Google內部一個熟悉此事的開發人員有過一次非常有趣的討論,我問他是否可以討論下這個話題,他說:“我不喜歡;我不喜歡;我不喜歡。這是一件很微妙的事情。”

除了增加構建時間之外,Kotlin還不支持增量註解處理,而在大約10個月前Java就已經支持增量註解處理了。

兩年前,我寫了一篇文章來警告開發者們在早期使用Kotlin時可能會遇到的潛在風險。在很長一段時間內我被稱為“kotlin的討厭者”。

但是,如果您今天閱讀這篇文章,您會發現我實際上低估了這些問題的嚴重性。在大型的Android項目上,構建時間是最糟糕的生產力殺手之一,而且即使在今天,即Kotlin被官方“正式採用”兩年多之後的今天,Kotlin仍然不如Java。不管Kotlin帶來什麼其他好處,所有這些都可能由於更長的構建時間而被否定。

也就是說,我們不應改忽視這樣一個事實:是谷歌將android開發的生態強行推向了kotlin,使得其使用率在穩步上升。

就我個人而言,我並沒有在我目前已經開始的新項目中選擇kotlin語言,我不想在kotlin上浪費我自己的時間。不過,從現在開始,我會認真考慮使用kotlin來開發新項目,我已經在幾個demo上嘗試過了。但是我不同意開發人員說你必須在新項目中使用Kotlin,這仍然是一種權衡。

至於你們是否應該將現有項目遷移到Kotlin,我無法提供任何一般性建議,您需要根據具體的情況進行仔細的分析。但是,如果您確實決定開始(或已經開始)遷移,那麼這個帖子可能會對您有用。

Summary

在過去的兩年中,我開發了三個新的應用程序。我認真研究了現有的項目並分析了早期技術決策所帶來的長期影響。我寫了一些博客,提供有關Android開發的高級課程。我花了很多時間在互聯網上討論Android開發相關主題。

儘管如此,我還是感覺自己無法跟上Android生態系統的變化。

如果是這樣的話,對於那些缺乏經驗、需要指導的Android開發人員,我深表歉意,而且我至今無法想象從頭開始學習Android開發的感覺。當您對框架和工具感到滿意的時候,其中許多將已過時或即將過時。加入這個原本很棒的社區可能是最糟糕的時刻。Google為他們的“包容性”感到非常自豪,但看起來它不適用於經驗不足的開發新手。

我個人認為Google對Android框架所做的更改會導致巨大的人類潛力浪費。閱讀所有這些更改需要花費數小時,更不用說實際實施它們了。我寧願花更多的時間來創造價值,而不是追逐自己的尾巴。

在這篇文章中,我試圖總結有關Android原生開發現狀的一些重要的內容,我還對未來做了一些預測。這篇文章並不完美:它可能包含一些錯誤,而且還錯過了一些其他重要內容。請隨時在下面的評論中糾正我。但是請記住,本文沒有任何私人內容。我知道我提出了一些非常有爭議的觀點,但是我相信這是對的。

我還在本文的多個地方引用了我之前寫的一些文章。我這樣做並不是為了炫耀並說“看,我是正確的!”,而是讓您能夠了解我過去的預測並將其與實際發生的情況進行比較。當我寫這些文章時,他們讀起來就像您今天讀本篇文章一樣瘋狂。但是我所做的預測卻非常準確。

當然,我也想說:“看,我是對的!”。我冒著巨大的專業風險發表了這些有爭議的預測,在得知自己沒有誤導讀者後我感到非常的欣慰。即使有時候我寧願自己是錯的,也希望Google成為真正的合作伙伴。但是到目前為止,情況並非如此。

最後附上文章作者對於跨平臺開發的一些看法,僅供參考。

[譯]Android原生開發的現狀,截止到2019年12月

相關文章

Redis6.0新特性之集群代理

iOS底層從頭梳理dyld加載流程

源碼分析|咋嘞?你的IDEA過期了吧!加個Jar包就破解了,為什麼?

驚呆了!Java程序員最常犯的錯竟然是這10個