2020面試題的答案

(1)java面試題(基礎+進階)(必須)
java中==和equals和hashCode的區別
==是運算符,用來比較兩個值、兩個對象的內存地址是否相等;
equals是Object類的方法,默認情況下比較兩個對象是否是同一個對象,內部實現是通過“==”來實現的。
如果想比較兩個對象的其他內容,則可以通過重寫equals方法,
hashCoed也是Object類裏面的方法,返回值是一個對象的哈希碼,同一個對象哈希碼一定相等,但不同對象哈希碼也有可能相等。
1、如果兩個對象equals,Java運行時環境會認爲他們的hashcode一定相等。 
2、如果兩個對象不equals,他們的hashcode有可能相等。 
3、如果兩個對象hashcode相等,他們不一定equals。 
4、如果兩個對象hashcode不相等,他們一定不equals。

int、char、long各佔多少字節數(筆試題多出現)
Int:4字節 chat:2字節 long\double:8字節

int與integer的區別 (筆試)
1、Integer是int的包裝類,int則是java的一種基本數據類型 
2、Integer變量必須實例化後才能使用,而int變量不需要 
3、Integer實際是對象的引用,當new一個Integer時,實際上是生成一個指針指向此對象;而int則是直接存儲數據值 
4、Integer的默認值是null,int的默認值是0

