Android 誕生已久,其開發方式保持着高頻更迭,相較於早期的開發方式已大不相同,尤其是近幾年 Google 熱切推崇的 MAD 開發技術。
其實很多開發者已經有意或無意地正在使用這門技術,借着 2022 開年探討技術趨勢的契機,想要完整地總結 MAD 的願景、構成、優勢以及一些學習建議。
MAD,全稱 Modern Android Development:是 Google 針對 Android 平台提出的全新開發技術。旨在指導我們利用官方推出的各項技術來進行高效的 App 開發。
有的時候 Google 會將其翻譯成現代安卓開發,有的時候又翻譯成新式安卓開發,個人覺得前者的翻譯雖然激進、倒也貼切。
下面按照 MAD 的構成要點逐步展開,幫助大家快速了解其理念。如果對其中的語言、工具包或框架產生了興趣,定要在日後的開發中嘗試和掌握。
內容前瞻官方一直在優化 App 的開發體驗:從 IDE 到語言再到框架,這些新技術愈發完善也愈發瑣碎。提出一個全新的概念來整合這些鬆散的技術方便介紹和推廣,也方便開發者們理解。
MAD 便是提出的全新理念,期望在語言、工具、框架等多個層面提供卓越的開發體驗,其願景和優勢:
其涵蓋的內容:
同時,官方針對 MAD 技術提供了認證考試和技能的計分插件,大家在實踐一段時間之後可以體驗一下:

Android Studio 剛推出的初期飽受批評,吃內存、Bug 多、不好用,開發者一度對 Eclipse 戀戀不捨。
隨着 Google 和開發者的不斷協力,AS 愈加穩定、功能愈加強大,大家可以活用 AS 的諸多特性以提高開發效率。
和 Chrome 一樣,針對不同需求,AS 提供了三個版本供開發者靈活選擇。
接下來介紹 AS 其中幾個好用的特性。
2.1 Database Inspector
Database Inspector 可以實時查看 Jetpack Room 框架生成的數據庫文件,同時也支持實時編輯和部署到設備當中。相較之前需要的 SQLite 命令或者額外導出並藉助 DB 工具的方式更為高效和直觀。
2.2 Layout / Motion EditorLayout Editor 擁有諸多優點,不知大家熟練運用了沒有:
Motion Editor 則是支持 MotionLayout 類型布局的視覺設計編輯器,可讓更輕鬆地創建和預覽和調試動畫。
Layout Inspector 則可以查看某進程某畫面的詳細布局,完整展示 View 樹的各項屬性。在不方便代碼調試或剖析其他 App 的情況下非常好用。同時已經支持直接檢查 Compose 編寫的 UI 布局了,喜極而泣。

AS 的 Realtime Profilers 工具可以幫助我們在如下四個方面監測和發現問題,有的時候在沒有其他 App 代碼的情況下通過 Memory Profilers 還可以查看其內部的實例和變量細節。

