HashMap簡(jiǎn)介
HashMap由數(shù)組+鏈表+ 紅黑樹(臨界值為8)組成的,可以存儲(chǔ)null鍵和null值,線程不安全,數(shù)組是HashMap的主體,鏈表則是主要為了解決哈希沖突而存在的,實(shí)現(xiàn)的基于key-value存取的工具類,在JDK1.8之前沒有紅黑樹這一數(shù)據(jù)結(jié)構(gòu),在JDK1.8之后對(duì)其進(jìn)行了優(yōu)化:考慮到發(fā)生大量Hash碰撞時(shí)鏈表查詢效率低,所以加入了紅黑樹這一數(shù)據(jù)結(jié)構(gòu)以提高此種情況的查詢效率,通過閾值控制,將鏈表和紅黑樹進(jìn)行相互轉(zhuǎn)化
為什么樹化的臨界值為8?
通過源碼我們得知HashMap源碼作者通過泊松分布算出,當(dāng)桶中結(jié)點(diǎn)個(gè)數(shù)為8時(shí),出現(xiàn)的幾率是億分之6的,因此常見的情況是桶中個(gè)數(shù)小于8的情況,此時(shí)鏈表的查詢性能和紅黑樹相差不多,因?yàn)檗D(zhuǎn)化為樹還需要時(shí)間和空間,所以此時(shí)沒有轉(zhuǎn)化成樹的必要。
HashMap的工作原理
HashMap 的實(shí)例有兩個(gè)參數(shù)影響其性能:“初始容量(初始size為16)” 和 “加載因子(默認(rèn)加載因子是 0.75,)”。
HashMap基于hashing原理,我們通過put()和get()方法儲(chǔ)存和獲取對(duì)象。當(dāng)我們將鍵值對(duì)傳遞給put()方法時(shí),它調(diào)用鍵對(duì)象的hashCode()方法來(lái)計(jì)算hashcode,然后找到bucket位置來(lái)儲(chǔ)存值對(duì)象。當(dāng)獲取對(duì)象時(shí),通過鍵對(duì)象的equals()方法找到正確的鍵值對(duì),然后返回值對(duì)象。HashMap使用鏈表來(lái)解決碰撞問題,當(dāng)發(fā)生碰撞了,對(duì)象將會(huì)儲(chǔ)存在鏈表的下一個(gè)節(jié)點(diǎn)中。 HashMap在每個(gè)鏈表節(jié)點(diǎn)中儲(chǔ)存鍵值對(duì)對(duì)象。在jdk1.8中,如果鏈表的長(zhǎng)度超過了閾值,鏈表就會(huì)轉(zhuǎn)換成紅黑樹,來(lái)提高性能,當(dāng)紅黑樹的節(jié)點(diǎn)個(gè)數(shù)小于6的時(shí)候,又會(huì)轉(zhuǎn)化成鏈表。如果所有的桶都滿了(容量 * 負(fù)載因子(0.75) ),這個(gè)時(shí)候就需要擴(kuò)容 resize(),并且重新計(jì)算Node對(duì)象的位置重新排列。
擴(kuò)容機(jī)制
// 默認(rèn)初始化容量大小,為16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容量:2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
// 負(fù)載因子,在擴(kuò)容時(shí)使用
static final float DEFAULT_LOAD_FACTOR = 0.75f;
擴(kuò)容(resize)就是重新計(jì)算容量,使用一個(gè)容量更大的數(shù)組來(lái)代替已有的容量小的數(shù)組,將原有Entry數(shù)組的元素拷貝到新的Entry數(shù)組里。
默認(rèn)的負(fù)載因子大小為0.75,也就是說(shuō),當(dāng)一個(gè)map填滿了75%的bucket時(shí)候,和其它集合類(如ArrayList等)一樣,將會(huì)創(chuàng)建原來(lái)HashMap大小的兩倍的bucket數(shù)組(jdk1.6,但不超過最大容量),來(lái)重新調(diào)整map的大小,并將原來(lái)的對(duì)象放入新的bucket數(shù)組中。
當(dāng)兩個(gè)對(duì)象的hashcode相同會(huì)發(fā)生什么
當(dāng)兩個(gè)對(duì)象的hashcode相同的時(shí)候,所以它們的bucket位置相同,會(huì)發(fā)生碰撞,因?yàn)閔ashmap使用鏈表來(lái)存儲(chǔ)key ,value的鍵值對(duì),所以這個(gè)Entry (包含有鍵值對(duì)的Map.Entry對(duì)象)會(huì)存儲(chǔ)在鏈表中。
有什么方法可以減少hash碰撞
- 擾動(dòng)函數(shù)可以減少碰撞,原理是他讓內(nèi)容不同的對(duì)象返回不同的hashcode值,這樣就會(huì)少產(chǎn)生碰撞,也就是在數(shù)據(jù)結(jié)構(gòu)中鏈表的結(jié)構(gòu)少了,在取值時(shí),會(huì)很少的調(diào)用equals方法,提高M(jìn)ap的性能,擾動(dòng)hash方法的內(nèi)部算法實(shí)現(xiàn),目的是讓不同的對(duì)象返回不同的hashcode。
- 使用不可變的,聲明為final的對(duì)象做鍵值,如String Integer。不可變使得能夠緩存不同鍵的hashcode。這將提高整個(gè)回去對(duì)象的速度。因?yàn)镾tring是final的,而且已經(jīng)重寫了equals()和hashCode()方法了。不可變性是必要的,因?yàn)闉榱艘?jì)算hashCode(),就要防止鍵值改變,如果鍵值在放入時(shí)和獲取時(shí)返回不同的hashcode的話,那么就不能從HashMap中找到你想要的對(duì)象。
HashMap的數(shù)組長(zhǎng)度為什么一定要是2的冪
答:hashMap的數(shù)組長(zhǎng)度一定保持2的次冪,數(shù)組長(zhǎng)度保持2的次冪,length-1的低位都為1,會(huì)使得獲得的數(shù)組索引index分布更加均勻,如果不是2的次冪,也就是低位不是全為1此時(shí),低位有可能是0,計(jì)算hash值得時(shí)候沖突幾率變大,發(fā)生hash碰撞,會(huì)造成空間的。
HashMap為什么線程不安全
HashMap在put的時(shí)候,插入的元素超過了容量(由負(fù)載因子決定)的范圍就會(huì)觸發(fā)擴(kuò)容操作,就是rehash,這個(gè)會(huì)重新將原數(shù)組的內(nèi)容重新hash到新的擴(kuò)容數(shù)組中,在多線程的環(huán)境下,存在同時(shí)其他的元素也在進(jìn)行put操作,如果hash值相同,可能出現(xiàn)同時(shí)在同一數(shù)組下用鏈表表示,造成閉環(huán),導(dǎo)致在get時(shí)會(huì)出現(xiàn)死循環(huán),所以HashMap是線程不安全的。
HashTable
底層數(shù)組+鏈表實(shí)現(xiàn),無(wú)論key還是value都不能為null,線程安全,實(shí)現(xiàn)線程安全的方式是在修改數(shù)據(jù)時(shí)鎖住整個(gè)HashTable,效率低,ConcurrentHashMap做了相關(guān)優(yōu)化
初始size為11,擴(kuò)容:newsize = olesize*2+1
計(jì)算index的方法:index = (hash & 0x7FFFFFFF) % tab.length
ConcurrentHashMap
底層采用分段的數(shù)組+鏈表實(shí)現(xiàn),線程安全
通過把整個(gè)Map分為N個(gè)Segment,可以提供相同的線程安全,但是效率提升N倍,默認(rèn)提升16倍。(讀操作不加鎖,由于HashEntry的value變量是 volatile的,也能保證讀取到最新的值。)
Hashtable的synchronized是針對(duì)整張Hash表的,即每次鎖住整張表讓線程獨(dú)占,ConcurrentHashMap允許多個(gè)修改操作并發(fā)進(jìn)行,其關(guān)鍵在于使用了鎖分離技術(shù)
有些方法需要跨段,比如size()和containsValue(),它們可能需要鎖定整個(gè)表而而不僅僅是某個(gè)段,這需要按順序鎖定所有段,操作完畢后,又按順序釋放所有段的鎖
擴(kuò)容:段內(nèi)擴(kuò)容(段內(nèi)元素超過該段對(duì)應(yīng)Entry數(shù)組長(zhǎng)度的75%觸發(fā)擴(kuò)容,不會(huì)對(duì)整個(gè)Map進(jìn)行擴(kuò)容),插入前檢測(cè)需不需要擴(kuò)容,有效避免無(wú)效擴(kuò)容
哈希表的實(shí)現(xiàn)
哈希表(Hash table,也叫散列表), 是根據(jù)關(guān)鍵碼值(Key value)而直接進(jìn)行訪問的數(shù)據(jù)結(jié)構(gòu)。也就是說(shuō),它通過把關(guān)鍵碼值映射到表中一個(gè)位置來(lái)訪問記錄,以加快查找的速度。這個(gè)映射函數(shù)叫做散列函數(shù),存放記錄的數(shù)組叫做散列表。
哈希表hash table(key,value) 的做法其實(shí)很簡(jiǎn)單,就是把Key通過一個(gè)固定的算法函數(shù)既所謂的哈希函數(shù)轉(zhuǎn)換成一個(gè)整型數(shù)字,然后就將該數(shù)字對(duì)數(shù)組長(zhǎng)度進(jìn)行取余,取余結(jié)果就當(dāng)作數(shù)組的下標(biāo),將value存儲(chǔ)在以該數(shù)字為下標(biāo)的數(shù)組空間里。
而當(dāng)使用哈希表進(jìn)行查詢的時(shí)候,就是再次使用哈希函數(shù)將key轉(zhuǎn)換為對(duì)應(yīng)的數(shù)組下標(biāo),并定位到該空間獲取value,如此一來(lái),就可以充分利用到數(shù)組的定位性能進(jìn)行數(shù)據(jù)定位。
哈希表最大的優(yōu)點(diǎn),就是把數(shù)據(jù)的存儲(chǔ)和查找消耗的時(shí)間大大降低,
有幾種常見的單例模式?對(duì)于這幾種單例模式synchronized具體鎖的是什么東西?
餓漢式:
懶漢式:先聲明,用的時(shí)候在實(shí)例化,所以叫懶漢式
雙重校驗(yàn)鎖DCL(double checked locking)
、靜態(tài)內(nèi)部類實(shí)現(xiàn)(也叫類加載方式)的單例模式
、枚舉單例
synchronized具體鎖的是什么東西?(答案:被鎖的代碼塊叫作臨界區(qū),只有獲取到鎖資源才能進(jìn)入臨界區(qū),進(jìn)行相應(yīng)的操作)
Binder的運(yùn)行機(jī)制
Binder
Android系統(tǒng)中,涉及到多進(jìn)程間的通信底層都是依賴于Binder IPC機(jī)制。例如當(dāng)進(jìn)程A中的Activity要向進(jìn)程B中的Service通信,這便需要依賴于Binder IPC。不僅于此,整個(gè)Android系統(tǒng)架構(gòu)中,大量采用了Binder機(jī)制作為IPC(進(jìn)程間通信)方案。
-
為什么用binder
性能方面(Binder數(shù)據(jù)拷貝只需要一次,而管道、消息隊(duì)列、Socket都需要2次)
安全方面(Binder機(jī)制從協(xié)議本身就支持對(duì)通信雙方做身份校檢,因而大大提升了安全性)
對(duì)于用戶空間,不同進(jìn)程之間彼此是不能共享的,而內(nèi)核空間卻是可共享的。Client進(jìn)程向Server進(jìn)程通信,恰恰是利用進(jìn)程間可共享的內(nèi)核內(nèi)存空間來(lái)完成底層通信工作的,Client端與Server端進(jìn)程往往采用ioctl等方法跟內(nèi)核空間的驅(qū)動(dòng)進(jìn)行交互。
-
Binder原理
Binder通信采用client-server架構(gòu),從組件視角來(lái)說(shuō),包含Client、Server、ServiceManager以及binder驅(qū)動(dòng),其中ServiceManager用于管理系統(tǒng)中的各種服務(wù)。架構(gòu)圖如下所示:
Binder 機(jī)制
- 首先需要注冊(cè)服務(wù)端,只有注冊(cè)了服務(wù)端,客戶端才有通訊的目標(biāo),服務(wù)端通過 ServiceManager 注冊(cè)服務(wù),注冊(cè)的過程就是向 Binder 驅(qū)動(dòng)的全局鏈表 binder_procs 中插入服務(wù)端的信息,然后向 ServiceManager 的 svcinfo 列表中緩存一下注冊(cè)的服務(wù)。
- 有了服務(wù)端,客戶端就可以跟服務(wù)端通訊了,須先向ServiceManager中獲取相應(yīng)的Service,
- 有了服務(wù)端的引用我們就可以向服務(wù)端發(fā)送請(qǐng)求了,通過 BinderProxy 將我們的請(qǐng)求參數(shù)發(fā)送給 ServiceManager,通過共享內(nèi)存的方式使用內(nèi)核方法 copy_from_user() 將我們的參數(shù)先拷貝到內(nèi)核空間,這時(shí)我們的客戶端進(jìn)入等待狀態(tài),然后 Binder 驅(qū)動(dòng)向服務(wù)端的 todo 隊(duì)列里面插入一條事務(wù),執(zhí)行完之后把執(zhí)行結(jié)果通過 copy_to_user() 將內(nèi)核的結(jié)果拷貝到用戶空間(這里只是執(zhí)行了拷貝命令,并沒有拷貝數(shù)據(jù),binder只進(jìn)行一次拷貝),喚醒等待的客戶端并把結(jié)果響應(yīng)回來(lái),這樣就完成了一次通訊。
ClassLoader類加載器
ClassLoader的具體作用就是將class文件加載到j(luò)vm虛擬機(jī)中去,程序就可以正確運(yùn)行了。但是,jvm啟動(dòng)的時(shí)候,并不會(huì)一次性加載所有的class文件,而是根據(jù)需要去動(dòng)態(tài)加載。想想也是的,一次性加載那么多jar包那么多class,那內(nèi)存不崩潰。
熱修復(fù)原理 andfix
第一步 在服務(wù)器生成修復(fù)包,打包成dex 給用戶下載
第二步
app客戶端加載dex包
第三步
找到修復(fù)好的class 找到修復(fù)好的方法
使用注解標(biāo)識(shí)需要修復(fù)的類和方法
通過DexFile 加載手機(jī)里面的dex文件
服務(wù)器編程方法 需要指定包名和方法,讓他知道 需要去修復(fù)那個(gè)地方。其實(shí)這一步就是制造補(bǔ)丁包dex
***class 怎么打包成class?
sdk目錄build_tool 下的dx.bat工具
Thinker
思路:從服務(wù)器下載修復(fù)好的dex包如(class1.dex),替換原有bug的dex
注意事項(xiàng)
1,主包不能有bug 不然啟動(dòng)就閃退,不能加載dex
2,必須是運(yùn)行的時(shí)候補(bǔ)丁包如何生成?
dx.bat 打包dex,
說(shuō)起熱修復(fù)就不得不提類的加載機(jī)制,和常規(guī)的JVM類似,在Android中類的加載也是通過ClassLoader來(lái)完成,具體來(lái)說(shuō)就是PathClassLoader 和 DexClassLoader 這兩個(gè)Android專用的類加載器,這兩個(gè)類的區(qū)別如下:
PathClassLoader:只能加載已經(jīng)安裝到Android系統(tǒng)中的apk文件(/data/app目錄),是Android默認(rèn)使用的類加載器。
DexClassLoader:可以加載任意目錄下的dex/jar/apk/zip文件,也就是我們一開始提到的補(bǔ)丁。
這兩個(gè)類都是繼承自BaseDexClassLoader,我們可以看一下BaseDexClassLoader的構(gòu)造函數(shù)
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
這個(gè)構(gòu)造函數(shù)只做了一件事,就是通過傳遞進(jìn)來(lái)的相關(guān)參數(shù),初始化了一個(gè)DexPathList對(duì)象。DexPathList的構(gòu)造函數(shù),就是將參數(shù)中傳遞進(jìn)來(lái)的程序文件(就是補(bǔ)丁文件)封裝成Element對(duì)象,并將這些對(duì)象添加到一個(gè)Element的數(shù)組集合dexElements中去。
可能有多個(gè)dex 使用hashset存放需要修復(fù)的dex集合
我們現(xiàn)在要去查找一個(gè)名為name的class,那么DexClassLoader將通過以下步驟實(shí)現(xiàn):
在DexClassLoader的findClass 方法中通過一個(gè)DexPathList對(duì)象findClass()方法來(lái)獲取class
在DexPathList的findClass 方法中,對(duì)之前構(gòu)造好dexElements數(shù)組集合進(jìn)行遍歷,一旦找到類名與name相同的類時(shí),就直接返回這個(gè)class,找不到則返回null。
總的來(lái)說(shuō),通過DexClassLoader查找一個(gè)類,最終就是就是在一個(gè)數(shù)組中查找特定值的操作。
綜合以上所有的觀點(diǎn),我們很容易想到一種非常簡(jiǎn)單粗暴的熱修復(fù)方案。假設(shè)現(xiàn)在代碼中的某一個(gè)類或者是某幾個(gè)類有bug,那么我們可以在修復(fù)完bug之后,可以將這些個(gè)類打包成一個(gè)補(bǔ)丁文件,然后通過這個(gè)補(bǔ)丁文件封裝出一個(gè)Element對(duì)象,并且將這個(gè)Element對(duì)象插到原有dexElements數(shù)組的最前端,這樣當(dāng)DexClassLoader去加載類時(shí),優(yōu)先會(huì)從我們插入的這個(gè)Element中找到相應(yīng)的類,雖然那個(gè)有bug的類還存在于數(shù)組中后面的Element中,但由于雙親加載機(jī)制的特點(diǎn),這個(gè)有bug的類已經(jīng)沒有機(jī)會(huì)被加載了,這樣一個(gè)bug就在沒有重新安裝應(yīng)用的情況下修復(fù)了。
插件化的原理
通過上面的框架介紹,插件化的原理無(wú)非就是這些:
在Android中應(yīng)用插件化技術(shù),其實(shí)也就是動(dòng)態(tài)加載的過程,分為以下幾步:
通過DexClassLoader加載。把可執(zhí)行文件( .so/dex/jar/apk 等)拷貝到應(yīng)用 APP 內(nèi)部。
加載可執(zhí)行文件,更換靜態(tài)資源
調(diào)用具體的方法執(zhí)行業(yè)務(wù)邏輯
View和ViewGroup的區(qū)別:
可以從兩方面來(lái)說(shuō):
一.事件分發(fā)方面的區(qū)別;
二.UI繪制方面的區(qū)別;
UI繪制主要有五個(gè)方法:onDraw(),onLayout(),onMeasure(),dispatchDraw(),drawChild()
1.ViewGroup包含這五個(gè)方法,而View只包含onDraw(),onLayout(),onMeasure()三個(gè)方法,不包含dispatchDraw(),drawChild()。
Android的UI界面都是由View和ViewGroup及其派生類組合而成的。其中,View是所有UI組件的基類,而ViewGroup是容納View及其派生類的容器,ViewGroup也是從View派生出來(lái)的。一般來(lái)說(shuō),開發(fā)UI界面都不會(huì)直接使用View和ViewGroup(自定義控件的時(shí)候使用),而是使用其派生類。
Dalvik與ART的區(qū)別
(1)在Dalvik下,應(yīng)用每次運(yùn)行都需要通過即時(shí)編譯器(JIT)將字節(jié)碼轉(zhuǎn)換為機(jī)器碼,即每次都要編譯加運(yùn)行,這雖然會(huì)使安裝過程比較快,但是會(huì)拖慢應(yīng)用的運(yùn)行效率。而在ART 環(huán)境中,應(yīng)用在第一次安裝的時(shí)候,字節(jié)碼就會(huì)預(yù)編譯(AOT)成機(jī)器碼,這樣的話,雖然設(shè)備和應(yīng)用的首次啟動(dòng)(安裝慢了)會(huì)變慢,但是以后每次啟動(dòng)執(zhí)行的時(shí)候,都可以直接運(yùn)行,因此運(yùn)行效率會(huì)提高。
(2)ART占用空間比Dalvik大(原生代碼占用的存儲(chǔ)空間更大,字節(jié)碼變?yōu)闄C(jī)器碼之后,可能會(huì)增加10%-20%),這也是著名的“空間換時(shí)間大法"。
(4)預(yù)編譯也可以明顯改善電池續(xù)航,因?yàn)閼?yīng)用程序每次運(yùn)行時(shí)不用重復(fù)編譯了,從而減少了 CPU 的使用頻率,降低了能耗。
Android開發(fā)中怎樣用多進(jìn)程、用多進(jìn)程的好處、多進(jìn)程的缺陷、解決方法(轉(zhuǎn))
四大組件在AndroidManifest文件中注冊(cè)的時(shí)候,有個(gè)屬性android:process這里可以指定組件的所處的進(jìn)程。
好處:
1)分擔(dān)主進(jìn)程的內(nèi)存壓力。
多進(jìn)程的缺陷
進(jìn)程間的內(nèi)存空間是不可見的。開啟多進(jìn)程后,會(huì)引發(fā)以下問題:
1)Application的多次重建。
2)靜態(tài)成員的失效。
3)文件共享問題。
4)SharedPreferences的可靠性下降
Anr的主要原因
ANR一般有三種類型:
1:KeyDispatchTimeout(5 seconds) --主要類型
按鍵或觸摸事件在特定時(shí)間內(nèi)無(wú)法得到響應(yīng)
2:BroadcastTimeout(10 seconds)
BroadcastReceiver在的onRecieve運(yùn)行在主線程中,短時(shí)間內(nèi)無(wú)法處理完成導(dǎo)致
3:ServiceTimeout(20 seconds) --小概率類型
Service的各個(gè)聲明周期在特定時(shí)間內(nèi)無(wú)法處理完成
ANR產(chǎn)生時(shí),系統(tǒng)會(huì)生成一個(gè)traces.txt的文件放在/data/anr/下。)使用命令導(dǎo)出anr日志
adb pull /data/anr/traces.txt ~/Desktop/
分析關(guān)鍵信息
以每行的重點(diǎn)內(nèi)容沒準(zhǔn),每行自帶時(shí)間戳
因?yàn)橹骶€程被阻塞導(dǎo)致的關(guān)鍵信息。
為什么會(huì)超時(shí):事件沒有機(jī)會(huì)處理 & 事件處理超時(shí)
怎么避免ANR
ANR的關(guān)鍵
是處理超時(shí),所以應(yīng)該避免在UI線程,BroadcastReceiver 還有service主線程中,處理復(fù)雜的邏輯和計(jì)算
而交給work thread操作。
1)避免在activity里面做耗時(shí)操作,oncreate & onresume
2)避免在onReceiver里面做過多操作
3)避免在Intent Receiver里啟動(dòng)一個(gè)Activity,因?yàn)樗鼤?huì)創(chuàng)建一個(gè)新的畫面,并從當(dāng)前用戶正在運(yùn)行的程序上搶奪焦點(diǎn)。
4)盡量使用handler來(lái)處理UI thread & workthread的交互。
常見的內(nèi)存泄漏。
線程造成的內(nèi)存泄漏
Handler造成的內(nèi)存泄漏
單例導(dǎo)致內(nèi)存泄露
靜態(tài)變量導(dǎo)致內(nèi)存泄露
非靜態(tài)內(nèi)部類導(dǎo)致內(nèi)存泄露
未取消注冊(cè)(BroadcastReceiver )或回調(diào)導(dǎo)致內(nèi)存泄露
Timer和TimerTask導(dǎo)致內(nèi)存泄露
集合中的對(duì)象未清理造成內(nèi)存泄露
資源未關(guān)閉或釋放導(dǎo)致內(nèi)存泄露
屬性動(dòng)畫造成內(nèi)存泄露
WebView造成內(nèi)存泄露
一些建議
對(duì)于生命周期比Activity長(zhǎng)的對(duì)象如果需要應(yīng)該使用ApplicationContext
對(duì)于需要在靜態(tài)內(nèi)部類中使用非靜態(tài)外部成員變量(如:Context、View ),可以在靜態(tài)內(nèi)部類中使用弱引用來(lái)引用外部類的變量來(lái)避免內(nèi)存泄漏
對(duì)于不再需要使用的對(duì)象,顯示的將其賦值為null,比如使用完Bitmap后先調(diào)用recycle(),再賦為null
保持對(duì)對(duì)象生命周期的敏感,特別注意單例、靜態(tài)對(duì)象、全局性集合等的生命周期
對(duì)于生命周期比Activity長(zhǎng)的內(nèi)部類對(duì)象,并且內(nèi)部類中使用了外部類的成員變量,可以這樣做避免內(nèi)存泄漏:
將內(nèi)部類改為靜態(tài)內(nèi)部類
靜態(tài)內(nèi)部類中使用弱引用來(lái)引用外部類的成員變量
在涉及到Context時(shí)先考慮ApplicationContext,當(dāng)然它并不是萬(wàn)能的,對(duì)于有些地方則必須使用Activity的Context,對(duì)于Application,Service,Activity三者的Context的應(yīng)用場(chǎng)景如下:
主要描述如何檢測(cè)應(yīng)用在UI線程的卡頓,目前已經(jīng)有兩種比較典型方式來(lái)檢測(cè)了:
我們先來(lái)理一下FPS基本的概念:
60 fps 的意思是說(shuō),畫面每秒更新60次
這60次更新,是要均勻更新的,不是說(shuō)一會(huì)快,一會(huì)慢,那樣視覺上也會(huì)覺得不流暢
每秒60次,也就是 1/60 ~= 16.67 ms 要更新一次
使用BlockCanary開源方案。其原理是利用Looper中的loop輸出的
利用UI線程Looper打印的日志
在Android UI線程中有個(gè)Looper,在其loop方法中會(huì)不斷取出Message,調(diào)用其綁定的Handler在UI線程進(jìn)行執(zhí)行。
,同樣利用了Looper機(jī)制,只不過在非UI線程中,如果執(zhí)行耗時(shí)達(dá)到我們?cè)O(shè)置的閾值,則會(huì)執(zhí)行mLogRunnable,打印出UI線程當(dāng)前的堆棧信息;如果你閾值時(shí)間之內(nèi)完成,則會(huì)remove掉該runnable。
msg.target.dispatchMessage(msg);
此行代碼的執(zhí)行時(shí)間,就能夠檢測(cè)到部分UI線程是否有耗時(shí)操作了。可以看到在執(zhí)行此代碼前后,如果設(shè)置了logging,會(huì)分別打印出>>>>> Dispatching to和<<<<< Finished to
利用Choreographer
Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào),觸發(fā)對(duì)UI進(jìn)行渲染。SDK中包含了一個(gè)相關(guān)類,以及相關(guān)回調(diào)。理論上來(lái)說(shuō)兩次回調(diào)的時(shí)間周期應(yīng)該在16ms,如果超過了16ms我們則認(rèn)為發(fā)生了卡頓,我們主要就是利用兩次回調(diào)間的時(shí)間周期來(lái)判斷:
Layout Inspector 代替Hierarchy View 檢測(cè)布局復(fù)雜度工具
如何實(shí)現(xiàn)啟動(dòng)優(yōu)化,有什么工具可以使用?
重點(diǎn)提到了systrace、TraceView這兩個(gè)工具,
Systrace允許你監(jiān)視和跟蹤Android系統(tǒng)的行為(trace)。它會(huì)告訴你系統(tǒng)都在哪些工作上花費(fèi)時(shí)間、CPU周期都用在哪里,甚至你可以看到每個(gè)線程、進(jìn)程在指定時(shí)間內(nèi)都在干嘛
TraceView 是 Android SDK 中內(nèi)置的一個(gè)工具,它可以加載 trace 文件,用圖形的形式展示代碼的執(zhí)行時(shí)間、次數(shù)及調(diào)用棧,便于我們分析。
而對(duì)于內(nèi)存泄露的檢測(cè),常用的工具有LeakCanary、MAT(Memory Analyer Tools)、Android Studio自帶的Profiler
性能優(yōu)化
快:使用時(shí)避免出現(xiàn)卡頓,響應(yīng)速度快,減少用戶等待的時(shí)間,滿足用戶期望。
(UI 繪制、應(yīng)用啟動(dòng)、頁(yè)面跳轉(zhuǎn)、事件響應(yīng),
界面繪制。主要原因是繪制的層級(jí)深、頁(yè)面復(fù)雜、刷新不合理,由于這些原因?qū)е驴D的場(chǎng)景更多出現(xiàn)在 UI 和啟動(dòng)后的初始界面以及跳轉(zhuǎn)到頁(yè)面的繪制上。
數(shù)據(jù)處理。導(dǎo)致這種卡頓場(chǎng)景的原因是數(shù)據(jù)處理量太大,一般分為三種情況,一是數(shù)據(jù)在處理 UI 線程,二是數(shù)據(jù)處理占用 CPU 高,導(dǎo)致主線程拿不到時(shí)間片,三是內(nèi)存增加導(dǎo)致 GC 頻繁,從而引起卡頓。)
在手機(jī)開發(fā)者模式下,有一個(gè)卡頓檢測(cè)工具叫做:Profile GPU Rendering,一個(gè)圖形監(jiān)測(cè)工具,能實(shí)時(shí)反應(yīng)當(dāng)前繪制的耗時(shí)
TraceView以分析到每一個(gè)方法的執(zhí)行時(shí)間,
穩(wěn):減低 crash 率和 ANR 率,不要在用戶使用過程中崩潰和無(wú)響應(yīng)。
(提高代碼質(zhì)量。比如開發(fā)期間的代碼審核,看些代碼設(shè)計(jì)邏輯,業(yè)務(wù)合理性等。
代碼靜態(tài)掃描工具。常見工具有Android Lint、Findbugs、Checkstyle、PMD等等。
Crash監(jiān)控。把一些崩潰的信息,異常信息及時(shí)地記錄下來(lái),以便后續(xù)分析解決。
Crash上傳機(jī)制。在Crash后,盡量先保存日志到本地,然后等下一次網(wǎng)絡(luò)正常時(shí)再上傳日志信息。)
省:節(jié)省流量和耗電,減少用戶使用成本,避免使用時(shí)導(dǎo)致手機(jī)發(fā)燙。
(:Battery Historian。Battery Historian 是一款由 Google 提供的 Android 系統(tǒng)電量分析工具)
小:安裝包小可以降低用戶的安裝成本。
代碼混淆。資源優(yōu)化,圖片優(yōu)化,避免重復(fù)功能的庫(kù),cdn
android內(nèi)存的優(yōu)化
android內(nèi)存泄露容易導(dǎo)致內(nèi)存溢出,又稱為OOM。
Android內(nèi)存優(yōu)化策略:
1)在循環(huán)內(nèi)盡量不要使用局部變量
2)不用的對(duì)象即時(shí)釋放,即指向NULL
3)數(shù)據(jù)庫(kù)的cursor即時(shí)關(guān)閉。
4)構(gòu)造adapter時(shí)使用緩存contentview
5)調(diào)用registerReceiver()后在對(duì)應(yīng)的生命周期方法中調(diào)用unregisterReceiver()
6)即時(shí)關(guān)閉InputStream/OutputStream。
7)android系統(tǒng)給圖片分配的內(nèi)存只有8M, 圖片盡量使用軟引用, 較大圖片可通過BitmapFactory縮放后再使用,并及時(shí)recycle
8)盡量避免static成員變量引用資源耗費(fèi)過多的實(shí)例。
加載大圖片的時(shí)候如何防止內(nèi)存溢出
答: android系統(tǒng)給圖片分配的內(nèi)存只有8M,當(dāng)加載大量圖片時(shí)往往會(huì)出現(xiàn)OOM。
Android加載大量圖片內(nèi)存溢出解決方案:
使用BitmapRegionDecoder類加載高清巨圖方案
最好的解決方案是局部加載,這里就涉及到BitmapRegionDecoder類
BitmapRegionDecoder提供了一系列的newInstance方法來(lái)構(gòu)造對(duì)象,支持傳入文件路徑,文件描述符,文件的inputstrem等。
1)盡量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource來(lái)設(shè)置一張大圖,因?yàn)檫@些函數(shù)在完成decode后,最終都是通過java層的createBitmap來(lái)完成的,需要消耗更多內(nèi)存,可以通過BitmapFactory.decodeStream方法,創(chuàng)建出一個(gè)bitmap,再將其設(shè)為ImageView的 source ,decodeStream最大的秘密在于其直接調(diào)用JNI
2)使用BitmapFactory.Options對(duì)圖片進(jìn)行壓縮
InputStream is = this.getResources().openRawResource(R.drawable.pic1);
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 10; //width,hight設(shè)為原來(lái)的十分一
Bitmap btp =BitmapFactory.decodeStream(is,null,options);
3)運(yùn)用Java軟引用,進(jìn)行圖片緩存,將需要經(jīng)常加載的圖片放進(jìn)緩存里,避免反復(fù)加載
及時(shí)銷毀不再使用的Bitmap對(duì)象
if(!bmp.isRecycle() ){
bmp.recycle() //回收?qǐng)D片所占的內(nèi)存
system.gc() //提醒系統(tǒng)及時(shí)回收
}
深度優(yōu)先搜索和廣度優(yōu)先搜索的區(qū)別
深度優(yōu)先搜素算法:類似先序遍歷,不全部保留結(jié)點(diǎn),占用空間少;有回溯操作(即有入棧、出棧操作),運(yùn)行速度慢。
廣度優(yōu)先搜索算法:保留全部結(jié)點(diǎn),占用空間大; 無(wú)回溯操作(即無(wú)入棧、出棧操作),運(yùn)行速度快。
廣度優(yōu)先遍歷:又叫層次遍歷,從上往下對(duì)每一層依次訪問,在每一層中,從左往右(也可以從右往左)訪問結(jié)點(diǎn),訪問完一層就進(jìn)入下一層,直到?jīng)]有結(jié)點(diǎn)可以訪問為止。
兩種方法最大的區(qū)別在于前者從頂點(diǎn)的第一個(gè)鄰接點(diǎn)一直訪問下去再訪問頂點(diǎn)的第二個(gè)鄰接點(diǎn);后者從頂點(diǎn)開始訪問該頂點(diǎn)的所有鄰接點(diǎn)再依次向下,一層一層的訪問。
Java中常見的鎖類型
常見的鎖分類大致有:排它鎖、共享鎖、樂觀鎖、悲觀鎖、分段鎖、自旋鎖、公平鎖、非公平鎖、可重入鎖等。
是Java多線程加鎖機(jī)制,有兩種:
1,Synchronized synchronized是一種互斥鎖 一次只能允許一個(gè)線程進(jìn)入被鎖住的代碼塊
當(dāng)方法(代碼塊)執(zhí)行完畢后會(huì)自動(dòng)釋放鎖,不需要做任何的操作。
當(dāng)一個(gè)線程執(zhí)行的代碼出現(xiàn)異常時(shí),其所持有的鎖會(huì)自動(dòng)釋放。
2,顯式Lock
可以簡(jiǎn)單概括一下:
Lock方式來(lái)獲取鎖支持中斷、超時(shí)不獲取、是非阻塞的
提高了語(yǔ)義化,哪里加鎖,哪里解鎖都得寫出來(lái)
Lock顯式鎖可以給我們帶來(lái)很好的靈活性,但同時(shí)我們必須手動(dòng)釋放鎖
支持Condition條件對(duì)象
允許多個(gè)讀線程同時(shí)訪問共享資源
為什么wait(), notify(), notifyAll()必須要在synchronized方法/塊
synchronized同步塊使用了monitorenter和monitorexit指令實(shí)現(xiàn)同步,這兩個(gè)指令,本質(zhì)上都是對(duì)一個(gè)對(duì)象的監(jiān)視器(monitor)進(jìn)行獲取,線程執(zhí)行到monitorenter指令時(shí),會(huì)嘗試獲取對(duì)象所對(duì)應(yīng)的monitor所有權(quán),也就是嘗試獲取對(duì)象的鎖,而執(zhí)行monitorexit,就是釋放monitor的所有權(quán)。
調(diào)用wait方法,首先會(huì)獲取監(jiān)視器鎖,獲得成功以后,會(huì)讓當(dāng)前線程進(jìn)入等待狀態(tài)進(jìn)入等待隊(duì)列并且釋放鎖。
wait方法的語(yǔ)義有兩個(gè),一個(gè)是釋放當(dāng)前的對(duì)象鎖、另一個(gè)是使得當(dāng)前線程進(jìn)入阻塞隊(duì)列,而這些操作都和監(jiān)視器是相關(guān)的,所以wait必須要獲得一個(gè)監(jiān)視器鎖。
因?yàn)檫@三個(gè)方法都是釋放鎖的,如果沒有synchronize先獲取鎖就調(diào)用會(huì)引起異常
死鎖產(chǎn)生的4個(gè)必要條件
1、互斥:某種資源一次只允許一個(gè)進(jìn)程訪問,即該資源一旦分配給某個(gè)進(jìn)程,其他進(jìn)程就不能再訪問,直到該進(jìn)程訪問結(jié)束。
2、占有且等待:一個(gè)進(jìn)程本身占有資源(一種或多種),同時(shí)還有資源未得到滿足,正在等待其他進(jìn)程釋放該資源。
3、不可搶占:別人已經(jīng)占有了某項(xiàng)資源,你不能因?yàn)樽约阂残枰撡Y源,就去把別人的資源搶過來(lái)。
4、循環(huán)等待:存在一個(gè)進(jìn)程鏈,使得每個(gè)進(jìn)程都占有下一個(gè)進(jìn)程所需的至少一種資源。
當(dāng)以上四個(gè)條件均滿足,必然會(huì)造成死鎖,發(fā)生死鎖的進(jìn)程無(wú)法進(jìn)行下去,它們所持有的資源也無(wú)法釋放。這樣會(huì)導(dǎo)致CPU的吞吐量下降。所以死鎖情況是會(huì)浪費(fèi)系統(tǒng)資源和影響計(jì)算機(jī)的使用性能的。那么,解決死鎖問題就是相當(dāng)有必要的了。
死鎖原理
根據(jù)操作系統(tǒng)中的定義:死鎖是指在一組進(jìn)程中的各個(gè)進(jìn)程均占有不會(huì)釋放的資源,但因互相申請(qǐng)被其他進(jìn)程所站用不會(huì)釋放的資源而處于的一種永久等待狀態(tài)。
一、死鎖的定義
所謂死鎖是指多個(gè)線程因競(jìng)爭(zhēng)資源而造成的一種僵局(互相等待),若無(wú)外力作用,這些進(jìn)程都將無(wú)法向前推進(jìn)。
處理死鎖的基本方法
1.預(yù)防死鎖:通過設(shè)置一些限制條件,去破壞產(chǎn)生死鎖的必要條件
2.避免死鎖:在資源分配過程中,使用某種方法避免系統(tǒng)進(jìn)入不安全的狀態(tài),從而避免發(fā)生死鎖
3.檢測(cè)死鎖:允許死鎖的發(fā)生,但是通過系統(tǒng)的檢測(cè)之后,采取一些措施,將死鎖清除掉
4.解除死鎖:該方法與檢測(cè)死鎖配合使用
內(nèi)存模型
1、虛擬機(jī)棧
2、本地方法棧
3、PC 寄存器
4、堆
5、方法區(qū)
Java的堆內(nèi)存和棧內(nèi)存
Java把內(nèi)存劃分成兩種:一種是堆內(nèi)存,一種是棧內(nèi)存。
堆:主要用于存儲(chǔ)實(shí)例化的對(duì)象,數(shù)組。由JVM動(dòng)態(tài)分配內(nèi)存空間。一個(gè)JVM只有一個(gè)堆內(nèi)存,線程是可以共享數(shù)據(jù)的。
棧:主要用于存儲(chǔ)局部變量和對(duì)象的引用變量,每個(gè)線程都會(huì)有一個(gè)獨(dú)立的棧空間,所以線程之間是不共享數(shù)據(jù)的。
棧內(nèi)存的更新速度要快于堆內(nèi)存,因?yàn)榫植孔兞康纳芷诟蹋?br>
棧內(nèi)存存放的變量生命周期一旦結(jié)束就會(huì)被釋放,而堆內(nèi)存存放的會(huì)被垃圾回收期機(jī)制不定時(shí)的回收。
GC機(jī)制判斷對(duì)象是否存活的算法:
1、引用計(jì)數(shù)算法
給對(duì)象添加一個(gè)引用計(jì)數(shù)器,當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器就減1;任何時(shí)候計(jì)數(shù)器都為0的對(duì)象就是不可能再被使用的。引用計(jì)數(shù)器算法(Reference Counting)實(shí)現(xiàn)簡(jiǎn)單,判定效率也很高,在大部分情況下他都是一個(gè)不錯(cuò)的算法。但是,Java語(yǔ)言中沒有選用引用計(jì)數(shù)算法來(lái)管理內(nèi)存,其中最主要的原因是他很難解決對(duì)象之間的互相循環(huán)引用的問題。
2、根搜索算法
根搜索算法(GC Roots Tracing)的基本思路是通過一系列名為“GC Roots”的對(duì)象作為起始點(diǎn),從這個(gè)節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連(用圖論術(shù)語(yǔ)描述就是從GC Roots到這個(gè)對(duì)象不可達(dá))時(shí),則證明此對(duì)象是不可用的。在主流的商用程序語(yǔ)言中(Java、C#),都是使用根搜索算法判定對(duì)象是否存活的。
常見回收算法
1、標(biāo)記-清除算法:
標(biāo)記階段:先通過根節(jié)點(diǎn),標(biāo)記所有從根節(jié)點(diǎn)開始的可達(dá)對(duì)象。因此,未被標(biāo)記的對(duì)象就是未被引用的垃圾對(duì)象;
清除階段:清除所有未被標(biāo)記的對(duì)象。
2、復(fù)制算法:(新生代的GC)
將原有的內(nèi)存空間分為兩塊,每次只使用其中一塊,在垃圾回收時(shí),將正在使用的內(nèi)存中的存活對(duì)象復(fù)制到未使用的內(nèi)存塊中,然后清除正在使用的內(nèi)存塊中的所有對(duì)象。
3、標(biāo)記-整理算法:(老年代的GC)
標(biāo)記階段:先通過根節(jié)點(diǎn),標(biāo)記所有從根節(jié)點(diǎn)開始的可達(dá)對(duì)象。因此,未被標(biāo)記的對(duì)象就是未被引用的垃圾對(duì)象
整理階段:將將所有的存活對(duì)象壓縮到內(nèi)存的一端;之后,清理邊界外所有的空間
4、分代收集算法:
存活率低:少量對(duì)象存活,適合復(fù)制算法:在新生代中,每次GC時(shí)都發(fā)現(xiàn)有大批對(duì)象死去,只有少量存活(新生代中98%的對(duì)象都是“朝生夕死”),那就選用復(fù)制算法,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成GC。
存活率高:大量對(duì)象存活,適合用標(biāo)記-清理/標(biāo)記-整理:在老年代中,因?yàn)閷?duì)象存活率高、沒有額外空間對(duì)他進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記-清理”/“標(biāo)記-整理”算法進(jìn)行GC。
ViewModel、LiveData及Lifecycles
1、Lifecycler的原理
Lifecycler為每個(gè)活動(dòng)組件添加了一個(gè)沒有界面的Fragment,利用Fragment周期會(huì)根據(jù)活動(dòng)聲明周期變化的特性實(shí)現(xiàn)的特性,從而實(shí)現(xiàn)生命周期的感知,然后根據(jù)注解的Event查找執(zhí)行相應(yīng)的方法。
添加一個(gè)ReportFragment的實(shí)例
根據(jù)Fragment的每個(gè)生命周期的回調(diào),調(diào)用dispatch()處理回調(diào)事件
Lifecycler的實(shí)現(xiàn)主要使用兩個(gè)主要枚舉來(lái)跟蹤其關(guān)聯(lián)組件的生命周期狀態(tài)
Event:從框架和Lifecycle類派發(fā)的生命周期事件。 這些事件映射到活動(dòng)和片段中的回調(diào)事件。
State:由Lifecycle對(duì)象跟蹤的組件的當(dāng)前狀態(tài)。
2、LiveData原理:
內(nèi)部保存了LifecycleOwner和Observer,利用LifecycleOwner感知并處理聲明中期的變化,Observer在數(shù)據(jù)改變時(shí)遍歷Observer,在數(shù)據(jù)改變時(shí)回調(diào)所有的觀察者
Android程序Crash時(shí)的異常上報(bào)
https://blog.csdn.net/singwhatiwanna/article/details/17289479
android中有處理這類問題的方法,請(qǐng)看下面Thread類中的一個(gè)方法#setDefaultUncaughtExceptionHandler
新建一個(gè)類,比如叫CrashHandler.java實(shí)現(xiàn)UncaughtExceptionHandler
為ui線程添加默認(rèn)異常事件Handler,我們推薦大家在Application中添加而不是在Activity中添加。Application標(biāo)識(shí)著整個(gè)應(yīng)用,在Android聲明周期中是第一個(gè)啟動(dòng)的,早于任何的Activity、Service等。
當(dāng)crash發(fā)生的時(shí)候,我們可以捕獲到異常信息,把異常信息存儲(chǔ)到SD卡中,然后在合適的時(shí)機(jī)通過網(wǎng)絡(luò)將crash信息上傳到服務(wù)器上,這樣開發(fā)人員就可以分析用戶crash的場(chǎng)景從而在后面的版本中修復(fù)此類crash。我們還可以在crash發(fā)生時(shí),彈出一個(gè)通知告訴用戶程序crash了,然后再退出,這樣做比閃退要溫和一點(diǎn)。
Listview條目有很多圖片,如果讓可見條目的圖片快速加載?(面試官提示:定義任務(wù)的優(yōu)先級(jí))
發(fā)現(xiàn)它們都是只加載屏幕內(nèi)的圖片,list停止?jié)L動(dòng)時(shí)加載圖片 ,停止?jié)L動(dòng)的時(shí)候//設(shè)置當(dāng)前屏幕顯示的起始index和結(jié)束index
優(yōu)化:
1.ListView滑動(dòng)停止后才加載可見項(xiàng)
2.滑動(dòng)時(shí),取消所有加載項(xiàng)
怎么通過tag來(lái)獲取對(duì)應(yīng)的ImageView?
通過findViewWithTag方法。
本質(zhì)上是考察多線程處理策略,對(duì)線程池有以下要求,1:可以指定線程優(yōu)先級(jí),用于響應(yīng)當(dāng)前可見item的快速加載。2.可以動(dòng)態(tài)取消線程,以便優(yōu)化存儲(chǔ),在Fling動(dòng)作發(fā)生時(shí)暫停無(wú)效item加載。3.就是手勢(shì)動(dòng)作優(yōu)化了
Android中ListView常見優(yōu)化方案
1、ViewHolder模式提高效率
Viewholder模式利用了ListView的視圖緩存機(jī)制,避免了每次在調(diào)用getView的時(shí)候都去通過findViewById實(shí)例化數(shù)據(jù)。
2、耗時(shí)操作放到異步線程中
比如說(shuō):加載圖片
3、item錯(cuò)位
由于耗時(shí)操作,而且又用到了view的復(fù)用,可能會(huì)出現(xiàn)item錯(cuò)位
解決錯(cuò)位方法:可以為每一個(gè)item設(shè)置一個(gè)tag
4、加載數(shù)據(jù)量大的數(shù)據(jù)
1.設(shè)置本地緩存
2.分頁(yè)加載:我們不用每次把ListView所有的Item都一次性加載完畢,這樣做沒必要也很累。我們僅僅需要加載那部分顯示在屏幕部分的Item即可,這樣所要加載的數(shù)據(jù)就減少了很多
3、滑動(dòng)時(shí)停止加載:當(dāng)用戶滑動(dòng)時(shí),顯示在屏幕的Item會(huì)不斷的變化,如果只是加載顯示在屏幕的Item,這也沒有必要,因此我們應(yīng)該在停止滑動(dòng)時(shí)再加載數(shù)據(jù)。
冒泡排序優(yōu)化
第一種優(yōu)化方式是(內(nèi)層循環(huán)優(yōu)化)設(shè)置一個(gè)標(biāo)記位,
定義一個(gè)flag=0,用來(lái)判斷有沒有進(jìn)行交換,如果在某次內(nèi)層循環(huán)中沒有交換操作,就說(shuō)明此時(shí)數(shù)組已經(jīng)是有序了的,不用再進(jìn)行判斷,這樣可以節(jié)省時(shí)間
第二種 (外層循環(huán)優(yōu)化)如果用一個(gè)flag來(lái)判斷一下,當(dāng)前數(shù)組是否已經(jīng)有序,如果有序就退出循環(huán),這樣可以明顯的提高冒泡排序的性能
Android的wrap_content是如何計(jì)算的
先處理wrap_content,簡(jiǎn)單說(shuō):在View類中,當(dāng)該View的布局寬高值為wrap_content,或match_parent時(shí),該View測(cè)量的最終大小就是MeasureSpec中的測(cè)量大小-->SpecSize。因此,在自定義View時(shí),需要重寫onMeasure(w,h)用來(lái)處理wrap_content的情況,然后調(diào)用setMeasuredDimession(w,h)完成測(cè)量。
當(dāng)布局寬高為wrap_content時(shí),它的測(cè)量模式specMode必然為AT_MOST。于是取出寬高的測(cè)量模式進(jìn)行判斷,如果布局寬高的不是wrap_content時(shí),就按照View$onMeasure(w,h)中方式處理,也就是用父view給的建議測(cè)量大小specSize,作為最終測(cè)量大小值。重寫onMeasure(w,h)后執(zhí)行程序,
EventBus
關(guān)于觀察者模式
EventBus是Android下高效的發(fā)布/訂閱事件總線機(jī)制。是基于JVM內(nèi)部的數(shù)據(jù)傳輸系統(tǒng),其核心對(duì)象為Event和EventHandler。
簡(jiǎn)介:觀察者模式是設(shè)計(jì)模式中的一種。它是為了定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,即當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。
在這個(gè)模式中主要包含兩個(gè)重要的角色:發(fā)布者和訂閱者(又稱觀察者)。對(duì)應(yīng)EventBus來(lái)說(shuō),發(fā)布者即發(fā)送消息的一方(即調(diào)用EventBus.getDefault().post(event)的一方)
,訂閱者即接收消息的一方(即調(diào)用EventBus.getDefault().register()的一方)。
,通過EventBus的靜態(tài)方法getDefault來(lái)獲取一個(gè)實(shí)例,getDefault本身會(huì)調(diào)用其內(nèi)部的構(gòu)造方法,通過傳入一個(gè)默認(rèn)的EventBusBuilder來(lái)創(chuàng)建EventBus。
獲取了訂閱者的class對(duì)象,然后SubscriberMethodFinder會(huì)找出所有訂閱的方法。
//首先從緩存中取出subscriberMethodss,如果有則直接返回該已取得的方法
通過上面的方法把訂閱方法找出來(lái)了,并保存在集合中,finduserinfo findState
unregister
//遍歷所有訂閱的事件
調(diào)用了EventBus#unsubscribeByEventType,把訂閱者以及事件作為參數(shù)傳遞了進(jìn)去,那么應(yīng)該是解除兩者的聯(lián)系。
post
通過currentPostingThreadState.get()來(lái)獲取PostingThreadState,currentPostingThreadState是一個(gè)ThreadLocal,而ThreadLocal是每個(gè)線程獨(dú)享的,其數(shù)據(jù)別的線程是不能訪問的,因此是線程安全的。我們?cè)俅位氐絇ost()方法,繼續(xù)往下走,是一個(gè)while循環(huán),這里不斷地從隊(duì)列中取出事件,并且分發(fā)出去,調(diào)用的是EventBus#postSingleEvent方法。
線程切換過程(Scheduler)
RxJava最好用的特點(diǎn)就是提供了方便的線程切換,被觀察者切換線程使用observeOn(),觀察者切換線程使用subscriberOn(),可切換為多種線程,但它的原理歸根結(jié)底還是lift,使用subscribeOn()的原理就是創(chuàng)建一個(gè)新的Observable,把它的call過程開始的執(zhí)行投遞到需要的線程中;而 observeOn() 則是把線程切換的邏輯放在自己創(chuàng)建的Subscriber中來(lái)執(zhí)行。把對(duì)于最終的Subscriber1的執(zhí)行過程投遞到需要的線程中來(lái)進(jìn)行。
subscribeOn()的線程切換發(fā)生在 OnSubscribe 中,即在它通知上一級(jí) OnSubscribe 時(shí),這時(shí)事件還沒有開始發(fā)送,因此 subscribeOn() 的線程控制可以從事件發(fā)出的開端就造成影響;而 observeOn() 的線程切換則發(fā)生在它內(nèi)建的 Subscriber 中,即發(fā)生在它即將給下一級(jí) Subscriber 發(fā)送事件時(shí),因此 observeOn() 控制的是它后面的線程。
為什么 subscribeOn()只有第一個(gè)有效?
舉個(gè)例子,subscribeOn2從通知開始將后面的執(zhí)行全部投遞到需要的線程2來(lái)執(zhí)行,但是之后的投遞會(huì)受到在subscribeOn2的上級(jí)subscribeOn1的的影響,subscribeOn1又會(huì)把執(zhí)行投遞到線程1中去,這樣執(zhí)行就不受到subscribeOn2的控制了。所以只有第一個(gè)subscribeOn有效。
什么是Retrofit
Retrofit是針對(duì)于Android/Java的、基于okHttp的、一種輕量級(jí)且安全的、Retrofit采用注解方式開發(fā)。通過注解構(gòu)建不同的請(qǐng)求和請(qǐng)求的參數(shù),
首先通過構(gòu)造者模式獲取Retrofit對(duì)象,然后通過動(dòng)態(tài)代理獲取到所定義的接口實(shí)例,得到實(shí)例后就可以調(diào)用接口的方法來(lái)獲取Call對(duì)象最后進(jìn)行網(wǎng)絡(luò)請(qǐng)求操作
調(diào)用call對(duì)象的enqueue方法,并且傳入Callback參數(shù),實(shí)現(xiàn)onResponse方法和onFailure方法,表示請(qǐng)求成功和失敗時(shí)候的回調(diào)。
獲得了這個(gè)call對(duì)象就可以開始訪問網(wǎng)絡(luò)了。Call接口提供enqueue(Callback callback)方法,其中泛型T即Call的泛型,在這里就是ResponseBody。Callback接口包含兩個(gè)方法,OnResponse和onFailure,分別在請(qǐng)求成功和失敗的時(shí)候回調(diào)。于是call調(diào)用的形式如下:
ButterKnife 原理解析
原理解析
首先得到要綁定的 Activity 對(duì)應(yīng)的 Class,然后用根據(jù) Class 得到一個(gè)繼承了Unbinder的Constructor,最后通過反射constructor.newInstance(target, source)得到Unbinder子類的一個(gè)實(shí)例,
BINDINGS是一個(gè)LinkedHashMap:
緩存了對(duì)應(yīng)的 Class 和 Constructor 以提高效率!
所以最終bind()方法返回的是MainActivity_ViewBinding類的實(shí)例
核心就是把findRequiredView()得到的 View 轉(zhuǎn)成指定類型的 View ,如果 xml 中定義的 View 和 Activity 中通過注解綁定的 View 類型不一致,就會(huì)拋出上邊方法的異常
apk的打包過程分7步:
打包流程
1、打包資源文件,生成R.java文件
2、處理aidl文件,生成相應(yīng)java 文件
3、編譯工程源代碼,生成相應(yīng)class 文件
4、轉(zhuǎn)換所有class文件,生成classes.dex文件
5、打包生成apk6、對(duì)apk文件進(jìn)行簽名
7、對(duì)簽名后的apk文件進(jìn)行對(duì)齊處理
Android 異步消息處理機(jī)制 讓你深入理解 Looper、Handler、Message三者關(guān)系
三者關(guān)系
1、首先Looper.prepare()在本線程中保存一個(gè)Looper實(shí)例,然后該實(shí)例中保存一個(gè)MessageQueue對(duì)象;因?yàn)長(zhǎng)ooper.prepare()在一個(gè)線程中只能調(diào)用一次,所以MessageQueue在一個(gè)線程中只會(huì)存在一個(gè)。
2、Looper.loop()會(huì)讓當(dāng)前線程進(jìn)入一個(gè)無(wú)限循環(huán),不端從MessageQueue的實(shí)例中讀取消息,然后回調(diào)msg.target.dispatchMessage(msg)方法。
3、Handler的構(gòu)造方法,會(huì)首先得到當(dāng)前線程中保存的Looper實(shí)例,進(jìn)而與Looper實(shí)例中的MessageQueue想關(guān)聯(lián)。
4、Handler的sendMessage方法,會(huì)給msg的target賦值為handler自身,然后加入MessageQueue中。
5、在構(gòu)造Handler實(shí)例時(shí),我們會(huì)重寫handleMessage方法,也就是msg.target.dispatchMessage(msg)最終調(diào)用的方法。
好了,總結(jié)完成,大家可能還會(huì)問,那么在Activity中,我們并沒有顯示的調(diào)用Looper.prepare()和Looper.loop()方法,為啥Handler可以成功創(chuàng)建呢,這是因?yàn)樵贏ctivity的啟動(dòng)代碼中,已經(jīng)在當(dāng)前UI線程調(diào)用了Looper.prepare()和Looper.loop()方法。
主線程中的Looper.loop()一直無(wú)限循環(huán)為什么不會(huì)造成ANR?
造成ANR的原因一般有兩種:
- 當(dāng)前的事件沒有機(jī)會(huì)得到處理(即主線程正在處理前一個(gè)事件,沒有及時(shí)的完成或者looper被某種原因阻塞住了)
- 當(dāng)前的事件正在處理,但沒有及時(shí)完成
ActivityThread的main方法主要就是做消息循環(huán),一旦退出消息循環(huán),那么你的應(yīng)用也就退出了。因?yàn)锳ndroid 的是由事件驅(qū)動(dòng)的,looper.loop() 不斷地接收事件、處理事件,每一個(gè)點(diǎn)擊觸摸或者說(shuō)Activity的生命周期都是運(yùn)行在 Looper.loop() 的控制之下,如果它停止了,應(yīng)用也就停止了。只能是某一個(gè)消息或者說(shuō)對(duì)消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。
總結(jié):Looer.loop()方法可能會(huì)引起主線程的阻塞,但只要它的消息循環(huán)沒有被阻塞,能一直處理事件就不會(huì)產(chǎn)生ANR異常。
Android加密算法有多種多樣,常見的有MD5、RSA、AES、3DES四種。
Android加密
MD5是不可逆的加密算法,也就是無(wú)法解密,主要用于客戶端的用戶密碼加密。
RSA算法在客戶端使用公鑰加密,在服務(wù)端使用私鑰解密。這樣一來(lái),即使加密的公鑰被泄露,沒有私鑰仍然無(wú)法解密。(注意:使用RSA加密之前必須在AndroidStudio的libs目錄下導(dǎo)入bcprov-jdk的jar包)
SurfaceView和View最本質(zhì)的區(qū)別在于,
surfaceView是在一個(gè)新起的單獨(dú)線程中可以重新繪制畫面. 而View必須在UI的主線程中更新畫面。那么在UI的主線程中更新畫面 可能會(huì)引發(fā)問題,比如你更新畫面的時(shí)間過長(zhǎng),那么你的主UI線程會(huì)被你正在畫的函數(shù)阻塞。那么將無(wú)法響應(yīng)按鍵,觸屏等消息。 當(dāng)使用surfaceView 由于是在新的線程中更新畫面所以不會(huì)阻塞你的UI主線程。但這也帶來(lái)了另外一個(gè)問題,就是事件同步。比如你觸屏了一下,你需要surfaceView中thread處理,一般就需要有一個(gè)event queue的設(shè)計(jì)來(lái)保存touch event,這會(huì)稍稍復(fù)雜一點(diǎn),因?yàn)樯婕暗骄€程同步。
Serilizeable和Parcelable的區(qū)別?
Serializable使用IO讀寫存儲(chǔ)在硬盤上,
Serializable的作用是將數(shù)據(jù)對(duì)象存入字節(jié)流當(dāng)中,在需要時(shí)重新生成對(duì)象,主要應(yīng)用是利用外部存儲(chǔ)設(shè)備保存對(duì)象狀態(tài),以及通過網(wǎng)絡(luò)傳輸對(duì)象等。
implements Serializable接口的的作用就是給對(duì)象打了一個(gè)標(biāo)記,系統(tǒng)會(huì)自動(dòng)將其序列化。
實(shí)現(xiàn)Parcelable接口
而Parcelable是直接在內(nèi)存中讀寫,很明顯內(nèi)存的讀寫速度通常大于IO讀寫,所以在Android中通常優(yōu)先選擇Parcelable。
a、在使用內(nèi)存的時(shí)候,Parcelable比Serializable性能高,所以推薦使用Parcelable類。
b、Serializable在序列化的時(shí)候會(huì)產(chǎn)生大量的臨時(shí)變量,從而引起頻繁的GC。
使用Serilizeable序列化的時(shí)候,有一個(gè)序列化id,它的作用是什么?
關(guān)于serialVersionUID最好顯式定義,表明類的版本,即根據(jù)類型和成員變量生成一個(gè)64位Hash值
反序列化時(shí)就可以根據(jù)這個(gè)id校驗(yàn)類版本
文件斷點(diǎn)續(xù)傳的簡(jiǎn)單實(shí)現(xiàn)
傳輸開始之前發(fā)送方先向接收方發(fā)送一個(gè)確認(rèn)信息,然后再向接收方發(fā)送準(zhǔn)備發(fā)送的文件的文件名
接收方收到確認(rèn)信息之后,接收從發(fā)送方發(fā)送過來(lái)的文件名,接收完之后向發(fā)送方發(fā)送一個(gè)確認(rèn)信息表示文件名接收完畢,然后接收方根據(jù)收到的文件名創(chuàng)建一個(gè)“.temp”File對(duì)象和一個(gè)“.temp”RandomAccessFile對(duì)象。獲取這個(gè)File對(duì)象所對(duì)應(yīng)文件的長(zhǎng)度(大小)(這個(gè)長(zhǎng)度就是接收方已經(jīng)接受的長(zhǎng)度,如果之前沒有接收過這個(gè)文件,長(zhǎng)度就為0),并把文件長(zhǎng)度發(fā)送給發(fā)送方。
發(fā)送方收到確認(rèn)信息之后,接收接受方發(fā)送的文件長(zhǎng)度,然后向接收方發(fā)送準(zhǔn)備發(fā)送的文件的總長(zhǎng)度,并向接收方發(fā)送一個(gè)確認(rèn)信息。然后根據(jù)接收方發(fā)送的文件長(zhǎng)度,從文件對(duì)應(yīng)長(zhǎng)度的位置開始發(fā)送。
接收方收到確認(rèn)信息之后,接受發(fā)送方發(fā)送過來(lái)的數(shù)據(jù),然后從此文件的末尾寫入。接受完成之后再將“.temp”文件重命名為正常的文件名。
ok”表示確認(rèn)信息
能夠?qū)崿F(xiàn)斷點(diǎn)續(xù)傳的關(guān)鍵就是使用了RandomAccessFile,此類的實(shí)例支持對(duì)隨機(jī)訪問文件的讀取和寫入。
Java通過ThreadpoolExecutors創(chuàng)建線程池,
總共有四類
newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過處理需要,可靈活回收空閑線程,若無(wú)可回收,則新建線程
newFixedThreadPool創(chuàng)建一個(gè)定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待
。newScheduledThreadPool創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)和周期性任務(wù)執(zhí)行
newSingleThreadExecutor創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO,LIFO,優(yōu)先級(jí))執(zhí)行。
i++和++i的線程安全分為兩種情況:
1、如果i是局部變量(在方法里定義的),那么是線程安全的。因?yàn)榫植孔兞渴蔷€程私有的,別的線程訪問不到,其實(shí)也可以說(shuō)沒有線程安不安全之說(shuō),因?yàn)閯e的線程對(duì)他造不成影響。
2、如果i是全局變量(類的成員變量),那么是線程不安全的。因?yàn)槿绻侨肿兞康脑挘贿M(jìn)程中的不同線程都有可能訪問到。
平衡二叉樹,
它的左子樹和右子樹都是平衡二叉樹,且左子樹和右子樹的深度之差的絕對(duì)值不超過1
5.0到9.0 新特性
Android 5.0(Android Lollipop)開始,android迎來(lái)了扁平化時(shí)代,使用一種新的Material Design 設(shè)計(jì)風(fēng)格,設(shè)計(jì)了全新的通知中心,
Android 6.0引入了動(dòng)態(tài)權(quán)限管理,指紋識(shí)別
7.0分屏多任務(wù),新通知消息,通知消息快捷回復(fù),夜間模式
8.0 畫中畫功能, 在Android 8.0“奧利奧”中,應(yīng)用圖標(biāo)的右上角有一個(gè)小點(diǎn),它代表未讀通知,通知延時(shí)提醒,自動(dòng)填充
9.0 劉海設(shè)計(jì),黑白模式切換,加入長(zhǎng)截圖,允許定制主屏搜索欄,應(yīng)用多開