談談對java多態的理解
多態是指:父類引用指向子類對象,在執行期間判斷所引用對象的實際類型,根據其實際的類型調用其相應的方法。(同一消息可以根據發送對象的不同而採用多種不同的行爲方式。

多態的作用:消除類型之間的耦合關係。

實現多態的技術稱爲:動態綁定(dynamic binding),是指在執行期間判斷所引用對象的實際類型,根據其實際的類型調用其相應的方法。

實現多態的三要素:繼承,重寫,父類引用指向子類對象(即,聲明是父類,實際指向的是子類的一個對象)

String、StringBuffer、StringBuilder區別
1、三者在執行速度上:StringBuilder > StringBuffer > String (由於String是常量,不可改變,拼接時會重新創建新的對象)。
2、StringBuffer是線程安全的,StringBuilder是線程不安全的。(由於StringBuffer有緩衝區)

什麼是內部類?內部類的作用
內部類:將一個類定義在另一個類裏面或者一個方法裏面,這樣的類稱爲內部類。
作用:1.每個內部類都能獨立的繼承一個接口的實現,所以無論外部類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。內部類使得多繼承的解決方案變得完整,   
2.方便將存在一定邏輯關係的類組織在一起,又可以對外界隱藏。   
3.方便編寫事件驅動程序   
4.方便編寫線程代碼

抽象類和接口區別
相同:
1、都能被繼承
2、繼承的類都必須將未實現的函數實現
3、只關注方法的定義,不關注方法的實現
差異:
1、一個子類可以繼承多個接口,但是隻能繼承一個父類
2、抽象類在對象中只能表示一種對象,接口可以被很多對象繼承

抽象類與接口的應用場景
如果你擁有一些方法並且想讓它們中的一些有默認實現,那麼使用抽象類吧。
如果你想實現多重繼承,那麼你必須使用接口。由於Java不支持多繼承,子類不能夠繼承多個類,但可以實現多個接口。因此你就可以使用接口來解決它。
如果基本功能在不斷改變,那麼就需要使用抽象類。如果不斷改變基本功能並且使用接口,那麼就需要改變所有實現了該接口的類。

抽象類是否可以沒有方法和屬性?
抽象類專用於派生出子類,子類必須實現抽象類所聲明的抽象方法,否則,子類仍是抽象類。
包含抽象方法的類一定是抽象類,但抽象類中的方法不一定是抽象方法。
抽象類中可以沒有抽象方法,但有抽象方法的一定是抽象類。所以,java中 抽象類裏面可以沒有抽象方法。 抽象類的作用在於子類對其的繼承和實現,也就是多態;而沒有抽象方法的抽象類的存在價值在於:實例化了沒有意義,因爲類已經定義好了,不能改變其中的方法體,但是實例化出來的對象卻滿足不了要求,只有繼承並重寫了他的子類才能滿足要求。所以才把它定義爲沒有抽象方法的抽象類

泛型中extends和super的區別
1、< extends T>限定參數類型的上界:參數類型必須是T或T的子類型
限定參數類型的下界:參數類型必須是T或T的超類型
2、 只能用於方法返回,告訴編譯器此返參的類型的最小繼承邊界爲T,T和T的父類都能接收,但是入參類型無法確定,只能接受null的傳入
只能用於限定方法入參,告訴編譯器入參只能是T或其子類型,而返參只能用Object類接收既不能用於入參也不能用於返參

父類的靜態方法能否被子類重寫
不能,父類的靜態方法能夠被子類繼承,但是不能夠被子類重寫,即使子類中的靜態方法與父類中的靜態方法完全一樣,也是兩個完全不同的方法。

進程和線程的區別(問的蠻多的,回答的時候用口語說出來,不要背書)
進程是cpu資源分配的最小單位,線程是cpu調度的最小單位。
進程之間不能共享資源,而線程共享所在進程的地址空間和其它資源。
一個進程內可擁有多個線程,進程可開啓進程,也可開啓線程。
一個線程只能屬於一個進程,線程可直接使用同進程的資源,線程依賴於進程而存在。

final,finally,finalize的區別
final:修飾類、成員變量和成員方法,類不可被繼承,成員變量不可變,成員方法不可重寫
finally:與try...catch...共同使用,確保無論是否出現異常都能被調用到
finalize:類的方法,垃圾回收之前會調用此方法,子類可以重寫finalize()方法實現對資源的回收

Serializable 和Parcelable 的區別
Serializable Java 序列化接口 在硬盤上讀寫 讀寫過程中有大量臨時變量的生成,內部執行大量的i/o操作,效率很低。
Parcelable Android 序列化接口 效率高 使用麻煩 在內存中讀寫(AS有相關插件 一鍵生成所需方法) ,對象不能保存到磁盤中

靜態屬性和靜態方法是否可以被繼承?是否可以被重寫?以及原因?
可繼承 不可重寫 而是被隱藏
如果子類裏面定義了靜態方法和屬性,那麼這時候父類的靜態方法或屬性稱之爲"隱藏"。如果你想要調用父類的靜態方法和屬性,直接通過父類名.方法或變量名完成。

成員內部類、靜態內部類、局部內部類和匿名內部類的理解,以及項目中的應用
java中內部類主要分爲成員內部類、局部內部類(嵌套在方法和作用域內)、匿名內部類(沒構造方法)、靜態內部類(static修飾的類,不能使用任何外圍類的非static成員變量和方法, 不依賴外圍類)
使用內部類最吸引人的原因是:每個內部類都能獨立地繼承一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。
因爲Java不支持多繼承,支持實現多個接口。但有時候會存在一些使用接口很難解決的問題,這個時候我們可以利用內部類提供的、可以繼承多個具體的或者抽象的類的能力來解決這些程序設計問題。可以這樣說,接口只是解決了部分問題,而內部類使得多重繼承的解決方案變得更加完整。

string 轉換成 integer的方式及原理
String —>integer Intrger.parseInt(string);
Integer—> string Integer.toString();
原理:
parseInt(String s)--內部調用parseInt(s,10)(默認爲10進制)
正常判斷null,進制範圍,length等
判斷第一個字符是否是符號位
循環遍歷確定每個字符的十進制值
通過*= 和-= 進行計算拼接
判斷是否爲負值 返回結果。

哪些情況下的對象會被垃圾回收機制處理掉?
1.所有實例都沒有活動線程訪問。
2.沒有被其他任何實例訪問的循環引用實例。
3.Java 中有不同的引用類型。判斷實例是否符合垃圾收集的條件都依賴於它的引用類型。
要判斷怎樣的對象是沒用的對象。這裏有2種方法:
1.採用標記計數的方法:
給內存中的對象給打上標記,對象被引用一次,計數就加1,引用被釋放了,計數就減一,當這個計數爲0的時候,這個對象就可以被回收了。當然,這也就引發了一個問題:循環引用的對象是無法被識別出來並且被回收的。所以就有了第二種方法:
2.採用根搜索算法:
從一個根出發,搜索所有的可達對象,這樣剩下的那些對象就是需要被回收的

靜態代理和動態代理的區別,什麼場景使用?
由程序員創建或由特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。動態代理類:在程序運行時,運用反射機制動態創建而成。
場景:著名的Spring框架、Hibernate框架等等都是動態代理的使用例子

Java的異常體系
Throwable,Error,Exception

談談你對解析與分派的認識。
解析:Java中方法調用的目標方法在Class文件裏面都是常量池中的符號引用,在類加載的解析階段,會將其中的一部分符號引用轉化爲直接引用。這種解析的前提是:方法在程序真正運行之前就有一個可以確定的調用版本,並且這個方法的調用版本在運行期是不可改變的,即“編譯期可知,運行期不可變”,這類目標的方法的調用稱爲解析(Resolve)。

只要能被invokestatic和invokespecial指令調用的方法,都可以在解析階段中確定唯一的調用版本,符合條件的有靜態方法(invokestatic指令)、私有方法、實例構造方法、父類方法(這3個是invokespecial指令),它們在類加載的的解析階段就會將符號引用解析爲該方法的直接引用。
分派:分派是多態性的體現,Java虛擬機底層提供了我們開發中“重載”(Overload)“和重寫”(Override)的底層實現。其中重載屬於靜態分派,而重寫則是動態分派的過程。
解析調用一定是個靜態的過程,在編譯期就完全確定,在類加載的解析階段就將涉及的符號引用全部轉變爲可以確定的直接引用,不會延遲到運行期再去完成。

Java中實現多態的機制是什麼?
答:方法的重寫Overriding和重載Overloading是Java多態性的不同表現
重寫Overriding是父類與子類之間多態性的一種表現
重載Overloading是一個類中多態性的一種表現.

說說你對Java反射的理解
JAVA反射機制是在運行狀態中, 對於任意一個類, 都能夠知道這個類的所有屬性和方法; 對於任意一個對象, 都能夠調用它的任意一個方法和屬性。 從對象出發,通過反射(Class類)可以取得取得類的完整信息(類名 Class類型,所在包、具有的所有方法 Method[]類型、某個方法的完整信息(包括修飾符、返回值類型、異常、參數類型)、所有屬性 Field[]、某個屬性的完整信息、構造器 Constructors),調用類的屬性或方法自己的總結: 在運行過程中獲得類、對象、方法的所有信息。

說說你對Java註解的理解
元註解
元註解的作用就是負責註解其他註解。java5.0的時候,定義了4個標準的meta-annotation類型,它們用來提供對其他註解的類型作說明。
1.@Target
2.@Retention
3.@Documented
4.@Inherited

Java中String的瞭解
在源碼中string是用final 進行修飾,它是不可更改,不可繼承的常量。

String爲什麼要設計成不可變的?
1、字符串池的需求
字符串池是方法區(Method Area)中的一塊特殊的存儲區域。當一個字符串已經被創建並且該字符串在 池 中,該字符串的引用會立即返回給變量,而不是重新創建一個字符串再將引用返回給變量。如果字符串不是不可變的,那麼改變一個引用(如: string2)的字符串將會導致另一個引用(如: string1)出現髒數據。
2、允許字符串緩存哈希碼
在java中常常會用到字符串的哈希碼,例如: HashMap 。String的不變性保證哈希碼始終一,因此,他可以不用擔心變化的出現。 這種方法意味着不必每次使用時都重新計算一次哈希碼——這樣,效率會高很多。
3、安全
String廣泛的用於java 類中的參數,如:網絡連接(Network connetion),打開文件(opening files )等等。如果String不是不可變的,網絡連接、文件將會被改變——這將會導致一系列的安全威脅。操作的方法本以爲連接上了一臺機器,但實際上卻不是。由於反射中的參數都是字符串,同樣,也會引起一系列的安全問題。

Object類的equal和hashCode方法重寫,爲什麼?
首先equals與hashcode間的關係是這樣的:
1、如果兩個對象相同(即用equals比較返回true),那麼它們的hashCode值一定要相同;
2、如果兩個對象的hashCode相同,它們並不一定相同(即用equals比較返回false)
由於爲了提高程序的效率才實現了hashcode方法,先進行hashcode的比較,如果不同,那沒就不必在進行equals的比較了,這樣就大大減少了equals比較的次數,這對比需要比較的數量很大的效率提高是很明顯的

java的集合以及集合之間的繼承關係

List,Set,Map的區別
Set是最簡單的一種集合。集合中的對象不按特定的方式排序,並且沒有重複對象。 Set接口主要實現了兩個實現類:HashSet: HashSet類按照哈希算法來存取集合中的對象,存取速度比較快
TreeSet :TreeSet類實現了SortedSet接口,能夠對集合中的對象進行排序。
List的特徵是其元素以線性方式存儲,集合中可以存放重複對象。
ArrayList() : 代表長度可以改變得數組。可以對元素進行隨機的訪問,向ArrayList()中插入與刪除元素的速度慢。
LinkedList(): 在實現中採用鏈表數據結構。插入和刪除速度快,訪問速度慢。
Map 是一種把鍵對象和值對象映射的集合,它的每一個元素都包含一對鍵對象和值對象。 Map沒有繼承於Collection接口 從Map集合中檢索元素時,只要給出鍵對象,就會返回對應的值對象。
HashMap:Map基於散列表的實現。插入和查詢“鍵值對”的開銷是固定的。可以通過構造器設置容量capacity和負載因子load factor,以調整容器的性能。
LinkedHashMap: 類似於HashMap,但是迭代遍歷它時,取得“鍵值對”的順序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一點。而在迭代訪問時發而更快,因爲它使用鏈表維護內部次序。
TreeMap : 基於紅黑樹數據結構的實現。查看“鍵”或“鍵值對”時,它們會被排序(次序由Comparabel或Comparator決定)。TreeMap的特點在 於,你得到的結果是經過排序的。TreeMap是唯一的帶有subMap()方法的Map,它可以返回一個子樹。
WeakHashMao :弱鍵(weak key)Map,Map中使用的對象也被允許釋放: 這是爲解決特殊問題設計的。如果沒有map之外的引用指向某個“鍵”,則此“鍵”可以被垃圾收集器回收。

List和Set和Map的實現方式以及存儲方式
List:
常用實現方式有:ArrayList和LinkedList
ArrayList 的存儲方式:數組,查詢快
LinkedList的存儲方式:鏈表,插入,刪除快

Set:
常用實現方式有:HashSet和TreeSet
HashSet的存儲方式:哈希碼算法,加入的對象需要實現hashcode()方法,快速查找元素
TreeSet的存儲方式:按序存放,想要有序就要實現Comparable接口

附加:
集合框架提供了2個實用類:collections(排序,複製、查找)和Arrays對數組進行(排序,複製、查找)

Map:
常用實現方式有:HashMap和TreeMap
HashMap的存儲方式:哈希碼算法,快速查找鍵值
TreeMap存儲方式:對鍵按序存放

數組(如arryList)中數組容量不夠了,怎麼擴容?
在JDK1.7中如果通過無參構造的話,初始數組容量是0,當數組進行add()添加時,才真正的分配容量,通過位運算,每次按照1.5倍的比例擴容。
在JDK1.6中,初始數組容量爲10,每次通過cope of方式擴容1.5倍+1.

HashMap的實現原理,如何put數據和get數據?
在JDK1.6,JDK1.7中,HashMap採用數組+鏈表實現,即使用鏈表處理衝突,同一hash值的鏈表都存儲在一個鏈表裏。但是當位於一個鏈表中的元素較多,即hash值相等的元素較多時,通過key值依次查找的效率較低。而JDK1.8中,HashMap採用位數組+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減少了查找時間。
當鏈表數組的容量超過初始容量*加載因子(默認0.75)時,再散列將鏈表數組擴大2倍,把原鏈表數組的搬移到新的數組中。爲什麼需要使用加載因子?爲什麼需要擴容呢?因爲如果填充比很大,說明利用的空間很多,如果一直不進行擴容的話,鏈表就會越來越長,這樣查找的效率很低,擴容之後,將原來鏈表數組的每一個鏈表分成奇偶兩個子鏈表分別掛在新鏈表數組的散列位置,這樣就減少了每個鏈表的長度,增加查找效率。

HashMap在put時候,底層源碼可以看出,當程序試圖將一個key-value對象放入到HashMap中,首先根據該key的hashCode()返回值決定該Entry的存儲位置,如果兩個Entry的key的hashCode()方法返回值相同,那他們的存儲位置相同,如果這兩個Entry的key通過equals比較返回true,新添加的Entry的value將會覆蓋原來的Entry的value,但是key不會被覆蓋,反之,如果返回false,新添加的Entry將與集合中原有的Entry形成Entry鏈,新添加的位於頭部,舊的位於尾部。
存:

取:

ArrayMap和HashMap的對比
1、存儲方式不同
HashMap內部有一個HashMapEntry[]對象,每一個鍵值對都存儲在這個對象裏,當使用put方法添加鍵值對時,就會new一個HashMapEntry對象,
2、添加數據時擴容時的處理不一樣,進行了new操作,重新創建對象,開銷很大。ArrayMap用的是copy數據,所以效率相對要高。
3、ArrayMap提供了數組收縮的功能,在clear或remove後,會重新收縮數組,是否空間
4、ArrayMap採用二分法查找;
List,Set,Map的區別
Set是最簡單的一種集合。集合中的對象不按特定的方式排序,並且沒有重複對象。 Set接口主要實現了兩個實現類:HashSet: HashSet類按照哈希算法來存取集合中的對象,存取速度比較快
TreeSet :TreeSet類實現了SortedSet接口,能夠對集合中的對象進行排序。

List的特徵是其元素以線性方式存儲,集合中可以存放重複對象。
ArrayList() : 代表長度可以改變得數組。可以對元素進行隨機的訪問,向ArrayList()中插入與刪除元素的速度慢。
LinkedList(): 在實現中採用鏈表數據結構。插入和刪除速度快,訪問速度慢。

Map 是一種把鍵對象和值對象映射的集合,它的每一個元素都包含一對鍵對象和值對象。 Map沒有繼承於Collection接口 從Map集合中檢索元素時,只要給出鍵對象,就會返回對應的值對象。
HashMap:Map基於散列表的實現。插入和查詢“鍵值對”的開銷是固定的。可以通過構造器設置容量capacity和負載因子load factor,以調整容器的性能。
LinkedHashMap: 類似於HashMap,但是迭代遍歷它時,取得“鍵值對”的順序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一點。而在迭代訪問時發而更快,因爲它使用鏈表維護內部次序。
TreeMap : 基於紅黑樹數據結構的實現。查看“鍵”或“鍵值對”時,它們會被排序(次序由Comparabel或Comparator決定)。TreeMap的特點在 於,你得到的結果是經過排序的。TreeMap是唯一的帶有subMap()方法的Map,它可以返回一個子樹。
WeakHashMap :弱鍵(weak key)Map,Map中使用的對象也被允許釋放: 這是爲解決特殊問題設計的。如果沒有map之外的引用指向某個“鍵”,則此“鍵”可以被垃圾收集器回收。

HashMap和HashTable的區別
HashMap允許key和value爲null;
HashMap是非同步的,線程不安全,也可以通過Collections.synchronizedMap()方法來得到一個同步的HashMap
HashMap存取速度更快,效率高
HashMap去掉了HashTable中的contains方法,加上了containsValue和containsKey方法

HashMap與HashSet的區別

HashSet與HashMap怎麼判斷集合元素重複?
HashSet不能添加重複的元素,當調用add(Object)方法時候,
首先會調用Object的hashCode方法判hashCode是否已經存在,如不存在則直接插入元素;如果已存在則調用Object對象的equals方法判斷是否返回true,如果爲true則說明元素已經存在,如爲false則插入元素。

集合Set實現Hash怎麼防止碰撞
重寫hashcode()和equles()方法

ArrayList和LinkedList的區別,以及應用場景
ArrayList是基於數組實現的,ArrayList線程不安全。
LinkedList是基於雙鏈表實現的:
使用場景:
(1)如果應用程序對各個索引位置的元素進行大量的存取或刪除操作,ArrayList對象要遠優於LinkedList對象;
( 2 ) 如果應用程序主要是對列表進行循環,並且循環時候進行插入或者刪除操作,LinkedList對象要遠優於ArrayList對象;

數組和鏈表的區別
數組:是將元素在內存中連續存儲的;它的優點:因爲數據是連續存儲的,內存地址連續,所以在查找數據的時候效率比較高;它的缺點:在存儲之前,我們需要申請一塊連續的內存空間,並且在編譯的時候就必須確定好它的空間的大小。在運行的時候空間的大小是無法隨着你的需要進行增加和減少而改變的,當數據兩比較大的時候,有可能會出現越界的情況,數據比較小的時候,又有可能會浪費掉內存空間。在改變數據個數時,增加、插入、刪除數據效率比較低。
鏈表:是動態申請內存空間,不需要像數組需要提前申請好內存的大小,鏈表只需在用的時候申請就可以,根據需要來動態申請或者刪除內存空間,對於數據增加和刪除以及插入比數組靈活。還有就是鏈表中數據在內存中可以在任意的位置,通過應用來關聯數據(就是通過存在元素的指針來聯繫)

堆和樹的區別
節點的順序
在二叉搜索樹中,左子節點必須比父節點小,右子節點必須必比父節點大。但是在堆中並非如此。在最大堆中兩個子節點都必須比父節點小,而在最小堆中,它們都必須比父節點大。

內存佔用
普通樹佔用的內存空間比它們存儲的數據要多。你必須爲節點對象以及左/右子節點指針分配額外內存。堆僅僅使用一個數據來存儲數組,且不使用指針。

平衡
二叉搜索樹必須是“平衡”的情況下,其大部分操作的複雜度才能達到O(log n)。你可以按任意順序位置插入/刪除數據,或者使用 AVL 樹或者紅黑樹,但是在堆中實際上不需要整棵樹都是有序的。我們只需要滿足對屬性即可,所以在堆中平衡不是問題。因爲堆中數據的組織方式可以保證O(log n) 的性能。

搜索
在二叉樹中搜索會很快,但是在堆中搜索會很慢。在堆中搜索不是第一優先級,因爲使用堆的目的是將最大(或者最小)的節點放在最前面,從而快速的進行相關插入、刪除操作。

什麼是深拷貝和淺拷貝
淺拷貝:對基本數據類型進行值傳遞,對引用數據類型進行引用傳遞般的拷貝,此爲淺拷貝。
深拷貝:對基本數據類型進行值傳遞,對引用數據類型,創建一個新的對象,並複製其內容,此爲深拷貝。

判斷單鏈表成環與否?
使用快慢指針遍歷鏈表:
慢指針:
從頭節點開始,一次跳一個節點。
快指針:
從頭節點開始,一次跳兩個節點。
如果是成環的,這兩個指針一定會相遇。

開啓線程的三種方式?
java有三種創建線程的方式,分別是繼承Thread類、實現Runable接口和使用線程池

線程和進程的區別?
線程是進程的子集,一個進程可以有很多線程,每條線程並行執行不同的任務。不同的進程使用不同的內存空間,而所有的線程共享一片相同的內存空間。別把它和棧內存搞混,每個線程都擁有單獨的棧內存用來存儲本地數據。

爲什麼要有線程,而不是僅僅用進程?
線程可以增加併發的程度啊。其實多進程也是可以併發,但是爲什麼要是線程呢?因爲線程是屬於進程的,是個輕量級的對象。所以再切換線程時只需要做少量的工作,而切換進程消耗很大。這是從操作系統角度講。
從用戶程序角度講,有些程序在邏輯上需要線程,比如掃雷,它需要一個線程等待用戶的輸入,另一個線程的來更新時間。還有一個例子就是聊天程序,一個線程是響應用戶輸入,一個線程是響應對方輸入。如果沒有多線程,那麼只能你說一句我說一句,你不說我這裏就不能動,我還不能連續說。所以用戶程序有這種需要,操作系統就要提供響應的機制

run()和start()方法區別
這個問題經常被問到,但還是能從此區分出面試者對Java線程模型的理解程度。start()方法被用來啓動新創建的線程,而且start()內部調用了run()方法,這和直接調用run()方法的效果不一樣。當你調用run()方法的時候,只會是在原來的線程中調用,沒有新的線程啓動,start()方法纔會啓動新線程。

如何控制某個方法允許併發訪問線程的個數?
semaphore.acquire() 請求一個信號量,這時候的信號量個數-1(一旦沒有可使用的信號量,也即信號量個數變爲負數時,再次請求的時候就會阻塞,直到其他線程釋放了信號量)
semaphore.release() 釋放一個信號量,此時信號量個數+1

在Java中wait和seelp方法的不同;
Java程序中wait 和 sleep都會造成某種形式的暫停,它們可以滿足不同的需要。wait()方法用於線程間通信,如果等待條件爲真且其它線程被喚醒時它會釋放鎖,而sleep()方法僅僅釋放CPU資源或者讓當前線程停止執行一段時間,但不會釋放鎖。

談談wait/notify關鍵字的理解
等待對象的同步鎖,需要獲得該對象的同步鎖纔可以調用這個方法,否則編譯可以通過,但運行時會收到一個異常:IllegalMonitorStateException。
調用任意對象的 wait() 方法導致該線程阻塞,該線程不可繼續執行,並且該對象上的鎖被釋放。
喚醒在等待該對象同步鎖的線程(只喚醒一個,如果有多個在等待),注意的是在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先級。
調用任意對象的notify()方法則導致因調用該對象的 wait()方法而阻塞的線程中隨機選擇的一個解除阻塞(但要等到獲得鎖後才真正可執行)。

什麼導致線程阻塞?
阻塞式方法是指程序會一直等待該方法完成期間不做其他事情,ServerSocket的accept()方法就是一直等待客戶端連接。這裏的阻塞是指調用結果返回之前,當前線程會被掛起,直到得到結果之後纔會返回。此外,還有異步和非阻塞式方法在任務完成前就返回。

線程如何關閉?
一種是調用它裏面的stop()方法
另一種就是你自己設置一個停止線程的標記 (推薦這種)

講一下java中的同步的方法(另一種問法:數據一致性如何保證?)
1.即有synchronized關鍵字修飾的方法。
2.同步代碼塊(如:雙重判斷的單例模式)
3.使用特殊域變量(volatile)實現線程同步
4.使用重入鎖實現線程同步
5.使用局部變量實現線程同步

如何保證線程安全?
1.synchronized;
2.Object方法中的wait,notify;
3.ThreadLocal機制 來實現的。

如何實現線程同步?
1、synchronized關鍵字修改的方法。2、synchronized關鍵字修飾的語句塊3、使用特殊域變量(volatile)實現線程同步

兩個進程同時要求寫或者讀,能不能實現?如何防止進程的同步?
可以實現的。
同步方式有: 互斥鎖、條件變量、讀寫鎖、記錄鎖(文件鎖)和信號燈

線程間操作List
List list = Collections.synchronizedList(new ArrayList());

Synchronized用法及原理
用法:修飾靜態方法、實例方法、代碼塊
原理:不是一兩句話能說清,建議去深入瞭解一下。

談談對Synchronized關鍵字,類鎖,方法鎖,重入鎖的理解
java的對象鎖和類鎖:java的對象鎖和類鎖在鎖的概念上基本上和內置鎖是一致的,但是,兩個鎖實際是有很大的區別的,對象鎖是用於對象實例方法,或者一個對象實例上的,類鎖是用於類的靜態方法或者一個類的class對象上的。我們知道,類的對象實例可以有很多個,但是每個類只有一個class對象,所以不同對象實例的對象鎖是互不干擾的,但是每個類只有一個類鎖。但是有一點必須注意的是,其實類鎖只是一個概念上的東西,並不是真實存在的,它只是用來幫助我們理解鎖定實例方法和靜態方法的區別的

static synchronized 方法的多線程訪問和作用
1.synchronized static是某個類的範圍,synchronized static cSync{}防止多個線程同時訪問這個類中的synchronized static 方法。它可以對類的所有對象實例起作用。

2.synchronized 是某實例的範圍,synchronized isSync(){}防止多個線程同時訪問這個實例中的synchronized 方法。

同一個類裏面兩個synchronized方法,兩個線程同時訪問的問題
同一個object中多個方法都加了synchronized關鍵字的時候,其中調用任意方法之後需等該方法執行完成才能調用其他方法,即同步的,阻塞的;
此結論同樣適用於對於object中使用synchronized(this)同步代碼塊的場景;
synchronized鎖定的都是當前對象!

volatile的作用,原理,性能。
作用:1、保持內存可見性 2、防止指令重排
原理:獲取JIT(即時Java編譯器,把字節碼解釋爲機器語言發送給處理器)的彙編代碼,發現volatile多加了lock addl指令,這個操作相當於一個內存屏障,使得lock指令後的指令不能重排序到內存屏障前的位置。這也是爲什麼JDK1.5以後可以使用雙鎖檢測實現單例模式。
lock前綴的另一層意義是使得本線程工作內存中的volatile變量值立即寫入到主內存中,並且使得其他線程共享的該volatile變量無效化,這樣其他線程必須重新從主內存中讀取變量值。
性能:讀操作與普通變量無差別,寫操作會慢一些,大多情況比鎖消耗低。

談談NIO的理解
如果問到這個,很容易就會問到和IO的比較,所以可以直接看看這個。
https://www.cnblogs.com/lingqin/p/11324502.html

synchronized 和volatile 關鍵字的區別
1.volatile本質是在告訴jvm當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。
2.volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的
3.volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性
4.volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。
5.volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化

synchronized與Lock的區別及使用場景
synchronized原始採用的是CPU悲觀鎖機制,即線程獲得的是獨佔鎖。獨佔鎖意味着其他線程只能依靠阻塞來等待線程釋放鎖。而在CPU轉換線程阻塞時會引起線程上下文切換,當有很多線程競爭鎖的時候,會引起CPU頻繁的上下文切換導致效率很低;
而Lock用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止。樂觀鎖實現的機制就是CAS操作(Compare and Swap)。我們可以進一步研究ReentrantLock的源代碼,會發現其中比較重要的獲得鎖的一個方法是compareAndSetState。這裏其實就是調用的CPU提供的特殊指令。
使用場景:在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。

ReentrantLock 、synchronized和volatile比較
java在過去很長一段時間只能通過synchronized關鍵字來實現互斥,它有一些缺點。比如你不能擴展鎖之外的方法或者塊邊界,嘗試獲取鎖時不能中途取消等。Java 5 通過Lock接口提供了更復雜的控制來解決這些問題。 ReentrantLock 類實現了 Lock,它擁有與 synchronized 相同的併發性和內存語義且它還具有可擴展性。

死鎖的四個必要條件?怎麼避免死鎖?
死鎖產生的原因

  1. 系統資源的競爭
    系統資源的競爭導致系統資源不足,以及資源分配不當,導致死鎖。
  2. 進程運行推進順序不合適
    互斥條件:一個資源每次只能被一個進程使用,即在一段時間內某 資源僅爲一個進程所佔有。此時若有其他進程請求該資源,則請求進程只能等待。
    請求與保持條件:進程已經保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其他進程佔有,此時請求進程被阻塞,但對自己已獲得的資源保持不放。
    不可剝奪條件:進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,即只能 由獲得該資源的進程自己來釋放(只能是主動釋放)。
    循環等待條件: 若干進程間形成首尾相接循環等待資源的關係
    這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。
    死鎖的避免與預防:
    死鎖避免的基本思想:
    系統對進程發出每一個系統能夠滿足的資源申請進行動態檢查,並根據檢查結果決定是否分配資源,如果分配後系統可能發生死鎖,則不予分配,否則予以分配。這是一種保證系統不進入死鎖狀態的動態策略。
    理解了死鎖的原因,尤其是產生死鎖的四個必要條件,就可以最大可能地避免、預防和解除死鎖。所以,在系統設計、進程調度等方面注意如何讓這四個必要條件不成立,如何確定資源的合理分配算法,避免進程永久佔據系統資源。此外,也要防止進程在處於等待狀態的情況下佔用資源。因此,對資源的分配要給予合理的規劃。
    死鎖避免和死鎖預防的區別:
    死鎖預防是設法至少破壞產生死鎖的四個必要條件之一,嚴格的防止死鎖的出現,而死鎖避免則不那麼嚴格的限制產生死鎖的必要條件的存在,因爲即使死鎖的必要條件存在,也不一定發生死鎖。死鎖避免是在系統運行過程中注意避免死鎖的最終發生。

什麼是線程池,如何使用?
創建線程要花費昂貴的資源和時間,如果任務來了才創建線程那麼響應時間會變長,而且一個進程能創建的線程數有限。爲了避免這些問題,在程序啓動的時候就創建若干線程來響應處理,它們被稱爲線程池,裏面的線程叫工作線程。從JDK1.5開始,Java API提供了Executor框架讓你可以創建不同的線程池。比如單線程池,每次處理一個任務;數目固定的線程池或者是緩存線程池(一個適合很多生存期短的任務的程序的可擴展線程池)

談談對多線程的理解
線程是由一個主線程和很多個子線程組成的,主線程消失,子線程也會消失,但是子線程消失其中一個主線程不會消失
線程的生命週期分爲5個步驟像人的一生一樣,這5個步驟分別對應了5個方法
新生-->啓動-->運行-->阻塞-->銷燬
繼承Thread類or實現runnable方法-->start-->run-->sleep(睡眠)or wait(掛起)-->destroy

多線程有什麼要注意的問題?
給線程起有意義的名字,這樣方便找Bug
縮小同步範圍,從而減少鎖的爭用,例如對於 synchronized,應該儘量使用同步塊而不是同步方法
多用同步工具少用 wait() 和 notify()。首先,CountDownLatch, CyclicBarrier, Semaphore 和Exchanger 這些同步類簡化了編碼操作,而用 wait() 和 notify() 很難實現複雜控制流;其次,這些同步類是由最好的企業編寫和維護,在後續的 JDK 中還會不斷優化和完善。
使用BlockingQueue實現生產者消費者問題
多用併發集合少用同步集合,例如應該使用 ConcurrentHashMap 而不是 Hashtable
使用本地變量和不可變類來保證線程安全
使用線程池而不是直接創建線程,這是因爲創建線程代價很高,線程池可以有效地利用有限的線程來啓動任務

自己去設計網絡請求框架,怎麼做?
這種並沒有一個完全正確的答案,看個人的思路與理解

okhttp源碼
自己看一遍源碼即可,最好能夠手寫出他的流程。

從網絡加載一個10M的圖片,說下注意事項
圖片緩存、異常恢復、質量壓縮,從這幾方面說就好了

TCP的3次握手和四次揮手
三次握手:
第一次:客戶端發送請求到服務器,服務器知道客戶端發送,自己接收正常。SYN=1,seq=x
第二次:服務器發給客戶端,客戶端知道自己發送、接收正常,服務器接收、發送正常。ACK=1,ack=x+1,SYN=1,seq=y
第三次:客戶端發給服務器:服務器知道客戶端發送,接收正常,自己接收,發送也正常.seq=x+1,ACK=1,ack=y+1

四次揮手:
第一次:客戶端請求斷開FIN,seq=u
第二次:服務器確認客戶端的斷開請求ACK,ack=u+1,seq=v
第三次:服務器請求斷開FIN,seq=w,ACK,ack=u+1
第四次:客戶端確認服務器的斷開ACK,ack=w+1,seq=u+1

爲什麼連接的時候是三次握手,關閉的時候卻是四次握手?
因爲當Server端收到Client端的SYN連接請求報文後,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當Server端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴Client端,"你發的FIN報文我收到了"。只有等到我Server端所有的報文都發送完了,我才能發送FIN報文,因此不能一起發送。故需要四步握手。

爲什麼不能用兩次握手進行連接?
3次握手完成兩個重要的功能,既要雙方做好發送數據的準備工作(雙方都知道彼此已準備好),也要允許雙方就初始序列號進行協商,這個序列號在握手過程中被髮送和確認。
現在把三次握手改成僅需要兩次握手,死鎖是可能發生的。

爲什麼TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?
雖然按道理,四個報文都發送完畢,我們可以直接進入CLOSE狀態了,但是我們必須假象網絡是不可靠的,有可以最後一個ACK丟失。所以TIME_WAIT狀態就是用來重發可能丟失的ACK報文。在Client發送出最後的ACK回覆,但該ACK可能丟失。Server如果沒有收到ACK,將不斷重複發送FIN片段。所以Client不能立即關閉,它必須確認Server接收到了該ACK。Client會在發送出ACK之後進入到TIME_WAIT狀態。Client會設置一個計時器,等待2MSL的時間。如果在該時間內再次收到FIN,那麼Client會重發ACK並再次等待2MSL。所謂的2MSL是兩倍的MSL(Maximum Segment Lifetime)。MSL指一個片段在網絡中最大的存活時間,2MSL就是一個發送和一個回覆所需的最大時間。如果直到2MSL,Client都沒有再次收到FIN,那麼Client推斷ACK已經被成功接收,則結束TCP連接。

TCP與UDP的區別
tcp是面向連接的,由於tcp連接需要三次握手,所以能夠最低限度的降低風險,保證連接的可靠性。
udp 不是面向連接的,udp建立連接前不需要與對象建立連接,無論是發送還是接收,都沒有發送確認信號。所以說udp是不可靠的。
由於udp不需要進行確認連接,使得UDP的開銷更小,傳輸速率更高,所以實時行更好。

TCP與UDP的應用
從特點上我們已經知道,TCP 是可靠的但傳輸速度慢 ,UDP 是不可靠的但傳輸速度快。因此在選用具體協議通信時,應該根據通信數據的要求而決定。 
若通信數據完整性需讓位與通信實時性,則應該選用 TCP 協議(如文件傳輸、重要狀態的更新等);反之,則使用 UDP 協議(如視頻傳輸、實時通信等)。

Http https區別,此處延伸:https的實現原理
1、https協議需要到ca申請證書,一般免費證書較少,因而需要一定費用。
2、http是超文本傳輸協議,信息是明文傳輸,https則是具有安全性的ssl加密傳輸協議。
3、http和https使用的是完全不同的連接方式,用的端口也不一樣,前者是80,後者是443。
4、http的連接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。
https實現原理:
(1)客戶使用https的URL訪問Web服務器,要求與Web服務器建立SSL連接。
(2)Web服務器收到客戶端請求後,會將網站的證書信息(證書中包含公鑰)傳送一份給客戶端。
(3)客戶端的瀏覽器與Web服務器開始協商SSL連接的安全等級,也就是信息加密的等級。
(4)客戶端的瀏覽器根據雙方同意的安全等級,建立會話密鑰,然後利用網站的公鑰將會話密鑰加密,並傳送給網站。
(5)Web服務器利用自己的私鑰解密出會話密鑰。
(6)Web服務器利用會話密鑰加密與客戶端之間的通信。
7、Http位於TCP/IP模型中的第幾層?爲什麼說Http是可靠的數據傳輸協議?
tcp/ip的五層模型:
從下到上:物理層->數據鏈路層->網絡層->傳輸層->應用層
其中tcp/ip位於模型中的網絡層,處於同一層的還有ICMP(網絡控制信息協議)。http位於模型中的應用層
由於tcp/ip是面向連接的可靠協議,而http是在傳輸層基於tcp/ip協議的,所以說http是可靠的數據傳輸協議。

8、HTTP鏈接的特點
HTTP連接最顯著的特點是客戶端發送的每次請求都需要服務器回送響應,在請求結束後,會主動釋放連接。
從建立連接到關閉連接的過程稱爲“一次連接”。

HTTP報文結構
一個HTTP請求報文由四個部分組成:請求行、請求頭部、空行、請求數據。
1.請求行
請求行由請求方法字段、URL字段和HTTP協議版本字段3個字段組成,它們用空格分隔。比如 GET /data/info.html HTTP/1.1
2.請求頭部
HTTP客戶程序(例如瀏覽器),向服務器發送請求的時候必須指明請求類型(一般是GET或者 POST)。如有必要,客戶程序還可以選擇發送其他的請求頭。大多數請求頭並不是必需的,但Content-Length除外。對於POST請求來說 Content-Length必須出現。
3.空行
它的作用是通過一個空行,告訴服務器請求頭部到此爲止。
4.請求數據
若方法字段是GET,則此項爲空,沒有數據。若方法字段是POST,則通常來說此處放置的就是要提交的數據

HTTP與HTTPS的區別以及如何實現安全性
區別:http是明文傳輸,傳輸的數據很可能被中間節點獲取,從而導致數據傳輸不安全 
https是加密傳輸,可以保證數據的傳輸安全
如何實現:http是應用層協議,它會將要傳輸的數據以明文的方式給傳輸層,這樣顯然不安全。https則是在應用層與傳輸層之間又加了一層,該層遵守SSL/TLS協議,用於數據加密。

如何驗證證書的合法性?
1、證書是否是信任的有效證書。所謂信任:瀏覽器內置了信任的根證書,就是看看web服務器的證書是不是這些信任根發的或者信任根的二級證書機構頒發的。所謂有效,就是看看web服務器證書是否在有效期,是否被吊銷了。2、對方是不是上述證書的合法持有者。簡單來說證明對方是否持有證書的對應私鑰。驗證方法兩種,一種是對方籤個名,我用證書驗證簽名;另外一種是用證書做個信封,看對方是否能解開。以上的所有驗證,除了驗證證書是否吊銷需要和CA關聯,其他都可以自己完成。驗證正式是否吊銷可以採用黑名單方式或者OCSP方式。黑名單就是定期從CA下載一個名單列表,裏面有吊銷的證書序列號,自己在本地比對一下就行。優點是效率高。缺點是不實時。OCSP是實時連接CA去驗證,優點是實時,缺點是效率不高。

client如何確定自己發送的消息被server收到?
HTTP協議裏,有請求就有響應,根據響應的狀態嗎就能知道。

HttpClient與HttpUrlConnection的區別 (此處延伸:Volley裏用的哪種請求方式(2.3前HttpClient,2.3後HttpUrlConnection)
首先HttpClient和HttpUrlConnection 這兩種方式都支持Https協議,都是以流的形式進行上傳或者下載數據,也可以說是以流的形式進行數據的傳輸,還有ipv6,以及連接池等功能。HttpClient這個擁有非常多的API,所以如果想要進行擴展的話,並且不破壞它的兼容性的話,很難進行擴展,也就是這個原因,Google在Android6.0的時候,直接就棄用了這個HttpClient.
而HttpUrlConnection相對來說就是比較輕量級了,API比較少,容易擴展,並且能夠滿足Android大部分的數據傳輸。比較經典的一個框架volley,在2.3版本以前都是使用HttpClient,在2.3以後就使用了HttpUrlConnection。

WebSocket與socket的區別
1.WebSocket protocol 是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信(full-duplex)。一開始的握手需要藉助HTTP請求完成。
2.Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把...
3.區別 Socket是傳輸控制層協議,WebSocket是應用層協議。

談談你對安卓簽名的理解。
每個應用都必須簽名
應用可以被不同的簽名文件簽名(如果有源代碼或者反編譯後重新編譯)
同一個應用如果簽名不同則不能覆蓋安裝

請解釋安卓爲啥要加簽名機制?
發送者的身份認證:由於開發商可能通過使用相同的 Package Name 來混淆替換已經安裝的程序,以此保證簽名不同的包不被替換
保證信息傳輸的完整性:簽名對於包中的每個文件進行處理,以此確保包中內容不被替換
防止交易中的抵賴發生:Market(應用市場)對軟件的要求

視頻加密傳輸
DES加密。用java中提供的加密包。
將視頻文件的數據流前100個字節中的每個字節與其下標進行異或運算。解密時只需將加密過的文件再進行一次異或運算即可。

App 是如何沙箱化,爲什麼要這麼做?
在Android系統中,應用(通常)都在一個獨立的沙箱中運行,即每一個Android應用程序都在它自己的進程中運行,都擁有一個獨立的Dalvik虛擬機實例。Dalvik經過優化,允許在有限的內存中同時高效地運行多個虛擬機的實例,並且每一個Dalvik應用作爲一個獨立的Linux進程執行。Android這種基於Linux的進程“沙箱”機制,是整個安全設計的基礎之一。
Android擴展了Linux內核安全模型的用戶與權限機制,將多用戶操作系統的用戶隔離機制巧妙地移植爲應用程序隔離。將UID(一個用戶標識)不同的應用程序自然形成資源隔離,如此便形成了一個操作系統級別的應用程序“沙箱”。

(2)Android面試題(基礎+進階)(必須)

四大組件是什麼(這個不知道的話,沒必要去面試了,轉行吧)
Android四大組件有Activity,Service服務,Content Provider內容提供,BroadcastReceiver。

四大組件的生命週期和簡單用法
activity:onCreate() -> onStart() -> onResume() -> onPause() -> onStop() -> onDetroy()
Service:
service 啓動方式有兩種,一種是通過startService()方式進行啓動,另一種是通過bindService()方式進行啓動。不同的啓動方式他們的生命週期是不一樣.
通過startService()這種方式啓動的service,生命週期是這樣:調用startService() --> onCreate()--> onStartConmon()--> onDestroy()。這種方式啓動的話,需要注意一下幾個問題,第一:當我們通過startService被調用以後,多次在調用startService(),onCreate()方法也只會被調用一次,而onStartConmon()會被多次調用當我們調用stopService()的時候,onDestroy()就會被調用,從而銷燬服務。第二:當我們通過startService啓動時候,通過intent傳值,在onStartConmon()方法中獲取值的時候,一定要先判斷intent是否爲null。
通過bindService()方式進行綁定,這種方式綁定service,生命週期走法:bindService-->onCreate()-->onBind()-->unBind()-->onDestroy() bingservice 這種方式進行啓動service好處是更加便利activity中操作service,比如加入service中有幾個方法,a,b ,如果要在activity中調用,在需要在activity獲取ServiceConnection對象,通過ServiceConnection來獲取service中內部類的類對象,然後通過這個類對象就可以調用類中的方法,當然這個類需要繼承Binder對象
contentProvider:contentProvider的生命週期、理解應該跟進程一樣,它作爲系統應用組件、其生命週期應該跟app應用的生命週期類似,只是它屬於系統應用、所以隨系統啓動而初始化,隨系統關機而結束;但也存在其他狀態下結束進程、比如說系統內存不夠時,進行內存回收、會根據生成時間態、用戶操作等情況進行是否內存回收。
BroadcastReceiver:廣播的生命週期從調用開始到onReceiver執行完畢結束,需要注意的是,一般廣播的生命週期都極短,需要在10s內處理完onReceiver中的所有工作,所以,一般不進行耗時長的工作,如果有耗時長的工作,應當通過Intent傳遞給Service進行處理。(注意,不要在onReceiver中開啓線程進行耗時任務處理,否則,在10s後,該線程會變成空線程,從而導致任務的丟失。同樣的,也不要使用bindService來綁定服務。)
值得注意的是,如果是在代碼中動態註冊的廣播,如:在Activity註冊,那麼在Activity的onDestory中需要使用unregisterReceiver註銷廣播。

Activity之間的通信方式
Intent
藉助類的靜態變量
藉助全局變量/Application
藉助外部工具 
藉助SharedPreference 
使用Android數據庫SQLite 
赤裸裸的使用File 
Android剪切板
藉助Service

橫豎屏切換的時候,Activity 各種情況下的生命週期
分兩種情況:
1.不設置Activity的android:configChanges,或設置Activity的android:configChanges="orientation",或設置Activity的android:configChanges="orientation|keyboardHidden",切屏會重新調用各個生命週期,切橫屏時會執行一次,切豎屏時會執行一次。
橫豎屏切換造成 activity 的生命週期
onPause()-onSaveInstanceState()-onStop()-onDestroy()-onCreat()-onStart()-onRestoreInstanceState()-onResume()即會導致 activity 的銷燬和重建 。

2.配置 android:configChanges="orientation|keyboardHidden|screenSize",纔不會銷燬 activity,且只調用 onConfigurationChanged方法。
onSaveInstanceState() 與onRestoreIntanceState() 資源相關的系統配置發生改變或者資源不足時(例如屏幕旋轉),當前 Activity 會銷燬,並且在 onStop 之前回調 onSaveInstanceState 保存數據,在重新創建 Activity 的時候在onStart 之後回調 onRestoreInstanceState。其中 Bundle 數據會傳到 onCreate(不一定有數據)和 onRestoreInstanceState(一定有數據)。
用戶或者程序員主動去銷燬一個 Activity 的時候不會回調(如代碼中 finish()或用戶按下 back,不會回調),其他情況都會調用,來保存界面信息。

Activity與Fragment之間生命週期比較
a. 在創建的過程中,是 Activity 帶領 Fragment 執行生命週期的方法,所以它們生命週期執行的順序如下:

Activity -- onCreate() ,
Fragment -- onAttach() -> onCreate() -> onCreateView() -> onActivityCreated

.

Activity -- onStart()

Fragment -- onStart()


Activity -- onResume()

Fragment -- onResume()


最後,在銷燬時是 Fragment 帶領 Activity 執行生命週期的方法:

Fragment -- onPause()

Activity -- onPause()


Fragment -- onStop()

Activity -- onStop()


Fragment -- onDestroyView() -> onDestroy() -> onDetach()

Activity -- onDestroy()

Activity上有Dialog的時候按Home鍵時的生命週期
有 Dialog 和 無 Dialog 按 Home 鍵效果一樣:

  1. 正常啓動: onCreate() -> onStart() -> onResume()
  2. 按 home 鍵: onPause() -> onStop()
  3. 再次啓動: onRestart() -> onStart() -> onResume()

兩個Activity 之間跳轉時必然會執行的是哪幾個方法?
a. 正常情況下 Activity A 跳轉到 Activity B 時:
A調用 onCreate() 方法 -> onStart() 方法 -> onResume() 方法,此時 A 前臺可見。當 A 跳轉到 B 時,A 調用 onPause() 方法,然後調用新的 Activity B 中的 onCreate() 方法 -> onStart() 方法 -> onResume() 方法。最後 A 再調用onStop()方法。
b. 當 Activity B 爲透明主題時:
除了最後 Activity A 不調用 onStop() 方法之外,其它都和 a 中的一樣。

Activity的四種啓動模式對比
此處延伸:棧(First In Last Out)與隊列(First In First Out)的區別
區別:隊列先進先出,棧先進後出
對插入和刪除操作的"限定"。 棧是限定只能在表的一端進行插入和刪除操作的線性表。 隊列是限定只能在表的一端進行插入和在另一端進行刪除操作的線性表。
遍歷數據速度不同

standard 模式
這是默認模式,每次激活Activity時都會創建Activity實例,並放入任務棧中。使用場景:大多數Activity。
singleTop 模式
如果在任務的棧頂正好存在該Activity的實例,就重用該實例( 會調用實例的 onNewIntent() ),否則就會創建新的實例並放入棧頂,即使棧中已經存在該Activity的實例,只要不在棧頂,都會創建新的實例。使用場景如新聞類或者閱讀類App的內容頁面。
singleTask 模式
如果在棧中已經有該Activity的實例,就重用該實例(會調用實例的 onNewIntent() )。重用時,會讓該實例回到棧頂,因此在它上面的實例將會被移出棧。如果棧中不存在該實例,將會創建新的實例放入棧中。使用場景如瀏覽器的主界面。不管從多少個應用啓動瀏覽器,只會啓動主界面一次,其餘情況都會走onNewIntent,並且會清空主界面上面的其他頁面。
singleInstance 模式
在一個新棧中創建該Activity的實例,並讓多個應用共享該棧中的該Activity實例。一旦該模式的Activity實例已經存在於某個棧中,任何應用再激活該Activity時都會重用該棧中的實例( 會調用實例的 onNewIntent() )。其效果相當於多個應用共享一個應用,不管誰激活該 Activity 都會進入同一個應用中。使用場景如鬧鈴提醒,將鬧鈴提醒與鬧鈴設置分離。singleInstance不要用於中間頁面,如果用於中間頁面,跳轉會有問題,比如:A -> B (singleInstance) -> C,完全退出後,在此啓動,首先打開的是B。

Activity狀態保存於恢復
當 Activity 在異常情況( 系統內存不足或者系統配置發生了改變等 )被銷燬重建後, 在銷燬的時候 Activity 會調用 onSaveInstanceState() 方法用於保存 Activity 相關的狀態和數據,然後在重建後的 Activity 的中我們可以通過 onCreate() 或者 onRestoreInstanceState() 方法恢復數據,這裏我們需要注意的是如果通過 onCreate() 方法恢復,那麼得先判斷它的 intent 參數 是否爲空,如果在 onRestoreInstanceState() 方法恢復就不會,因爲只要 onRestoreInstanceState() 方法被調用就說明一定有數據,不會爲空。Google 推薦使用 onRestoreInstanceState() 方法。

如何實現Fragment的滑動?
將Fragment與viewpager綁定,通過viewpager中的touch事件,會進行move事件的滑動處理。

fragment之間傳遞數據的方式?
1、在fragment中設置一個方法,然後進行調用
2、採取接口回調的方式進行數據傳遞。
3、廣播或者是使用三方開源框架:EventBus

Activity 怎麼和Service 綁定?怎麼在Activity 中啓動自己對應的Service?
1、activity能進行綁定得益於Serviece的接口。爲了支持Service的綁定,實現onBind方法。
2、Service和Activity的連接可以用ServiceConnection來實現。需要實現一個新的ServiceConnection,重現onServiceConnected和OnServiceDisconnected方法,一旦連接建立,就能得到Service實例的引用。
3、執行綁定,調用bindService方法,傳入一個選擇了要綁定的Service的Intent(顯示或隱式)和一個你實現了的ServiceConnection的實例

service和activity怎麼進行數據交互?
1.通過 broadcast:通過廣播發送消息到 activitry
2.通過 Binder:通過與 activity 進行綁定
(1)添加一個繼承 Binder 的內部類,並添加相應的邏輯方法。
(2)重寫 Service 的 onBind 方法,返回我們剛剛定義的那個內部類實例。
(3)Activity 中創建一個 ServiceConnection 的匿名內部類,並且 重 寫 裏 面 的 onServiceConnected 方 法 和onServiceDisconnected 方法,這兩個方法分別會在活動與服務成功綁定以及解除綁定的時候調用(在onServiceConnected方法中,我們可以得到一個剛纔那個 service 的 binder 對象,通過對這個 binder 對象進行向下轉型,得到我們那個自定義的 Binder 實例,有了這個實例,做可以調用這個實例裏面的具體方法進行需要的操作了)。

Service的開啓方式,請描述一下Service 的生命週期,請描述一下Service 的生命週期
service 啓動方式有兩種,一種是通過startService()方式進行啓動,另一種是通過bindService()方式進行啓動。不同的啓動方式他們的生命週期是不一樣.
通過startService()這種方式啓動的service,生命週期是這樣:調用startService() --> onCreate()--> onStartConmon()--> onDestroy()。這種方式啓動的話,需要注意一下幾個問題,第一:當我們通過startService被調用以後,多次在調用startService(),onCreate()方法也只會被調用一次,而onStartConmon()會被多次調用當我們調用stopService()的時候,onDestroy()就會被調用,從而銷燬服務。第二:當我們通過startService啓動時候,通過intent傳值,在onStartConmon()方法中獲取值的時候,一定要先判斷intent是否爲null。
通過bindService()方式進行綁定,這種方式綁定service,生命週期走法:bindService-->onCreate()-->onBind()-->unBind()-->onDestroy() bingservice 這種方式進行啓動service好處是更加便利activity中操作service,比如加入service中有幾個方法,a,b ,如果要在activity中調用,在需要在activity獲取ServiceConnection對象,通過ServiceConnection來獲取service中內部類的類對象,然後通過這個類對象就可以調用類中的方法,當然這個類需要繼承Binder對象

請描述一下廣播BroadcastReceiver的理解
廣播,是一個全局的監聽器,屬於Android四大組件之一。Android 廣播分爲兩個角色:廣播發送者、廣播接收者。作用是監聽 / 接收 應用 App 發出的廣播消息,並 做出響應
可應用在:
Android不同組件間的通信(含 :應用內 / 不同應用之間)
多線程通信
與 Android 系統在特定情況下的通信
如:電話呼入時、網絡可用時

Broadcast註冊方式與區別 (此處延伸:什麼情況下用動態註冊)
Broadcast廣播,註冊方式主要有兩種.
第一種是靜態註冊,也可成爲常駐型廣播,這種廣播需要在Androidmanifest.xml中進行註冊,這中方式註冊的廣播,不受頁面生命週期的影響,即使退出了頁面,也可以收到廣播這種廣播一般用於想開機自啓動啊等等,由於這種註冊的方式的廣播是常駐型廣播,所以會佔用CPU的資源。
第二種是動態註冊,而動態註冊的話,是在代碼中註冊的,這種註冊方式也叫非常駐型廣播,收到生命週期的影響,退出頁面後,就不會收到廣播,我們通常運用在更新UI方面。這種註冊方式優先級較高。最後需要解綁,否會會內存泄露
廣播是分爲有序廣播和無序廣播。

在manifest 和代碼中如何註冊和使用BroadcastReceiver?
          

本地廣播和全局廣播有什麼差別?
BroadcastReceiver是針對應用間、應用與系統間、應用內部進行通信的一種方式
LocalBroadcastReceiver僅在自己的應用內發送接收廣播,也就是隻有自己的應用能收到,數據更加安全廣播只在這個程序裏,而且效率更高。

BroadcastReceiver,LocalBroadcastReceiver 區別
一、應用場景不同
1、BroadcastReceiver用於應用之間的傳遞消息;
2、而LocalBroadcastManager用於應用內部傳遞消息,比broadcastReceiver更加高效。
二、使用安全性不同
1、BroadcastReceiver使用的Content API,所以本質上它是跨應用的,所以在使用它時必須要考慮到不要被別的應用濫用;
2、LocalBroadcastManager不需要考慮安全問題,因爲它只在應用內部有效。

AlertDialog,popupWindow區別
(1)Popupwindow在顯示之前一定要設置寬高,Dialog無此限制。
(2)Popupwindow默認不會響應物理鍵盤的back,除非顯示設置了popup.setFocusable(true);而在點擊back的時候,Dialog會消失。
(3)Popupwindow不會給頁面其他的部分添加蒙層,而Dialog會。

(4)Popupwindow沒有標題,Dialog默認有標題,可以通過dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);取消標題
(5)二者顯示的時候都要設置Gravity。如果不設置,Dialog默認是Gravity.CENTER。
(6)二者都有默認的背景,都可以通過setBackgroundDrawable(new ColorDrawable(android.R.color.transparent));去掉。
最本質的區別:AlertDialog是非阻塞式對話框:AlertDialog彈出時,後臺還可以做事情;而PopupWindow是阻塞式對話框:PopupWindow彈出時,程序會等待,在PopupWindow退出前,程序一直等待,只有當我們調用了dismiss方法的後,PopupWindow退出,程序纔會向下執行。

講解一下Context 
Context是一個抽象基類。在翻譯爲上下文,也可以理解爲環境,是提供一些程序的運行環境基礎信息。Context下有兩個子類,ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現類。而ContextWrapper又有三個直接的子類, ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity,所以Activity和Service以及Application的Context是不一樣的,只有Activity需要主題,Service不需要主題。Context一共有三種類型,分別是Application、Activity和Service。這三個類雖然分別各種承擔着不同的作用,但它們都屬於Context的一種,而它們具體Context的功能則是由ContextImpl類去實現的,因此在絕大多數場景下,Activity、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊,比如啓動Activity,還有彈出Dialog。出於安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啓動必須要建立在另一個Activity的基礎之上,也就是以此形成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),因此在這種場景下,我們只能使用Activity類型的Context,否則將會出錯。
getApplicationContext()和getApplication()方法得到的對象都是同一個application對象,只是對象的類型不一樣。
Context數量 = Activity數量 + Service數量 + 1 (1爲Application)