Apk 的下載會耗費網絡流量,安裝了還會占用存儲空間。其體積的大小會對 App 安裝和留存產生影響,分析和優化其體積顯得尤為必要。
藉助 AS 的 APK Analyzer 可以幫助完成如下幾項工作:
篇幅原因只介紹了少部分特性,其他的還有很多,需要各位自行探索:
相比之下,Google 官方的這篇「Android Studio 新特性詳解」介紹得更新、更全,大家可以一看。
3.Android App Bundleandroid app bundle 是一種發布格式,其中包含您應用的所有經過編譯的代碼和資源,它會將 APK 生成及簽名交由 Google Play 來完成。
這個新格式對面向海外市場的 3rd Party App 影響較大,對面向國內市場的 App 影響不大。但作為未來的構建格式,了解和適配是遲早的事。
Google 對 .aab 格式非常重視,也極力推廣:從去年也就是 2021 年 8 月起,規定新的 App 必須採用該格式才能在 Google Play 上架。
fun 神的「AAB 扶正!APK 將退出歷史舞台」文章針對 AAB 技術有完整的說明,可以進一步了解。
4.KotlinA modern programming language that makes developers happier.
Kotlin是 大名鼎鼎的 JetBrains 公司於 2011 年開發的面向 JVM 的新語言,對於 Android 開發者來說,選擇 Kotlin 開發 App 有如下理由:
Kotlin 代碼簡潔、可讀性高:縮減了大量樣板代碼,以縮短編寫和閱讀代碼的時間
可與 Java 互相調用,靈活搭配
容易上手,尤其是熟悉 Java 的 Android 開發者
代碼安全,編譯器嚴格檢查代碼錯誤
專屬的協程機制,大大簡化異步編程
提供了大量 Android 專屬的 KTX 擴展
唯一支持 Android 全新 UI 編程方式 Compose 的開發語言
很多知名 App 都已經採用 Kotlin 進行開發,比如 Evernote、Twiiter、Pocket、WeChat 等。
下面我們選取 Kotlin 的幾個典型特性,結合代碼簡單介紹下其優勢。
4.2 簡化函數聲明Kotlin 語法的簡潔體現在很多地方,就比如函數聲明的簡化。
如下是一個包含條件語句的 Java 函數的寫法:
StringgenerateAnswerString(intcount,intcountThreshold){if(count>countThreshold){return"Ihavetheanswer.";}else{return"Theanswereludesme.";}}Java 支持三元運算符可以進一步簡化。
StringgenerateAnswerString(intcount,intcountThreshold){returncount>countThreshold?"Ihavetheanswer.":"Theanswereludesme.";}Kotlin 的語法並不支持三元運算符,但可以做到同等的簡化效果:
fungenerateAnswerString(count:Int,countThreshold:Int):String{returnif(count>countThreshold)"Ihavetheanswer."else"Theanswereludesme."}它同時還可以省略大括號和 return 關鍵字,採用賦值形式進一步簡化。這樣子的寫法已經很接近於語言的日常表達,高級~
fungenerateAnswerString(count:Int,countThreshold:Int):String=if(count>countThreshold)"Ihavetheanswer."else"Theanswereludesme."反編譯 Class 之後發現其實際上仍採用的三元運算符的寫法,這種語法糖會體現在 Kotlin 的很多地方😅。
publicfinalStringgenerateAnswerString2(intcount,intcountThreshold){returncount>countThreshold?"Ihavetheanswer.":"Theanswereludesme.";}4.3 高階函數介紹高階函數之前,我們先看一個向函數內傳入回調接口的例子。
一般來說,需要先定義一個回調接口,調用函數傳入接口實現的實例,函數進行一些處理之後執行回調,藉助Lambda 表達式可以對接口的實現進行簡化。
interfaceMapper{intmap(Stringinput);}classTemp{voidmain(){stringMapper("Android",input->input.length()+2);}intstringMapper(Stringinput,Mappermapper){//Dosomething...returnmapper.map(input);}}Kotlin 則無需定義接口,直接將匿名回調函數作為參數傳入即可。(匿名函數是最後一個參數的話,方法體可單獨拎出,增加可讀性)
這種接受函數作為參數或返回值的函數稱之為高階函數,非常方便。
classTemp{funmain(){stringMapper("Android"){input->input.length+2}}funstringMapper(input:String,mapper:(String)->Int):Int{//Dosomething...returnmapper(input)}}事實上這也是語法糖,編譯器會預設默認接口來幫忙實現高階函數。
4.4 Null 安全可以說 Null 安全是 Kotlin 語言的一大特色。試想一下 Java 傳統的 Null 處理無非是在調用之前加上空判斷或衛語句,這種寫法既繁瑣,更容易遺漏。
voidfunction(Beanbean){//Nullcheckif(bean!=null){bean.doSometh();}//或者衛語句if(bean==null){return;}bean.doSometh();}而 Kotlin 要求變量在定義的時候需要聲明是否可為空:帶上 ? 即表示可能為空,反之不為空。作為參數傳遞給函數的話也要保持是否為空的類型一致,否則無法通過編譯。
比如下面的 functionA() 調用 functionB() 將導致編譯失敗,但 functionB() 的參數在聲明的時候沒有添加 ? 即為非空類型,那麼函數內可直接使用該參數,沒有 NPE 的風險。
funfunctionA(){varbean:Bean?=nullfunctionB(bean)}funfunctionB(bean:Bean){bean.doSometh()}為了通過編譯,可以將變量 bean 聲明中的 ? 去掉, 並賦上正常的值。
但很多時候變量的值是不可控的,我們無法保證它不為空。那麼為了通過編譯,還可以選擇將參數 bean 添加上 ? 的聲明。這個時候函數內不就不可直接使用該參數了,需要做明確的 Null 處理,比如:
總結起來將很好理解:
反編譯一段 Null 處理後可以看到,非空類型本質上是利用 @NotNull 的註解,可空類型調用前的 ? 則是手動的 null 判斷。
publicfinalintstringMapper(@NotNullStringstr,@NotNullFunction1mapper){...return((Number)mapper.invoke(str)).intValue();}privatefinalvoidfunction(Stringbean){if(bean!=null){booleanvar3=false;Double.parseDouble(bean); }}4.5 協程 Coroutines介紹 Coroutines 之前,先來回顧下 Java 或 Android 如何進行線程間通信?有何痛點?
比如:AsyncTask、Handler、HandlerThread、IntentService、RxJava、LiveData 等。它們都有複雜易錯、不簡潔、回調冗餘的痛點。
比如一個請求網絡登錄的簡單場景:我們需要新建線程去請求,然後將結果通過 Handler 或 RxJava 回傳給主線程,其中的登錄請求必須明確寫在非 UI 線程中。
voidlogin(Stringusername,Stringtoken){StringjsonBody="{username:\"$username\",token:\"$token\"}";Executors.newSingleThreadExecutor().execute(()->{Resultresult;try{result=makeLoginRequest(jsonBody);}catch(IOExceptione){result=newResult(e);}ResultfinalResult=result;newHandler(Looper.getMainLooper()).post(()->updateUI(finalResult));});}ResultmakeLoginRequest(StringjsonBody)throwsIOException{URLurl=newURL("https://example.com/login");HttpURLConnectionhttpURLConnection=(HttpURLConnection)url.openConnection();httpURLConnection.setRequestMethod("POST");...httpURLConnection.connect();intcode=httpURLConnection.getResponseCode();if(code==200){//Handleinputstream...returnnewResult(bean);}else{returnnewResult(code);}}Kotlin 的 Coroutines 則是以順序的編碼方式實現異步操作、同時不阻塞調用線程的簡化並發處理的設計模式。
其具備如下的異步編程優勢:
採用協程實現異步處理的將變得清晰、簡潔,同時因為指定耗時邏輯運行在工作線程的緣故,無需管理線程切換可直接更新 UI。
funlogin(username:String,token:String){valjsonBody="{username:\"\$username\",token:\"\$token\"}"GlobalScope.launch(Dispatchers.Main){valresult=try{makeLoginRequest(jsonBody)}catch(e:Exception){Result(e)}updateUI(result)}}@Throws(IOException::class)suspendfunmakeLoginRequest(jsonBody:String):Result{valurl=URL("https://example.com/login")varresult:ResultwithContext(Dispatchers.IO){valhttpURLConnection=url.openConnection()asHttpURLConnectionhttpURLConnection.run{requestMethod="POST"...}httpURLConnection.connect()valcode=httpURLConnection.responseCoderesult=if(code==200){Result(bean)}else{Result(code)}}returnresult}4.6 KTXKTX 是專門為 Android 庫設計的 Kotlin 擴展程序,以提供簡潔易用的 Kotlin 代碼。
比如使用 SharedPreferences 寫入數據的話,我們會這麼編碼:
voidupdatePref(SharedPreferencessharedPreferences,booleanvalue){sharedPreferences.edit().putBoolean("key",value).apply();}引入 KTX 擴展函數之後將變得更加簡潔。
funupdatePref(sharedPreferences:SharedPreferences,value:Boolean){sharedPreferences.edit{putBoolean("key",value)}這只是 KTX 擴展的冰山一角,還有大量好用的擴展以及 Kotlin 的優勢值得大家學習和實踐,比如:
Jetpack 單詞的本意是火箭人,框架的 Logo 也可以看出來是個綁着火箭的 Android。Google 用它命名,含義非常明顯,希望這些框架能夠成為 Android 開發的助推器:助力 App 開發,體驗飛速提升。
Jetpack 分為架構、UI、基礎功能和特定功能等幾個方面,其中架構板塊是全新設計的,涵蓋了 Google 花費大量精力開發的系列框架,是本章節着力講解的方面。
架構以外的部分實際上是 AOSP 本身的一些組件進行優化之後集成到了Jetpack 體系內而已,這裡不再提及。

Jetpack 具備如下的優勢供我們在實現某塊功能的時候收腰選擇:
如果對 Jetpack 的背景由來感興趣的朋友可以看我之前寫的一篇文章:「從Preference組件的更迭看Jetpack的前世今生」。下面,我們選取 Jetpack 中幾個典型的框架來了解和學習下它具體的優勢。
5.1 View Binding通常的話綁定布局裡的 View 實例有哪些辦法?又有哪些缺點?
AS 現在默認採用 ViewBinding 框架幫我們綁定 View。
來簡單了解一下它的用法:
<!--result_profile.xml--><LinearLayout...><TextViewandroid:id="@+id/name"/></LinearLayout>ViewBinding 框架初始化之後,無需額外的綁定處理,即可直接操作 View 實例。
classMainActivity:AppCompatActivity(){overridefunonCreate(savedInstanceState:Bundle){super.onCreate(savedInstanceState)valbinding=ResultProfileBinding.inflate(layoutInflater)setContentView(binding.root)binding.name.text="Helloworld"}}原理比較簡單:編譯器將生成布局同名的綁定類文件,然後在初始化的時候將布局裡的 Root View 和其他預設了 ID 的 View 實例緩存起來。事實上無論是上面的註解,插件還是這個框架,其本質上都是通過 findViewById 實現的 View 綁定,只是進行了封裝。
ViewBinding 框架能改善通常做法的缺陷,但也並非完美。特殊情況下仍需使用通常做法,比如操作布局以外的系統 View 實例 ContentView,ActionBar 等。
一般來說,將數據反映到 UI 上需要經過如下步驟:
而 DataBinding 框架可以免去上面的步驟 2 和 3。它需要我們在步驟 1 的布局當中就聲明好數據和 UI 的關係,比如文本內容的數據來源、是否可見的邏輯條件等。
<layout...><data><importtype="android.view.View"/><variablename="viewModel"type="com.example.splash.ViewModel"/></data><LinearLayout...><TextView...android:text="@{viewModel.userName}"android:visibility="@{viewModel.age>=18?View.VISIBLE:View.GONE}"/></LinearLayout></layout>上述 DataBinding 布局展示的是當 ViewModel 的 age 屬性大於 18 歲才顯示文本,而文本內容來自於 ViewModel 的 userName 屬性。
valbinding=ResultProfileBinding.inflate(layoutInflater)binding.viewModel=viewModelActivity 中無需綁定和手動更新 View,像 ViewBinding 一樣初始化之後指定數據來源即可,後續的 UI 展示和刷新將被自動觸發。DataBinding 還有諸多妙用,大家可自行了解。
5.3 Lifecycle監聽 Activity 的生命周期並作出相應處理是 App 開發的重中之重,通常有如下兩種思路。
而 Lifecycle 框架則可以高效管理生命周期。
使用 Lifecycle 框架需要先定義一個生命周期的觀察者 LifecycleObserver,給生命周期相關處理添加上 OnLifecycleEvent 註解,並指定對應的生命狀態。比如 onCreate 的時候執行初始化,onStart 的時候開始連接,onPause 的時候斷開連接。
classMyLifecycleObserver(privatevallifecycle:Lifecycle):LifecycleObserver{...@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)funinit(){enabled=checkStatus()}@OnLifecycleEvent(Lifecycle.Event.ON_START)funstart(){if(enabled){connect()}}@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)funstop(){if(connected){disconnect()}}}然後在對應的 Activity 里添加觀察:
classMainActivity:AppCompatActivity(){overridefunonCreate(savedInstanceState:Bundle){...MyLifecycleObserver(lifecycle).also{lifecycle.addObserver(it)}}}Lifecycle 的簡單例子可以看出生命周期的管理變得很清晰,同時能和 Activity 的代碼解耦。
繼續看上面的小例子:假使初始化操作 init() 是異步耗時操作怎麼辦?
init 異步的話,onStart 狀態回調的時候 init 可能沒有執行完畢,這時候 start 的連接處理 connect 可能被跳過。這時候 Lifecycle 提供的 State 機制就可以派上用場了。
使用很簡單,在異步初始化回調的時候再次執行一下開始鏈接的處理,但需要加上 STARTED 的 State 條件。這樣既可以保證 onStart 時跳過連接之後能手動執行連接,還能保證只有在 Activity 處於 STARTED 及以後的狀態下才執行連接。
classMyLifecycleObserver(...):LifecycleObserver{@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)funinit(){checkStatus{result->if(result){enable()}}}funenable(){enabled=true//初始化完畢的時候確保只有在STARTED及以後的狀態下執行連接if(lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)){if(!connected){connect()}}}...}5.4 Live DataLiveData 是一種新型的可觀察的數據存儲框架,比如下面的使用示例,數據的封裝和發射非常便捷:
classStockLiveData(symbol:String):LiveData<BigDecimal>(){privatevalstockManager=StockManager(symbol)privatevallistener={price:BigDecimal->//將請求到的數據發射出去value=price}//畫面活動狀態下才請求overridefunonActive(){stockManager.requestPriceUpdates(listener)}//非活動狀態下移除請求overridefunonInactive(){stockManager.removeUpdates(listener)}}classMainActivity:AppCompatActivity(){overridefunonCreate(savedInstanceState:Bundle?){//註冊觀察StockLiveData("Tesla").run{observe(this@MainActivity,Observer{...})}}}支持異步傳遞數據以外,LiveData 還有很多優勢:
但必須要說 LiveData 的定位和使用有這樣那樣的問題,官方的態度也一直在變,了解之後多使用 Flow 來完成異步的數據提供。
5.5 RoomAndroid 上開發數據庫有哪些痛點?
官方推出的 Room 是在 SQLite 上提供了一個抽象層,通過註解簡化數據庫的開發。以便在充分利用 SQLite 的強大功能的同時,能夠高效地訪問數據庫。

需要定義 Entity,Dao 以及 Database 三塊即可完成數據庫的配置,其他的數據庫實現交由框架即可。
@EntityclassMovie():BaseObservable(){@PrimaryKey(autoGenerate=true)varid=0@ColumnInfo(name="movie_name",defaultValue="HarryPotter")lateinitvarname:String...}@DaointerfaceMovieDao{@Insertfuninsert(varargmovies:Movie?):LongArray?@Deletefundelete(movie:Movie?):Int@Updatefunupdate(varargmovies:Movie?):Int@get:Query("SELECT*FROMmovie")valallMovies:LiveData<List<Movie?>?>}@Database(entities=[Movie::class],version=1)abstractclassMovieDataBase:RoomDatabase(){abstractfunmovieDao():MovieDaocompanionobject{@VolatileprivatevarsInstance:MovieDataBase?=nullprivateconstvalDATA_BASE_NAME="jetpack_movie.db"@JvmStaticfungetInstance(context:Context):MovieDataBase?{if(sInstance==null){synchronized(MovieDataBase::class.java){if(sInstance==null){sInstance=createInstance(context)}}}returnsInstance}privatefuncreateInstance(context:Context):MovieDataBase{returnRoom.databaseBuilder(context.applicationContext,MovieDataBase::class.java,DATA_BASE_NAME).build()}}}在 ViewModel 初始化 DataBase 接口之後即可利用其提供的 DAO 接口執行操作,接着利用 LiveData 將數據發射到 UI。
classMovieViewModel(application:Application):AndroidViewModel(application){privatevalmediatorLiveData=MediatorLiveData<List<Movie?>?>()privatevaldb:MovieDataBase?init{db=MovieDataBase.getInstance(application)if(db!=null){mediatorLiveData.addSource(db.movieDao().allMovies){movieList->if(db.databaseCreated.value!=null){mediatorLiveData.postValue(movieList)}}};}fungetMovieList(owner:LifecycleOwner?,observer:Observer<List<Movie?>?>?){if(owner!=null&&observer!=null)mediatorLiveData.observe(owner,observer)}}Room 具備很多優勢值得選作數據庫的開發首選:
ViewModel 框架和 AppCompat、Lifecycle 框架一樣,可謂是 Jetpack 框架最重要的幾個基礎框架。雖功能不僅限於此,但我們想要藉此探討一下它在數據緩存方面的作用。
通常怎麼處理橫豎屏切換導致的 Activity 重繪?一可以選擇自生自滅,只有部分 View 存在自行恢復的處理、也可以配置 ConfigurationChange 手動復原重要的狀態、或者保存數據至 BundleState,在 onCreate 等時機去手動恢復。
得益於 ViewModel 實例在 Activity 重繪之後不銷毀,其緩存的數據不受外部配置變化的影響,進而確保數據可以自動恢復數據,無需處理。
這裡定義一個 ViewModel,其中提供一個獲取數據的方法,用來返回一個 30 歲名叫 Ellison 的朋友。Activity 取得 vm 實例之後觀察數據的變化,並將數據反映到 UI 上。當屏幕方向變化後,名字和年齡的 TextView 可自動恢復,無需額外處理。
classPersonContextModel(application:Application):AndroidViewModel(application){valpersonLiveData=MutableLiveData<Person>()valpersonInWork:Unitget(){valtestPerson=Person(30,"Ellison")personLiveData.postValue(testPerson)}}classMainActivity:AppCompatActivity(){overridefunonCreate(savedInstanceState:Bundle?){...valmodel=ViewModelProvider(this).get(PersonContextModel::class.java)model.personLiveData.observe(this,Observer{person:Person->binding.name.setText(person.name)binding.age.setText(person.age.toString())})binding.get.setOnClickListener({view->model.personInWork})}}ViewModel 的眾多優勢:
完成一個相機預覽的功能,使用 Camera2 的話需要如下諸多流程,會比較繁瑣:

而採用 CameraX進行開發的話,幾十行代碼即可完成預覽功能。
privatevoidsetupCamera(PreviewViewpreviewView){ListenableFuture<ProcessCameraProvider>cameraProviderFuture=ProcessCameraProvider.getInstance(this);cameraProviderFuture.addListener(()->{try{mCameraProvider=cameraProviderFuture.get();bindPreview(mCameraProvider,previewView);}catch(ExecutionException|InterruptedExceptione){e.printStackTrace();}},ContextCompat.getMainExecutor(this));}privatevoidbindPreview(@NonNullProcessCameraProvidercameraProvider,PreviewViewpreviewView){mPreview=newPreview.Builder().build();mCamera=cameraProvider.bindToLifecycle(this,CameraSelector.DEFAULT_BACK_CAMERA,mPreview);mPreview.setSurfaceProvider(previewView.getSurfaceProvider());}上面是 CameraX 的架構,可以看到其底層仍然是 Camera2,外加高度封裝的接口,以及 Vendor 自定義的功能庫。
使用它來作為全新的相機使用框架,具備很多優勢:
這是一家銀行服務公司並提供了同名應用,僅在移動設備上提供數字金融服務。他們的使命是向每個人傳授生財之道。為了完成新客戶註冊,Monzo 應用會拍攝身份證明文件(例如護照、駕照或身份證)的圖片,並拍攝自拍視頻來證明身份證明文件屬於申請者。
早期版本使用的是 camera2 API。在某些設備上會隨機發生崩潰和異常行為,這導致 25% 的潛在客戶無法繼續進行身份證明拍攝和自拍視頻步驟。
5.8 其他框架篇幅有限,Jetpack 集合中還有非常多其他的優質框架等待大家的挖掘。
在開發某個功能的時候,看看是否有輪子可用,尤其是官方的。
5.9 官方推薦的應用架構我在官方的推薦架構上做了些補充,一般的 App 推薦採用如下的架構組件。

架構絕非固定模式,依實際需求和最佳實踐自由搭配~
6.Jetpack ComposeJetpack Compose 是 Google 耗費五年傾力打造,用於構建 Android 原生界面的全新 UI 工具包。Android 誕生多年,UI 體系早已成熟,為什麼這麼要重造一個輪子?🤔
原因:
其發展歷程:

去年上半年 Google 啟動了為期四周的全球 Compose 挑戰賽,提供了 500 多份樂高聯名積木,十幾部 Pixel 手機獎品,引發數萬計Android開發者嘗鮮,提交作品。

這些比賽內容其實涵蓋了 Compose 所需要用到的大部分技術。Google 的大力推廣也足見其決心和重視程度,日後必將成為Android平台上重要的UI編寫方式,早日上車!💪
6.2 編程思想我們通過一個展示 「Hello World」 文本的小例子,來直觀感受一下 Compose 編程思想的明顯差異。
傳統的 UI 編程方式
我們再熟悉不過了。常見的操作是先定義一個 xml,然後通過 Activity 的 setContentView() 將 xml 放進去,之後就交給系統來加載。
Compose UI 工具包則依賴 Composable 註解將展示 UI 的函數聲明為可組合函數,Compose 編譯器負責標記可組合函數內的組件,並進行展示。
布局的部分均需要放在該函數內交由 Compose 組合。
classMainActivity:AppCompatActivity(){overridefunonCreate(savedInstanceState:Bundle?){...setContent{SimpleComposable()}}@ComposablefunSimpleComposable(){Text("HelloWorld")}}6.3 進階示例來看一下下面這個簡單的動態效果,並思考一下:如果採用傳統的 View 編程方式來實現,你需要多少代碼量?

那如果採用 Compose 來實現呢?只需要 10 行即可。
篇幅有限,事實上 Compose 具備非常多的優勢,亟待大家的挖掘:
聲明式 UI:只負責描述界面,Compose 系統負責其餘工作
狀態驅動:界面隨着狀態自動更新
高效渲染:固定測量,層級嵌套性能仍是 O(n)
結合 AS 的 Preview 視圖可實時查看和直接交互 UI
兼容傳統 View 樹編程方式,可混合使用
支持 Material Design 設計語言
擁有 Jetpack 框架的大力配合
基於 Kotlin,代碼簡潔,大量 Kotlin 專屬 API
跨平台亦有布局:Desktop、 Web
大家可以利用 Compose 先來實現一個新畫面,或者改造一個現有畫面,逐步推進 Compose 的學習和實踐。但是 Compose UI 工具包目前在部分場景下的組件支持有限,比如 WebView、``CameraView` 等,這些場景下仍需要配合 Android 原生的 View 方式來完成。
6.5 Sample官方 Sample:完全使用 Compose 設計的八大主流場景的 App,專業、全面。https://github.com/android/compose-samples

Movie客戶端 本人使用 Compose 的大部分 UI 組件、視圖切換和數據刷新重構的電影搜索 App。https://github.com/ellisonchan/ComposeMovie
俄羅斯方塊 fun 神將自定義 Compose 組件和狀態管理髮揮到了極致,搭配定時器和各式動畫實現,非常值得用來深入學習 Compose 技術。https://github.com/vitaviva/compose-tetris

ComposeBird 本人在 fun 神的俄羅斯方塊遊戲的激勵下使用 Compose 復刻了風靡一時的 Flappy Bird,感興趣的也可以學習實現思路。https://github.com/ellisonchan/ComposeBird
本次介紹了 MAD 涵蓋的諸多新技術,大家可以感受到 Google 在一刻不停地革新技術。從工具到語言、框架到發行方式都在進行全方位地改良,之前耕耘多年的技術說廢就廢,絕不手軟。
究其原因,繞不開產品生命的兩大角色:開發者和消費者。
然而新事物的出現必然伴隨着舊事物的衰落,開發者該如何對待老技術、如何看待層出不窮、前途不明的新技術?光跨平台這一項,Google 和 Jetbrains 就推出了 Flutter、KMM、Compose Multiplatform 三個技術,任何人都卷不過來的。
我總結了幾句四字短語,與你分享我的感受和態度:

推薦文章
打造一個 Compose 版的俄羅斯方塊
歡迎 Jetpack 新成員 SplashScreen
AAB 什麼鬼?竟敢打壓鴻蒙?
網友提議為 Kotlin 引入這些新特性 ...