目錄
1.Service
2.廣播
3.如何判斷Activity是否在運(yùn)行
4.自定義View
5.理解Java內(nèi)存
6.線程安全問題理解
7.布局問題
前言
本文主要從原理上深入理解Android中常用的一些知識點挚币,我們做開發(fā)時可能直接調(diào)用相應(yīng)的API,沒有關(guān)心一些原理知識亮蒋。功能也可以照樣實現(xiàn)。但理解掌握其原理面對問題時才會更從蓉妆毕。
一.Service
- Service的onCreate回調(diào)函數(shù)是不可以做耗時操作的慎玖,因為是在主線程中執(zhí)行的。要執(zhí)行耗時操作的話可以開啟一個線程笛粘,或者使用IntentService趁怔,它是Service的一個子類,可以處理異步請求薪前。在需要的時候創(chuàng)建润努,在任務(wù)執(zhí)行完畢之后自動關(guān)閉(不需要考慮在什么時候關(guān)閉了)
源碼分析:在onStartCommand中回調(diào)了onStart,onStart中通過mServiceHandler發(fā)送消息到該handler的handleMessage中去示括。最后handleMessage中回調(diào)onHandleIntent(intent)铺浇。回調(diào)完成后回調(diào)用 stopSelf(msg.arg1)垛膝,注意這個msg.arg1是個int值鳍侣,相當(dāng)于一個請求的唯一標(biāo)識丁稀。每發(fā)送一個請求,會生成一個唯一的標(biāo)識倚聚,然后將請求放入隊列线衫,當(dāng)全部執(zhí)行完成(最后一個請求也就相當(dāng)于getLastStartId == startId),或者當(dāng)前發(fā)送的標(biāo)識是最近發(fā)出的那一個(getLastStartId == startId)秉沼,則會銷毀我們的Service.如果傳入的是-1則直接銷毀桶雀。當(dāng)任務(wù)完成銷毀Service回調(diào)onDestory,可以看到在onDestroy中釋放了我們的Looper:mServiceLooper.quit()唬复。
http://www.reibang.com/p/4ca760e5040b
二.廣播
- 有序廣播和無序廣播矗积,有序廣播會優(yōu)先發(fā)給優(yōu)先級高的,并且優(yōu)先級高的可以決定是否發(fā)送給下一個敞咧。除此之外還有一種廣播sendStickyBroadcast棘捣,這種在發(fā)送廣播時Reciever還沒有被注冊,但它注冊后還是可以收到在它之前發(fā)送的那條廣播休建。有時候基于數(shù)據(jù)安全考慮乍恐,我們想發(fā)送廣播只有自己(本進(jìn)程)能接收到,那么該如何去做呢测砂?使用LocalBroadcastManager類茵烈,Support V4包里的一個類。LocalBroadcastManager源碼分析:單例實現(xiàn)砌些,在私有化構(gòu)造函數(shù)中呜投,基于主線程的 Looper 新建了一個 Handler,在handleMessage中會調(diào)用接收器對廣播的消息進(jìn)行處理存璃。注冊接收器方法中仑荐,有兩個HashMap,mReceivers 存儲廣播和過濾器信息,以BroadcastReceiver作為 key纵东,IntentFilter鏈表作為 value粘招。mReceivers 是接收器和IntentFilter的對應(yīng)表,主要作用是方便在unregisterReceiver(…)取消注冊偎球,mActions 以Action為 key洒扎,注冊這個Action的BroadcastReceiver鏈表為 value。mActions 的主要作用是方便在廣播發(fā)送后快速得到可以接收它的BroadcastReceiver衰絮。發(fā)送廣播:先根據(jù)Action從mActions中取出ReceiverRecord列表逊笆,循環(huán)每個ReceiverRecord判斷 filter 和 intent 中的action、type岂傲、scheme难裆、data、categoried 是否 match,是的話則保存到receivers列表中乃戈,發(fā)送 what 為MSG_EXEC_PENDING_BROADCASTS的消息褂痰,
用廣播來更新界面是否合適?更新界面也分很多種情況症虑,如果不是頻繁地刷新缩歪,使用廣播來做也是可以的。但對于較頻繁地刷新動作谍憔,建議還是不要使用這種方式匪蝙。廣播的發(fā)送和接收是有一定的代價的,它的傳輸是通過Binder進(jìn)程間通信機(jī)制來實現(xiàn)的(細(xì)心人會發(fā)現(xiàn)Intent是實現(xiàn)了Parcelable接口的)习贫,那么系統(tǒng)定會為了廣播能順利傳遞做一些進(jìn)程間通信的準(zhǔn)備逛球。除此之外,還可能有其他的因素讓廣播發(fā)送和到達(dá)是不準(zhǔn)時的苫昌,這種情況可能嗎颤绕?很可能,而且很容易發(fā)生祟身。我們要先了解Android的ActivityManagerService有一個專門的消息隊列來接收發(fā)送出來的廣播奥务,sendBroadcast執(zhí)行完后就立即返回,但這時發(fā)送來的廣播只是被放入到隊列袜硫,并不一定馬上被處理氯葬。當(dāng)處理到當(dāng)前廣播時,又會把這個廣播分發(fā)給注冊的廣播接收分發(fā)器ReceiverDispatcher婉陷,ReceiverDispatcher最后又把廣播交給接Receiver所在的線程的消息隊列去處理(就是你熟悉的UI線程的Message Queue)帚称。整個過程從發(fā)送--ActivityManagerService--ReceiverDispatcher進(jìn)行了兩次Binder進(jìn)程間通信,最后還要交到UI的消息隊列憨攒,如果基中有一個消息的處理阻塞了UI世杀,當(dāng)然也會延遲你的onReceive的執(zhí)行阀参。
三.如何判斷Activity是否在運(yùn)行肝集?
很多人可能都用過isFinishing來判斷,用多了就會發(fā)現(xiàn)好象不太準(zhǔn)蛛壳,因為該方法直接返回mFinished杏瞻,而mFinished是在finish()中被賦值的,也就是說只有通過調(diào)用finish()結(jié)束的Activity衙荐,mFinished的值才會被置為true捞挥。所以有時候Activity的生命周期沒有按我們預(yù)想的來走時(如內(nèi)存緊張時),會出現(xiàn)判斷出錯的情況忧吟∑龊看看Google工程師是怎么判斷的(AsyncTask中的onPostExecute片段):
if (activity == null || activity.isDestroyed() || activity.isFinishing()) { return; }
多了一個isDestroyed()的判斷。
四.自定義View
- 做過自定義View的人很容易遇到這個問題,因為Activity轉(zhuǎn)屏讹俊,或Home鍵到后臺很容易在被系統(tǒng)銷毀垦沉,恢復(fù)時我們肯定是希望看到View保持之前狀態(tài)。自定義View的狀態(tài)是如何保存的仍劈?
可以隨便從一個Android自帶的控件中看到厕倍,如TextView的源代碼;BaseSavedState是View的一個內(nèi)部靜態(tài)類,從代碼上我們也很容易看出是把控件的屬性(如selStart)打包到Parcel容器贩疙,Activity的onSaveInstanceState讹弯、onRestoreInstanceState最終也會調(diào)用到控件的這兩個同名方法。無法保證系統(tǒng)會在銷毀Activity前一定調(diào)用onSaveInstanceState这溅,例如用戶使用“返回” 按退出 Activity 時组民,因為用戶的行為是在顯式關(guān)閉 Activity,所以不會調(diào)用onSaveInstanceState芍躏。(在onSop之前調(diào)用)Activity類的onSaveInstanceState默認(rèn)實現(xiàn)會恢復(fù)Activity的狀態(tài)邪乍,默認(rèn)實現(xiàn)會為布局中的每個View調(diào)用相應(yīng)的 onSaveInstanceState方法,讓每個View都能保存自身的信息对竣。
注意:想要保存View的狀態(tài)庇楞,需要在XML布局文件中提供一個唯一的ID(android:id),如果沒有設(shè)置這個ID的話,View控件的onSaveInstanceState是不會被調(diào)用的贞远。
自定義View控件的狀態(tài)被保存需要滿足兩個條件:
View有唯一的ID熄诡;
View的初始化時要調(diào)用setSaveEnabled(true) ;
里面的SparseArray(完整的參數(shù)是:SparseArray<Parcelable> )是一個KEY-VALUE的Map睛驳,KEY當(dāng)然就是View的ID了。所以細(xì)看一下源碼的調(diào)用過程膜廊,你一下就理解為什么一定要給View調(diào)置一個唯一的ID了乏沸。如果是通過new出來的View,如果設(shè)置了id狀態(tài)也是會被保存的爪瓜。
五.理解Java內(nèi)存
Java源代碼文件(.java)會被Java編譯器編譯為字節(jié)碼文件(.class)蹬跃,然后由JVM中的類加載器加載各個類的字節(jié)碼文件,加載完畢之后铆铆,交由JVM執(zhí)行引擎執(zhí)行蝶缀。JVM在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域。JVM會用一段空間來存儲程序執(zhí)行期間需要用到的數(shù)據(jù)和相關(guān)信息薄货,這段空間一般被稱作為Runtime Data Area(運(yùn)行時數(shù)據(jù)區(qū))翁都,也就是我們常說的JVM內(nèi)存。
我們可以把上圖的“運(yùn)行時數(shù)據(jù)區(qū)”分為線程私有和共享數(shù)據(jù)區(qū)兩大類谅猾。其中線程私有的數(shù)據(jù)區(qū)包含程序計數(shù)器柄慰、虛擬機(jī)棧鳍悠、本地方法區(qū),所有線程共享的數(shù)據(jù)區(qū)包含Java堆坐搔、方法區(qū)贼涩,在方法區(qū)內(nèi)有一個常量池。
- 程序計數(shù)器 :記錄正在執(zhí)行的虛擬機(jī)字節(jié)碼的地址
由于Java 虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的薯蝎,在任何一個確定的時刻遥倦,一個處理器(對于多核處理器來說是一個內(nèi)核)只會執(zhí)行一條線程中的指令。因此占锯,為了線程切換后能恢復(fù)到正確的執(zhí)行位置袒哥,每條線程都需要有一個獨立的程序計數(shù)器,各條線程之間的計數(shù)器互不影響消略,獨立存儲堡称。線程私有的。如果正在執(zhí)行的是Natvie 方法艺演,這個計數(shù)器值則空却紧。此內(nèi)存區(qū)域是唯一一個在Java 虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。
- 虛擬機(jī)棧(JVM Stack)
也就是我們說的棧胎撤,方法執(zhí)行的內(nèi)存晓殊,虛擬機(jī)棧描述的是Java 方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧伤提、動態(tài)鏈接巫俺、方法出口等信息。每一個方法被調(diào)用直至執(zhí)行完成的過程肿男,就對應(yīng)著一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程介汹。
- 本地方法棧(Native Method Stack)
本地方法棧則為虛擬機(jī)使用到的Native方法提供內(nèi)存空間。
- 堆
堆是Java 虛擬機(jī)所管理的內(nèi)存中最大的一塊舶沛。Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域嘹承,在虛擬機(jī)啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實例如庭,幾乎所有的對象實例都在這里分配內(nèi)存叹卷。但是隨著JIT 編譯器的發(fā)展與逃逸分析技術(shù)的逐漸成熟,棧上分配柱彻、標(biāo)量替換優(yōu)化技術(shù)將會導(dǎo)致一些微妙的變化發(fā)生豪娜,所有的對象都分配在堆上也漸漸變得不是那么“絕對”了餐胀。
堆是垃圾收集器管理的主要區(qū)域
- 方法區(qū)(Method Area)
主要存放的是已被虛擬機(jī)加載的類信息哟楷、常量、靜態(tài)變量否灾、編譯器編譯后的代碼等數(shù)據(jù)卖擅。
- 常量池(Runtime Constant Pool)
存放編譯器生成的各種字面量和符號引用,是方法區(qū)的一部分。
常量池
Java中的常量池惩阶,實際上分為兩種形態(tài):靜態(tài)常量池和運(yùn)行時常量池挎狸。
- 謂靜態(tài)常量池,即*.class文件中的常量池断楷,class文件中的常量池不僅僅包含字符串(數(shù)字)字面量锨匆,還包含類、方法的信息冬筒,占用class文件絕大部分空間恐锣。
- 運(yùn)行時常量池,則是jvm虛擬機(jī)在完成類裝載操作后舞痰,將class文件中的常量池載入到內(nèi)存中土榴,并保存在方法區(qū)中,我們常說的常量池响牛,就是指方法區(qū)中的運(yùn)行時常量池玷禽。
看一下下面的例子,以下面的例子來說明:
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // false
System.out.println(s1 == s9); // false
System.out.println(s4 == s5); // false
System.out.println(s1 == s6); // true
首先說明一點呀打,在java 中矢赁,對于引用類型直接使用==操作符,比較的是兩個字符串的引用地址
s1 == s2這個非常好理解贬丛,s1坯台、s2在賦值時,均使用的字符串字面量瘫寝,說白話點蜒蕾,就是直接把字符串寫死,在編譯期間焕阿,這種字面量會直接放入class文件的常量池中咪啡,從而實現(xiàn)復(fù)用,載入運(yùn)行時常量池后暮屡,s1撤摸、s2指向的是同一個內(nèi)存地址,所以相等褒纲。
s1 == s3這個地方有個坑准夷,s3雖然是動態(tài)拼接出來的字符串,但是所有參與拼接的部分都是已知的字面量莺掠,在編譯期間衫嵌,這種拼接會被優(yōu)化,編譯器直接幫你拼好彻秆,因此String s3 = "Hel" + "lo";在class文件中被優(yōu)化成String s3 = "Hello";楔绞,所以s1 == s3成立结闸。
s1 == s4當(dāng)然不相等,s4雖然也是拼接出來的酒朵,但new String("lo")這部分不是已知字面量桦锄,是一個不可預(yù)料的部分,編譯器不會優(yōu)化蔫耽,必須等到運(yùn)行時才可以確定結(jié)果结耀,結(jié)合字符串不變定理所以地址肯定不同。
s7匙铡、s8在賦值的時候使用的字符串字面量饼记,但是拼接成s9的時候,s7慰枕、s8作為兩個變量具则,都是不可預(yù)料的,編譯器畢竟是編譯器具帮,不可能當(dāng)解釋器用博肋,所以不做優(yōu)化,等到運(yùn)行時蜂厅,s7匪凡、s8拼接成的新字符串,在堆中地址不確定掘猿,不可能與方法區(qū)常量池中的s1地址相同病游。
s4 == s5已經(jīng)不用解釋了,絕對不相等稠通,二者都在堆中衬衬,但地址不同。
s1 == s6這兩個相等完全歸功于intern方法改橘,s5在堆中滋尉,內(nèi)容為Hello ,intern方法會嘗試將Hello字符串添加到常量池中飞主,并返回其在常量池中的地址狮惜,因為常量池中已經(jīng)有了Hello字符串,所以intern方法直接返回地址碌识;而s1在編譯期就已經(jīng)指向常量池了碾篡,因此s1和s6指向同一地址,相等筏餐。
我們可以得出三個非常重要的結(jié)論:
必須要關(guān)注編譯期的行為开泽,才能更好的理解常量池。
運(yùn)行時常量池中的常量胖烛,基本來源于各個class文件中的常量池眼姐。
程序運(yùn)行時,除非手動向常量池中添加常量(比如調(diào)用intern方法)佩番,否則jvm不會自動添加常量到常量池
Java內(nèi)存模型
java內(nèi)存模型即Java Memory Model众旗,簡稱JMM。JMM定義了Java 虛擬機(jī)(JVM)在計算機(jī)內(nèi)存(RAM)中的工作方式趟畏。程序中的變量存儲在主內(nèi)存中贡歧,每個線程擁有自己的工作內(nèi)存并存放變量的拷貝,線程讀寫自己的工作內(nèi)存赋秀,通過主內(nèi)存進(jìn)行變量的交互利朵。JMM就是規(guī)定了工作內(nèi)存和主內(nèi)存之間變量訪問的細(xì)節(jié),通過保障原子性猎莲、有序性绍弟、可見性來實現(xiàn)線程的有效協(xié)同和數(shù)據(jù)的安全。
- JVM如何判斷一個對象實例是否應(yīng)該被回收著洼?
標(biāo)準(zhǔn)答案: 垃圾回收器會建立有向圖的方式進(jìn)行內(nèi)存管理樟遣,通過GC Roots來往下遍歷,當(dāng)發(fā)現(xiàn)有對象處于不可達(dá)狀態(tài)的時候身笤,就會對其標(biāo)記為不可達(dá)豹悬,以便于后續(xù)的GC回收。
- 說說JVM的垃圾回收策略液荸。
標(biāo)準(zhǔn)答案: JVM采用分代垃圾回收瞻佛。在JVM的內(nèi)存空間中把堆空間分為年老代和年輕代。將大量創(chuàng)建了沒多久就會消亡的對象存儲在年輕代娇钱,而年老代中存放生命周期長久的實例對象伤柄。
六.線程安全問題理解
計算機(jī)在執(zhí)行程序時,每條指令都是在CPU中執(zhí)行的文搂,而執(zhí)行指令過程中响迂,勢必涉及到數(shù)據(jù)的讀取和寫入。由于程序運(yùn)行過程中的臨時數(shù)據(jù)是存放在主存(物理內(nèi)存)當(dāng)中的细疚,這時就存在一個問題蔗彤,由于CPU執(zhí)行速度很快,而從內(nèi)存讀取數(shù)據(jù)和向內(nèi)存寫入數(shù)據(jù)的過程跟CPU執(zhí)行指令的速度比起來要慢的多疯兼,因此如果任何時候?qū)?shù)據(jù)的操作都要通過和內(nèi)存的交互來進(jìn)行然遏,會大大降低指令執(zhí)行的速度。因此在CPU里面就有了高速緩存吧彪。也就是待侵,當(dāng)程序在運(yùn)行過程中,會將運(yùn)算需要的數(shù)據(jù)從主存復(fù)制一份到CPU的高速緩存當(dāng)中姨裸,那么CPU進(jìn)行計算時就可以直接從它的高速緩存讀取數(shù)據(jù)和向其中寫入數(shù)據(jù)秧倾,當(dāng)運(yùn)算結(jié)束之后怨酝,再將高速緩存中的數(shù)據(jù)刷新到主存當(dāng)中。
同理那先,線程拷貝變量保存在本地內(nèi)存(比如機(jī)器的寄存器)中农猬,而不是直接在主存中進(jìn)行讀寫。先在線程私有的內(nèi)存中修改售淡,然后在合適的時候?qū)懟刂鲀?nèi)存斤葱,這就可能造成一個線程在主存中修改了一個變量的值,而另外一個線程還繼續(xù)使用它在寄存器中的變量值的拷貝揖闸,造成數(shù)據(jù)的不一致揍堕。 也就是多線程安全問題衩茸。
并發(fā)編程中的三個概念
原子性:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷递瑰,要么就都不執(zhí)行。
可見性 :是指當(dāng)多個線程訪問同一個變量時隙畜,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值慎颗。
有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行俯萎。
指令重排序:處理器為了提高程序運(yùn)行效率夫啊,可能會對輸入代碼進(jìn)行優(yōu)化撇眯,它不保證程序中各個語句的執(zhí)行先后順序同代碼中的順序一致虱咧,但是它會保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的腕巡。指令重排序不會影響單個線程的執(zhí)行煎楣,但是會影響到線程并發(fā)執(zhí)行的正確性。
也就是說喻喳,要想并發(fā)程序正確地執(zhí)行沸枯,必須要保證原子性、可見性以及有序性盈魁。只要有一個沒有被保證杨耙,就有可能會導(dǎo)致程序運(yùn)行不正確珊膜。
volatile關(guān)鍵字
使用volatile關(guān)鍵字會強(qiáng)制將修改的值立即寫入主存车柠;保證了不同線程對這個變量進(jìn)行操作時的可見性竹祷,即一個線程修改了某個變量的值塑陵,這新值對其他線程來說是立即可見的令花。但是volatile不能保證原子性彭则。
七.布局問題
優(yōu)化布局的嵌套:
-
merge標(biāo)簽
的作用是合并UI布局,使用該標(biāo)簽?zāi)芙档蚒I布局的嵌套層次瓦胎。
merge標(biāo)簽可用于兩種情況:
- 布局頂結(jié)點是FrameLayout且不需要設(shè)置background或padding等屬性,可以用merge代替北戏,因為Activity內(nèi)容試圖的parent view就是個FrameLayout,所以可以用merge消除只剩一個蠕嫁。
- 某布局作為子布局被其他布局include時剃毒,使用merge當(dāng)作該布局的頂節(jié)點,這樣在被引入時頂結(jié)點會自動被忽略基公,而將其子節(jié)點全部合并到主布局中轰豆。
-
ViewStub
:延時加載
ViewStub標(biāo)簽引入的布局默認(rèn)不會inflate,既不會顯示也不會占用位置雨席。 ViewStub常用來引入那些默認(rèn)不會顯示陡厘,只在特殊情況下顯示的布局,如數(shù)據(jù)加載進(jìn)度布局是目、出錯提示布局等谤饭。
-
include
: 引入子布局
將可復(fù)用的組件抽取出來并通過include標(biāo)簽使用,但<include>標(biāo)簽不能減少布局的層次。include主要解決的是相同布局的復(fù)用問題揉抵。
RelativeLayout和LinearLayout性能PK
RelativeLayout需要對其子View進(jìn)行兩次measure過程冤今。而LinearLayout則只需一次measure過程闺兢,所以顯然會快于RelativeLayout,但是如果LinearLayout中有weight屬性,則也需要進(jìn)行兩次measure淮逊,但即便如此郎任,應(yīng)該仍然會比RelativeLayout的情況好一點车猬。
RelativeLayout對View的measure方法里對繪制過程做了一個優(yōu)化霉猛,如果我們或者我們的子View沒有要求強(qiáng)制刷新,而父View給子View的傳入值也沒有變化(也就是說子View的位置沒變化)珠闰,就不會做無謂的measure惜浅。但是上面已經(jīng)說了RelativeLayout要做兩次measure,而在做橫向的測量時承绸,縱向的測量結(jié)果尚未完成裸影,只好暫時使用myHeight傳入子View系統(tǒng),假如子View的Height不等于(設(shè)置了margin)myHeight的高度军熏,那么measure中上面代碼所做得優(yōu)化將不起作用轩猩,這一過程將進(jìn)一步影響RelativeLayout的繪制性能。而LinearLayout則無這方面的擔(dān)憂。解決這個問題也很好辦均践,如果可以画饥,盡量使用padding代替margin。
- 1.RelativeLayout會讓子View調(diào)用2次onMeasure浊猾,LinearLayout 在有weight時抖甘,也會調(diào)用子View2次onMeasure
- 2.RelativeLayout的子View如果高度和RelativeLayout不同,則會引發(fā)效率問題葫慎,當(dāng)子View很復(fù)雜時衔彻,這個問題會更加嚴(yán)重。如果可以偷办,盡量使用padding代替margin艰额。
- 3.在不影響層級深度的情況下,使用LinearLayout和FrameLayout而不是RelativeLayout。為什么Google給開發(fā)者默認(rèn)新建了個RelativeLayout椒涯,而自己卻在DecorView中用了個LinearLayout柄沮。因為DecorView的層級深度是已知而且固定的,上面一個標(biāo)題欄废岂,下面一個內(nèi)容欄祖搓。采用RelativeLayout并不會降低層級深度,所以此時在根節(jié)點上用LinearLayout是效率最高的湖苞。而之所以給開發(fā)者默認(rèn)新建了個RelativeLayout是希望開發(fā)者能采用盡量少的View層級來表達(dá)布局以實現(xiàn)性能最優(yōu)拯欧,因為復(fù)雜的View嵌套對性能的影響會更大一些。
新式布局
- ConstraintLayout
ConstraintLayout即約束布局财骨,在2016年由Google I/O推出镐作。ConstraintLayout和RelativeLayout有點類似,控件之間根據(jù)依賴關(guān)系而存在隆箩,但比RelativeLayout更加靈活该贾。創(chuàng)建大型復(fù)雜的布局仍然可以使用扁平的層級(不用嵌套View Group),說的簡單些就是捌臊,再復(fù)雜的界面也可以只有2層層次杨蛋。
- FlexBoxLayout
FlexBoxLayout可以理解成一種更高級的LinearLayout,不過比LinearLayout更加強(qiáng)大和靈活娃属。如果我們使用LinearLayout布局的話六荒,那么不同的分辨率,也許我們要重新調(diào)整布局矾端,勢必會需要跟多的布局文件放在不同的資源目錄掏击。而使用FlexBoxLayout來布局的話,它可以適應(yīng)各種界面的改變(所以叫響應(yīng)式布局)秩铆。