Android屬性動畫特性
(1) 對任意對象的屬性執行動畫操作:屬性動畫允許對任意對象的屬性執行動畫操作,因爲屬性動畫的性質是通過反射實現的。
(2)可改變背景顏色。
(3)真正改變 View 本身:因爲是通過反射改變其屬性,並刷新,如改變width,他會搜索getWidth(),反射獲取,再通過進行某種計算,將值通過setWidth()設置進去並更新。

LinearLayout、RelativeLayout、FrameLayout的特性及對比,並介紹使用場景。
RelativeLayout的onMeasure過程
根據源碼我們發現RelativeLayout會根據2次排列的結果對子View各做一次measure。
首先RelativeLayout中子View的排列方式是基於彼此的依賴關係,在確定每個子View的位置的時候,需要先給所有的子View排序一下,所以需要橫向縱向分別進行一次排序測量

LinearLayout的onMeasure過程
LinearLayout會先做一個簡單橫縱方向判斷
需要注意的是在每次對child測量完畢後,都會調用child.getMeasuredHeight()/getMeasuredWidth()獲取該子視圖最終的高度,並將這個高度添加到mTotalLength中。
但是getMeasuredHeight暫時避開了lp.weight>0且高度爲0子View,因爲後面會將把剩餘高度按weight分配給相應的子View。因此可以得出以下結論:
(1)如果我們在LinearLayout中不使用weight屬性,將只進行一次measure的過程。(如果使用weight屬性,則遍歷一次wiew測量後,再遍歷一次view測量)
(2)如果使用了weight屬性,LinearLayout在第一次測量時獲取所有子View的高度,之後再將剩餘高度根據weight加到weight>0的子View上。由此可見,weight屬性對性能是有影響的。
1)RelativeLayout慢於LinearLayout是因爲它會讓子View調用2次measure過程,而LinearLayout只需一次,但是有weight屬性存在時,LinearLayout也需要兩次measure。
2)在不響應層級深度的情況下,使用Linearlayout而不是RelativeLayout。

談談對接口與回調的理解
接口回調就是指: 可以把使用某一接口的類創建的對象的引用賦給該接口聲明的接口變量,那麼該接口變量就可以調用被類實現的接口的方法。實際上,當接口變量調用被類實現的接口中的方法時,就是通知相應的對象調用接口的方法,這一過程稱爲對象功能的接口回調。

Android中View,SurfaceView和GLSurfaceView
View:顯示視圖,內置畫布,提供圖形繪製函數,觸屏事件,按鍵事件函數;必須在UI線程中更新畫面,速度較慢。
SurfaceView:基於View視圖進行拓展的視圖類,更適合2D遊戲的開發;是View的子類,類似雙緩機制,在新的線程中更新畫面,所以刷新界面速度比View快。(雙緩機制:即前臺緩存和後臺緩存,後臺緩存計算場景、產生畫面,前臺緩存顯示後臺緩存已畫好的畫面。)
GLSurfaceView:基於SurfaceView視圖再次進行擴展的視圖類,專用於3D遊戲開發的視圖;是SurfaceView的子類,OpenGL專用。(OpenGL:是一個開放的三維圖形軟件包。)

序列化的作用,以及Android兩種序列化的區別
作用:java序列化主要有2個作用:
對象持久化,對象生存在內存中,想把一個對象持久化到磁盤,必須已某種方式來組織這個對象包含的信息,這種方式就是序列化;
遠程網絡通信,內存中的對象不能直接進行網絡傳輸,發送端把對象序列化成網絡可傳輸的字節流,接收端再把字節流還原成對象。

Serializable Java 序列化接口 在硬盤上讀寫 讀寫過程中有大量臨時變量的生成,內部執行大量的i/o操作,效率很低。
Parcelable Android 序列化接口 效率高 使用麻煩 在內存中讀寫(AS有相關插件 一鍵生成所需方法) ,對象不能保存到磁盤中

差值器和估值器
差值器: 根據時間流逝的百分比計算當前屬性改變的百分比。
估值器: 根據當前屬性改變的百分比計算改變後的屬性值

Android中數據存儲方式
1 使用SharedPreferences存儲數據
適用範圍:保存少量的數據,且這些數據的格式非常簡單:字符串型、基本類型的值。
比如應用程序的各種配置信息(如是否打開音效等),解鎖口 令密碼等
核心原理:保存基於XML文件存儲的key-value鍵值對數據,通常用來存儲一些簡單的配置信息。

2 文件存儲數據
核心原理: Context提供了兩個方法來打開數據文件裏的文件IO流:
FileInputStream openFileInput(String name);
FileOutputStream openFileOutput(String name , int mode)
3 SQLite數據庫存儲數據
4 使用ContentProvider存儲數據
5 網絡存儲數據
Requestlayout,onlayout,onDraw,DrawChild區別與聯繫
requestLayout()方法 :會導致調用measure()過程 和 layout()過程 。 說明:只是對View樹重新佈局layout過程包括measure()和layout()過程,不會調用draw()過程,但不會重新繪製 任何視圖包括該調用者本身。
onLayout()方法(如果該View是ViewGroup對象,需要實現該方法,對每個子視圖進行佈局)
調用onDraw()方法繪製視圖本身 (每個View都需要重載該方法,ViewGroup不需要實現該方法)
drawChild()去重新回調每個子視圖的draw()方法

invalidate和postInvalidate的區別及使用
1、postInvalidate() 方法在非 UI 線程中調用,通知 UI 線程重繪。
2、invalidate()方法在 UI 線程中調用,重繪當前 UI。Invalidate不能直接在線程中調用,因爲他是違背了單線程模型:Android UI操作並不是線程安全的,並且這些操作必須在UI線程中調用。

Activity-Window-View三者的差別
這個問題真的很不好回答。所以這裏先來個算是比較恰當的比喻來形容下它們的關係吧。Activity像一個工匠(控制單元),Window像窗戶(承載模型),View像窗花(顯示視圖)LayoutInflater像剪刀,Xml配置像窗花圖紙。
1:Activity構造的時候會初始化一個Window,準確的說是PhoneWindow。
2:這個PhoneWindow有一個“ViewRoot”,這個“ViewRoot”是一個View或者說ViewGroup,是最初始的根視圖。
3:“ViewRoot”通過addView方法來一個個的添加View。比如TextView,Button等
4:這些View的事件監聽,是由WindowManagerService來接受消息,並且回調Activity函數。比如onClickListener,onKeyDown等。

ActivityThread,AMS,WMS的工作原理
Activity與Window:
Activity只負責生命週期和事件處理
Window只控制視圖
一個Activity包含一個Window,如果Activity沒有Window,那就相當於Service。

AMS與WMS:
AMS統一調度所有應用程序的Activity
WMS控制所有Window的顯示與隱藏以及要顯示的位置。在視圖層次中,Activity在WIndow之上

ActivityThread:是Android應用的主線程(UI線程)

WMS(WindowManagerService):管理的整個系統所有窗口的UI
作用:
爲所有窗口分配Surface:客戶端向WMS添加一個窗口的過程,其實就是WMS爲其分配一塊Suiface的過程,一塊塊Surface在WMS的管理下有序的排布在屏幕上。Window的本質就是Surface。(簡單的說Surface對應了一塊屏幕緩衝區)
管理Surface的顯示順序、尺寸、位置
管理窗口動畫
輸入系統相關:WMS是派發系統按鍵和觸摸消息的最佳人選,當接收到一個觸摸事件,它需要尋找一個最合適的窗口來處理消息,而WMS是窗口的管理者,系統中所有的窗口狀態和信息都在其掌握之中,完成這一工作不在話下。

AMS(ActivityManagerService)
ActivityManager是客戶端用來管理系統中正在運行的所有Activity包括Task、Memory、Service等信息的工具。但是這些這些信息的維護工作卻不是又ActivityManager負責的。在ActivityManager中有大量的get()方法,那麼也就說明了他只是提供信息給AMS,由AMS去完成交互和調度工作。

作用:
統一調度所有應用程序的Activity的生命週期
啓動或殺死應用程序的進程
啓動並調度Service的生命週期
註冊BroadcastReceiver,並接收和分發Broadcast
啓動併發布ContentProvider
調度task
處理應用程序的Crash
查詢系統當前運行狀態

自定義View如何考慮機型適配
佈局類建議:
合理使用warp_content,match_parent.
儘可能的是使用RelativeLayout
引入android的百分比佈局。
針對不同的機型,使用不同的佈局文
件放在對應的目錄下,android會自動匹配。

Icon類建議:
儘量使用svg轉換而成xml。
切圖的時候切大分辨率的圖,應用到佈局當中。在小分辨率的手機上也會有很好的顯示效果。
使用與密度無關的像素單位dp,sp

AsyncTask 如何使用?
Android的AsyncTask比Handler更輕量級一些,適用於簡單的異步處理。
首先明確Android之所以有Handler和AsyncTask,都是爲了不阻塞主線程(UI線程),且UI的更新只能在主線程中完成,因此異步處理是不可避免的。
Android爲了降低這個開發難度,提供了AsyncTask。AsyncTask就是一個封裝過的後臺任務類,顧名思義就是異步任務。

AsyncTask直接繼承於Object類,位置爲android.os.AsyncTask。要使用AsyncTask工作我們要提供三個泛型參數,並重載幾個方法(至少重載一個)。

例:

public class Task extends AsyncTask//Void是三個泛型參數的原始狀態,並且Void也是一個類而不是void

AsyncTask定義了三種泛型類型 Params,Progress和Result。

Params 啓動任務執行的輸入參數,比如HTTP請求的URL。(可傳入多個參數)
Progress 後臺任務執行的百分比。
Result 後臺執行任務最終返回的結果,比如String。
使用過AsyncTask 的同學都知道一個異步加載數據最少要重寫以下這兩個方法:

doInBackground(Params…) 後臺執行,比較耗時的操作都可以放在這裏。注意這裏不能直接操作UI。此方法在後臺線程執行,完成任務的主要工作,通常需要較長的時間。在執行過程中可以調用publicProgress(Progress…)來更新任務的進度。
publicProgress(Progress…)會將Progress...傳給onProgressUpdate(Progress...)作爲ProgressUpdate(Progress...)的接收參數。
onPostExecute(Result) 相當於Handler 處理UI的方式,在這裏面可以使用在doInBackground 得到的結果處理操作UI。 此方法在主線程執行,任務執行的結果作爲此方法的參數返回
有必要的話你還得重寫以下這三個方法,但不是必須的:

onProgressUpdate(Progress…) 可以使用進度條增加用戶體驗度。 此方法在主線程執行,用於顯示任務執行的進度。
onPreExecute() 這裏是最終用戶調用Excute時的接口,當任務執行之前開始調用此方法,可以在這裏顯示進度對話框。
onCancelled() 用戶調用取消時,要做的操作
使用AsyncTask類,以下是幾條必須遵守的準則:

Task的實例必須在UI thread(主線程)中創建;
execute方法必須在UI thread中調用;
不要手動的調用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)這幾個方法;
該task只能被執行一次,否則多次調用時將會出現異常;

SpareArray與HashMap比較,應用場景
SparseArray採用的不是哈希算法,HashMap採用的是哈希算法。
SparseArray採用的是兩個一維數組分別用於存儲鍵和值,HashMap採用的是一維數組+單向鏈表或二叉樹。
SparseArray key只能是int類型,而HashMap的key是Object。
SparseArray key是有序存儲(升序),而HashMap不是。
SparseArray 默認容量是10,而HashMap默認容量是16。
SparseArray 默認每次擴容是2倍於原來的容量,而HashMap默認每次擴容時是原容量*0.75倍
SparseArray value的存儲被不像HashMap一樣需要額外的需要一個實體類(Node)進行包裝
SparseArray查找元素總體而言比HashMap要遜色,因爲SparseArray查找是需要經過二分法的過程,而HashMap不存在衝突的情況其技術處的hash對應的下標直接就可以取到值。
針對上面與HashMap的比較,採用SparseArray還是HashMap,建議根據如下需求選取:

如果對內存要求比較高,而對查詢效率沒什麼大的要求,可以是使用SparseArray
數量在百級別的SparseArray比HashMap有更好的優勢
要求key是int類型的,因爲HashMap會對int自定裝箱變成Integer類型
要求key是有序的且是升序

請介紹下ContentProvider 是如何實現數據共享的?
一個程序可以通過實現一個Content provider的抽象接口將自己的數據完全暴露出去,而且Content providers是以類似數據庫中表的方式將數據暴露。Content providers存儲和檢索數據,通過它可以讓所有的應用程序訪問到,這也是應用程序之間唯一共享數據的方法。
要想使應用程序的數據公開化,可通過2種方法:創建一個屬於你自己的Content provider或者將你的數據添加到一個已經存在的Content provider中,前提是有相同數據類型並且有寫入Content provider的權限。
如何通過一套標準及統一的接口獲取其他應用程序暴露的數據?
Android提供了ContentResolver,外界的程序可以通過ContentResolver接口訪問ContentProvider提供的數據。

AndroidService與Activity之間通信的幾種方式
通過 broadcast:通過廣播發送消息到 activitry
通過 Binder:通過與 activity 進行綁定

IntentService原理及作用是什麼?
IntentService是繼承於Service並處理異步請求的一個類,在IntentService內有一個工作線程來處理耗時操作,啓動IntentService的方式和啓動傳統Service一樣,同時,當任務執行完後,IntentService會自動停止,而不需要我們去手動控制。另外,可以啓動IntentService多次,而每一個耗時操作會以工作隊列的方式在IntentService的onHandleIntent回調方法中執行,並且,每次只會執行一個工作線程,執行完第一個再執行第二個,以此類推。
所有請求都在一個單線程中,不會阻塞應用程序的主線程(UI Thread),同一時間只處理一個請求。

說說Activity、Intent、Service 是什麼關係
一個 Activity 通常是一個單獨的屏幕,每一個 Activity 都被實現爲一個單獨的類,這些類都 是從 Activity 基類中繼承來的, Activity 類會顯示由視圖控件組成的用戶接口,並對視圖控 件的事件做出響應。

Intent 的調用是用來進行架構屏幕之間的切換的。 Intent 是描述應用想要做什麼。 Intent 數 據結構中兩個最重要的部分是動作和動作 對應的數據, 一個動作對應一個動作數據。

Android Service 是運行在後臺的代碼,不能與用戶交互,可以運行在自己的進程,也可以 運行在其他應用程序進程的上下文裏。需要通過某一個 Activity 或者其他 Context 對象來調 用。 Activity 跳轉到 Activity,Activity 啓動 Service,Service 打開 Activity ,Activity 跳轉到 Activity,Activity 啓動 Service,Service 打開 Activity

SP是進程同步的嗎?有什麼方法做到同步?
SharedPreferences不支持進程同步
一個進程的情況,經常採用SharePreference來做,但是SharePreference不支持多進程,它基於單個文件的,默認是沒有考慮同步互斥,而且,APP對SP對象做了緩存,不好互斥同步。

考慮用ContentProvider來實現SharedPreferences的進程同步,ContentProvider基於Binder,不存在進程間互斥問題,對於同步,也做了很好的封裝,不需要開發者額外實現。另外ContentProvider的每次操作都會重新getSP,保證了sp的一致性。

談談多線程在Android中的使用
Handler+Thread
AsyncTask
ThreadPoolExecutor
IntentService

進程和 Application 的生命週期
onCreate():Application創建的時候調用
onConfigurationChanged(Configuration newConfig):當配置信息改變的時候會調用,如屏幕旋轉、語言切換時。
onLowMemory():Android系統整體內存較低時候調用,通常在這裏釋放一些不重要的資源,或者提醒用戶清一下垃圾,來保證內存足夠而讓APP進程不被系統殺掉。它和OnTrimMemory中的TRIM_MEMORY_COMPLETE級別相同。
onTrimMemory(int level):Android 4.0 之後提供的一個API,用於取代onLowMemory()。在系統內存不足的時會被調用,提示開發者清理部分資源來釋放內存,從而避免被 Android 系統殺死。詳見《Android代碼內存優化建議-OnTrimMemory優化》
onTerminate():Application結束的時候會調用,由系統決定調用的時機

封裝View的時候怎麼知道view的大小
這裏考點是自定義的那幾個方法含義,源碼理解。需要自己去看看源碼,然後用自己的話表達出來。

AndroidManifest的作用與理解
AndroidManifest.xml 是每個android程序中必須的文件。它位於整個項目的根目錄,描述了package中暴露的組件(activities, services等等),他們各自的實現類,各種能被處理的數據和啓動位置。 除了能聲明程序中的Activities, ContentProviders, Services, 和Intent Receivers,還能指定permissions和instrumentation(安全控制和測試)。

Handler、Thread和HandlerThread的差別
Handler:在Android中負責發送和處理消息,通過它可以實現其他支線線程與主線程之間的消通訊
Thread:線程,可以看作是進程的一個實體,是CPU調度和分派的基本單位,他是比進程更小的獨立運行的基本單位
HandlerThread:封裝了Handler + ThreadHandlerThread適合在有需要一個工作線程(非UI線程)+任務的等待隊列的形式,優點是不會有堵塞,減少了對性能的消耗,缺點是不能同時進行多個任務的處理,需要等待進行處理。處理效率低,可以當成一個輕量級的線程池來用

爲什麼在主線程可以直接使用 Handler?
因爲主線程已經創建了 Looper 對象並開啓了消息循環

關於Handler,在任何地方new Handler 都是什麼線程下?
不傳遞 Looper 創建 Handler:Handler handler = new Handler();上文就是 Handler 無參創建的源碼,
可以看到是通過 Looper.myLooper() 來獲取 Looper 對象,也就是說對於不傳遞 Looper 對象的情況下,
在哪個線程創建 Handler 默認獲取的就是該線程的 Looper 對象,那麼 Handler 的一系列操作都是在該
線程進行的。

對於傳遞 Looper 對象創建 Handler 的情況下,傳遞的 Looper 是哪個線程的,Handler 綁定的就是該線程。
ThreadLocal原理,實現及如何保證Local屬性?
ThreadLocal:當某些數據是以線程爲作用域並且不同線程具有不同的數據副本的時候,就可以考慮採用ThreadLocal。(Looper、ActivityThread以及AMS中都用到了),如使用ThreadLocal可以解決不同線程不同Looper的需求。

雖然在不同線程中訪問的是同一個ThreadLocal對象,但是它們通過ThreadLocal來獲取到的值卻是不一樣的,這就是ThreadLocal的奇妙之處。ThreadLocal之所以有這麼奇妙的效果,是因爲不同線程訪問同一個ThreadLocal的get方法,ThreadLocal內部會從各自的線程中取出一個數組,然後再從數組中根據當前ThreadLocal的索引去查找出對應的value值,很顯然,不同線程中的數組是不同的,這就是爲什麼通過ThreadLocal可以在不同的線程中維護一套數據的副本並且彼此互不干擾。(從ThreadLocal的set和get方法可以看出,它們所操作的對象都是當前線程的localValues對象的table數組,因此在不同線程中訪問同一個ThreadLocal的set和get方法,它們對ThreadLocal所做的讀寫操作僅限於各自線程的內部,這就是爲什麼ThreadLocal可以在多個線程中互不干擾地存儲和修改數據。)

Handler機制和底層實現

上面一共出現了幾種類,ActivityThread,Handler,MessageQueue,Looper,msg(Message),對這些類作簡要介紹:
ActivityThread:程序的啓動入口,該類就是我們說的主線程,它對Looper進行操作的。
Handler:字面意思是操控者,該類有比較重要的地方,就是通過handler來發送消息(sendMessage)到
MessageQueue和 操作控件的更新(handleMessage)。handler下面持有這MessageQueue和Looper的對象。
MessageQueue:字面意思是消息隊列,就是封裝Message類。對Message進行插入和取出操作。
Message:這個類是封裝消息體並被髮送到MessageQueue中的,給類是通過鏈表實現的,其好處方便MessageQueue的插入和取出操作。還有一些字段是(int what,Object obj,int arg1,int arg2)。what是用戶定義的消息和代碼,以便接收者(handler)知道這個是關於什麼的。obj是用來傳輸任意對象的,arg1和arg2是用來傳遞一些簡單的整數類型的。

先獲取looper,如果沒有就創建

創建過程:

ActivityThread 執行looperMainPrepare(),該方法先實例化MessageQueue對象,然後實例化Looper對象,封裝mQueue和主線程,把自己放入ThreadLocal中

再執行loop()方法,裏面會重複死循環執行讀取MessageQueue。接着ActivityThread 執行Looper對象中的loop()方法)
此時調用sendMessage()方法,往MessageQueue中添加數據,其取出消息隊列中的handler,執行dispatchMessage(),進而執行handleMessage(),Message的數據結構是基於鏈表的

Looper爲什麼要無限循環?
主線程中如果沒有looper進行循環,那麼主線程一運行完畢就會退出。那麼我們還能運行APP嗎,顯然,這是不可能的,Looper主要就是做消息循環,然後由Handler進行消息分發處理,一旦退出消息循環,那麼你的應用也就退出了。

主線程中的Looper.loop()一直無限循環爲什麼不會造成ANR?
主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,並且往管道文件寫數據,主線程即被喚醒,從管道文件讀取數據,主線程被喚醒只是爲了讀取消息,當消息讀取完畢,再次睡眠。因此loop的循環並不會對CPU性能有過多的消耗。

請解釋下在單線程模型中Message、Handler、Message Queue、Looper之間的關係
Android中主線程是不能進行耗時操作的,子線程是不能進行更新UI的。所以就有了handler,它的作用就是實現線程之間的通信。
handler整個流程中,主要有四個對象,handler,Message,MessageQueue,Looper。當應用創建的時候,就會在主線程中創建handler對象,
我們通過要傳送的消息保存到Message中,handler通過調用sendMessage方法將Message發送到MessageQueue中,Looper對象就會不斷的調用loop()方法
不斷的從MessageQueue中取出Message交給handler進行處理。從而實現線程之間的通信。

請描述一下View事件傳遞分發機制
Touch事件分發中只有兩個主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三個相關事件。View包含dispatchTouchEvent、onTouchEvent兩個相關事件。其中ViewGroup又繼承於View。
2.ViewGroup和View組成了一個樹狀結構,根節點爲Activity內部包含的一個ViwGroup。
3.觸摸事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸摸事件中,Down和Up都只有一個,Move有若干個,可以爲0個。
4.當Acitivty接收到Touch事件時,將遍歷子View進行Down事件的分發。ViewGroup的遍歷可以看成是遞歸的。分發的目的是爲了找到真正要處理本次完整觸摸事件的View,這個View會在onTouchuEvent結果返回true。
5.當某個子View返回true時,會中止Down事件的分發,同時在ViewGroup中記錄該子View。接下去的Move和Up事件將由該子View直接進行處理。由於子View是保存在ViewGroup中的,多層ViewGroup的節點結構時,上級ViewGroup保存的會是真實處理事件的View所在的ViewGroup對象:如ViewGroup0-ViewGroup1-TextView的結構中,TextView返回了true,它將被保存在ViewGroup1中,而ViewGroup1也會返回true,被保存在ViewGroup0中。當Move和UP事件來時,會先從ViewGroup0傳遞至ViewGroup1,再由ViewGroup1傳遞至TextView。
6.當ViewGroup中所有子View都不捕獲Down事件時,將觸發ViewGroup自身的onTouch事件。觸發的方式是調用super.dispatchTouchEvent函數,即父類View的dispatchTouchEvent方法。在所有子View都不處理的情況下,觸發Acitivity的onTouchEvent方法。
7.onInterceptTouchEvent有兩個作用:1.攔截Down事件的分發。2.中止Up和Move事件向目標View傳遞,使得目標View所在的ViewGroup捕獲Up和Move事件。

事件分發中的onTouch 和onTouchEvent 有什麼區別,又該如何使用?
OnTouch方法:
onTouch()是OnTouchListener接口的方法,它是獲取某一個控件的觸摸事件,因此使用時。通過getAction()方法可以獲取當前觸摸事件的狀態:如:ACTION_DOWN:表示按下了屏幕的狀態。

OnTouchEvent()方法:
onTouchEvent是手機屏幕事件的處理方法,是獲取的對屏幕的各種操作,比如向左向右滑動,點擊返回按鈕等等。
通過查看安卓源碼中View對dispatchTouchEvent的實現,可以知道onTouchListener(onTouch方法在其中)的接口的執行順序是要先於onTouchEvent的,onTouch方法會先觸發。

如果onTouchListener中的onTouch方法返回true,表示此次事件已經被消費了,那onTouchEvent是接收不到消息的。(內置諸如click事件的實現等等都基於onTouchEvent,這些事件將不會被觸發),如果onTouch方法返回false會接着觸發onTouchEvent。

View刷新機制
通過ViewRootImpl的scheduleTraversals()進行界面的三大流程。
調用到scheduleTraversals()時不會立即執行,而是將該操作保存到待執行隊列中。並給底層的刷新信號註冊監聽。當VSYNC信號到來時,會從待執行隊列中取出對應的scheduleTraversals()操作,並將其加入到主線程的消息隊列中。
主線程從消息隊列中取出並執行三大流程: onMeasure()-onLayout()-onDraw()

View繪製流程
自定義控件:
1、組合控件。這種自定義控件不需要我們自己繪製,而是使用原生控件組合成的新控件。如標題欄。
2、繼承原有的控件。這種自定義控件在原生控件提供的方法外,可以自己添加一些方法。如製作圓角,圓形圖片。
3、完全自定義控件:這個View上所展現的內容全部都是我們自己繪製出來的。比如說製作水波紋進度條。
View的繪製流程:OnMeasure()——>OnLayout()——>OnDraw()
第一步:OnMeasure():測量視圖大小。從頂層父View到子View遞歸調用measure方法,measure方法又回調OnMeasure。
第二步:OnLayout():確定View位置,進行頁面佈局。從頂層父View向子View的遞歸調用view.layout方法的過程,即父View根據上一步measure子View所得到的佈局大小和佈局參數,將子View放在合適的位置上。
第三步:OnDraw():繪製視圖。ViewRoot創建一個Canvas對象,然後調用OnDraw()。六個步驟:①、繪製視圖的背景;②、保存畫布的圖層(Layer);③、繪製View的內容;④、繪製View子視圖,如果沒有就不用;
⑤、還原圖層(Layer);⑥、繪製滾動條。

自定義View如何提供獲取View屬性的接口?
自定義屬性的實現流程: 
1.在values目錄下定義一個attrs.xml :在res/values/attr.xml中定義相關屬性。
2.在對應的類文件裏生成某些組件 :在對應類的構造函數中通過 obtainStyledAttributes()方法獲得自定義屬性的相關值
3.在layout佈局文件裏爲這些屬性賦值:在佈局中添加爲該自定義組件設置一個命名空間,並且相關屬性賦值

AsyncTask是什麼?
AsyncTask使得可以恰當和簡單地使用 UI線程。這個class允許你在後臺做一些事情,然後把進度和結果告訴UI線程,而不需要操作handler和線程。

AsyncTask設計的思想是什麼?
AsyncTask的設計是爲了成爲一個關於Thread和Handler的幫助類,並不是一個通用的線程框架。AsyncTask理想情況下,應該被使用於非常短的操作(最多幾秒)。如果您希望您的線程可以運行很長時間,非常建議您使用java.util.concurrent包裏面的API。例如Executor,ThreadPoolExecutor 和FutureTask

AsyncTask原理及不足
原理:
AsyncTask是Android本身提供的一種輕量級的異步任務類。它可以在線程池中執行後臺任務,然後把執行的進度和最終的結果傳遞給主線程更新UI。實際上,AsyncTask內部是封裝了Thread和Handler。雖然AsyncTask很方便的執行後臺任務,以及在主線程上更新UI,但是,AsyncTask並不合適進行特別耗時的後臺操作,對於特別耗時的任務,個人還是建議使用線程池。
AsyncTask提供有4個核心方法:
1、onPreExecute():該方法在主線程中執行,在執行異步任務之前會被調用,一般用於一些準備工作。
2、doInBackground(String... params):這個方法是在線程池中執行,此方法用於執行異步任務。在這個方法中可以通過publishProgress方法來更新任務的進度,publishProgress方法會調用onProgressUpdate方法,另外,任務的結果返回給onPostExecute方法。
3、onProgressUpdate(Object... values):該方法在主線程中執行,主要用於任務進度更新的時候,該方法會被調用。
4、onPostExecute(Long aLong):在主線程中執行,在異步任務執行完畢之後,該方法會被調用,該方法的參數及爲後臺的返回結果。
除了這幾個方法之外還有一些不太常用的方法,如onCancelled(),在異步任務取消的情況下,該方法會被調用。
源碼可以知道從上面的execute方法內部調用的是executeOnExecutor()方法,即executeOnExecutor(sDefaultExecutor, params);而sDefaultExecutor實際上是一個串行的線程池。而onPreExecute()方法在這裏就會被調用了。接着看這個線程池。AsyncTask的執行是排隊執行的,因爲有關鍵字synchronized,而AsyncTask的Params參數就封裝成爲FutureTask類,FutureTask這個類是一個併發類,在這裏它充當了Runnable的作用。接着FutureTask會交給SerialExecutor的execute方法去處理,而SerialExecutor的executor方法首先就會將FutureTask添加到mTasks隊列中,如果這個時候沒有任務,就會調用scheduleNext()方法,執行下一個任務。如果有任務的話,則執行完畢後最後在調用 scheduleNext();執行下一個任務。直到所有任務被執行完畢。而AsyncTask的構造方法中有一個call()方法,而這個方法由於會被FutureTask的run方法執行。所以最終這個call方法會在線程池中執行。而doInBackground這個方法就是在這裏被調用的。我們好好研究一下這個call()方法。mTaskInvoked.set(true);表示當前任務已經執行過了。接着執行doInBackground方法,最後將結果通過postResult(result);方法進行傳遞。postResult()方法中通過sHandler來發送消息,sHandler的中通過消息的類型來判斷一個MESSAGE_POST_RESULT,這種情況就是調用onPostExecute(result)方法或者是onCancelled(result)。另一種消息類型是MESSAGE_POST_PROGRESS則調用更新進度onProgressUpdate。

不足:AsyncTask的優點在於執行完後臺任務後可以很方便的更新UI,然而使用它存在着諸多的限制。先拋開內存泄漏問題,使用AsyncTask主要存在以下侷限性:
在Android 4.1版本之前,AsyncTask類必須在主線程中加載,這意味着對AsyncTask類的第一次訪問必須發生在主線程中;在Android 4.1以及以上版本則不存在這一限制,因爲ActivityThread(代表了主線程)的main方法中會自動加載AsyncTask 
AsyncTask對象必須在主線程中創建 
AsyncTask對象的execute方法必須在主線程中調用 
一個AsyncTask對象只能調用一次execute方法

如何取消AsyncTask?
1.調用cancel():但是他是在在doInBackground()之後執行

如果調用cancel()方法,它不會立即執行,只有當doInBackground()方法執行完有返回值之後,會在UI主線程調用cancel(),同時也會間接的調用iscancelled(),並且返回true ,這個時候就不會再調onPostExecute(),然後在doInBackground()裏定期檢查iscancelled()方法的返回值,是否被cancel,如果return true,就儘快停止。

2.在耗時操作中設置一些flag:我們可以在這個線程中的耗時操作中設置一些flag,也就是AsyncTask的doInBackground方法中的某些關鍵步驟。
然後在外層需要終止此線程的地方改變這個flag值,線程中的耗時代碼一步步執行,當某一時刻發現flag的值變了,throwException,線程就不會再繼續執行了。爲了保險起見,在外層我們還要捕獲這個異常,進行相應處理。(子線程被髮生異常後會自己死掉而不會引起其他問題,更不會影響到主線程,更何況我們爲了更加安全還捕獲了異常並做處理)

AsyncTask適合做什麼?
必須同時滿足以下條件:
a.執行過程單一,僅輸入一次,輸出一次。
b.花費時間非常短但是仍然需要到後臺去做事情,然後更新UI。例如加載文件,web頁面或者數據庫到UI。
c.執行線程必須是UI線程
d.不需要長期維護狀態。

AsyncTask不適合做什麼?
a.長時間的任務。
b.可重複調用的任務。
c.需要線程執行多個不同任務,任務之間又有關聯。
d.執行線程不是UI線程。
e.任務執行後仍然需要維護一些狀態。
f.後臺服務模塊,需要提供獨立的API.

爲什麼不能在子線程更新UI?
目的在於提高移動端更新UI的效率和和安全性,以此帶來流暢的體驗。原因是:
Android的UI訪問是沒有加鎖的,多個線程可以同時訪問更新操作同一個UI控件。也就是說訪問UI的時候,android系統當中的控件都不是線程安全的,這將導致在多線程模式下,當多個線程共同訪問更新操作同一個UI控件時容易發生不可控的錯誤,而這是致命的。所以Android中規定只能在UI線程中訪問UI,這相當於從另一個角度給Android的UI訪問加上鎖,一個僞鎖。
ANR產生的原因是什麼?
ANR即Application Not Responding,顧名思義就是應用程序無響應。

在Android中,一般情況下,四大組件均是工作在主線程中的,Android中的Activity Manager和Window Manager會隨時監控應用程序的響應情況,如果因爲一些耗時操作(網絡請求或者IO操作)造成主線程阻塞一定時間(例如造成5s內不能響應用戶事件或者BroadcastReceiver的onReceive方法執行時間超過10s),那麼系統就會顯示ANR對話框提示用戶對應的應用處於無響應狀態。

  1. 不要讓主線程乾耗時的工作
  2. 不要讓其他線程阻塞主線程的執行

ANR定位和修正
ANR一般有三種類型
KeyDispatchTimeout(5 seconds) –主要類型
按鍵或觸摸事件在特定時間內無響應
BroadcastTimeout(10 seconds)
BroadcastReceiver在特定時間內無法處理完成
ServiceTimeout(20 seconds) –小概率類型
Service在特定的時間內無法處理完成

1、主線程當中執行IO/網絡操作,容易阻塞。
2、主線程當中執行了耗時的計算。(比如自定義控件中的onDraw()方法)
在onDraw()方法裏面創建對象容易導致內存抖動(繪製動作時會大量不間斷調用,產生大量垃圾對象導致GC很頻繁,就造成了內存抖動),內存抖動就容易造成UI出現掉幀、卡頓等問題。
3、BroadCastReceiver沒有在10秒內完成處理。
4、BroadCastReceiver的onReceived代碼中也要儘量減少耗時的操作,建議使用IntentService處理。
5、Service執行了耗時的操作,因爲Service也是在主線程當中執行的,所以耗時操作應該在Service裏面開啓子線程來做。
6、使用AsyncTask處理耗時的IO等操作。
7、Activity的onCreate和onResume回調中儘量耗時的操作。

oom是什麼?怎麼導致的,怎麼解決?
內存泄漏是什麼?
什麼情況導致內存泄漏?(1)內存溢出(OOM)和內存泄露(對象無法被回收)的區別。 
(2)引起內存泄露的原因
(3) 內存泄露檢測工具 ------>LeakCanary
內存溢出 out of memory:是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory;比如申請了一個integer,但給它存了long才能存下的數,那就是內存溢出。內存溢出通俗的講就是內存不夠用。
內存泄露 memory leak:是指程序在申請內存後,無法釋放已申請的內存空間,一次內存泄露危害可以忽略,但內存泄露堆積後果很嚴重,無論多少內存,遲早會被佔光
內存泄露原因:
一、Handler 引起的內存泄漏。
解決:將Handler聲明爲靜態內部類,就不會持有外部類SecondActivity的引用,其生命週期就和外部類無關,
如果Handler裏面需要context的話,可以通過弱引用方式引用外部類
二、單例模式引起的內存泄漏。
解決:Context是ApplicationContext,由於ApplicationContext的生命週期是和app一致的,不會導致內存泄漏
三、非靜態內部類創建靜態實例引起的內存泄漏。
解決:把內部類修改爲靜態的就可以避免內存泄漏了
四、非靜態匿名內部類引起的內存泄漏。
解決:將匿名內部類設置爲靜態的。
五、註冊/反註冊未成對使用引起的內存泄漏。
註冊廣播接受器、EventBus等,記得解綁。
六、資源對象沒有關閉引起的內存泄漏。
在這些資源不使用的時候,記得調用相應的類似close()、destroy()、recycler()、release()等方法釋放。
七、集合對象沒有及時清理引起的內存泄漏。
通常會把一些對象裝入到集合中,當不使用的時候一定要記得及時清理集合,讓相關對象不再被引用。

如何防止線程的內存泄漏(出現在棧內存)?
使用線程池來進行對線程進行管理。

LruCache默認緩存大小
基本上設置爲手機內存的1/8

如何通過廣播攔截和abort一條短信?
首先添加接收短信的權限

在清單文件中註冊廣播接收器,設置該廣播接收器優先級,儘量設高一點
創建一個BroadcastReceiver來實現廣播的處理,並設置攔截器abortBroadcast();

廣播是否可以請求網絡?
子線程可以,主線程超過10s引起anr

廣播引起anr的時間限制是多少?
在Android中Activity中阻塞5秒、Service 20秒、BroadCastReceiver 10秒,就會造成ANR。
計算一個view的嵌套層級
循環追尋父類父類,看看有多少,便有多少嵌套層級

while (view.getParents() != null) {
count++;
view = view.getParents();
}

Android線程有沒有上限?
Android系統會給每個應用分配一個內存空間(不同的系統分配的內存大小不同),這塊內存空間大小是有限的。
創建線程需要佔用內存空間,
不可能拿有限的內存空間創建無限的線程。
結論:
Android線程是有上限的。如果應用創建線程的數量過多,而沒有及時釋放會導致OOM

線程池有沒有上限?
其實這個沒有上限的,因爲資源都限制在這個進程裏,你開多少線程都最多用這些資源。

ListView重用的是什麼?
1.如果複用的View爲null時,我們要創建一個新的item以及ViewHolder,
然後把item視圖中的控件通過findViewById方法尋找到,
並添加到ViewHolder中,setTag方法,將viewholder傳進去,完成viewholder與item之間的綁定

2.如果複用的View不是爲null,那麼通過getTag()方法直接拿過來用,並且從裏面拿出ViewHolder,因爲每一個複用的ViewHolder肯定是經過處創建並且返回的

Android爲什麼引入Parcelable?
Serializable 會使用反射,序列化和反序列化過程需要大量 I/O 操作, Parcelable 自已實現封送和解封(marshalled &unmarshalled)操作不需要用反射,數據也存放在 Native 內存中,效率要快很多。

Parcelable也是一個接口,只要實現這個接口,一個類的對象就可以實現序列化並可以通過Intent和Binder傳遞(除基本類外的類數據)

Parcelable主要用在內存序列化上,通過Parcelable將對象序列化到存儲設備中或者將對象序列化後通過網絡傳輸也都是可以的

有沒有嘗試簡化Parcelable的使用?
as的插件
在Android studio上下載android parcelable code generator插件
點擊右鍵彈出提示框,選擇Parcelable生成即可:
序列化時選擇需要的屬性:
最後是自動生成的代碼,也表示成功的實現了Parcelable接口

ListView 中圖片錯位的問題是如何產生的?
圖片錯位原理:
如果我們只是簡單顯示list中數據,而沒用convertview的複用機制和異步操作,就不會產生圖片錯位;
重用convertview但沒用異步,也不會有錯位現象。但我們的項目中list一般都會用,不然會很卡。
在上圖中,我們能看到listview中整屏剛好顯示7個item,當向下滑動時,顯示出item8,而item8是重用的item1,如果此時異步網絡請求item8的圖片,比item1的圖片慢,那麼item8就會顯示item1的image。當item8下載完成,此時用戶向上滑顯示item1時,又複用了item8的image,這樣就導致了圖片錯位現象(item1和item8是用的同一塊內存哦)。

解決方法:
對imageview設置tag,並預設一張圖片。
向下滑動後,item8顯示,item1隱藏。但由於item1是第一次進來就顯示,所以一般情況下,item1都會比item8先下載完,但由於此時可見的item8的tag,和隱藏了的item1的url不匹配,所以就算item1的圖片下載完也不會顯示到item8中,因爲tag標識的永遠是可見圖片中的url。

屏幕適配的處理技巧都有哪些?
字體使用sp,使用dp,多使用match_parent,wrap_content,weight
圖片資源,不同圖片的的分辨率,放在相應的文件夾下可使用百分比代替。

怎麼去除重複代碼?
1、爲你的項目定義一個基Activity或Fragment
2、抽取相同部分
3、用include減少局部佈局的重複
4、用ViewStub減少整體的佈局的重複
5、多用引用而不是寫死,對於資源文件的引用,比如文字text、文字大小textSize、文字顏色textColor等統一處理

畫出 Android 的大體架構圖
Android的系統架構採用了分層架構的思想,如圖1所示。從上層到底層共包括四層,分別是應用層、應用框架層、系統庫和Android運行時和Linux內核。

Recycleview和ListView的區別
RecyclerView可以完成ListView,GridView的效果,還可以完成瀑布流的效果。同時還可以設置列表的滾動方向(垂直或者水平);
RecyclerView中view的複用不需要開發者自己寫代碼,系統已經幫封裝完成了。
RecyclerView可以進行局部刷新。
RecyclerView提供了API來實現item的動畫效果。
緩存機制:ListView(兩級緩存)RecyclerView(四級緩存)
在性能上:
如果需要頻繁的刷新數據,需要添加動畫,則RecyclerView有較大的優勢。
如果只是作爲列表展示,則兩者區別並不是很大。

RecyclerView與ListView(緩存原理,區別聯繫,優缺點)
ListView有兩級緩存,在屏幕與非屏幕內。
RecyclerView比ListView多兩級緩存,支持多個離屏ItemView緩存(匹配pos獲取目標位置的緩存,如果匹配則無需再次bindView),支持開發者自定義緩存處理邏輯,支持所有RecyclerView共用同一個RecyclerViewPool(緩存池)。
緩存不同:
ListView緩存View。
RecyclerView緩存RecyclerView.ViewHolder,抽象可理解爲:
View + ViewHolder(避免每次createView時調用findViewById) + flag(標識狀態);

優點
RecylerView提供了局部刷新的接口,通過局部刷新,就能避免調用許多無用的bindView。
RecyclerView的擴展性更強大(LayoutManager、ItemDecoration等)。

Android系統爲什麼會設計ContentProvider?
ContentProvider應用程序間非常通用的共享數據的一種方式,也是Android官方推薦的方式。Android中許多系統應用都使用該方式實現數據共享,比如通訊錄、短信等。

設計用意在於:

封裝。對數據進行封裝,提供統一的接口,使用者完全不必關心這些數據是在DB,XML、Preferences或者網絡請求來的。當項目需求要改變數據來源時,使用我們的地方完全不需要修改。
提供一種跨進程數據共享的方式。
就是數據更新通知機制了。因爲數據是在多個應用程序中共享的,當其中一個應用程序改變了這些共享數據的時候,它有責任通知其它應用程序,讓它們知道共享數據被修改了,這樣它們就可以作相應的處理。

下拉狀態欄是不是影響activity的生命週期
不會影響

requestLayout,invalidate,postInvalidate區別與聯繫
相同點:三個方法都有刷新界面的效果。
不同點:invalidate和postInvalidate只會調用onDraw()方法;requestLayout則會重新調用onMeasure、onLayout、onDraw。
調用了invalidate方法後,會爲該View添加一個標記位,同時不斷向父容器請求刷新,父容器通過計算得出自身需要重繪的區域,直到傳遞到ViewRootImpl中,最終觸發performTraversals方法,進行開始View樹重繪流程(只繪製需要重繪的視圖)。
調用requestLayout方法,會標記當前View及父容器,同時逐層向上提交,直到ViewRootImpl處理該事件,ViewRootImpl會調用三大流程,從measure開始,對於每一個含有標記位的view及其子View都會進行測量onMeasure、佈局onLayout、繪製onDraw。

Bitmap 使用時候注意什麼?
注意oom
1,要選擇合適的圖片規格(bitmap類型),即:
ALPHA_8 每個像素佔用1byte內存
ARGB_4444 每個像素佔用2byte內存
ARGB_8888 每個像素佔用4byte內存 不設置的話默認這個。
RGB_565 每個像素佔用2byte內存

2,降低採樣率。BitmapFactory.Options 參數inSampleSize的使用,先把options.inJustDecodeBounds設爲true,只是去讀取圖片的大小,在拿到圖片的大小之後和要顯示的大小做比較通過calculateInSampleSize()函數計算inSampleSize的具體值,得到值之後。options.inJustDecodeBounds設爲false讀圖片資源。
3,複用內存。即,通過軟引用(內存不夠的時候纔會回收掉),複用內存塊,不需要在重新給這個bitmap申請一塊新的內存,避免了一次內存的分配和回收,從而改善了運行效率。
4,及時回收。即,recycle。
5,壓縮圖片。compress。
6,儘量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource來設置一張大圖,因爲這些函數在完成decode後,最終都是通過java層的createBitmap來完成的,需要消耗更多內存,可以通過BitmapFactory.decodeStream方法,創建出一個bitmap,再將其設爲ImageView的 source。

Android中開啓攝像頭的主要步驟
獲得攝像頭管理器CameraManager mCameraManager,mCameraManager.openCamera()來打開攝像頭
指定要打開的攝像頭,並創建openCamera()所需要的CameraDevice.StateCallback stateCallback
在CameraDevice.StateCallback stateCallback中調用takePreview(),這個方法中,使用CaptureRequest.Builder創建預覽需要的CameraRequest,並初始化了CameraCaptureSession,最後調用了setRepeatingRequest(previewRequest, null, childHandler)進行了預覽
點擊屏幕,調用takePicture(),這個方法內,最終調用了capture(mCaptureRequest, null, childHandler)
在new ImageReader.OnImageAvailableListener(){}回調方法中,將拍照拿到的圖片進行展示

數據庫的優化
1、索引
簡單的說,索引就像書本的目錄,目錄可以快速找到所在頁數,數據庫中索引可以幫助快速找到數據,而不用全表掃描,合適的索引可以大大提高數據庫查詢的效率。
(1). 優點
大大加快了數據庫檢索的速度,包括對單表查詢、連表查詢、分組查詢、排序查詢。經常是一到兩個數量級的性能提升,且隨着數據數量級增長。

(2). 缺點
索引的創建和維護存在消耗,索引會佔用物理空間,且隨着數據量的增加而增加。
在對數據庫進行增刪改時需要維護索引,所以會對增刪改的性能存在影響。

2、使用事務
使用事務的兩大好處是原子提交和更優性能。
(1) 原子提交
原則提交意味着同一事務內的所有修改要麼都完成要麼都不做,如果某個修改失敗,會自動回滾使得所有修改不生效。

(2) 更優性能
Sqlite默認會爲每個插入、更新操作創建一個事務,並且在每次插入、更新後立即提交。
這樣如果連續插入100次數據實際是創建事務->執行語句->提交這個過程被重複執行了100次。如果我們顯示的創建事務->執行100條語句->提交會使得這個創建事務和提交這個過程只做一次,通過這種一次性事務可以使得性能大幅提升。尤其當數據庫位於sd卡時,時間上能節省兩個數量級左右。

熱修復原理
我們知道Java虛擬機 —— JVM 是加載類的class文件的,而Android虛擬機——Dalvik/ART VM 是加載類的dex文件,
而他們加載類的時候都需要ClassLoader,ClassLoader有一個子類BaseDexClassLoader,而BaseDexClassLoader下有一個
數組——DexPathList,是用來存放dex文件,當BaseDexClassLoader通過調用findClass方法時,實際上就是遍歷數組,
找到相應的dex文件,找到,則直接將它return。而熱修復的解決方法就是將新的dex添加到該集合中,並且是在舊的dex的前面,
所以就會優先被取出來並且return返回。

插件化原理分析
插件化要解決的三個核心問題:類加載、資源加載、組件生命週期管理。
類加載:Android中常用的兩種類加載器:PathClassLoader和DexClassLoader,它們都繼承於BaseDexClassLoader。
DexClassLoader的構造函數比PathClassLoader多了一個,optimizedDirectory參數,這個是用來指定dex的優化產物odex的路徑,在源碼註釋中,指出這個參數從API 26後就棄用了。
PathClassLoader主要用來加載系統類和應用程序的類,在ART虛擬機上可以加載未安裝的apk的dex,在Dalvik則不行。
DexClassLoader用來加載未安裝apk的dex。
資源加載:Android系統通過Resource對象加載資源,因此只需要添加資源(即apk文件)所在路徑到AssetManager中,即可實現對插件資源的訪問。由於AssetManager的構造方法時hide的,需要通過反射區創建。
組件生命週期管理:對於Android來說,並不是說類加載進來就可以使用了,很多組件都是有“生命”的;因此對於這些有血有肉的類,必須給他們注入活力,也就是所謂的組件生命週期管理。
在解決插件中組件的生命週期,通常的做法是通過Hook相應的系統對象,實現欺上瞞下,後面將通過Activity的插件化來進行講解。

模塊化實現(好處,原因)
那麼什麼是模塊化呢?《 Java 應用架構設計:模塊化模式與 OSGi 》一書中對它的定義是:模塊化是一種處理複雜系統分解爲更好的可管理模塊的方式。

爲什麼模塊間解耦,複用?
原因:對業務進行模塊化拆分後,爲了使各業務模塊間解耦,因此各個都是獨立的模塊,它們之間是沒有依賴關係。每個模塊負責的功能不同,業務邏輯不同,模塊間業務解耦。模塊功能比較單一,可在多個項目中使用。
爲什麼可單獨編譯某個模塊,提升開發效率?
原因:每個模塊實際上也是一個完整的項目,可以進行單獨編譯,調試

爲什麼可以多團隊並行開發,測試?
原因:每個團隊負責不同的模塊,提升開發,測試效率

組件化與模塊化
組件化是指以重用化爲目的,將一個系統拆分爲一個個單獨的組件避免重複造輪子,節省開發維護成本;
降低項目複雜性,提升開發效率;多個團隊公用同一個組件,在一定層度上確保了技術方案的統一性。
模塊化業務分層:由下到上

基礎組件層:
底層使用的庫和封裝的一些工具庫(libs),比如okhttp,rxjava,rxandroid,glide等
業務組件層:
與業務相關,封裝第三方sdk,比如封裝後的支付,即時通行等
業務模塊層:
按照業務劃分模塊,比如說IM模塊,資訊模塊等

描述清點擊 Android Studio 的 build 按鈕後發生了什麼

  1. 通過aapt打包res資源文件,生成R.java、resources.arsc和res文件
  2. 處理.aidl文件,生成對應的Java接口文件
  3. 通過Java Compiler編譯R.java、Java接口文件、Java源文件,生成.class文件
  4. 通過dex命令,將.class文件和第三方庫中的.class文件處理生成classes.dex
  5. 通過apkbuilder工具,將aapt生成的resources.arsc和res文件、assets文件和classes.dex一起打包生成apk
  6. 通過Jarsigner工具,對上面的apk進行debug或release簽名
  7. 通過zipalign工具,將簽名後的apk進行對齊處理。 
    這樣就得到了一個可以安裝運行的Android程序。

手寫出單例模式
單例模式:分爲惡漢式和懶漢式
惡漢式:(效率高)
public class Singleton { 
private static Singleton instance = new Singleton();

public static Singleton getInstance() { 
return instance ; 

}

懶漢式:(線程安全)
public class Singleton02 { 
private static Singleton02 instance;

public static Singleton02 getInstance() { 
if (instance == null) { 
synchronized (Singleton02.class){ 
if (instance == null) { 
instance = new Singleton02(); 



return instance; 

}

MVC MVP MVVM原理和區別
MVP模式,對應着Model—業務邏輯和實體模型,view--對應着activity,負責View的繪製以及與用戶交互,Presenter--負責View和Model之間的交互,MVP模式是在MVC模式的基礎上,將Model與View徹底分離使得項目的耦合性更低,在Mvc中項目中的activity對應着mvc中的C--Controllor,而項目中的邏輯處理都是在這個C中處理,同時View與Model之間的交互,也是也就是說,mvc中所有的邏輯交互和用戶交互,都是放在Controllor中,也就是activity中。View和model是可以直接通信的。
MVP模式:則是分離的更加徹底,分工更加明確Model—業務邏輯和實體模型,view--負責與用戶交互,Presenter 負責完成View於Model間的交互,MVP和MVC最大的區別是MVC中是允許Model和View進行交互的,而MVP中很明顯,Model與View之間的交互由Presenter完成。還有一點就是Presenter與View之間的交互是通過接口的
MVVM 模式將 Presenter 改名爲 ViewModel,基本上與 MVP 模式完全一致。可以說是MVP的升級版
唯一的區別是,它採用雙向綁定(data-binding):View 的變動,自動反映在 ViewModel,反之亦然。

你所知道的設計模式有哪些?(說自己熟悉的)
單例模式,觀察者模式,工廠模式,builder模式,生產者/消費者模式,適配器模式,裝飾者模式。。。。。(23種,隨你挑)

用到的一些開源框架,介紹一個看過源碼的,內部實現過程。
這個自己準備幾個網絡請求的,圖片加載的,異步任務的等等(最好手寫出流程圖)

談談對RxJava的理解
RxJava:它是一個響應式的編程,區別於我們常用的應試編程,是一種觀察者的設計模式,封裝後是一個實現異步的操作庫
響應式編程可以將數據和展示分層,很好的解耦,例如我們最常見的Buttom事件,設置監聽事件,觸發後回調
RxJava就是基於這種響應式的方式設計,在看看他是實現方式,有的人一開始會覺得它的代碼好多,剛開始的時候我要註冊,還要回調,主要你還是沒有了解它的操作符的強大,操作符也是他的核心,你想用到的方法在裏面都能找到,特別是當代碼量多了之後,你會發現RxJava的代碼看起來簡潔,便於後期的維護
再說說它的操作符,常用的創建,交換,過濾這些就不說了,主要想說說它的錯誤操作符,它極大的簡化了錯誤處理,便於我們後期維護排查
最後說說它的調度器,RxJava本身就是一個異步操作庫,當我做一些耗時的操作時,往往會編寫很多多線程,都知道多線程的編寫和維護都是很煩的,RxJava很好的解決了這個鬼問題
好處:
Rx使代碼簡化
函數式風格:對可觀察數據流使用無副作用的輸入輸出函數,避免了程序裏錯綜複雜的狀態
簡化代碼:Rx的操作符通通常可以將複雜的難題簡化爲很少的幾行代碼
異步錯誤處理:傳統的try/catch沒辦法處理異步計算,Rx提供了合適的錯誤處理機制
輕鬆使用併發:Rx的Observables和Schedulers讓開發者可以擺脫底層的線程同步和各種併發問題

說說EventBus原理
EvenetBus是一種發佈-訂閱事件總線.代碼簡潔,開銷小,並很好的實現了發送者和接收者的解耦.(是一種觀察者模式)
使用:compile ‘org.greenrobot:eventbus:3.1.1'
傳遞數據:
public class MessageEvent {
private String message;
public MessageEvent(String message){
this.message = message;
}
public String getMessage(){
return message;
}
}

//註冊成爲訂閱者
EventBus.getDefault().register(this);

//解除註冊
EventBus.getDefault().unregister(this);

在發送消息的地方 
EventBus.getDefault().post(new MessageEvent("從fragment將數據傳遞到activity22222222"));
(三)四個訂閱方法

onEvent:

如果使用onEvent作爲訂閱函數,那麼該事件在哪個線程發佈出來的,onEvent就會在這個線程中運行,也就是說發佈事件和接收事件線程在同一個線程。使用這個方法時,在onEvent方法中不能執行耗時操作,如果執行耗時操作容易導致事件分發延遲。

onEventMainThread:

如果使用onEventMainThread作爲訂閱函數,那麼不論事件是在哪個線程中發佈出來的,onEventMainThread都會在UI線程中執行,接收事件就會在UI線程中運行,這個在Android中是非常有用的,因爲在Android中只能在UI線程中跟新UI,所以在onEvnetMainThread方法中是不能執行耗時操作的。

onEventBackground:

如果使用onEventBackgrond作爲訂閱函數,那麼如果事件是在UI線程中發佈出來的,那麼onEventBackground就會在子線程中運行,如果事件本來就是子線程中發佈出來的,那麼onEventBackground函數直接在該子線程中執行。

onEventAsync:

使用這個函數作爲訂閱函數,那麼無論事件在哪個線程發佈,都會創建新的子線程在執行onEventAsync。

3.0以前訂閱者的訂閱方法爲onEvent()、onEventMainThread()、onEventBackgroundThread()和onEventAsync()。在Event Bus3.0之後統一採用註解@Subscribe的形式
@Subscribe(threadMode = ThreadMode.MAIN)
public void eventBusMain(String str){
Log.i("TAG", "MAIN:"+str+" Thread=“+Thread.currentThread().getId());

調用getDefault(),裏面採用單利雙重鎖模式創建Eventbus對象
static volatile EventBus defaultInstance;
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;

2,構造方法
2.1,粘性事件,保存到ConCurrenHashMap集合,(在構造方法中實現),
HashMap效率高,但線程不安全,在多線程的情況下,儘量用ConcurrentHashMap,避免多線程併發異常

EventBus(EventBusBuilder builder) {
logger = builder.getLogger();
subscriptionsByEventType = new HashMap<>();
typesBySubscriber = new HashMap<>();
stickyEvents = new ConcurrentHashMap<>(); //線程安全,
mainThreadSupport = builder.getMainThreadSupport();
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
backgroundPoster = new BackgroundPoster(this);
asyncPoster = new AsyncPoster(this);
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
logSubscriberExceptions = builder.logSubscriberExceptions;
logNoSubscriberMessages = builder.logNoSubscriberMessages;
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
throwSubscriberException = builder.throwSubscriberException;
eventInheritance = builder.eventInheritance;
executorService = builder.executorService;
}

註冊register()方法主要做了3件事:
3.1,從緩存中獲取訂閱方法列表,

3.2,如果緩存中不存在則通過反射獲取到訂閱者所有的函數,

3.3,遍歷再通過權限修飾符.參數長度(只允許一個參數).註解(@Subscribe)來判斷是否是具備成爲訂閱函數的前提

3.4,具備則構建一個SubscriberMethod(訂閱方法,其相當於一個數據實體類,包含方法,threadmode,參數類型,優先級,是否粘性事件這些參數),循環結束訂閱函數列表構建完成添加進入緩存

public void register(Object subscriber) {
Class subscriberClass = subscriber.getClass();
//找到訂閱者的方法.找出傳進來的訂閱者的所有訂閱方法,然後遍歷訂閱者的方法.
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
//訂閱者的註冊
subscribe(subscriber, subscriberMethod);
}
}
}

private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// 通過反射來獲取訂閱者中所有的方法
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
//
}

事件發送post()
3.1,post調用後先使用ThreadLocal來存儲事件,他可以隔離多個線程對數據的訪問衝突
3.2,根據事件類型(也就是Post參數的類型)爲key從subscriptionsByEventType獲取訂閱方法和訂閱者,這個容器不瞭解的可以看下注冊的總結
此處我們已經具備了訂閱者.訂閱方法.待發送事件.post線程.threadmode等信息

public void post(Object event) {
//PostingThreadState 保存着事件隊列和線程狀態信息
PostingThreadState postingState = currentPostingThreadState.get();
//獲取事假隊列,並將當期事件插入事件隊列
List eventQueue = postingState.eventQueue;
eventQueue.add(event);

 


 
  • if (!postingState.isPosting) {
  • postingState.isMainThread = isMainThread();

  • postingState.isPosting = true;

  • if (postingState.canceled) {

  • throw new EventBusException("Internal error. Abort state was not reset");

  • }

  • try {

  • //處理隊列中的所有事件,

  • //將所有的事情交給postSingleEvent處理,並移除該事件

  • while (!eventQueue.isEmpty()) {

  • postSingleEvent(eventQueue.remove(0), postingState);

  • }

  • } finally {

  • postingState.isPosting = false;

  • postingState.isMainThread = false;

  • }

  • }

}

取消事件訂閱
public synchronized void unregister(Object subscriber) {
//typesBySubscriber是一個map集合,
//通過subscriber找到 subscribedTypes (事件類型集合),
List> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
//將subscriber對應的eventType從typesBySubscriber移除
typesBySubscriber.remove(subscriber);

從0設計一款App整體架構,如何去做?
無非就是技術選擇(使用哪些第三方框架),使用那種設計模式(MVP,MVC,MVVM爲什麼選這個?),包的結構,分類等等

AIDL理解(此處延伸:簡述Binder)
AIDL: 每一個進程都有自己的Dalvik VM實例,都有自己的一塊獨立的內存,都在自己的內存上存儲自己的數據,執行着自己的操作,都在自己的那片狹小的空間裏過完自己的一生。而aidl就類似與兩個進程之間的橋樑,使得兩個進程之間可以進行數據的傳輸,跨進程通信有多種選擇,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 佔用的系統資源比較多,如果是頻繁的跨進程通信的話顯然是不可取的;Messenger 進行跨進程通信時請求隊列是同步進行的,無法併發執行。

Binde機制簡單理解:
在Android系統的Binder機制中,是有Client,Service,ServiceManager,Binder驅動程序組成的,其中Client,service,Service Manager運行在用戶空間,Binder驅動程序是運行在內核空間的。而Binder就是把這4種組件粘合在一塊的粘合劑,其中核心的組件就是Binder驅動程序,Service Manager提供輔助管理的功能,而Client和Service正是在Binder驅動程序和Service Manager提供的基礎設施上實現C/S 之間的通信。其中Binder驅動程序提供設備文件/dev/binder與用戶控件進行交互,
Client、Service,Service Manager通過open和ioctl文件操作相應的方法與Binder驅動程序進行通信。而Client和Service之間的進程間通信是通過Binder驅動程序間接實現的。而Binder Manager是一個守護進程,用來管理Service,並向Client提供查詢Service接口的能力。

app優化
app優化:(工具:Hierarchy Viewer 分析佈局 工具:TraceView 測試分析耗時的)
App啓動優化,佈局優化,響應優化,內存優化,電池使用優化,網絡優化,
App啓動優化(針對冷啓動)
App啓動的方式有三種:
冷啓動:App沒有啓動過或App進程被killed, 系統中不存在該App進程, 此時啓動App即爲冷啓動。
熱啓動:熱啓動意味着你的App進程只是處於後臺, 系統只是將其從後臺帶到前臺, 展示給用戶。
介於冷啓動和熱啓動之間, 一般來說在以下兩種情況下發生:
(1)用戶back退出了App, 然後又啓動. App進程可能還在運行, 但是activity需要重建。
(2)用戶退出App後, 系統可能由於內存原因將App殺死, 進程和activity都需要重啓, 但是可以在onCreate中將被動殺死鎖保存的狀態(saved instance state)恢復。
優化:
Application的onCreate(特別是第三方SDK初始化),首屏Activity的渲染都不要進行耗時操作,如果有,就可以放到子線程或者IntentService中
佈局優化
儘量不要過於複雜的嵌套。可以使用,,
響應優化
Android系統每隔16ms會發出VSYNC信號重繪我們的界面(Activity)。
頁面卡頓的原因:
(1)過於複雜的佈局.(使用merge,viewstub,include 進行處理)
(2)UI線程的複雜運算
(3)頻繁的GC,導致頻繁GC有兩個原因:1、內存抖動, 即大量的對象被創建又在短時間內馬上被釋放.2、瞬間產生大量的對象會嚴重佔用內存區域。
內存優化:參考內存泄露和內存溢出部分
電池使用優化(使用工具:Batterystats & bugreport)
(1)優化網絡請求
(2)定位中使用GPS, 請記得及時關閉
網絡優化(網絡連接對用戶的影響:流量,電量,用戶等待)可在Android studio下方logcat旁邊那個工具Network Monitor檢測
API設計:App與Server之間的API設計要考慮網絡請求的頻次, 資源的狀態等. 以便App可以以較少的請求來完成業務需求和界面的展示.
Gzip壓縮:使用Gzip來壓縮request和response, 減少傳輸數據量, 從而減少流量消耗.
圖片的Size:可以在獲取圖片時告知服務器需要的圖片的寬高, 以便服務器給出合適的圖片, 避免浪費.
網絡緩存:適當的緩存, 既可以讓我們的應用看起來更快, 也能避免一些不必要的流量消耗.
24、圖片優化
(1)對圖片本身進行操作。儘量不要使用setImageBitmap、setImageResource、BitmapFactory.decodeResource來設置一張大圖,因爲這些方法在完成decode後,
最終都是通過java層的createBitmap來完成的,需要消耗更多內存.
(2)圖片進行縮放的比例,SDK中建議其值是2的指數值,值越大會導致圖片不清晰。
(3)不用的圖片記得調用圖片的recycle()方法

view的事件分發和view的工作原理
Android自定義view,我們都知道實現有三部曲,onMeasure(),onLayout(),onDraw()。View的繪製流程是從viewRoot的perfromTraversal方法開始的。它經過measure,layout,draw方法才能夠將view繪製出來。其中measure是測量寬高的,layout是確定view在父容器上的擺佈位置的,draw是將view繪製到屏幕上的。
Measure:
view的測量是需要MeasureSpc(測量規格),它代表一個32位int值,高2位代表SpecMode(測量模式),低(30)位的代表SpecSize(某種測量模式下的規格大小)。而一組SpecMode和SpeSize可以打包爲一個MeasureSpec,反之,MeasureSpec可以解包得到SpecMode和SpeSize的值。SpecMode有三類:
unSpecified:父容器不對view有任何限制,要多大有多大。一般系統用這個多。
Exactly:父容器已經檢測出view所需要的精確大小,這個時候,view的大小就是SpecSize所指定的值,它對應者layout佈局中的math_parent或者是具體的數值
At_most:父容器指定了一個可用大小的SpecSize,view的大小不能夠大於這個值,它對應這佈局中的wrao_content.
對於普通的view,它的MeasureSpec是由父容器的MeasureSpec和自身的layoutParam共同決定的,一旦MeasureSpec確定後,onMeasure就可以確定view的寬高了。
View的measure過程:
onMeasure方法中有個setMeasureDimenSion方法來設置view的寬高測量值,而setMeasureDimenSion有個getDefaultSize()方法作爲參數。一般情況下,我們只需要關注at_most和exactly兩種情況,getDefaultSize的返回值就是measureSpec中的SpecSize,而這個值基本就是view測量後的大小。而UnSpecified這種情況,一般是系統內部的測量過程,它是需要考慮view的背景這些因素的。
前面說的是view的測量過程,而viewGroup的measure過程:
對於viewGroup來說,除了完成自己的measure過程以外,還要遍歷去調用子類的measure方法,各個子元素在遞歸執行這個過程,viewGroup是一個抽象的類,沒有提供有onMeasure方法,但是提供了一個measureChildren的方法。measureChild方法的思想就是取出子元素的layoutParams,然後通過getChildMeasureSpec來常見子元素的MeasureSpec,然後子元素在電泳measure方法進行測量。由於viewGroup子類有不同的佈局方式,導致他們的測量細節不一樣,所以viewGroup不能象view一樣調用onMeasure方法進行測量。
注意:在activity的生命週期中是沒有辦法正確的獲取view的寬高的,原因就是view沒有測量完。
在onWindowFocuschanged方法中獲取 ----改方法含義是view已經初始化完畢
View.post()方法,將潤那邊了投遞到消息隊列的尾部。
使用viewTreeObserver的回調來完成。
通過view.measure方式手動測量。
onLayout
普通的view的話,可以通過setFrame方法來的到view四個頂點的位置,也就確定了view在父容器的位置,接着就調用onLayout方法,該方法是父容器確定子元素的位置。
onDraw
該方法就是將view繪製到屏幕上。分以下幾步
繪製背景,
繪製自己,
繪製child,
繪製裝飾。

Android中性能優化
由於手機硬件的限制,內存和CPU都無法像pc一樣具有超大的內存,Android手機上,過多的使用內存,會容易導致oom,過多的使用CPU資源,會導致手機卡頓,甚至導致anr。我主要是從一下幾部分進行優化:
佈局優化,繪製優化,內存泄漏優化,響應速度優化,listview優化,bitmap優化,線程優化
佈局優化:工具 hierarchyviewer,解決方式:
1、刪除無用的空間和層級。
2、選擇性能較低的viewgroup,如Relativelayout,如果可以選擇Relativelayout也可以使用LinearLayout,就優先使用LinearLayout,因爲相對來說Relativelayout功能較爲複雜,會佔用更多的CPU資源。
3、使用標籤重用佈局,減少層級,進行預加載,使用的時候才加載。
繪製優化
繪製優化指view在ondraw方法中避免大量的耗時操作,由於ondraw方法可能會被頻繁的調用。
1、ondraw方法中不要創建新的局部變量,ondraw方法被頻繁的調用,很容易引起GC。
2、ondraw方法不要做耗時操作。
內存優化:參考內存泄漏。
響應優化
主線程不能做耗時操作,觸摸事件5s,廣播10s,service20s。
listview優化:
1、getview方法中避免耗時操作。
2、view的複用和viewholder的使用。
3、滑動不適合開啓異步加載。
4、分頁處理數據。
5、圖片使用三級緩存。
Bitmap優化:
1、等比例壓縮圖片。
2、不用的圖片,及時recycler掉
線程優化
線程優化的思想是使用線程池來管理和複用線程,避免程序中有大量的Thread,同時可以控制線程的併發數,避免相互搶佔資源而導致線程阻塞。
其他優化
1、少用枚舉,枚舉佔用空間大。
2、使用Android特有的數據結構,如SparseArray來代替hashMap。
3、適當的使用軟引用和弱引用。

用IDE如何分析內存泄漏?(LeakCanary的原理)
不停的gc某一段覺得有問題的代碼,然後打開Android Monitor,查看內存變化。

LeakCanary原理:
1、通過application.registeractivitylifecydecallback()方法註冊activity的生命週期的監聽,當頁面銷燬的時候獲取該對象。
2、通過weakReference+ReferenceQueue來判斷對象是否被系統GC回收,weakReference創建時,可以傳入一個ReferenceQueue對象,當weakReference引用的對象生命週期結束,一旦被檢查到GC將會把該對象添加到ReferenceQueue中,等待處理,當GC過後,對象一直沒有被加入ReferenceQueue中,就有可能內存泄露,再次GC確認

Java多線程引發的性能問題,怎麼解決?
使用線程池來進行管理

啓動頁白屏及黑屏解決?
把啓動圖bg_splash設置爲窗體背景,避免剛剛啓動App的時候出現,黑/白屏
@drawable/bg_splash

App啓動崩潰異常捕捉
自定義一個應用異常捕獲類AppUncaughtExceptionHandler,它必須得實現Thread.UncaughtExceptionHandler接口,另外還需要重寫uncaughtException方法,去按我們自己的方式來處理異常

Bitmap如何處理大圖,如一張30M的大圖,如何預防OOM
java中的四種引用的區別以及使用場景
(1)強引用(StrongReference)
強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。
(2)軟引用(SoftReference)
如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。

軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
(3)弱引用(WeakReference)
弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。

(4)虛引用(PhantomReference)
"虛引用"顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之 關聯的引用隊列中。

強引用置爲null,會不會被回收?
會,GC執行時,就被回收掉,前提是沒有被引用的對象

請介紹一下NDK
定義:Native Development Kit,是 Android的一個工具開發包 。NDK是屬於 Android 的,與Java並無直接關係。
作用:快速開發C、 C++的動態庫,並自動將so和應用一起打包成 APK (即可通過 NDK在 Android中 使用 JNI與本地代碼(如C、C++)交互)。
應用場景:在Android的場景下 使用JNI (即 Android開發的功能需要本地代碼(C/C++)實現)。

NDK特點
1.性能方面:
(1)運行效率高:在開發要求高性能的需求中,採用C/C++更加有效率(如使用本地代碼(C/C++)執行算法,能大大提高算大的執行效率)
(2)代碼安全性高:java是半解釋型語言,容易被反編譯後得到源代碼,而本地有些代碼類型(如C/C++)則不會,能提高系統的安全性
2.功能方面:
功能擴展性好:可方便地使用其他開發語言的開源庫,除了java的開源庫,還可以使用開發語言(C/C++)的開源庫
3.使用方面:
易於代碼複用和移植:用本地代碼(如C/C++)開發的代碼不僅可在Android中使用,還可嵌入其他類型平臺上使用

jni用過嗎?
定義:Java Native Interface,即 Java本地接口
作用: 使得Java 與 本地其他類型語言(如C、C++)交互(即在 Java代碼 裏調用 C、C++等語言的代碼 或 C、C++代碼調用 Java 代碼)
特別注意:
1.JNI是 Java 調用 Native 語言的一種特性
2.JNI 是屬於 Java 的,與 Android 無直接關係

如何在jni中註冊native函數,有幾種註冊方式?
靜態方法:
這種方法我們比較常見,但比較麻煩,大致流程如下:

先創建Java類,聲明Native方法,編譯成.class文件。
使用Javah命令生成C/C++的頭文件,例如:javah -jni com.devilwwj.jnidemo.TestJNI,則會生成一個以.h爲後綴的文件com_devilwwj_jnidemo_TestJNI.h。
創建.h對應的源文件,然後實現對應的native方法

這種方法的弊端:
需要編譯所有聲明瞭native函數的Java類,每個所生成的class文件都得用javah命令生成一個頭文件。
javah生成的JNI層函數名特別長,書寫起來很不方便
初次調用native函數時要根據函數名字搜索對應的JNI層函數來建立關聯關係,這樣會影響運行效率

動態註冊
我們知道Java Native函數和JNI函數時一一對應的,JNI中就有一個叫JNINativeMethod的結構體來保存這個對應關係,實現動態註冊方就需要用到這個結構體。

區別:
靜態註冊
優點: 理解和使用方式簡單, 屬於傻瓜式操作, 使用相關工具按流程操作就行, 出錯率低
缺點: 當需要更改類名,包名或者方法時, 需要按照之前方法重新生成頭文件, 靈活性不高
動態註冊
優點: 靈活性高, 更改類名,包名或方法時, 只需對更改模塊進行少量修改, 效率高
缺點: 對新手來說稍微有點難理解, 同時會由於搞錯簽名, 方法, 導致註冊失敗

Java如何調用c、c++語言?
java調用c

Android通過JNI來實現Java層調用C層代碼,我們必須創建c代碼,然後編譯so庫,編寫JNI中的代碼,最後Java層通過System.loadLibrary()方法加載so動態庫,即可實現。

編寫一個具有so,jni , java整體模塊,一般可以這樣來做:
(1)編寫Java層代碼,裏面主要實現兩個步驟,一個是定義native方法,另一個是調用System.loadLibrary()方法加載C層要寫的動態庫;
(2)由Java代碼使用javah命令生成JNI中所需的.h頭文件; (3)實現以上所生成的.h頭文件;
(4)編寫Android.mk文件,生存*.so庫
(5)編譯整個工程生成apk。

jni如何調用java層代碼?
C代碼回調Java方法步驟:
①獲取字節碼對象(jclass (FindClass)(JNIEnv, const char*);)
②通過字節碼對象找到方法對象(jmethodID (GetMethodID)(JNIEnv, jclass, const char, const char);)
③通過字節碼文件創建一個object對象(該方法可選,方法中已經傳遞一個object,如果需要調用的方法與本地方法不在同一個文件夾則需要新創建object(jobject (AllocObject)(JNIEnv, jclass);),如果需要反射調用的java方法與本地方法不在同一個類中,需要創建該方法,但是如果是這樣,並且需要跟新UI操作,例如打印一個Toast 會報空指針異常,因爲這時候調用的方法只是一個方法,沒有actiivty的生命週期。)
④通過對象調用方法,可以調用空參數方法,也可以調用有參數方法,並且將參數通過調用的方法傳入(void (CallVoidMethod)(JNIEnv, jobject, jmethodID, ...);)

進程間通信的方式?
Android 跨進程通信,像intent,contentProvider,廣播,service都可以跨進程通信。
intent:這種跨進程方式並不是訪問內存的形式,它需要傳遞一個uri,比如說打電話。
contentProvider:這種形式,是使用數據共享的形式進行數據共享。
service:遠程服務,aidl
廣播

Binder機制
直觀來說,Binder是Android中的一個類,它實現了IBinder接口,從IPC的角度來說,Binder是Android中的一種跨進程通信的一種方式,同時還可以理解爲是一種虛擬的物理設備,它的設備驅動是/dev/binder/。從Framework角度來說,Binder是ServiceManager的橋樑。從應用層來說,Binder是客戶端和服務端進行通信的媒介。
我們先來了解一下這個類中每個方法的含義:
DESCRIPTOR:Binder的唯一標識,一般用於當前Binder的類名錶示。
asInterface(android.os.IBinder obj):用於將服務端的Binder對象轉換成客戶端所需的AIDL接口類型的對象,這種轉化過程是區分進程的,如果客戶端和服務端位於同一個進程,那麼這個方法返回的是服務端的stub對象本身,否則返回的是系統封裝後的Stub.proxy對象。
asBinder():用於返回當前Binder對象。
onTransact:該方法運行在服務端的Binder線程池中,當客戶端發起跨進程通信請求的時候,遠程請求通過系統底層封裝後交給該方法處理。注意這個方法public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服務端通過code可以確定客戶端所請求的目標方法是什麼,接着從data中取出目標方法所需的參數,然後執行目標方法。當目標方法執行完畢後,就像reply中寫入返回值。這個方法的執行過程就是這樣的。如果這個方法返回false,客戶端是會請求失敗的,所以我們可以在這個方法中做一些安全驗證。

Binder的工作機制但是要注意一些問題:1、當客戶端發起請求時,由於當前線程會被掛起,直到服務端返回數據,如果這個遠程方法很耗時的話,那麼是不能夠在UI線程,也就是主線程中發起這個遠程請求的。
2、由於Service的Binder方法運行在線程池中,所以Binder方法不管是耗時還是不耗時都應該採用同步的方式,因爲它已經運行在一個線程中了。

什麼是AIDL?
AIDL: 每一個進程都有自己的Dalvik VM實例,都有自己的一塊獨立的內存,都在自己的內存上存儲自己的數據,執行着自己的操作,都在自己的那片狹小的空間裏過完自己的一生。而aidl就類似與兩個進程之間的橋樑,使得兩個進程之間可以進行數據的傳輸,跨進程通信有多種選擇,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 佔用的系統資源比較多,如果是頻繁的跨進程通信的話顯然是不可取的;Messenger 進行跨進程通信時請求隊列是同步進行的,無法併發執行。

AIDL解決了什麼問題?
AIDL出現的目的就是爲了解決一對多併發及時進程通信了

Android進程分類?
前臺進程,可見進程,服務進程,後臺進程,空進程

JAVA GC原理
垃圾收集算法的核心思想是:對虛擬機可用內存空間,即堆空間中的對象進行識別,如果對象正在被引用,那麼稱其爲存活對象
,反之,如果對象不再被引用,則爲垃圾對象,可以回收其佔據的空間,用於再分配。垃圾收集算法的選擇和垃圾收集系統參數的合理調節直接影響着系統性能。

java虛擬機和Dalvik虛擬機的區別 
Java虛擬機:
1、java虛擬機基於棧。 基於棧的機器必須使用指令來載入和操作棧上數據,所需指令更多更多。
2、java虛擬機運行的是java字節碼。(java類會被編譯成一個或多個字節碼.class文件)
Dalvik虛擬機:
1、dalvik虛擬機是基於寄存器的
2、Dalvik運行的是自定義的.dex字節碼格式。(java類被編譯成.class文件後,會通過一個dx工具將所有的.class文件轉換成一個.dex文件,然後dalvik虛擬機會從其中讀取指令和數據
3、常量池已被修改爲只使用32位的索引,以 簡化解釋器。
4、一個應用,一個虛擬機實例,一個進程(所有android應用的線程都是對應一個linux線程,都運行在自己的沙盒中,不同的應用在不同的進程中運行。每個android dalvik應用程序都被賦予了一個獨立的linux PID(app_*))

Art和Dalvik對比
(1)在Dalvik下,應用每次運行都需要通過即時編譯器(JIT)將字節碼轉換爲機器碼,即每次都要編譯加運行,這雖然會使安裝過程比較快,但是會拖慢應用的運行效率。而在ART 環境中,應用在第一次安裝的時候,字節碼就會預編譯(AOT)成機器碼,這樣的話,雖然設備和應用的首次啓動(安裝慢了)會變慢,但是以後每次啓動執行的時候,都可以直接運行,因此運行效率會提高。
(2)ART佔用空間比Dalvik大(原生代碼佔用的存儲空間更大,字節碼變爲機器碼之後,可能會增加10%-20%),這也是著名的“空間換時間大法"。
(4)預編譯也可以明顯改善電池續航,因爲應用程序每次運行時不用重複編譯了,從而減少了 CPU 的使用頻率,降低了能耗。

垃圾回收機制與調用System.gc()區別
垃圾回收機制:
1.垃圾收集算法的核心思想
Java語言建立了垃圾收集機制,用以跟蹤正在使用的對象和發現並回收不再使用(引用)的對象。
該機制可以有效防範動態內存分配中可能發生的兩個危險:因內存垃圾過多而引發的內存耗盡,以及不恰當的內存釋放所造成的內存非法引用。

垃圾收集算法的核心思想是:
對虛擬機可用內存空間,即堆空間中的對象進行識別,如果對象正在被引用,那麼稱其爲存活對象,反之,如果對象不再被引用,則爲垃圾對象,可以回收其佔據的空間,用於再分配。

2.觸發主GC(Garbage Collector)的條件
JVM進行次GC的頻率很高,但因爲這種GC佔用時間極短,所以對系統產生的影響不大。更值得關注的是主GC的觸發條件,因爲它對系統影響很明顯。總的來說,有兩個條件會觸發主GC:

①當應用程序空閒時,即沒有應用線程在運行時,GC會被調用。因爲GC在優先級最低的線程中進行,所以當應用忙時,GC線程就不會被調用,但以下條件除外。

②Java堆內存不足時,GC會被調用。當應用線程在運行,並在運行過程中創建新對象,若這時內存空間不足,JVM就會強制地調用GC線程,以便回收內存用於新的分配。若GC一次之後仍不能滿足內存分配的要求,JVM會再進行兩次GC作進一步的嘗試,若仍無法滿足要求,則 JVM將報“out of memory”的錯誤,Java應用將停止。

由於是否進行主GC由JVM根據系統環境決定,而系統環境在不斷的變化當中,所以主GC的運行具有不確定性,無法預計它何時必然出現,但可以確定的是對一個長期運行的應用來說,其主GC是反覆進行的。

3.減少GC開銷的措施
根據上述GC的機制,程序的運行會直接影響系統環境的變化,從而影響GC的觸發。(若不針對GC的特點進行設計和編碼,就會出現內存駐留等一系列負面影響。)
爲了避免這些影響,基本的原則就是儘可能地減少垃圾和減少GC過程中的開銷。具體措施包括以下幾個方面:
(1)不要顯式調用System.gc()
  此函數建議JVM進行主GC,雖然只是建議而非一定,但很多情況下它會觸發主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數。
(2)儘量減少臨時對象的使用
  臨時對象在跳出函數調用後,會成爲垃圾,少用臨時變量就相當於減少了垃圾的產生,從而延長了出現上述第二個觸發條件出現的時間,減少了主GC的機會。
(3)對象不用時最好顯式置爲Null

  一般而言,爲Null的對象都會被作爲垃圾處理,所以將不用的對象顯式地設爲Null,有利於GC收集器判定垃圾,從而提高了GC的效率。
(4)儘量使用StringBuffer,而不用String來累加字符串
  由於String是固定長的字符串對象,累加String對象時,並非在一個String對象中擴增,而是重新創建新的String對象(如 Str5=Str1+Str2+Str3+Str4,這條語句執行過程中會產生多個垃圾對象,因爲對次作“+”操作時都必須創建新的String對象,但這些過渡對象對系統來說是沒有實際意義的,只會增加更多的垃圾)。避免這種情況可以改用StringBuffer來累加字符串,因StringBuffer 是可變長的,它在原有基礎上進行擴增,不會產生中間對象。

(5)能用基本類型如Int,Long,就不用Integer,Long對象
  基本類型變量佔用的內存資源比相應對象佔用的少得多,如果沒有必要,最好使用基本變量。
(6)儘量少用靜態對象變量
  靜態變量屬於全局變量,不會被GC回收,它們會一直佔用內存。
(7)分散對象創建或刪除的時間
集中在短時間內大量創建新對象,特別是大對象,會導致突然需要大量內存,JVM在面臨這種情況時,只能進行主GC,以回收內存或整合內存碎片, 從而增加主GC的頻率。集中刪除對象,道理也是一樣的。它使得突然出現了大量的垃圾對象,空閒空間必然減少,從而大大增加了下一次創建新對象時強制主GC 的機會。

System.gc()使用介紹:
Java中的內存分配是隨着new一個新的對象來實現的,這個很簡單,而且也還是有一些可以“改進”內存回收的機制的,其中最顯眼的就是這個System.gc()函數。
其實這個gc()函數的作用只是提醒虛擬機:程序員希望進行一次垃圾回收。但是它不能保證垃圾回收一定會進行,而且具體什麼時候進行是取決於具體的虛擬機的,不同的虛擬機有不同的對策。

gc()進行回收的準則是什麼?也就是說什麼樣的對象可以被回收?
簡單來說就是:沒有被任何可達變量指向的對象。這裏的可達是意思就是能夠找到的(沒有任何可達變量指向你,你還有活下去的理由嗎?你就算活下去誰能找得到你呢?)
所以說,C++中將釋放了的指針置爲null的習慣要保留到Java中,因爲這有可能是你釋放內存的唯一途徑。
不要頻繁使用gc函數。
保持代碼健壯(記得將不用的變量置爲null),讓虛擬機去管理內存

Ubuntu編譯安卓系統
1.初始化編譯環境
. build/envsetup.sh
2.選擇編譯目標
lunch aosp_arm64-eng
3.開始編譯
make -j8

系統啓動流程是什麼?(提示:Zygote進程 –> SystemServer進程 –> 各種系統服務 –> 應用進程)
第一步:啓動電源以及系統啓動
當電源按下,引導芯片代碼開始從預定義的地方(固化在ROM)開始執行。加載引導程序到RAM,然後執行。
第二步:引導程序
引導程序是在Android操作系統開始運行前的一個小程序。引導程序是運行的第一個程序,因此它是針對特定的主板與芯片的。設備製造商要麼使用很受歡迎的引導程序比如redboot、uboot、qi bootloader或者開發自己的引導程序,它不是Android操作系統的一部分。
引導程序是OEM廠商或者運營商加鎖和限制的地方。引導程序分兩個階段執行。第一個階段,檢測外部的RAM以及加載對第二階段有用的程序;第二階段,引導程序設置網絡、內存等等。這些對於運行內核是必要的,爲了達到特殊的目標,引導程序可以根據配置參數或者輸入數據設置內核。
Android引導程序可以在\bootable\bootloader\legacy\usbloader找到。
傳統的加載器包含的個文件,需要在這裏說明:
init.s初始化堆棧,清零BBS段,調用main.c的_main()函數;
main.c初始化硬件(鬧鐘、主板、鍵盤、控制檯),創建linux標籤。
更多關於Android引導程序的可以在這裏瞭解。
第三步:內核
Android內核與桌面linux內核啓動的方式差不多。內核啓動時,設置緩存、被保護存儲器、計劃列表,加載驅動。當內核完成系統設置,它首先在系統文件中尋找”init”文件,然後啓動root進程或者系統的第一個進程。
第四步:init進程
init是第一個進程,我們可以說它是root進程或者所有進程的父進程。init進程有兩個責任,一是掛載目錄,比如/sys、/dev、/proc,二是運行init.rc腳本。
init進程可以在/system/core/init找到。
init.rc文件可以在/system/core/rootdir/init.rc找到。
readme.txt可以在/system/core/init/readme.txt找到。
對於init.rc文件,Android中有特定的格式以及規則。在Android中,我們叫做Android初始化語言。Android初始化語言由四大類型的聲明組成,即Actions(動作)、Commands(命令)、Services(服務)、以及Options(選項)。
Action(動作):動作是以命令流程命名的,有一個觸發器決定動作是否發生。
Service(服務):服務是init進程啓動的程序、當服務退出時init進程會視情況重啓服務。
Options(選項):選項是對服務的描述。它們影響init進程如何以及何時啓動服務。
在這個階段你可以在設備的屏幕上看到“Android”logo了。
第五步
在Java中,我們知道不同的虛擬機實例會爲不同的應用分配不同的內存。假如Android應用應該儘可能快地啓動,但如果Android系統爲每一個應用啓動不同的Dalvik虛擬機實例,就會消耗大量的內存以及時間。因此,爲了克服這個問題,Android系統創造了”Zygote”。Zygote讓Dalvik虛擬機共享代碼、低內存佔用以及最小的啓動時間成爲可能。Zygote是一個虛擬器進程,正如我們在前一個步驟所說的在系統引導的時候啓動。Zygote預加載以及初始化核心庫類。通常,這些核心類一般是隻讀的,也是Android SDK或者核心框架的一部分。在Java虛擬機中,每一個實例都有它自己的核心庫類文件和堆對象的拷貝。
Zygote加載進程
加載ZygoteInit類,源代碼:/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
registerZygoteSocket()爲zygote命令連接註冊一個服務器套接字。preloadClassed “preloaded-classes”是一個簡單的包含一系列需要預加載類的文本文件,你可以在/frameworks/base找到“preloaded-classes”文件。preloadResources() preloadResources也意味着本地主題、佈局以及android.R文件中包含的所有東西都會用這個方法加載。在這個階段,你可以看到啓動動畫。
第六步:系統服務或服務
完成了上面幾步之後,運行環境請求Zygote運行系統服務。系統服務同時使用native以及java編寫,系統服務可以認爲是一個進程。同一個系統服務在Android SDK可以以System Services形式獲得。系統服務包含了所有的System Services。
Zygote創建新的進程去啓動系統服務。你可以在ZygoteInit類的”startSystemServer”方法中找到源代碼。
核心服務:
啓動電源管理器;創建Activity管理器;啓動電話註冊;啓動包管理器;設置Activity管理服務爲系統進程;啓動上下文管理器;啓動系統Context Providers;啓動電池服務;啓動定時管理器;啓動傳感服務;啓動窗口管理器;啓動藍牙服務;啓動掛載服務。
其他服務:
啓動狀態欄服務;啓動硬件服務;啓動網絡狀態服務;啓動網絡連接服務;啓動通知管理器;啓動設備存儲監視服務;啓動定位管理器;啓動搜索服務;啓動剪切板服務;啓動登記服務;啓動壁紙服務;啓動音頻服務;啓動耳機監聽;啓動AdbSettingsObserver(處理adb命令)。
第七步:引導完成
一旦系統服務在內存中跑起來了,Android就完成了引導過程。在這個時候“ACTION_BOOT_COMPLETED”開機啓動廣播就會發出去。

大體說清一個應用程序安裝到手機上時發生了什麼
(1)拷貝apk文件到指定目錄:
用戶安裝的apk首先會被拷貝到/data/app目錄下
/data/app目錄是用戶有權限訪問的目錄,在安裝apk的時候會自動選擇該目錄存放用戶安裝的文件
系統出廠的apk文件則被放到了/system分區下,包括 /system/app,/system/vendor/app,以及 /system/priv-app 等等,該分區只有Root權限的用戶才能
2)解壓apk,拷貝文件,創建應用的數據目錄:
爲了加快app的啓動速度,apk在安裝的時候,會首先將app的可執行文件(dex)拷貝到 /data/dalvik-cache 目錄,緩存起來。
然後,在/data/data/目錄下創建應用程序的數據目錄(以應用的包名命名),存放應用的相關數據,如數據庫、xml文件、cache、二進制的so動態庫等等。
3)解析apk的AndroidManifinest.xml文件:解析的內容會被存儲到/data/system/packages.xml和/data/system/packages.list中。
packages.list:中指名了該應用默認存儲的位置/data/data/cn.hadcn.example。
packages.xml:中包含了該應用申請的權限、簽名和代碼所在位置等信息,並且兩者都有一個userId爲10060。之所以每個應用都有一個userId,是因爲Android在系統設計上把每個應用當作Linux系統上的一個用戶對待,這樣就可以利用已有的Linux上用戶管理機制來設計Android應用,比如應用目錄,應用權限,應用進程管理等。

做完以上操作,就相當於應用在系統註冊了,可以被系統識別。
注:目錄是由 包名-1 組成,有時候此處是 -2。這是爲了升級使用,升級時會新創建一個-1 或 -2的目錄,如果升級成功,則刪除原目錄並更改packages.xml中codePath到新目錄
在Dalvik模式下,會使用dexopt把base.apk中的dex文件優化爲odex,存儲在/data/dalvik-cache中,
如果是ART模式,則會使用dex2oat優化成oat文件也存儲在該目錄下,並且文件名一樣,但文件大小會大很多,因爲ART模式會在安裝時把dex優化爲機器碼,所以在ART模式下的應用運行更快,但apk安裝速度相對Dalvik模式下變慢,並且會佔用更多的ROM。

簡述Activity啓動全部過程
app啓動的過程有兩種情況,第一種是從桌面launcher上點擊相應的應用圖標,第二種是在activity中通過調用startActivity來啓動一個新的activity。
我們創建一個新的項目,默認的根activity都是MainActivity,而所有的activity都是保存在堆棧中的,我們啓動一個新的activity就會放在上一個activity上面,而我們從桌面點擊應用圖標的時候,由於launcher本身也是一個應用,當我們點擊圖標的時候,系統就會調用startActivitySately(),一般情況下,我們所啓動的activity的相關信息都會保存在intent中,比如action,category等等。我們在安裝這個應用的時候,系統也會啓動一個PackaManagerService的管理服務,這個管理服務會對AndroidManifest.xml文件進行解析,從而得到應用程序中的相關信息,比如service,activity,Broadcast等等,然後獲得相關組件的信息。當我們點擊應用圖標的時候,就會調用startActivitySately()方法,而這個方法內部則是調用startActivty(),而startActivity()方法最終還是會調用startActivityForResult()這個方法。而在startActivityForResult()這個方法。因爲startActivityForResult()方法是有返回結果的,所以系統就直接給一個-1,就表示不需要結果返回了。而startActivityForResult()這個方法實際是通過Instrumentation類中的execStartActivity()方法來啓動activity,Instrumentation這個類主要作用就是監控程序和系統之間的交互。而在這個execStartActivity()方法中會獲取ActivityManagerService的代理對象,通過這個代理對象進行啓動activity。啓動會就會調用一個checkStartActivityResult()方法,如果說沒有在配置清單中配置有這個組件,就會在這個方法中拋出異常了。當然最後是調用的是Application.scheduleLaunchActivity()進行啓動activity,而這個方法中通過獲取得到一個ActivityClientRecord對象,而這個ActivityClientRecord通過handler來進行消息的發送,系統內部會將每一個activity組件使用ActivityClientRecord對象來進行描述,而ActivityClientRecord對象中保存有一個LoaderApk對象,通過這個對象調用handleLaunchActivity來啓動activity組件,而頁面的生命週期方法也就是在這個方法中進行調用。

邏輯地址與物理地址,爲什麼使用邏輯地址?
邏輯地址:存儲單元的地址可以用段基值和段內偏移量來表示,段基值確定它所在的段居於整個存儲空間的位置,偏移量確定它在段內的位置,這種地址表示方式稱爲邏輯地址。8086體系的CPU一開始是20根地址線, 尋址寄存器是16位, 16位的寄存器可以訪問64K的地址空間, 如果程序要想訪問大於64K的內存, 就要把內存分段, 每段64K, 用段地址+偏移量的方法來訪問 。386CPU出來之後, 採用了32條地址線, 地址寄存器也擴爲32位, 這樣就可以不用分段了, 直接用一個地址寄存器來線性訪問4G的內存了. 這就叫平面模式.

線性地址:又叫虛擬地址,是一個32位無符號整數,可以用來表示高達4GB的地址,跟邏輯地址類似,它也是一個不真實的地址,如果邏輯地址是對應的硬件平臺段式管理轉換前地址的話,那麼線性地址則對應了硬件頁式內存的轉換前地址。

物理地址:用於內存芯片級的單元尋址,與處理器和CPU連接的地址總線相對應。

注意:
1.CPU將一個虛擬內存空間中的地址轉換爲物理地址,需要進行兩步:首先將給定一個邏輯地址(其實是段內偏移量,這個一定要理解!!!),CPU要利用其段式內存管理單元,先將爲個邏輯地址轉換成一個線性地址,再利用其頁式內存管理單元,轉換爲最終物理地址。
邏輯地址----段式內存管理單元----線性地址----頁式內存管理單元----物理地址
這樣做兩次轉換,的確是非常麻煩而且沒有必要的,因爲直接可以把線性地址抽像給進程。之所以這樣冗餘,Intel完全是爲了兼容而已。

Android爲每個應用程序分配的內存大小是多少?
準確的說話是 google原生OS的默認值是16M,但是各個廠家的系統會對這個值進行修改。不同廠商的值不同
(1)未設定屬性android:largeheap = "true"時,可以申請到的最大內存空間。
(2)設定屬性android:largeheap = "true"時, 可以申請的最大內存空間爲原來的兩倍多一些。

進程保活的方式
此處延伸:進程的優先級是什麼
當前業界的Android進程保活手段主要分爲** 黑、白、灰 **三種,其大致的實現思路如下:
黑色保活:不同的app進程,用廣播相互喚醒(包括利用系統提供的廣播進行喚醒)
白色保活:啓動前臺Service
灰色保活:利用系統的漏洞啓動前臺Service
黑色保活
所謂黑色保活,就是利用不同的app進程使用廣播來進行相互喚醒。舉個3個比較常見的場景:
場景1:開機,網絡切換、拍照、拍視頻時候,利用系統產生的廣播喚醒app
場景2:接入第三方SDK也會喚醒相應的app進程,如微信sdk會喚醒微信,支付寶sdk會喚醒支付寶。由此發散開去,就會直接觸發了下面的 場景3
場景3:假如你手機裏裝了支付寶、淘寶、天貓、UC等阿里系的app,那麼你打開任意一個阿里系的app後,有可能就順便把其他阿里系的app給喚醒了。(只是拿阿里打個比方,其實BAT系都差不多)
白色保活
白色保活手段非常簡單,就是調用系統api啓動一個前臺的Service進程,這樣會在系統的通知欄生成一個Notification,用來讓用戶知道有這樣一個app在運行着,哪怕當前的app退到了後臺。如下方的LBE和QQ音樂這樣:
灰色保活
灰色保活,這種保活手段是應用範圍最廣泛。它是利用系統的漏洞來啓動一個前臺的Service進程,與普通的啓動方式區別在於,它不會在系統通知欄處出現一個Notification,看起來就如同運行着一個後臺Service進程一樣。這樣做帶來的好處就是,用戶無法察覺到你運行着一個前臺進程(因爲看不到Notification),但你的進程優先級又是高於普通後臺進程的。那麼如何利用系統的漏洞呢,大致的實現思路和代碼如下:
思路一:API < 18,啓動前臺Service時直接傳入new Notification(); 思路二:API >= 18,同時啓動兩個id相同的前臺Service,然後再將後啓動的Service做stop處理
熟悉Android系統的童鞋都知道,系統出於體驗和性能上的考慮,app在退到後臺時系統並不會真正的kill掉這個進程,而是將其緩存起來。打開的應用越多,後臺緩存的進程也越多。在系統內存不足的情況下,系統開始依據自身的一套進程回收機制來判斷要kill掉哪些進程,以騰出內存來供給需要的app。這套殺進程回收內存的機制就叫 Low Memory Killer ,它是基於Linux內核的 OOM Killer(Out-Of-Memory killer)機制誕生。
進程的重要性,劃分5級:
前臺進程 (Foreground process)
可見進程 (Visible process)
服務進程 (Service process)
後臺進程 (Background process)
空進程 (Empty process)
瞭解完 Low Memory Killer,再科普一下oom_adj。什麼是oom_adj?它是linux內核分配給每個系統進程的一個值,代表進程的優先級,進程回收機制就是根據這個優先級來決定是否進行回收。對於oom_adj的作用,你只需要記住以下幾點即可:
進程的oom_adj越大,表示此進程優先級越低,越容易被殺回收;越小,表示進程優先級越高,越不容易被殺回收
普通app進程的oom_adj>=0,系統進程的oom_adj纔可能<0
有些手機廠商把這些知名的app放入了自己的白名單中,保證了進程不死來提高用戶體驗(如微信、QQ、陌陌都在小米的白名單中)。如果從白名單中移除,他們終究還是和普通app一樣躲避不了被殺的命運,爲了儘量避免被殺,還是老老實實去做好優化工作吧。
還有一種就是放到對應手機廠商的白名單中,那進行不會被殺死了。

View的繪製原理
View的繪製從ActivityThread類中Handler的處理RESUME_ACTIVITY事件開始,在執行performResumeActivity之後,創建Window以及DecorView並調用WindowManager的addView方法添加到屏幕上,addView又調用ViewRootImpl的setView方法,最終執行performTraversals方法,依次執行performMeasure,performLayout,performDraw。也就是view繪製的三大過程。
measure過程測量view的視圖大小,最終需要調用setMeasuredDimension方法設置測量的結果,如果是ViewGroup需要調用measureChildren或者measureChild方法進而計算自己的大小。
layout過程是擺放view的過程,View不需要實現,通常由ViewGroup實現,在實現onLayout時可以通過getMeasuredWidth等方法獲取measure過程測量的結果進行擺放。
draw過程先是繪製背景,其次調用onDraw()方法繪製view的內容,再然後調用dispatchDraw()調用子view的draw方法,最後繪製滾動條。ViewGroup默認不會執行onDraw方法,如果複寫了onDraw(Canvas)方法,需要調用 setWillNotDraw(false);清楚不需要繪製的標記。

getWidth()方法和getMeasureWidth()區別呢?
首先getMeasureWidth()方法在measure()過程結束後就可以獲取到了,而getWidth()方法要在layout()過程結束後才能獲取到。另外,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設置的,而getWidth()方法中的值則是通過視圖右邊的座標減去左邊的座標計算出來的。

onStart()與onResume()有什麼區別?
onStart()是activity界面被顯示出來的時候執行的,但不能與它交互;
onResume()是當該activity與用戶能進行交互時被執行,用戶可以獲得activity的焦點,能夠與用戶交互。

爲什麼Android引入廣播機制?
a:從MVC的角度考慮(應用程序內) 其實回答這個問題的時候還可以這樣問,android爲什麼要有那4大組件,現在的移動開發模型基本上也是照搬的web那一套MVC架構,只不過是改了點嫁妝而已。

android的四大組件本質上就是爲了實現移動或者說嵌入式設備上的MVC架構
它們之間有時候是一種相互依存的關係,有時候又是一種補充關係,引入廣播機制可以方便幾大組件的信息和數據交互。

b:程序間互通消息(例如在自己的應用程序內監聽系統來電)
c:效率上(參考UDP的廣播協議在局域網的方便性)
d:設計模式上(反轉控制的一種應用,類似監聽者模式)

如何將打開res aw目錄中的數據庫文件?
在Android中不能直接打開res aw目錄中的數據庫文件,而需要在程序第一次啓動時將該文件複製到手機內存或SD卡的某個目錄中,然後再打開該數據庫文件。
複製的基本方法是使用getResources().openRawResource方法獲得res aw目錄中資源的 InputStream對象,然後將該InputStream對象中的數據寫入其他的目錄中相應文件中。
在Android SDK中可以使用SQLiteDatabase.openOrCreateDatabase方法來打開任意目錄中的SQLite數據庫文件。

什麼是aar?aar是jar有什麼區別?
jar打包不能包含資源文件
“aar”包是 Android 的類庫項目的二進制發行包。文件擴展名是.aar

ListView卡頓的原因以及優化策略
重用converView: 通過複用converview來減少不必要的view的創建,另外Infalte操作會把xml文件實例化成相應的View實例,屬於IO操作,是耗時操作。

減少findViewById()操作: 將xml文件中的元素封裝成viewholder靜態類,通過converview的setTag和getTag方法將view與相應的holder對象綁定在一起,避免不必要的findviewbyid操作

避免在 getView 方法中做耗時的操作: 例如加載本地 Image 需要載入內存以及解析 Bitmap ,都是比較耗時的操作,如果用戶快速滑動listview,會因爲getview邏輯過於複雜耗時而造成滑動卡頓現象。用戶滑動時候不要加載圖片,待滑動完成再加載,可以使用這個第三方庫glide

Item的佈局層次結構儘量簡單,避免佈局太深或者不必要的重繪

儘量能保證 Adapter 的 hasStableIds() 返回 true 這樣在 notifyDataSetChanged() 的時候,如果item內容並沒有變化,ListView 將不會重新繪製這個 View,達到優化的目的

在一些場景中,ScollView內會包含多個ListView,可以把listview的高度寫死固定下來。 由於ScollView在快速滑動過程中需要大量計算每一個listview的高度,阻塞了UI線程導致卡頓現象出現,如果我們每一個item的高度都是均勻的,可以通過計算把listview的高度確定下來,避免卡頓現象出現

使用 RecycleView 代替listview: 每個item內容的變動,listview都需要去調用notifyDataSetChanged來更新全部的item,太浪費性能了。RecycleView可以實現當個item的局部刷新,並且引入了增加和刪除的動態效果,在性能上和定製上都有很大的改善

ListView 中元素避免半透明: 半透明繪製需要大量乘法計算,在滑動時不停重繪會造成大量的計算,在比較差的機子上會比較卡。 在設計上能不半透明就不不半透明。實在要弄就把在滑動的時候把半透明設置成不透明,滑動完再重新設置成半透明。

儘量開啓硬件加速: 硬件加速提升巨大,避免使用一些不支持的函數導致含淚關閉某個地方的硬件加速。當然這一條不只是對 ListView。

Android動畫原理
Animation框架定義了透明度,旋轉,縮放和位移幾種常見的動畫,而且控制的是整個View
實現原理是每次繪製視圖時View所在的ViewGroup中的drawChild函數獲取該View的Animation的Transformation值
然後調用canvas.concat(transformToApply.getMatrix()),通過矩陣運算完成動畫幀,如果動畫沒有完成,繼續調用invalidate()函數,啓動下次繪製來驅動動畫
動畫過程中的幀之間間隙時間是繪製函數所消耗的時間,可能會導致動畫消耗比較多的CPU資源,最重要的是,動畫改變的只是顯示,並不能相應事件

線程池
Android中常見的線程池有四種,FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor。

FixedThreadPool線程池是通過Executors的new FixedThreadPool方法來創建。它的特點是該線程池中的線程數量是固定的。即使線程處於閒置的狀態,它們也不會被回收,除非線程池被關閉。當所有的線程都處於活躍狀態的時候,新任務就處於隊列中等待線程來處理。注意,FixedThreadPool只有核心線程,沒有非核心線程。

CachedThreadPool線程池是通過Executors的newCachedThreadPool進行創建的。它是一種線程數目不固定的線程池,它沒有核心線程,只有非核心線程,當線程池中的線程都處於活躍狀態,就會創建新的線程來處理新的任務。否則就會利用閒置的線程來處理新的任務。線程池中的線程都有超時機制,這個超時機制時長是60s,超過這個時間,閒置的線程就會被回收。這種線程池適合處理大量並且耗時較少的任務。這裏得說一下,CachedThreadPool的任務隊列,基本都是空的。

ScheduledThreadPool線程池是通過Executors的newScheduledThreadPool進行創建的,它的核心線程是固定的,但是非核心線程數是不固定的,並且當非核心線程一處於空閒狀態,就立即被回收。這種線程適合執行定時任務和具有固定週期的重複任務。

SingleThreadExecutor線程池是通過Executors的newSingleThreadExecutor方法來創建的,這類線程池中只有一個核心線程,也沒有非核心線程,這就確保了所有任務能夠在同一個線程並且按照順序來執行,這樣就不需要考慮線程同步的問題。

加密算法(base64、MD5、對稱加密和非對稱加密)和使用場景。
什麼是Rsa加密?
RSA算法是最流行的公鑰密碼算法,使用長度可以變化的密鑰。RSA是第一個既能用於數據加密也能用於數字簽名的算法。

RSA算法原理如下:
1.隨機選擇兩個大質數p和q,p不等於q,計算N=pq; 
2.選擇一個大於1小於N的自然數e,e必須與(p-1)(q-1)互素。 
3.用公式計算出d:d×e = 1 (mod (p-1)(q-1)) 。
4.銷燬p和q。

最終得到的N和e就是“公鑰”,d就是“私鑰”,發送方使用N去加密數據,接收方只有使用d才能解開數據內容。

RSA的安全性依賴於大數分解,小於1024位的N已經被證明是不安全的,而且由於RSA算法進行的都是大數計算,使得RSA最快的情況也比DES慢上倍,這是RSA最大的缺陷,因此通常只能用於加密少量數據或者加密密鑰,但RSA仍然不失爲一種高強度的算法。

使用場景:項目中除了登陸,支付等接口採用rsa非對稱加密,之外的採用aes對稱加密,今天我們來認識一下aes加密。

什麼是MD5加密?
MD5英文全稱“Message-Digest Algorithm 5”,翻譯過來是“消息摘要算法5”,由MD2、MD3、MD4演變過來的,是一種單向加密算法,是不可逆的一種的加密方式。

MD5加密有哪些特點?

壓縮性:任意長度的數據,算出的MD5值長度都是固定的。
容易計算:從原數據計算出MD5值很容易。
抗修改性:對原數據進行任何改動,哪怕只修改1個字節,所得到的MD5值都有很大區別。
強抗碰撞:已知原數據和其MD5值,想找到一個具有相同MD5值的數據(即僞造數據)是非常困難的。

MD5應用場景:一致性驗證,數字簽名,安全訪問認證

什麼是aes加密?
高級加密標準(英語:Advanced Encryption Standard,縮寫:AES),在密碼學中又稱Rijndael加密法,是美國聯邦政府採用的一種區塊加密標準。這個標準用來替代原先的DES,已經被多方分析且廣爲全世界所使用。

(3)Gradle面試題 (去年去面試的企業都問到了)
Gradle瞭解多少?groovy語法會嗎?多渠道打包這些

(4)混合開發面試題 (擴展 — 談錢資本)
談談對kotlin的理解
Kotlin是一門靜態語言,支持多種平臺,包括移動端、服務端以及瀏覽器端,此外,Kotlin還是一門融合了面向對象與函數式編程的語言,支持泛型、安全的空判斷,並且Kotlin與Java可以做到完全的交互。

Kotlin特點
代碼量少且代碼末尾沒有分號。
被調用的方法需放到上邊。
Kotlin是空安全的:在編譯時期就處理了各種null的情況,避免了執行時異常。
它可擴展函數:我們也可以擴展任意類的更多的特性。
它也是函數式的:比如,使用lambda表達式來更方便地解決問題。
高度互操作性:你可以繼續使用所有用Java寫的代碼和庫,甚至可以在一個項目中使用Kotlin和Java兩種語言混合編程

Hybrid通信原理是什麼,有做研究嗎?
Android與JS通過WebView互相調用方法,實際上是:
Android去調用JS的代碼
通過WebView的loadUrl(),使用該方法比較簡潔,方便。但是效率比較低,獲取返回值比較困難。
通過WebView的evaluateJavascript(),該方法效率高,但是4.4以上的版本才支持,4.4以下版本不支持。所以建議兩者混合使用。

JS去調用Android的代碼
通過WebView的addJavascriptInterface()進行對象映射 ,該方法使用簡單,僅將Android對象和JS對象映射即可,但是存在比較大的漏洞。
漏洞產生原因是:當JS拿到Android這個對象後,就可以調用這個Android對象中所有的方法,包括系統類(java.lang.Runtime 類),從而進行任意代碼執行。
解決方式:
(1)Google 在Android 4.2 版本中規定對被調用的函數以 @JavascriptInterface進行註解從而避免漏洞攻擊。
(2)在Android 4.2版本之前採用攔截prompt()進行漏洞修復。
通過 WebViewClient 的shouldOverrideUrlLoading ()方法回調攔截 url 。這種方式的優點:不存在方式1的漏洞;缺點:JS獲取Android方法的返回值複雜。(ios主要用的是這個方式)
(1)Android通過 WebViewClient 的回調方法shouldOverrideUrlLoading ()攔截 url
(2)解析該 url 的協議
(3)如果檢測到是預先約定好的協議,就調用相應方法
通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調攔截JS對話框alert()、confirm()、prompt() 消息
這種方式的優點:不存在方式1的漏洞;缺點:JS獲取Android方法的返回值複雜。

Dart語言有研究過嗎?
Flutter爲什麼選擇Dart語言?筆者根據官方解釋以及自己對Flutter的理解總結了以下幾條(由於其它跨平臺框架都將JavaScript作爲其開發語言,所以主要將Dart和JavaScript做一個對比):

  1. 開發效率高
Dart運行時和編譯器支持Flutter的兩個關鍵特性的組合:
基於JIT的快速開發週期:Flutter在開發階段採用,採用JIT模式,這樣就避免了每次改動都要進行編譯,極大的節省了開發時間;
基於AOT的發佈包: Flutter在發佈時可以通過AOT生成高效的ARM代碼以保證應用性能。而JavaScript則不具有這個能力。
  2. 高性能
Flutter旨在提供流暢、高保真的的UI體驗。爲了實現這一點,Flutter中需要能夠在每個動畫幀中運行大量的代碼。這意味着需要一種既能提供高性能的語言,而不會出現會丟幀的週期性暫停,而Dart支持AOT,在這一點上可以做的比JavaScript更好。
  3. 快速內存分配
Flutter框架使用函數式流,這使得它在很大程度上依賴於底層的內存分配器。因此,擁有一個能夠有效地處理瑣碎任務的內存分配器將顯得十分重要,在缺乏此功能的語言中,Flutter將無法有效地工作。當然Chrome V8的JavaScript引擎在內存分配上也已經做的很好,事實上Dart開發團隊的很多成員都是來自Chrome團隊的,所以在內存分配上Dart並不能作爲超越JavaScript的優勢,而對於Flutter來說,它需要這樣的特性,而Dart也正好滿足而已。
  4. 類型安全
由於Dart是類型安全的語言,支持靜態類型檢測,所以可以在編譯前發現一些類型的錯誤,並排除潛在問題,這一點對於前端開發者來說可能會更具有吸引力。與之不同的,JavaScript是一個弱類型語言,也因此前端社區出現了很多給JavaScript代碼添加靜態類型檢測的擴展語言和工具,如:微軟的TypeScript以及Facebook的Flow。相比之下,Dart本身就支持靜態類型,這是它的一個重要優勢。
  5. Dart團隊就在你身邊
看似不起眼,實則舉足輕重。由於有Dart團隊的積極投入,Flutter團隊可以獲得更多、更方便的支持,正如Flutter官網所述“我們正與Dart社區進行密切合作,以改進Dart在Flutter中的使用。例如,當我們最初採用Dart時,該語言並沒有提供生成原生二進制文件的工具鏈(這對於實現可預測的高性能具有很大的幫助),但是現在它實現了,因爲Dart團隊專門爲Flutter構建了它。同樣,Dart VM之前已經針對吞吐量進行了優化,但團隊現在正在優化VM的延遲時間,這對於Flutter的工作負載更爲重要。”

Hybrid,weex,react native,快應用,flutter瞭解嗎?對比一下?
H5+原生混合開發
這類框架主要原理就是將APP的一部分需要動態變動的內容通過H5來實現,通過原生的網頁加載控件WebView (Android)或WKWebView(iOS)來加載(以後若無特殊說明,我們用WebView來統一指代android和iOS中的網頁加載控件)。這樣以來,H5部分是可以隨時改變而不用發版,動態化需求能滿足;同時,由於h5代碼只需要一次開發,就能同時在Android和iOS兩個平臺運行,這也可以減小開發成本,也就是說,H5部分功能越多,開發成本就越小。我們稱這種h5+原生的開發模式爲混合開發 ,採用混合模式開發的APP我們稱之爲混合應用或Hybrid APP ,如果一個應用的大多數功能都是H5實現的話,我們稱其爲Web APP 。
混合開發技術點
如之前所述,原生開發可以訪問平臺所有功能,而混合開發中,H5代碼是運行在WebView中,而WebView實質上就是一個瀏覽器內核,其JavaScript依然運行在一個權限受限的沙箱中,所以對於大多數系統能力都沒有訪問權限,如無法訪問文件系統、不能使用藍牙等。所以,對於H5不能實現的功能,都需要原生去做。而混合框架一般都會在原生代碼中預先實現一些訪問系統能力的API, 然後暴露給WebView以供JavaScript調用,這樣一來,WebView就成爲了JavaScript與原生API之間通信的橋樑,主要負責JavaScript與原生之間傳遞調用消息,而消息的傳遞必須遵守一個標準的協議,它規定了消息的格式與含義,我們把依賴於WebView的用於在JavaScript與原生之間通信並實現了某種消息傳輸協議的工具稱之爲WebView JavaScript Bridge, 簡稱 JsBridge,它也是混合開發框架的核心。
混合應用的優點是動態內容是H5,web技術棧,社區及資源豐富,缺點是性能不好,對於複雜用戶界面或動畫,WebView不堪重任

React Native 
React Native (簡稱RN)是Facebook於2015年4月開源的跨平臺移動應用開發框架,是Facebook早先開源的JS框架 React 在原生移動應用平臺的衍生產物,目前支持iOS和Android兩個平臺。RN使用Javascript語言,類似於HTML的JSX,以及CSS來開發移動應用,因此熟悉Web前端開發的技術人員只需很少的學習就可以進入移動應用開發領域。
由於RN和React原理相通,並且Flutter也是受React啓發,很多思想也都是相通的,萬丈高樓平地起,我們有必要深入瞭解一下React原理。React是一個響應式的Web框架,我們先了解一下兩個重要的概念:DOM樹與響應式編程。
DOM樹與控件樹
文檔對象模型(Document Object Model,簡稱DOM),是W3C組織推薦的處理可擴展標誌語言的標準編程接口,一種獨立於平臺和語言的方式訪問和修改一個文檔的內容和結構。換句話說,這是表示和處理一個HTML或XML文檔的標準接口。簡單來說,DOM就是文檔樹,與用戶界面控件樹對應,在前端開發中通常指HTML對應的渲染樹,但廣義的DOM也可以指Android中的XML佈局文件對應的控件樹,而術語DOM操作就是指直接來操作渲染樹(或控件樹), 因此,可以看到其實DOM樹和控件樹是等價的概念,只不過前者常用於Web開發中,而後者常用於原生開發中。
響應式編程
React中提出一個重要思想:狀態改變則UI隨之自動改變,而React框架本身就是響應用戶狀態改變的事件而執行重新構建用戶界面的工作,這就是典型的響應式編程範式,下面我們總結一下React中響應式原理:
• 開發者只需關注狀態轉移(數據),當狀態發生變化,React框架會自動根據新的狀態重新構建UI。
• React框架在接收到用戶狀態改變通知後,會根據當前渲染樹,結合最新的狀態改變,通過Diff算法,計算出樹中變化的部分,然後只更新變化的部分(DOM操作),從而避免整棵樹重構,提高性能。
值得注意的是,在第二步中,狀態變化後React框架並不會立即去計算並渲染DOM樹的變化部分,相反,React會在DOM的基礎上建立一個抽象層,即虛擬DOM樹,對數據和狀態所做的任何改動,都會被自動且高效的同步到虛擬DOM,最後再批量同步到真實DOM中,而不是每次改變都去操作一下DOM。爲什麼不能每次改變都直接去操作DOM樹?這是因爲在瀏覽器中每一次DOM操作都有可能引起瀏覽器的重繪或迴流:

  1. 如果DOM只是外觀風格發生變化,如顏色變化,會導致瀏覽器重繪界面。
  2. 如果DOM樹的結構發生變化,如尺寸、佈局、節點隱藏等導致,瀏覽器就需要回流(及重新排版佈局)。
    而瀏覽器的重繪和迴流都是比較昂貴的操作,如果每一次改變都直接對DOM進行操作,這會帶來性能問題,而批量操作只會觸發一次DOM更新。

上文已經提到React Native 是React 在原生移動應用平臺的衍生產物,那兩者主要的區別是什麼呢?其實,主要的區別在於虛擬DOM映射的對象是什麼?React中虛擬DOM最終會映射爲瀏覽器DOM樹,而RN中虛擬DOM會通過 JavaScriptCore 映射爲原生控件樹。
JavaScriptCore 是一個JavaScript解釋器,它在React Native中主要有兩個作用:

  1. 爲JavaScript提供運行環境。
  2. 是JavaScript與原生應用之間通信的橋樑,作用和JsBridge一樣,事實上,在iOS中,很多JsBridge的實現都是基於 JavaScriptCore 。
    而RN中將虛擬DOM映射爲原生控件的過程中分兩步:
  3. 佈局消息傳遞; 將虛擬DOM佈局信息傳遞給原生;
  4. 原生根據佈局信息通過對應的原生控件渲染控件樹;
    至此,React Native 便實現了跨平臺。 相對於混合應用,由於React Native是原生控件渲染,所以性能會比混合應用中H5好很多,同時React Native使用了Web開發技術棧,也只需維護一份代碼,同樣是跨平臺框架。
    Weex
    Weex是阿里巴巴於2016年發佈的跨平臺移動端開發框架,思想及原理和React Native類似,最大的不同是語法層面,Weex支持Vue語法和Rax語法,Rax 的 DSL(Domain Specific Language) 語法是基於 React JSX 語法而創造。與 React 不同,在 Rax 中 JSX 是必選的,它不支持通過其它方式創建組件,所以學習 JSX 是使用 Rax 的必要基礎。而React Native只支持JSX語法。

快應用
快應用是華爲、小米、OPPO、魅族等國內9大主流手機廠商共同制定的輕量級應用標準,目標直指微信小程序。它也是採用JavaScript語言開發,原生控件渲染,與React Native和Weex相比主要有兩點不同:

  1. 快應用自身不支持Vue或React語法,其採用原生JavaScript開發,其開發框架和微信小程序很像,值得一提的是小程序目前已經可以使用Vue語法開發(mpvue),從原理上來講,Vue的語法也可以移植到快應用上。
  2. React Native和Weex的渲染/排版引擎是集成到框架中的,每一個APP都需要打包一份,安裝包體積較大;而快應用渲染/排版引擎是集成到ROM中的,應用中無需打包,安裝包體積小,正因如此,快應用才能在保證性能的同時做到快速分發。

Flutter
Flutter是Google發佈的一個用於創建跨平臺、高性能移動應用的框架。Flutter和QT mobile一樣,都沒有使用原生控件,相反都實現了一個自繪引擎,使用自身的佈局、繪製系統。
跨平臺自繪引擎
Flutter與用於構建移動應用程序的其它大多數框架不同,因爲Flutter既不使用WebView,也不使用操作系統的原生控件。 相反,Flutter使用自己的高性能渲染引擎來繪製widget。這樣不僅可以保證在Android和iOS上UI的一致性,而且也可以避免對原生控件依賴而帶來的限制及高昂的維護成本。
Flutter使用Skia作爲其2D渲染引擎,Skia是Google的一個2D圖形處理函數庫,包含字型、座標轉換,以及點陣圖都有高效能且簡潔的表現,Skia是跨平臺的,並提供了非常友好的API,目前Google Chrome瀏覽器和Android均採用Skia作爲其繪圖引擎。
目前Flutter默認支持iOS、Android、Fuchsia(Google新的自研操作系統)三個移動平臺。但Flutter亦可支持Web開發(Flutter for web)和PC開發,本書的示例和介紹主要是基於iOS和Android平臺的,其它平臺讀者可以自行了解。
高性能
Flutter高性能主要靠兩點來保證,首先,Flutter APP採用Dart語言開發。Dart在 JIT(即時編譯)模式下,速度與 JavaScript基本持平。但是 Dart支持 AOT,當以 AOT模式運行時,JavaScript便遠遠追不上了。速度的提升對高幀率下的視圖數據計算很有幫助。其次,Flutter使用自己的渲染引擎來繪製UI,佈局數據等由Dart語言直接控制,所以在佈局過程中不需要像RN那樣要在JavaScript和Native之間通信,這在一些滑動和拖動的場景下具有明顯優勢,因爲在滑動和拖動過程往往都會引起佈局發生變化,所以JavaScript需要和Native之間不停的同步佈局信息,這和在瀏覽器中要JavaScript頻繁操作DOM所帶來的問題是相同的,都會帶來比較可觀的性能開銷

(5)高端技術面試題 (大廠必備)
Universal-ImageLoader,Picasso,Fresco,Glide對比( 源碼的話,得自己去看)
Fresco 是 Facebook 推出的開源圖片緩存工具,主要特點包括:兩個內存緩存加上 Native 緩存構成了三級緩存,
優點:

  1. 圖片存儲在安卓系統的匿名共享內存, 而不是虛擬機的堆內存中, 圖片的中間緩衝數據也存放在本地堆內存, 所以, 應用程序有更多的內存使用, 不會因爲圖片加載而導致oom, 同時也減少垃圾回收器頻繁調用回收 Bitmap 導致的界面卡頓, 性能更高。
  2. 漸進式加載 JPEG 圖片, 支持圖片從模糊到清晰加載。
  3. 圖片可以以任意的中心點顯示在 ImageView, 而不僅僅是圖片的中心。
  4. JPEG 圖片改變大小也是在 native 進行的, 不是在虛擬機的堆內存, 同樣減少 OOM。
  5. 很好的支持 GIF 圖片的顯示。
    缺點:
  6. 框架較大, 影響 Apk 體積
  7. 使用較繁瑣
    Universal-ImageLoader:(估計由於HttpClient被Google放棄,作者就放棄維護這個框架)
    優點:
    1.支持下載進度監聽
    2.可以在 View 滾動中暫停圖片加載,通過 PauseOnScrollListener 接口可以在 View 滾動中暫停圖片加載。
    3.默認實現多種內存緩存算法 這幾個圖片緩存都可以配置緩存算法,不過 ImageLoader 默認實現了較多緩存算法,如 Size 最大先刪除、使用最少先刪除、最近最少使用、先進先刪除、時間最長先刪除等。
    4.支持本地緩存文件名規則定義
    Picasso 優點
  8. 自帶統計監控功能。支持圖片緩存使用的監控,包括緩存命中率、已使用內存大小、節省的流量等。
    2.支持優先級處理。每次任務調度前會選擇優先級高的任務,比如 App 頁面中 Banner 的優先級高於 Icon 時就很適用。
    3.支持延遲到圖片尺寸計算完成加載
    4.支持飛行模式、併發線程數根據網絡類型而變。 手機切換到飛行模式或網絡類型變換時會自動調整線程池最大併發數,比如 wifi 最大併發爲 4,4g 爲 3,3g 爲 2。 這裏 Picasso 根據網絡類型來決定最大併發數,而不是 CPU 核數。
    5.“無”本地緩存。無”本地緩存,不是說沒有本地緩存,而是 Picasso 自己沒有實現,交給了 Square 的另外一個網絡庫 okhttp 去實現,這樣的好處是可以通過請求 Response Header 中的 Cache-Control 及 Expired 控制圖片的過期時間。
    Glide 優點
  9. 不僅僅可以進行圖片緩存還可以緩存媒體文件。Glide 不僅是一個圖片緩存,它支持 Gif、WebP、縮略圖。甚至是 Video,所以更該當做一個媒體緩存。
  10. 支持優先級處理。
  11. 與 Activity/Fragment 生命週期一致,支持 trimMemory。Glide 對每個 context 都保持一個 RequestManager,通過 FragmentTransaction 保持與 Activity/Fragment 生命週期一致,並且有對應的 trimMemory 接口實現可供調用。
  12. 支持 okhttp、Volley。Glide 默認通過 UrlConnection 獲取數據,可以配合 okhttp 或是 Volley 使用。實際 ImageLoader、Picasso 也都支持 okhttp、Volley。
  13. 內存友好。Glide 的內存緩存有個 active 的設計,從內存緩存中取數據時,不像一般的實現用 get,而是用 remove,再將這個緩存數據放到一個 value 爲軟引用的 activeResources map 中,並計數引用數,在圖片加載完成後進行判斷,如果引用計數爲空則回收掉。內存緩存更小圖片,Glide 以 url、view_width、view_height、屏幕的分辨率等做爲聯合 key,將處理後的圖片緩存在內存緩存中,而不是原始圖片以節省大小與 Activity/Fragment 生命週期一致,支持 trimMemory。圖片默認使用默認 RGB_565 而不是 ARGB_888,雖然清晰度差些,但圖片更小,也可配置到 ARGB_888。
    6.Glide 可以通過 signature 或不使用本地緩存支持 url 過期

Xutils, OKhttp, Volley, Retrofit對比(源碼需要自己去看)
Xutils這個框架非常全面,可以進行網絡請求,可以進行圖片加載處理,可以數據儲存,還可以對view進行註解,使用這個框架非常方便,但是缺點也是非常明顯的,使用這個項目,會導致項目對這個框架依賴非常的嚴重,一旦這個框架出現問題,那麼對項目來說影響非常大的。、

OKhttp:Android開發中是可以直接使用現成的api進行網絡請求的。就是使用HttpClient,HttpUrlConnection進行操作。okhttp針對Java和Android程序,封裝的一個高性能的http請求庫,支持同步,異步,而且okhttp又封裝了線程池,封裝了數據轉換,封裝了參數的使用,錯誤處理等。API使用起來更加的方便。但是我們在項目中使用的時候仍然需要自己在做一層封裝,這樣才能使用的更加的順手。

Volley:Volley是Google官方出的一套小而巧的異步請求庫,該框架封裝的擴展性很強,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley裏面也封裝了ImageLoader,所以如果你願意你甚至不需要使用圖片加載框架,不過這塊功能沒有一些專門的圖片加載框架強大,對於簡單的需求可以使用,稍複雜點的需求還是需要用到專門的圖片加載框架。Volley也有缺陷,比如不支持post大數據,所以不適合上傳文件。不過Volley設計的初衷本身也就是爲頻繁的、數據量小的網絡請求而生。

Retrofit:Retrofit是Square公司出品的默認基於OkHttp封裝的一套RESTful網絡請求框架,RESTful是目前流行的一套api設計的風格, 並不是標準。Retrofit的封裝可以說是很強大,裏面涉及到一堆的設計模式,可以通過註解直接配置請求,可以使用不同的http客戶端,雖然默認是用http ,可以使用不同Json Converter 來序列化數據,同時提供對RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以說是目前比較潮的一套框架,但是需要有比較高的門檻。

Volley VS OkHttp
Volley的優勢在於封裝的更好,而使用OkHttp你需要有足夠的能力再進行一次封裝。而OkHttp的優勢在於性能更高,因爲 OkHttp基於NIO和Okio ,所以性能上要比 Volley更快。IO 和 NIO這兩個都是Java中的概念,如果我從硬盤讀取數據,第一種方式就是程序一直等,數據讀完後才能繼續操作這種是最簡單的也叫阻塞式IO,還有一種是你讀你的,程序接着往下執行,等數據處理完你再來通知我,然後再處理回調。而第二種就是 NIO 的方式,非阻塞式, 所以NIO當然要比IO的性能要好了,而 Okio是 Square 公司基於IO和NIO基礎上做的一個更簡單、高效處理數據流的一個庫。理論上如果Volley和OkHttp對比的話,更傾向於使用 Volley,因爲Volley內部同樣支持使用OkHttp,這點OkHttp的性能優勢就沒了, 而且 Volley 本身封裝的也更易用,擴展性更好些。

OkHttp VS Retrofit
毫無疑問,Retrofit 默認是基於 OkHttp 而做的封裝,這點來說沒有可比性,肯定首選 Retrofit。

Volley VS Retrofit
這兩個庫都做了不錯的封裝,但Retrofit解耦的更徹底,尤其Retrofit2.0出來,Jake對之前1.0設計不合理的地方做了大量重構, 職責更細分,而且Retrofit默認使用OkHttp,性能上也要比Volley佔優勢,再有如果你的項目如果採用了RxJava ,那更該使用 Retrofit 。所以這兩個庫相比,Retrofit更有優勢,在能掌握兩個框架的前提下該優先使用 Retrofit。但是Retrofit門檻要比Volley稍高些,要理解他的原理,各種用法,想徹底搞明白還是需要花些功夫的,如果你對它一知半解,那還是建議在商業項目使用Volley吧。

(6)算法(大廠必須)
得自己去刷題了
強調文字

2020年Android面試題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章