吐血整理,看完可達年薪百萬,高級Android工程師必備知識點

Java部分

Java基礎

java基礎面試知識點

1. java中==和equals和hashCode的區別

基本數據類型的比較用==,這樣是比較它們的值。
引用類型(類,接口,數組)的比較,如果是雙等號,那麼比較的是它們的內存地址,除非是同一個new出來的對象,他們的比較後的結果爲true,否則比較後結果爲false。
看一下源碼大家都會明白,對於-128到127之間的數,會進行緩存,Integer b1 = 127時,會將127進行緩存,下次再寫Integer i6 = 127時,就會直接從緩存中取,就不會new了。
Integer b1 = 127;java在編譯的時候,被翻譯成-> Integer b1 = Integer.valueOf(127);
eauals一般都是通過對象的內容是否相等來判斷對象是否相等.
hascode部分
a.如果兩個對象equals,Java運行時環境會認爲他們的hashcode一定相等。
b.如果兩個對象不equals,他們的hashcode有可能相等。
c.如果兩個對象hashcode相等,他們不一定equals。
d.如果兩個對象hashcode不相等,他們一定不equals
引用鏈接: java中==和equals和hashCode的區別

2. int、char、long各佔多少字節數

Java基本類型佔用的字節數:
1字節: byte , boolean
2字節: short , char
4字節: int , float
8字節: long , double
編碼與中文:
Unicode/GBK: 中文2字節
UTF-8: 中文通常3字節,在拓展B區之後的是4字節
綜上,中文字符在編碼中佔用的字節數一般是2-4個字節

3. int與integer的區別

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

4.談談對Java多態的理解

理解的要點:多態意味着父親的變量可以指向子類對象
面向對象程序設計的三大支柱是封裝、繼承和多態
封裝對外把相應的屬性和方法實現的細節進行了隱藏。繼承關係使一個子類繼承父親的特徵,並且加上了一些新的特徵。子類是它的父親的特殊化,

每一個子類的實例都是其父親的實例,但是反過來就不成立。例如:每個圓都是一個幾何對象,但並非每一個幾何對象都是圓。因此,總可以將子類的實例傳給需要父親型的參數

5. String、StringBuffer、StringBuilder區別

1、長度是否可變
String 是被 final 修飾的,他的長度是不可變的,就算調用 String 的concat 方法,那也是把字符串拼接起來並重新創建一個對象,把拼接後的 String 的值賦給新創建的對象
StringBuffer 和 StringBuilder 類的對象能夠被多次的修改,並且不產生新的未使用對象,StringBuffer 與 StringBuilder 中的方法和功能完全是等價的。調用StringBuffer 的 append 方法,來改變 StringBuffer 的長度,並且,相比較於 StringBuffer,String 一旦發生長度變化,是非常耗費內存的!
2、執行效率
三者在執行速度方面的比較:StringBuilder > StringBuffer > String
3、應用場景
如果要操作少量的數據用 = String
單線程操作字符串緩衝區 下操作大量數據 = StringBuilder
多線程操作字符串緩衝區 下操作大量數據 = StringBuffer
StringBuffer和StringBuilder區別
1、是否線程安全
StringBuilder 類在 Java 5 中被提出,它和 StringBuffer 之間的最大不同在於 StringBuilder 的方法不是線程安全的(不能同步訪問),StringBuffer是線程安全的。只是StringBuffer 中的方法大都採用了 synchronized 關鍵字進行修飾,因此是線程安全的,而 StringBuilder 沒有這個修飾,可以被認爲是線程不安全的。
2、應用場景
由於 StringBuilder 相較於 StringBuffer 有速度優勢,所以多數情況下建議使用 StringBuilder 類。
然而在應用程序要求線程安全的情況下,則必須使用 StringBuffer 類。 append方法與直接使用+串聯相比,減少常量池的浪費

6.什麼是內部類?內部類的作用?

在java語言中,可以把一個類定義到另外一個類的內部,在類裏面的這個類就叫內部類,外面的類就叫外部類。
內部類好處
1.隱藏你不想讓別人知道的操作,也即封裝性。
2.一個內部類對象可以訪問創建它的外部類對象的內容,甚至包括私有變量!
內部類可以分爲多種;主要以下4種:靜態內部類,成員內部類,局部內部類,匿名內部類

靜態內部類
靜態內部類是指被聲明爲static的內部類,他可以不依賴內部類而實例,而通常的內部類需要實例化外部類,從而實例化。靜態內部類不可以有與外部類有相同的類名。不能訪問外部類的普通成員變量,但是可以訪問靜態成員變量和靜態方法(包括私有類型)

成員內部類
一個 靜態內部類去掉static 就是成員內部類,他可以自由的引用外部類的屬性和方法,無論是靜態還是非靜態。但是不可以有靜態屬性和方法

局部內部類
定義在一個代碼塊的內類,他的作用範圍是所在代碼塊,是內部類中最少使用的一類型。局部內部類跟局部變量一樣,不能被public ,protected,private以及static修飾,只能訪問方法中定義final類型的局部變量

匿名內部類
匿名內部類是一種沒有類名的內部類,不使用class,extends,implements,沒有構造函數,他必須繼承其他類或實現其他接口。匿名內部類的好處是使代碼更加簡潔,緊湊,但是帶來的問題是易讀性下降。

內部類的使用時機
1、實現事件監聽器的時候(比方說actionListener 。。。採用內部類很容易實現);
2、編寫事件驅動時(內部類的對象可以訪問外部類的成員方法和變量,注意包括私有成員);
3、在能實現功能的情況下,爲了節省編譯後產生的字節碼(內部類可以減少字節碼文件,即java文件編譯後的.class文件);

7.抽象類和接口區別

1.抽象類可以有構造方法,接口中不能有構造方法。
2.抽象類中可以有普通成員變量,接口中沒有普通成員變量
3.抽象類中可以包含非抽象的普通方法,接口中的所有方法必須都是抽象的,不能有非抽象的普通方法。
4. 抽象類中的抽象方法的訪問類型可以是public,protected和(默認類型,雖然eclipse下不報錯,但應該也不行),但接口中的抽象方法只能是public類型的,並且默認即爲public abstract類型。
5. 抽象類中可以包含靜態方法,接口中不能包含靜態方法
6. 抽象類和接口中都可以包含靜態成員變量,抽象類中的靜態成員變量的訪問類型可以任意,但接口中定義的變量只能是public static final類型,並且默認即爲public static final類型。
7. 一個類可以實現多個接口,但只能繼承一個抽象類。

8.抽象類的概述與特點

a:抽象類和抽象方法必須用abstract修飾
*abstract class 類名()
*public abstract void eat()
b:抽象類不一定有抽象方法,有抽象方法的類一定是抽象類或者是抽象接口
c:抽象類不能實例化,那麼抽象類如何實例化呢?
按照多態的方式,由具體的子類實例化,其實這也是多態的一種,抽象類多態
d:抽象類的子類
要麼是抽象類要麼重寫抽象類中的所有抽象方法

9. 泛型中extends和super的區別

? extends T :表示上界是T, ? 都是繼承自T的,都是T的子類;
? super T :表示下界是T,?都是T的父類;
第一、 頻繁往外讀取內容的,適合用 ? extends T;
第二、 經常往裏插入的,適合用 ? super T;

10.父類的靜態方法能否被子類重寫

不能重寫,但子類能把父類的靜態方法繼承過來,子類可以重新定義父類的靜態方法,並將父類的靜態方法屏蔽掉,取消屏蔽可以使用父類名.靜態方法名的方式調用。

11.進程和線程的區別

(1)進程
進程是程序的一次執行過程,是一個動態概念,是程序在執行過程中分配和管理資源的基本單位,每一個進程都有一個自己的地址空間,至少有 5 種基本狀態,它們是:初始態,執行態,等待狀態,就緒狀態,終止狀態。

(2)線程
線程是CPU調度和分派的基本單位,它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。

(3)聯繫
線程是進程的一部分,一個線程只能屬於一個進程,而一個進程可以有多個線程,但至少有一個線程。

(4)區別:理解它們的差別,我從資源使用的角度出發。(所謂的資源就是計算機裏的中央處理器,內存,文件,網絡等等)

根本區別:進程是操作系統資源分配的基本單位,而線程是任務調度和執行的基本單位

在開銷方面:每個進程都有獨立的代碼和數據空間(程序上下文),程序之間的切換會有較大的開銷;線程可以看做輕量級的進程,同一類線程共享代碼和數據空間,每個線程都有自己獨立的運行棧和程序計數器(PC),線程之間切換的開銷小。

所處環境:在操作系統中能同時運行多個進程(程序);而在同一個進程(程序)中有多個線程同時執行(通過CPU調度,在每個時間片中只有一個線程執行)

內存分配方面:系統在運行的時候會爲每個進程分配不同的內存空間;而對線程而言,除了CPU外,系統不會爲線程分配內存(線程所使用的資源來自其所屬進程的資源),線程組之間只能共享資源。

包含關係:沒有線程的進程可以看做是單線程的,如果一個進程內有多個線程,則執行過程不是一條線的,而是多條線(線程)共同完成的;線程是進程的一部分,所以線程也被稱爲輕權進程或者輕量級進程。

借鑑地址:https://www.cnblogs.com/jobbible/p/9766649.html

12.final,finally,finalize的區別

1、final修飾符(關鍵字)。被final修飾的類,就意味着不能再派生出新的子類,不能作爲父類而被子類繼承。因此一個類不能既被abstract聲明,又被final聲明。將變量或方法聲明爲final,可以保證他們在使用的過程中不被修改。被聲明爲final的變量必須在聲明時給出變量的初始值,而在以後的引用中只能讀取。被final聲明的方法也同樣只能使用,即不能方法重寫。
2、finally是在異常處理時提供finally塊來執行任何清除操作。不管有沒有異常被拋出、捕獲,finally塊都會被執行。try塊中的內容是在無異常時執行到結束。catch塊中的內容,是在try塊內容發生catch所聲明的異常時,跳轉到catch塊中執行。finally塊則是無論異常是否發生,都會執行finally塊的內容,所以在代碼邏輯中有需要無論發生什麼都必須執行的代碼,就可以放在finally塊中。

3、finalize是方法名。java技術允許使用finalize()方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在確定這個對象沒有被引用時對這個對象調用的。它是在object類中定義的,因此所有的類都繼承了它。子類覆蓋finalize()方法以整理系統資源或者被執行其他清理工作。finalize()方法是在垃圾收集器刪除對象之前對這個對象調用的。

13.序列化的方式

序列化的定義:將數據對象轉換爲二進制流的過程就稱爲對象的序列化(Serialization),反過來,將二進制流轉換爲對象就是反序列化(Deserializable)
1、數據持久化:在很多應用中,需要對好多對象進行序列化,存到物理硬盤,較長時間的保存,比如,Session對象,當有數萬用戶併發訪問的時候,就會有數萬的Session對象,內存會承受很大的壓力,這個時候,就會把一些對象先序列化到硬盤中,需要使用的時候再還原到內存中。序列化對象要保留充分的信息,用來恢復數據對象,但是爲了節約存儲空間和網絡帶寬,序列化出的二進制流要儘可能小。

2、網絡傳輸:當兩個進程在互相通信的時候,就會進行數據傳輸,不管是何種類型的數據,都必須要轉成二進制流來傳輸,接受方收到後再轉爲數據對象

java原生序列化:java類通過實現Serializable接口來實現。這個接口沒有任何方法,只是標識,java序列化保留了對象的元數據,以及對象數據,兼容性最好,但是不支持跨語言,性能也一般。

14.Serializable 和Parcelable 的區別

1、作用

Serializable的作用是爲了保存對象的屬性到本地文件、數據庫、網絡流、RMI(Remote Method Invocation)以方便數據傳輸,當然這種傳輸可以是程序內的也可以是兩個程序間的。使用了反射技術,並且期間產生臨時對象

Android的Parcelable的設計初衷是因爲Serializable效率過慢,爲了在程序內不同組件間以及不同Android程序間(AIDL)高效的傳輸數據而設計,這些數據僅在內存中存在,Parcelable是通過IBinder通信的消息的載體。

2、效率及選擇

Parcelable的性能比Serializable好,在內存開銷方面較小,所以在內存間數據傳輸時推薦使用Parcelable,如activity間傳輸數據,而Serializable可將數據持久化方便保存,所以在需要保存或網絡傳輸數據時選擇Serializable,因爲android不同版本Parcelable可能不同,所以不推薦使用Parcelable進行數據持久化

15.靜態屬性和靜態方法是否可以被繼承?是否可以被重寫?以及原因?

父類的靜態屬性和方法可以被子類繼承

不可以被子類重寫:當父類的引用指向子類時,使用對象調用靜態方法或者靜態變量,是調用的父類中的方法或者變量。並沒有被子類改寫。

原因:

因爲靜態方法從程序開始運行後就已經分配了內存,也就是說已經寫死了。所有引用到該方法的對象(父類的對象也好子類的對象也好)所指向的都是同一塊內存中的數據,也就是該靜態方法。

子類中如果定義了相同名稱的靜態方法,並不會重寫,而應該是在內存中又分配了一塊給子類的靜態方法,沒有重寫這一說。

16.靜態內部類的設計意圖

我們知道,在java中類是單繼承的,一個類只能繼承另一個具體類或抽象類(可以實現多個接口)。這種設計的目的是因爲在多繼承中,當多個父類中有重複的屬性或者方法時,子類的調用結果會含糊不清,因此用了單繼承。

而使用內部類的原因是:每個內部類都能獨立地繼承一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響

靜態內部類與非靜態內部類之間存在一個最大的區別:非靜態內部類在編譯完成之後會隱含地保存着一個引用,該引用是指向創建它的外圍內,但是靜態內部類卻沒有。

沒有這個引用就意味着:
它的創建是不需要依賴於外圍類的。
它不能使用任何外圍類的非static成員變量和方法。

17.成員內部類、靜態內部類、局部內部類和匿名內部類的理解,以及項目中的應用

成員內部類:雖然成員內部類可以無條件地訪問外部類的成員,而外部類想訪問成員內部類的成員卻不是這麼隨心所欲了。在外部類中如果要訪問成員內部類的成員,必須先創建一個成員內部類的對象,再通過指向這個對象的引用來訪問
局部內部類:局部內部類是定義在一個方法或者一個作用域裏面的類,它和成員內部類的區別在於局部內部類的訪問僅限於方法內或者該作用域內。
匿名內部類:匿名內部類應該是平時我們編寫代碼時用得最多的,在編寫事件監聽的代碼時使用匿名內部類不但方便,而且使代碼更加容易維護。
靜態內部類:靜態內部類也是定義在另一個類裏面的類,只不過在類的前面多了一個關鍵字static。靜態內部類是不需要依賴於外部類的,這點和類的靜態成員屬性有點類似,並且它不能使用外部類的非static成員變量或者方法,這點很好理解,因爲在沒有外部類的對象的情況下,可以創建靜態內部類的對象,如果允許訪問外部類的非static成員就會產生矛盾,因爲外部類的非static成員必須依附於具體的對象。

18.閉包和內部類的區別

通過內部類加接口,可以很好的實現多繼承的效果。
閉包能夠將一個方法作爲一個變量去存儲,這個方法有能力去訪問所在類的自由變量。

19.string 轉換成 integer的方式及原理

總結:integer.parseInt(string str)方法調用Integer內部的
parseInt(string str,10)方法,默認基數爲10,parseInt內部首先
判斷字符串是否包含符號(-或者+),則對相應的negative和limit進行
賦值,然後再循環字符串,對單個char進行數值計算Character.digit(char ch, int radix)在這個方法中,函數肯定進入到0-9字符的判斷(相對於string轉換到int),否則會拋出異常,數字就是如上面進行拼接然後生成的int類型數值。
https://blog.csdn.net/itboy_libing/article/details/80393530

java深入源碼級的面試題(有難度)

20.哪些情況下的對象會被垃圾回收機制處理掉?

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

21.講一下常見編碼方式?

計算機中存儲信息的最小單元是一個字節,即8個bit,所以能表示的字符範圍是0~255個
人類要表示的符號太多,無法用一個字節來完全表示
要解決這個矛盾必須要有一個新的數據結構char,從char到byte必須編碼。目前常用的編碼方式有ASCII、ISO8859-1、GB2312、GBK、UTF-8、UTF-16等

22.utf-8編碼中的中文佔幾個字節;int型幾個字節?

一個utf8數字佔1個字節
一個utf8英文字母佔1個字節
少數是漢字每個佔用3個字節,多數佔用4個字節。

23.靜態代理和動態代理的區別,什麼場景使用?

靜態代理類:由程序員創建或由特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。
動態代理類:在程序運行時,運用反射機制動態創建而成。
靜態代理通常只代理一個類,動態代理是代理一個接口下的多個實現類。
靜態代理事先知道要代理的是什麼,而動態代理不知道要代理什麼東西,只有在運行時才知道。
動態代理是實現JDK裏的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的業務類必須要實現接口,通過Proxy裏的newProxyInstance得到代理對象。
還有一種動態代理CGLIB,代理的是類,不需要業務類繼承接口,通過派生的子類來實現代理。通過在運行時,動態修改字節碼達到修改類的目的。
應用:被代理類龐大時,需要在某些方法執行前後處理一些事情時,亦或接口類與實現類經常變動時(因爲使用反射所以方法的增刪改並不需要修改invoke方法)。

24.Java的異常體系

https://www.cnblogs.com/aspirant/p/10790803.html
Java把異常作爲一種類,當做對象來處理。所有異常類的基類是Throwable類,兩大子類分別是Error和Exception。
系統錯誤由Java虛擬機拋出,用Error類表示。Error類描述的是內部系統錯誤,例如Java虛擬機崩潰。這種情況僅憑程序自身是無法處理的,在程序中也不會對Error異常進行捕捉和拋出。
異常(Exception)又分爲RuntimeException(運行時異常)和CheckedException(檢查時異常),兩者區別如下:
RuntimeException:程序運行過程中才可能發生的異常。一般爲代碼的邏輯錯誤。例如:類型錯誤轉換,數組下標訪問越界,空指針異常、找不到指定類等等。
CheckedException:編譯期間可以檢查到的異常,必須顯式的進行處理(捕獲或者拋出到上一層)。例如:IOException, FileNotFoundException等等。

25.談談你對解析與分派的認識。

1.方法在程序真正運行之前就有一個可確定的調用版本,並且這個方法的調用版本在運行期間是不可變的,即“編譯時可知,運行不可以變”,這類目標的方法的調用稱之爲解析
2.解析調用一定是個靜態的過程,在編譯期就完全確定,在類加載的解析階段就將涉及的符號引用全部轉變爲可以確定的直接引用,不會延遲到運行期再去完成。而分派(Dispatch)調用則可能是靜態的也可能是動的。於是分派方式就有靜態分派和動態分派。
靜態分派的最直接的解釋是在重載的時候是通過參數的靜態類型而不是實際類型作爲判斷依據的。因此在編譯階段,Javac編譯器會根據參數的靜態類型決定使用哪個重載版本。
顯然這裏不可能根據靜態類型來決定調用那個方法。導致這個現象很明顯的原因是因爲這兩個變量的實際類型不一樣,jvm根據實際類型來分派方法執行版本。

26.Java中實現多態的機制是什麼?

靠的是父類或接口的引用指向子類或實現類的對象,
調用的方法是內存中正在運行的那個對象的方法。

27.如何將一個Java對象序列化到文件裏

1.對象需要實現Seralizable接口
2.通過ObjectOutputStream的writeObject()方法寫入和ObjectInputStream的readObject()方法來進行讀取

28.說說你對Java反射的理解

先講反射機制,反射就是程序運行期間JVM會對任意一個類洞悉它的屬性和方法,對任意一個對象都能夠訪問它的屬性和方法。依靠此機制,可以動態的創建一個類的對象和調用對象的方法。其次就是反射相關的API,只講一些常用的,比如獲取一個Class對象。Class.forName(完整類名)。通過Class對象獲取類的構造方法,class.getConstructor。根據class對象獲取類的方法,getMethod和getMethods。使用class對象創建一個對象,class.newInstance等。最後可以說一下反射的優點和缺點,優點就是增加靈活性,可以在運行時動態獲取對象實例。缺點是反射的效率很低,而且會破壞封裝,通過反射可以訪問類的私有方法,不安全。

29.說說你對Java註解的理解

Java註解(Annotation)也叫作元數據,以‘@註解名’在代碼中存在,它是一種在源代碼中標註的特殊標記,可以標註源代碼中的類、屬性、方法、參數等代碼,主要用於創建文檔,跟蹤代碼中的依賴性,執行基本編譯時檢查。

30.你對依賴注入的理解

組件之間依賴關係由容器在運行期決定
依賴注入的目的並非爲軟件系統帶來更多功能,而是爲了提升組件重用的頻率,併爲系統搭建一個靈活、可擴展的平臺。通過依賴注入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。

誰依賴於誰:當然是應用程序依賴於IoC容器;
爲什麼需要依賴:應用程序需要IoC容器來提供對象需要的外部資源;
誰注入誰:很明顯是IoC容器注入應用程序某個對象,應用程序依賴的對象;
注入了什麼:就是注入某個對象所需要的外部資源(包括對象、資源、常量數據)

31.說一下泛型原理

泛型的作用在於在編譯階段保證我們使用了正確的類型,並且由編譯器幫我們加入轉型動作,使得轉型是不需要關心且安全的。
Java的泛型是僞泛型。在編譯期間,所有的泛型信息都會被擦除掉。正確理解泛型概念的首要前提是理解類型擦出(type erasure)。
Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會在編譯器在編譯的時候去掉。這個過程就稱爲類型擦除。

32.String爲什麼要設計成不可變的?

第一:在Java程序中String類型是使用最多的,這就牽扯到大量的增刪改查,每次增刪改差之前其實jvm需要檢查一下這個String對象的安全性,就是通過hashcode,當設計成不可變對象時候,就保證了每次增刪改查的hashcode的唯一性,也就可以放心的操作。

第二:網絡連接地址URL,文件路徑path通常情況下都是以String類型保存, 假若String不是固定不變的,將會引起各種安全隱患。就好比我們的密碼不能以String的類型保存,,如果你將密碼以明文的形式保存成字符串,那麼它將一直留在內存中,直到垃圾收集器把它清除。而由於字符串被放在字符串緩衝池中以方便重複使用,所以它就可能在內存中被保留很長時間,而這將導致安全隱患

第三:字符串值是被保留在常量池中的,也就是說假若字符串對象允許改變,那麼將會導致各種邏輯錯誤

33.Object類的equal和hashCode方法重寫,爲什麼?

字段屬性值完全相同的兩個對象因爲hashCode不同,所以在hashmap中的table數組的下標不同,從而這兩個對象就會同時存在於集合中,所以重寫equals()就一定要重寫hashCode()方法

數據結構

34.併發集合瞭解哪些?

(1)阻塞式集合(blocking collection):這類集合包括添加和移除數據的方法。當集合已滿或者爲空時,被調用的添加或者 移除方法就不能立即執行,那麼調用這個飯方法的線程將被阻塞,一直到該方法可以被成功執行。
(2)非阻塞式集合(Non-blocking collection):這類集合也包括添加和移除數據的方法。如果集合已滿或者爲空時,在調用添加或者移除方法時會返回null或者拋出異常,但是調用這個方法的線程不會被阻塞。
以下就是java常用的併發集合:
非阻塞式列表對應的實現類:ConcurrentLinkedDeque
阻塞式列表對應的實現類:LinkedBlockingDeque
用於數據生成或者消費的阻塞式列表對應的實現類:LinkedTransferQueue
按優先級排序列表元素的阻塞式列表對應的實現類:PriorityBlockingQueue
帶有延遲列表元素的阻塞式列表對應的實現類:DelayQueue
非阻塞式列表可遍歷映射對應的餓實現類:ConcurrentSkipListMap
隨機數字對應的實現類:ThreadLockRandom
原子變量對應的實現類:AtomicLong和AtomicIntegerArray

35.Java集合框架繼承關係

Collection<–List<–Vector
Collection<–List<–ArrayList
Collection<–List<–LinkedList
Collection<–Set<–HashSet
Collection<–Set<–HashSet<–LinkedHashSet
Collection<–Set<–SortedSet<–TreeSet
Map<–HashMap
Map<–TreeMap

36.List,Set,Map的區別

List:
1.可以允許重複的對象。
2.可以插入多個null元素。
3.是一個有序容器,保持了每個元素的插入順序,輸出的順序就是插入的順序。
4.常用的實現類有 ArrayList、LinkedList 和 Vector。

Set:
1.不允許重複對象
2.無序容器,你無法保證每個元素的存儲順序,TreeSet通過 Comparator 或者 Comparable 維護了一個排序順序。
3.只允許一個 null 元素
4.Set 接口最流行的幾個實現類是 HashSet、LinkedHashSet 以及 TreeSet。

Map:
1.Map不是collection的子接口或者實現類。Map是一個接口。
2.Map 的 每個 Entry 都持有兩個對象,也就是一個鍵一個值(鍵值對),Map 可能會持有相同的值對象但鍵對象必須是唯一的。
3.TreeMap 也通過 Comparator 或者 Comparable 維護了一個排序順序。
4.Map 裏你可以擁有隨意個 null 值但最多只能有一個 null 鍵。
5.Map 接口最流行的幾個實現類是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)

37.List和Map的實現方式以及存儲方式

List:
常用實現方式有:ArrayList和LinkedList
ArrayList 的存儲方式:數組,查詢快
LinkedList的存儲方式:鏈表,插入,刪除快
Set:
常用實現方式有:HashSet和TreeSet
HashSet的存儲方式:哈希碼算法,加入的對象需要實現hashcode()方法,快速查找元素
TreeSet的存儲方式:按序存放,想要有序就要實現Comparable接口
附加:
集合框架提供了2個實用類:collections(排序,複製、查找)和Arrays對數組進行(排序,複製、查找)
Map:
常用實現方式有:HashMap和TreeMap
HashMap的存儲方式:哈希碼算法,快速查找鍵值
TreeMap存儲方式:對鍵按序存放

38.HashMap的實現原理

HashMap基於hashing原理,我們通過put()和get()方法儲存和獲取對象。當我們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode,讓後找到bucket位置來儲存值對象。當獲取對象時,通過鍵對象的equals()方法找到正確的鍵值對,然後返回值對象。HashMap使用鏈表來解決碰撞問題,當發生碰撞了,對象將會儲存在鏈表的下一個節點中。 HashMap在每個鏈表節點中儲存鍵值對對象。

39.HashMap數據結構

jdk1.7 中使用個 Entry 數組來存儲數據,用key的 hashcode 取模來決定key會被放到數組裏的位置,如果 hashcode 相同,或者 hashcode 取模後的結果相同( hash collision ),那麼這些 key 會被定位到 Entry 數組的同一個格子裏,這些 key 會形成一個鏈表。在 hashcode 特別差的情況下,比方說所有key的 hashcode 都相同,這個鏈表可能會很長,那麼 put/get 操作都可能需要遍歷這個鏈表,也就是說時間複雜度在最差情況下會退化到 O(n)

jdk1.8 中使用一個 Node 數組來存儲數據,但這個 Node 可能是鏈表結構,也可能是紅黑樹結構,如果插入的 key 的 hashcode 相同,那麼這些key也會被定位到 Node 數組的同個格子裏。如果同一個格子裏的key不超過8個,使用鏈表結構存儲。如果超過了8個,那麼會調用 treeifyBin 函數,將鏈表轉換爲紅黑樹。那麼即使 hashcode 完全相同,由於紅黑樹的特點,查找某個特定元素,也只需要O(log n)的開銷也就是說put/get的操作的時間複雜度最差只有 O(log n),但是真正想要利用 JDK1.8 的好處,有一個限制:key的對象,必須正確的實現了 Compare 接口

40.HashMap如何put數據(從HashMap源碼角度講解)?

1:首先判斷數組是否被初始化,如果沒有初始化則調用擴容的方法resize,進行初始化。
2:獲取數組下標:index=(n-1)&hash,並賦值p=tab[(n-1)&hash]
3:如果p==null,則說明此下標index還沒有任何的值,所以直接把key-value封裝成Node放到數組中。
4:如果p!=null,說明此下標index已經有值了,要麼是鏈表,要麼是紅黑樹
4.1:如果是紅黑樹則直接調用putTreeVal方法,如果紅黑樹上已經存在key則直接覆蓋,如果不存在key則把新節點插入到紅黑樹,並對紅黑樹進行修復
4.2:如果是鏈表,則進行循環鏈表,如果鏈表中已經存在key,則這接覆蓋,如果不存在,則添加到鏈表的尾部,然後判斷鏈表是否達到了轉變紅黑的閥門,如果到了,直接鏈表到紅黑樹的轉變
4.3:從鏈表到紅黑樹的轉變,HashMap中會首先判斷數組的長度是否大於64,如果小於則調用resize()進行擴容,如果大於則進行鏈表到紅黑樹的轉變。
5:通過上面的操作,如果HashMap中存在將要插入的key,通過參數onlyIfAbSent判斷是否覆蓋舊值,如果onlyIfAbSent=true則覆蓋,否則則不覆蓋
6:如果HashMap中不存在將要插入的key,則插入,插入後需要判斷是否需要擴容,如果需要則調用resize()方法進行擴容。

41.HashMap怎麼手寫實現?

1.hashmap的實現
  ① 初始化
    1)定義一個Node<K, V>的數組來存放元素,但不立即初始化,在使用的時候再加載
    2)定義數組初始大小爲16
    3)定義負載因子,默認爲0.75,
    4)定義size用來記錄容器存放的元素數量
  ② put的實現思路
    1) 判斷容器是否爲空,爲空則初始化。
    2)判斷容器的size是否大於閥值,是的話就擴容爲以前長度的兩倍,並重新計算其中元素的存放位置,進行重新存放
    3)計算出key的index角標位置
    4)判斷計算出的index位置是否存在元素,存在的話則遍歷鏈表,判斷key是否存在,存在則更新,不存在則增加
  ③ get的實現思路
    1)通過key計算出它所在的index
    2)遍歷index位置處的鏈表,並獲取value返回。

/**
 * 自定義hashMap
 */
public class MyHashMap<K, V> implements MyMap<K, V>{
    //1.定義一個容器用來存放元素, 但不立即初始化,使用懶加載方式
    Node<K, V>[] table = null; 
    //2.定義容器的默認大小
    static int DEFAULT_INITIAL_CAPACITY = 16;
    //3.HashMap默認負載因子,負載因子越小,hash衝突機率越低,綜合結論得出0.75最爲合適
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //4.記錄當前容器實際大小
    static int size;
    @SuppressWarnings("unchecked")
    @Override
    public V put(K k, V v) {
        //1.判斷容器是否爲空爲空則初始化。
        if (table == null) {
            table = new Node[DEFAULT_INITIAL_CAPACITY];
        }
        //如果size大於閾值則進行擴容
        if (size > DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR) {
            resize();
        } 
        //2.計算出index角標
        int index = getIndex(k, DEFAULT_INITIAL_CAPACITY);   
        //3.將k-v鍵值對放進相對應的角標,如果計算出角標相同則以鏈表的形勢存放
        Node<K, V> node = table[index];
        if (node == null) {
            table[index] = new Node<>(k, v, null);
            size ++;
            return table[index].getValue();
        } else {
            Node<K, V> newNode = node;          
            //循環遍歷每個節點看看是否存在相同的key
            while (newNode != null) {
                //這裏要用equals 和 == 因爲key有可能是基本數據類型,也有可能是引用類型
                if (k.equals(newNode.getKey()) || k == newNode.getKey()) {
                    newNode.setValue(v);
                    size ++;
                    return v;
                } 
                newNode = node.getNextNode();
            }
            table[index] = new Node<K, V>(k, v, table[index]);
            size ++;           
            return table[index].getValue();
        }
    }
    /**
     * 獲取index
     * @param key
     * @param length
     * @return
     */
    public int getIndex(K key, int length) {
        int hashCode = key.hashCode();
        int index = hashCode % length;
        return index;
    }
    /**
     * 獲取key
     */
    @Override
    public V get(K k) {
        int index = getIndex(k, DEFAULT_INITIAL_CAPACITY);
        Node<K, V> node = table[index];
        if (k.equals(node.getKey()) || k == node.getKey()) {
            return node.getValue();
        } else {
            Node<K, V> nextNode = node.getNextNode();
            while(nextNode != null) {
                if (k.equals(nextNode.getKey()) || k == nextNode.getKey()) {
                    return nextNode.getValue();
                }
            }
        }
        return null;
    }
    /**
     * 對size進行擴容
     */
    @SuppressWarnings("unchecked")
    public void resize() {
        //1.創建新的table長度擴展爲以前的兩倍
        int newLength = DEFAULT_INITIAL_CAPACITY * 2;
        Node<K, V>[] newtable = new Node[newLength];
        //2.將以前table中的取出,並重新計算index存入
        for (int i = 0; i < table.length; i++) {
            Node<K, V> oldtable = table[i];
            while (oldtable != null) {
                //將table[i]的位置賦值爲空,
                table[i] = null;
                //計算新的index值
                K key = oldtable.getKey();
                int index = getIndex(key, newLength); 
                //將以前的nextnode保存下來
                Node<K, V> nextNode = oldtable.getNextNode();  
                //將newtable的值賦值在oldtable的nextnode上,如果以前是空,則nextnode也是空
                oldtable.setNextNode(newtable[index]);
                newtable[i] = oldtable;  
                //將以前的nextcode賦值給oldtable以便繼續遍歷
                oldtable = nextNode;
            }     
        }
        //3.將新的table賦值回老的table
        table = newtable;
        DEFAULT_INITIAL_CAPACITY = newLength;
        newtable = null;
        
    }
    @Override
    public int size() {
        return size;
    }
    @SuppressWarnings("hiding")
    class Node<K, V> implements Entry<K, V> {
        private K key;
        private V value;
        private Node<K, V> nextNode; //下一節點
        public Node(K key, V value, Node<K, V> nextNode) {
            super();
            this.key = key;
            this.value = value;
            this.nextNode = nextNode;
        }
        @Override
        public K getKey() {
            return this.key;
        }
        @Override
        public V getValue() {
            return this.value;
        }
        @Override
        public void setValue(V value) {
            this.value = value;
        }
        public Node<K, V> getNextNode() {
            return nextNode;
        }
        public void setNextNode(Node<K, V> nextNode) {
            this.nextNode = nextNode;
        }
        public void setKey(K key) {
            this.key = key;
        }
    }
    // 測試方法.打印所有的鏈表元素
    public void print() {
        for (int i = 0; i < table.length; i++) {
            Node<K, V> node = table[i];
            System.out.print("下標位置[" + i + "]");
            while (node != null) {
                System.out.print("[ key:" + node.getKey() + ",value:" + node.getValue() + "]");
                node = node.nextNode;
            }
            System.out.println();
        }
    } 
}

42.ConcurrentHashMap的實現原理

在JDK1.7中ConcurrentHashMap採用了數組+Segment+分段鎖的方式實現。
JDK8中ConcurrentHashMap參考了JDK8 HashMap的實現,採用了數組+鏈表+紅黑樹的實現方式來設計,內部大量採用CAS操作。
https://baijiahao.baidu.com/s?id=1617089947709260129&wfr=spider&for=pc

43.ArrayMap和HashMap的對比

HashMap和ArrayMap各自的優勢
1.查找效率
HashMap因爲其根據hashcode的值直接算出index,所以其查找效率是隨着數組長度增大而增加的。
ArrayMap使用的是二分法查找,所以當數組長度每增加一倍時,就需要多進行一次判斷,效率下降。
所以對於Map數量比較大的情況下,推薦使用
2.擴容數量
HashMap初始值16個長度,每次擴容的時候,直接申請雙倍的數組空間。
ArrayMap每次擴容的時候,如果size長度大於8時申請size*1.5個長度,大於4小於8時申請8個,小於4時申請4個。
這樣比較ArrayMap其實是申請了更少的內存空間,但是擴容的頻率會更高。因此,如果當數據量比較大的時候,還是使用HashMap更合適,因爲其擴容的次數要比ArrayMap少很多。

3.擴容效率
HashMap每次擴容的時候時重新計算每個數組成員的位置,然後放到新的位置。
ArrayMap則是直接使用System.arraycopy。
所以效率上肯定是ArrayMap更佔優勢。
這裏需要說明一下,網上有一種傳聞說因爲ArrayMap使用System.arraycopy更省內存空間,這一點我真的沒有看出來。arraycopy也是把老的數組的對象一個一個的賦給新的數組。當然效率上肯定arraycopy更高,因爲是直接調用的c層的代碼。
4.內存耗費
以ArrayMap採用了一種獨特的方式,能夠重複的利用因爲數據擴容而遺留下來的數組空間,方便下一個ArrayMap的使用。而HashMap沒有這種設計。
由於ArrayMap只緩存了長度是4和8的時候,所以如果頻繁的使用到Map,而且數據量都比較小的時候,ArrayMap無疑是相當的節省內存的。
5.總結
綜上所述,
數據量比較小,並且需要頻繁的使用Map存儲數據的時候,推薦使用ArrayMap。
而數據量比較大的時候,則推薦使用HashMap

44.HashTable實現原理

HashTable
和HashMap一樣,Hashtable 也是一個散列表,它存儲的內容是鍵值對(key-value)映射。
Hashtable 繼承於Dictionary,實現了Map、Cloneable、java.io.Serializable接口。
Hashtable 的函數都是同步的,這意味着它是線程安全的。它的key、value都不可以爲null。
Hashtable中的映射不是有序的。

45.TreeMap具體實現

TreeMap存儲K-V鍵值對,通過紅黑樹(R-B tree)實現;
TreeMap繼承了NavigableMap接口,NavigableMap接口繼承了SortedMap接口,可支持一系列的導航定位以及導航操作的方法,當然只是提供了接口,需要TreeMap自己去實現;
TreeMap實現了Cloneable接口,可被克隆,實現了Serializable接口,可序列化;
TreeMap因爲是通過紅黑樹實現,紅黑樹結構天然支持排序,默認情況下通過Key值的自然順序進行排序

46.HashMap和HashTable的區別

1.共同點:都是雙列集合,底層都是哈希算法
2.區別:

  • 1.HashMap是線程不安全的,效率高,JDK1.2版本
  • Hashtable是線程安全的,效率低,JDK1.0版本
  • 2.HashMap可以存儲null鍵和null值
  • Hashtable不可以存儲null鍵和null值

47.HashMap與HashSet的區別

HashMap HashSet
實現 Map 接口 實現 Set 接口
存儲鍵值對 僅存儲對象
調用 put() 向 map 中添加元素 調用 add() 方法向 Set 中添加元素
HashMap 使用鍵 (key) 計算 HashSet 使用成員對象來計算 hashcode 值,對於兩個對象來說,hashcode 可能相同,所以 equals() 方法從是用來判斷對象的相等性

48.HashSet與HashMap怎麼判斷集合元素重複

HashSet的底層是藉助HashMap來實現的,利用HashMap中Key的唯一性,來保證HashSet中不出現重複值。
爲了保證HashSet中的對象不會出現重複值,在被存放元素的類中必須要重寫hashCode()和equals()這兩個方法。

49.ArrayList和LinkedList的區別,以及應用場景

ArrayList底層是用數組實現的,可以認爲ArrayList是一個可改變大小的數組。隨着越來越多的元素被添加到ArrayList中,其規模是動態增加的。
LinkedList底層是通過雙向鏈表實現的, LinkedList和ArrayList相比,增刪的速度較快。但是查詢和修改值的速度較慢。
LinkedList更適合大量的循環,並且循環時進行插入或者刪除。
ArrayList更適合大量的存取和刪除操作。

50.數組和鏈表的區別

不同:鏈表是鏈式的存儲結構;數組是順序的存儲結構。
鏈表通過指針來連接元素與元素,數組則是把所有元素按次序依次存儲。
鏈表的插入刪除元素相對數組較爲簡單,不需要移動元素,且較爲容易實現長度擴充,但是尋找某個元素較爲困難;
數組尋找某個元素較爲簡單,但插入與刪除比較複雜,由於最大長度需要再編程一開始時指定,故當達到最大長度時,擴充長度不如鏈表方便。
相同:兩種結構均可實現數據的順序存儲,構造出來的模型呈線性結構。

51.二叉樹的深度優先遍歷和廣度優先遍歷

深度優先遍歷,也就是先序、中序、後續遍歷
深度優先遍歷從某個頂點出發,首先訪問這個頂點,然後訪問該頂點的第一個未被訪問的鄰結點,以此鄰結點爲頂點繼續訪問,同時記錄其餘未訪問的鄰接點,當一個頂點的所有鄰接點都被訪問時,回退一個頂點,將未訪問的鄰接點作爲頂點,重複此步驟,直到所有結點都被訪問完爲止。

廣度優先遍歷從某個頂點出發,首先訪問這個頂點,然後找出這個結點的所有未被訪問的鄰接點,訪問完後再訪問這些結點中第一個鄰接點的所有結點,重複此方法,直到所有結點都被訪問完爲止

52.堆和棧的區別

一、程序的內存分配方式不同
棧區(stack):編譯器自動分配釋放,存放函數的參數值,局部變量的值等,其操作方式類似於數據結構的棧。
堆區(heap):一般是由程序員分配釋放,若程序員不釋放的話,程序結束時可能由OS回收,值得注意的是他與數據結構的堆是兩回事,分配方式倒是類似於數據結構的鏈表。
二、申請方式不同
stack 由系統自動分配,heap 需要程序員自己申請。
C 中用函數 malloc分配空間,用 free 釋放,C++用 new 分配,用 delete 釋放。
三、申請後系統的響應不同
棧:只要棧的剩餘空間大於所申請的空間,系統將爲程序提供內存,否則將報異常提示棧溢出。
堆:首先應該知道操作系統有一個記錄內存地址的鏈表,當系統收到程序的申請時,遍歷該鏈表,尋找第一個空間大於所申請的空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序。另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的 delete 或 free 語句就能夠正確的釋放本內存空間。另外,由於找到的堆結點的大小不一定正好等於申請的大小,系統會將多餘的那部分重新放入空閒鏈表中。
四、 申請的大小限制不同
棧:在 windows 下,棧是向低地址擴展的數據結構,是一塊連續的內存區域,棧頂的地址和棧的最大容量是系統預先規定好的,能從棧獲得的空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域,這是由於系統是由鏈表在存儲空閒內存地址,自然堆就是不連續的內存區域,且鏈表的遍歷也是從低地址向高地址遍歷的,堆得大小受限於計算機系統的有效虛擬內存空間,由此空間,堆獲得的空間比較靈活,也比較大。
五、申請的效率不同
棧:棧由系統自動分配,速度快,但是程序員無法控制。
堆:堆是有程序員自己分配,速度較慢,容易產生碎片,不過用起來方便。
六、堆和棧的存儲內容不同
棧:在函數調用時,第一個進棧的是主函數中函數調用後的下一條指令的地址,然後函數的各個參數,在大多數的 C 編譯器中,參數是從右往左入棧的,當本次函數調用結束後,局部變量先出棧,然後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令。
堆:一般是在堆的頭部用一個字節存放堆的大小,具體內容由程序員安排。

53.什麼是深拷貝和淺拷貝?

淺拷貝(淺複製、淺克隆):
被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺拷貝僅僅複製所拷貝的對象,而不復制它所引用的對象。
深拷貝(深複製、深克隆):
被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深拷貝把要複製的對象所引用的對象都複製了一遍

54.開啓線程的三種方式?

1)繼承Thread類創建線程
2)實現Runnable接口創建線程
3)使用Callable和Future創建線程

55.線程和進程的區別?

(1)進程
進程是程序的一次執行過程,是一個動態概念,是程序在執行過程中分配和管理資源的基本單位,每一個進程都有一個自己的地址空間,至少有 5 種基本狀態,它們是:初始態,執行態,等待狀態,就緒狀態,終止狀態。
(2)線程
線程是CPU調度和分派的基本單位,它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。
(3)聯繫
線程是進程的一部分,一個線程只能屬於一個進程,而一個進程可以有多個線程,但至少有一個線程。
(4)區別:理解它們的差別,我從資源使用的角度出發。(所謂的資源就是計算機裏的中央處理器,內存,文件,網絡等等)
根本區別:進程是操作系統資源分配的基本單位,而線程是任務調度和執行的基本單位
在開銷方面:每個進程都有獨立的代碼和數據空間(程序上下文),程序之間的切換會有較大的開銷;線程可以看做輕量級的進程,同一類線程共享代碼和數據空間,每個線程都有自己獨立的運行棧和程序計數器(PC),線程之間切換的開銷小。
所處環境:在操作系統中能同時運行多個進程(程序);而在同一個進程(程序)中有多個線程同時執行(通過CPU調度,在每個時間片中只有一個線程執行)
內存分配方面:系統在運行的時候會爲每個進程分配不同的內存空間;而對線程而言,除了CPU外,系統不會爲線程分配內存(線程所使用的資源來自其所屬進程的資源),線程組之間只能共享資源。
包含關係:沒有線程的進程可以看做是單線程的,如果一個進程內有多個線程,則執行過程不是一條線的,而是多條線(線程)共同完成的;線程是進程的一部分,所以線程也被稱爲輕權進程或者輕量級進程。

56.run()和start()方法區別

(1)通過調用Thread類的start()方法來啓動一個線程,這時此線程是處於就緒狀態,並沒有運行。
(2)然後通過此Thread類調用方法run()來完成其運行操作的,這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容,Run方法運行結束,此線程終止,而CPU再運行其它線程
(3).run()方法當作普通方法的方式調用,程序還是要順序執行

57.如何控制某個方法允許併發訪問線程的個數?

package com.soyoungboy;

 import java.util.concurrent.Semaphore;
 /**
 *
 * @author soyoungboy 2017年1月25日15:51:15
 *
 */
public class SemaphoreTest {
static Semaphore semaphore = new Semaphore(5,true);
 public static void main(String[] args) {
 for(int i=0;i<100;i++){
 new Thread(new Runnable() {

 @Override
 public void run() {
 test();
 }
 }).start();
 }

 }

 public static void test(){
 try {
 //申請一個請求
 semaphore.acquire();
 } catch (InterruptedException e1) {
 e1.printStackTrace();
 }
 System.out.println(Thread.currentThread().getName()+"進來了");
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println(Thread.currentThread().getName()+"走了");
 //釋放一個請求
 semaphore.release();
 }
 }

構造函數創建了一個 Semaphore 對象,並且初始化了 5 個信號。這樣的效果是控件 test 方法最多只能有 5 個線程併發訪問,對於 5 個線程時就排隊等待,走一個來一下;
請求一個信號(消費一個信號),如果信號被用完了則等待;
釋放一個信號,釋放的信號新的線程就可以使用了.

58. 在Java中wait和seelp方法的不同

對於sleep()方法,我們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。
sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。
在調用sleep()方法的過程中,線程不會釋放對象鎖。
而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備獲取對象鎖進入運行狀態。

59.談談wait/notify關鍵字的理解

wait():
等待對象的同步鎖,需要獲得該對象的同步鎖纔可以調用這個方法,否則編譯可以通過,但運行時會收到一個異常:IllegalMonitorStateException。
調用任意對象的 wait() 方法導致該線程阻塞,該線程不可繼續執行,並且該對象上的鎖被釋放。

notify():
喚醒在等待該對象同步鎖的線程(只喚醒一個,如果有多個在等待),注意的是在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先級。
調用任意對象的notify()方法則導致因調用該對象的 wait()方法而阻塞的線程中隨機選擇的一個解除阻塞(但要等到獲得鎖後才真正可執行)。

notifyAll():
喚醒所有等待的線程,注意喚醒的是notify之前wait的線程,對於notify之後的wait線程是沒有效果的。

60.什麼導致線程阻塞?

阻塞狀態的線程的特點是:該線程放棄CPU的使用,暫停運行,只有等到導致阻塞的原因消除之後才恢復運行。或者是被其他的線程中斷,該線程也會退出阻塞狀態,同時拋出InterruptedException
1)線程執行了Thread.sleep(intmillsecond);方法,當前線程放棄CPU,睡眠一段時間,然後再恢復執行
2)線程執行一段同步代碼,但是尚且無法獲得相關的同步鎖,只能進入阻塞狀態,等到獲取了同步鎖,才能回覆執行。
3)線程執行了一個對象的wait()方法,直接進入阻塞狀態,等待其他線程執行notify()或者notifyAll()方法。
4)線程執行某些IO操作,因爲等待相關的資源而進入了阻塞狀態。比如說監聽system.in,但是尚且沒有收到鍵盤的輸入,則進入阻塞狀態。

61.• 線程如何關閉

1).正常關閉:推薦使用業務標誌位結束線程的工作流程,待線程工作結束自行關閉,如下 mWorking 進行控制線程的業務是否繼續進行
2).使用Android AsyncTask來關閉線程
3)stop()和interrupt()方法雖然可以中斷線程,但是會使線程代碼中邏輯執行不完整。

62. 講一下java中的同步的方法

爲何要使用同步?
java允許多線程併發控制,當多個線程同時操作一個可共享的資源變量時(如數據的增刪改查),將會導致數據不準確,相互之間產生衝突,
因此加入同步鎖以避免在該線程沒有完成操作之前,被其他線程的調用,從而保證了該變量的唯一性和準確性。
同步的方式
1.同步方法:即有synchronized關鍵字修飾的方法。由於java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,需要獲得內置鎖,否則就處於阻塞狀態。
2.同步代碼塊:即有synchronized關鍵字修飾的語句塊。
被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步。
3.使用特殊域變量(volatile)實現線程同步
a.volatile關鍵字爲域變量的訪問提供了一種免鎖機制,
b.使用volatile修飾域相當於告訴虛擬機該域可能會被其他線程更新,
c.因此每次使用該域就要重新計算,而不是使用寄存器中的值
d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變量
4.使用重入鎖實現線程同步
在JavaSE5.0中新增了一個java.util.concurrent包來支持同步。
ReentrantLock類是可重入、互斥、實現了Lock接口的鎖,
它與使用synchronized方法和快具有相同的基本行爲和語義,並且擴展了其能力
ReenreantLock類的常用方法有:
(1)ReentrantLock() : 創建一個ReentrantLock實例
(2)lock() : 獲得鎖
(3)unlock() : 釋放鎖

63.既然 ArrayList 是線程不安全的,怎麼保證它的線程安全性呢?或者有什麼替代方案?

java.util.Collections.SynchronizedList
它能把所有 List 接口的實現類轉換成線程安全的List,比 Vector 有更好的擴展性和兼容性,SynchronizedList的構造方法如下:

final List<E> list;
SynchronizedList(List<E> list) {
    super(list);
    this.list = list;
}

很可惜,它所有方法都是帶同步對象鎖的,和 Vector 一樣,它不是性能最優的。即使你能說到這裏,面試官還會繼續往下追問,比如在讀多寫少的情況,SynchronizedList這種集合性能非常差,還有沒有更合適的方案?
介紹兩個併發包裏面的併發集合類:
java.util.concurrent.CopyOnWriteArrayList
java.util.concurrent.CopyOnWriteArraySet
CopyOnWrite集合類也就這兩個,Java 1.5 開始加入,你要能說得上這兩個才能讓面試官信服。
CopyOnWriteArrayList
CopyOnWrite(簡稱:COW):即複製再寫入,就是在添加元素的時候,先把原 List 列表複製一份,再添加新的元素。
添加元素時,先加鎖,再進行復制替換操作,最後再釋放鎖。
可以看到,獲取元素並沒有加鎖。
這樣做的好處是,在高併發情況下,讀取元素時就不用加鎖,寫數據時才加鎖,大大提升了讀取性能。
CopyOnWriteArraySet
CopyOnWriteArraySet邏輯就更簡單了,就是使用 CopyOnWriteArrayList 的 addIfAbsent 方法來去重的,添加元素的時候判斷對象是否已經存在,不存在才添加進集合。
這兩種併發集合,雖然牛逼,但只適合於讀多寫少的情況,如果寫多讀少,使用這個就沒意義了,因爲每次寫操作都要進行集合內存複製,性能開銷很大,如果集合較大,很容易造成內存溢出。
總結
下次面試官問你線程安全的 List,你可以從 Vector > SynchronizedList > CopyOnWriteArrayList 這樣的順序依次說上來,這樣纔有帶入感,也能體現你對知識點的掌握程度。

64.談談NIO的理解

NIO主要用到的是塊,所以NIO的效率要比IO高很多。在Java API中提供了兩套NIO,一套是針對標準輸入輸出NIO,另一套就是網絡編程NIO。
1、面向流與面向緩衝
Java IO和NIO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。
Java IO面向流意味着每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前後移動流中的數據。如果需要前後移動從流中讀取的數據,需要先將它緩存到一個緩衝區。
Java NIO面向緩衝區的緩衝導向方法略有不同。數據讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有您需要處理的數據。而且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裏尚未處理的數據。

2、阻塞與非阻塞IO
Java IO的各種流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再幹任何事情了。
Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什麼都不會獲取,而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閒時間用於在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。

3、選擇器(Selectors)
Java NIO的選擇器允許一個單獨的線程來監視多個輸入通道,你可以註冊多個通道使用一個選擇器,然後使用一個單獨的線程來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。

65. 字節流與字符流有什麼區別:

計算機中的一切最終都是以二進制字節形式存在的,對於我們經常操作的字符串,在寫入時其實都是先得到了其對應的字節,然後將字節寫入到輸出流,在讀取時其實都是先讀到的是字節,然後將字節直接使用或者轉換爲字符給我們使用。由於對於字節和字符兩種操作的需求比較廣泛,所以 Java 專門提供了字符流與字節流相關IO類。對於程序運行的底層設備來說永遠都只接受字節數據,所以當我們往設備寫數據時無論是字節還是字符最終都是寫的字節流。字符流是字節流的包裝類,所以當我們將字符流向字節流轉換時要注意編碼問題(因爲字符串轉成字節數組的實質是轉成該字符串的某種字節編碼)。字符流和字節流的使用非常相似,但是實際上字節流的操作不會經過緩衝區(內存)而是直接操作文本本身的,而字符流的操作會先經過緩衝區(內存)然後通過緩衝區再操作文件。

字符流和字節流的使用非常相似,但是實際上字節流的操作不會經過緩衝區(內存)而是直接操作文本本身的,而字符流的操作會先經過緩衝區(內存)然後通過緩衝區再操作文件。

66. 字節流和字符流哪個好,如何選擇?

緩大多數情況下使用字節流會更好,因爲字節流是字符流的包裝,而大多數時候 IO 操作都是直接操作磁盤文件,所以這些流在傳輸時都是以字節的方式進行的(圖片等都是按字節存儲的)。
而如果對於操作需要通過 IO 在內存中頻繁處理字符串的情況使用字符流會好些,因爲字符流具備緩衝區,提高了性能。

67.• 死鎖的四個必要條件?

死鎖是指多個進程因競爭資源而造成的一種僵局(互相等待),若無外力作用,這些進程都將無法向前推進

二、死鎖產生的原因

  1. 系統資源的競爭
    系統資源的競爭導致系統資源不足,以及資源分配不當,導致死鎖。
  2. 進程運行推進順序不合適
    進程在運行過程中,請求和釋放資源的順序不當,會導致死鎖。

1.互斥條件:一個資源每次只能被一個進程使用,即在一段時間內某 資源僅爲一個進程所佔有。此時若有其他進程請求該資源,則請求進程只能等待。
2。請求與保持條件:進程已經保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其他進程佔有,此時請求進程被阻塞,但對自己已獲得的資源保持不放。
3.不可剝奪條件:進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,即只能 由獲得該資源的進程自己來釋放(只能是主動釋放)。
4.循環等待條件: 若干進程間形成首尾相接循環等待資源的關係
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。

68.怎麼避免死鎖?

我們可以通過破壞死鎖產生的4個必要條件來 預防死鎖,由於資源互斥是資源使用的固有特性是無法改變的。
破壞“不可剝奪”條件:一個進程不能獲得所需要的全部資源時便處於等待狀態,等待期間他佔有的資源將被隱式的釋放重新加入到 系統的資源列表中,可以被其他的進程使用,而等待的進程只有重新獲得自己原有的資源以及新申請的資源纔可以重新啓動,執行。
破壞”請求與保持條件“:第一種方法靜態分配即每個進程在開始執行時就申請他所需要的全部資源。第二種是動態分配即每個進程在申請所需要的資源時他本身不佔用系統資源。
破壞“循環等待”條件:採用資源有序分配其基本思想是將系統中的所有資源順序編號,將緊缺的,稀少的採用較大的編號,在申請資源時必須按照編號的順序進行,一個進程只有獲得較小編號的進程才能申請較大編號的進程。

69. 談談對多線程的理解

爲了解決負載均衡問題,充分利用CPU資源.爲了提高CPU的使用率,採用多線程的方式去同時完成幾件事情而不互相干擾多線程的好處:
1.使用線程可以把佔據時間長的程序中的任務放到後臺去處理
2.用戶界面更加吸引人,這樣比如用戶點擊了一個按鈕去觸發某件事件的處理,可以彈出一個進度條來顯示處理的進度
3.程序的運行效率可能會提高
4.在一些等待的任務實現上如用戶輸入,文件讀取和網絡收發數據等,線程就比較有用了.

多線程的缺點:
1.如果有大量的線程,會影響性能,因爲操作系統需要在它們之間切換.
2.更多的線程需要更多的內存空間
3.線程中止需要考慮對程序運行的影響.
4.通常塊模型數據是在多個線程間共享的,需要防止線程死鎖情況的發生

70.如何保證多線程讀寫文件的安全?

多線程文件併發安全其實就是在考察線程併發安全,最普通的方式就是使用 wait/notify、Condition、synchronized、ReentrantLock 等方式,這些方式默認都是排它操作(排他鎖),也就是說默認情況下同一時刻只能有一個線程可以對文件進行操作,所以可以保證併發文件操作的安全性,但是在併發讀數量遠多於寫數量的情況下性能卻不那麼好。因此推薦使用 ReadWriteLock 的實現類 ReentrantReadWriteLock,它也是 Lock 的一種實現,允許多個讀線程同時訪問,但不允許寫線程和讀線程、寫線程和寫線程同時訪問。所以相對於排他鎖來說提高了併發效率。ReentrantReadWriteLock 讀寫鎖裏面維護了兩個繼承自 Lock 的鎖,一個用於讀操作(ReadLock),一個用於寫操作(WriteLock)。

71.• 多線程斷點續傳原理

在下載大文件的時候,我們往往要使用多線程斷點續傳,保證數據的完整性,首先說多線程,我們要多線程下載一個大文件,就有開啓多個線程,多個connection,既然是一個文件分開幾個線程來下載,那肯定就是一個線程下載一個部分,如果文件的大小是200M, 使用兩個線程下載, 第一個線程下載1-100M, 第二個線程下載101-200M。
我們在請求的header裏面設置conn.setRequestProperty(“Range”, “bytes=”+startPos+"-"+endPos);
這裏startPos是指從數據端的哪裏開始,endPos是指數據端的結束
根據這樣我們就知道,只要多個線程,按順序指定好開始跟結束,就可以解決下載衝突的問題了。
每個線程找到自己開始寫的位置,就是seek(startPos)
這樣就可以保證數據的完整性,也不會重複寫入了

RandomAccessFile threadFile = new RandomAccessFile(this.saveFile,"rwd");  
threadFile.seek(startPos);  
threadFile.write(buffer,0,offset);

斷點續傳其實也很簡單,原理就是使用數據庫保存上次每個線程下載的位置和長度
例如我開了兩個線程T1,T2來下載一個文件,設文件總大小爲1024M,那麼就是每個線程下載512M
可是我的下載中斷了,那麼我下次啓動線程的時候(繼續下載),是不是應該要知道,我原來下載了多少呢
所以是這樣的,每下載一點,就更新數據庫的數據

Android面試題

Android基礎

72.• 四大組件生命週期及其基本用法

1.Activity::一個Activity通常就是一個單獨的屏幕,它上面可以顯示一些控件也可以監聽並處理用戶的事件做出響應。
Activity之間通過Intent進行通信
在這裏插入圖片描述
2.service:一個Service 是一段長生命週期的,沒有用戶界面的程序,可以用來開發如監控類程序。
在這裏插入圖片描述
3.BroadcastReceive廣播接收器生命週期:
生命週期只有十秒左右,如果在 onReceive() 內做超過十秒內的事情,就會報ANR(Application No Response) 程序無響應的錯誤信息
它的生命週期爲從回調onReceive()方法開始到該方法返回結果後結束
有動態註冊和靜態註冊
4.Content Provider內容提供者 :
android平臺提供了Content Provider使一個應用程序的指定數據集提供給其他應用程序。這些數據可以存儲在文件系統中、在一個SQLite數據庫、或以任何其他合理的方式,
其他應用可以通過ContentResolver類(見ContentProviderAccessApp例子)從該內容提供者中獲取或存入數據.(相當於在應用外包了一層殼),
只有需要在多個應用程序間共享數據是才需要內容提供者。例如,通訊錄數據被多個應用程序使用,且必須存儲在一個內容提供者中
它的好處:統一數據訪問方式。

73.Activity之間的通信方式

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

74.橫豎屏切換時,Activity各種情況下的生命週期

1.AndroidManifest沒有設置configChanges屬性
豎屏啓動:
onCreate -->onStart–>onResume
切換橫屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate–>onStart -->onRestoreInstanceState–>onResume -->onPause -->onStop -->onDestroy
(Android 6.0 Android 7.0 Android 8.0)
橫屏啓動:
onCreate -->onStart–>onResume
切換豎屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate–>onStart -->onRestoreInstanceState–>onResume -->onPause -->onStop -->onDestroy
(Android 6.0 Android 7.0 Android 8.0)
總結:沒有設置configChanges屬性Android 6.0 7.0 8.0 系統手機 表現都是一樣的,當前的界面調用onSaveInstanceState走一遍流程,然後重啓調用onRestoreInstanceState再走一遍完整流程,最終destory。

2.AndroidManifest設置了configChanges android:configChanges=“orientation”
豎屏啓動:
onCreate -->onStart–>onResume
切換橫屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate–>onStart -->onRestoreInstanceState–>onResume -->onPause -->onStop -->onDestroy
(Android 6.0)
onConfigurationChanged–>onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate–>onStart -->onRestoreInstanceState–>onResume -->onPause -->onStop -->onDestroy
(Android 7.0)
onConfigurationChanged
(Android 8.0)
橫屏啓動:
onCreate -->onStart–>onResume
切換豎屏:
onPause -->onSaveInstanceState -->onStop -->onDestroy -->onCreate–>onStart -->onRestoreInstanceState–> onResume -->onPause -->onStop -->onDestroy
(Android 6.0 )
onConfigurationChanged–>onPause -->onSaveInstanceState -->onStop -->onDestroy -->
onCreate–>onStart -->onRestoreInstanceState–>onResume -->onPause -->onStop -->onDestroy
(Android 7.0)
onConfigurationChanged
(Android 8.0)
總結:設置了configChanges屬性爲orientation之後,Android6.0 同沒有設置configChanges情況相同,完整的走完了兩個生命週期,調用了onSaveInstanceState和onRestoreInstanceState方法;Android 7.0則會先回調onConfigurationChanged方法,剩下的流程跟Android 6.0 保持一致;Android 8.0 系統更是簡單,
只是回調了onConfigurationChanged方法,並沒有走Activity的生命週期方法。

3.AndroidManifest設置了configChanges
android:configChanges=“orientation|keyboardHidden|screenSize”
豎(橫)屏啓動:onCreate -->onStart–>onResume
切換橫(豎)屏:onConfigurationChanged (Android 6.0 Android 7.0 Android 8.0)
總結:設置android:configChanges=“orientation|keyboardHidden|screenSize” 則都不會調用Activity的其他生命週期方法,只會調用onConfigurationChanged方法。

4.AndroidManifest設置了configChanges
android:configChanges=“orientation|screenSize”
豎(橫)屏啓動:onCreate -->onStart–>onResume
切換橫(豎)屏:onConfigurationChanged (Android 6.0 Android 7.0 Android 8.0)
總結:沒有了keyboardHidden跟3是相同的,orientation代表橫豎屏切換 screenSize代表屏幕大小發生了改變,
設置了這兩項就不會回調Activity的生命週期的方法,只會回調onConfigurationChanged 。

5.AndroidManifest設置了configChanges
android:configChanges=“orientation|keyboardHidden”
總結:跟只設置了orientation屬性相同,Android6.0 Android7.0會回調生命週期的方法,Android8.0則只回調onConfigurationChanged。說明如果設置了orientation 和 screenSize 都不會走生命週期的方法,keyboardHidden不影響。
1.不設置configChanges屬性不會回調onConfigurationChanged,且切屏的時候會回調生命週期方法。
2.只有設置了orientation 和 screenSize 纔會保證都不會走生命週期,且切屏只回調onConfigurationChanged。
3.設置orientation,沒有設置screenSize,切屏會回調onConfigurationChanged,但是還會走生命週期方法。
注:這裏只選擇了Android部分系統的手機做測試,由於不同系統的手機品牌也不相同,可能略微會有區別。
另:
代碼動態設置橫豎屏狀態(onConfigurationChanged當屏幕發生變化的時候回調)
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
獲取屏幕狀態(int ORIENTATION_PORTRAIT = 1; 豎屏 int ORIENTATION_LANDSCAPE = 2; 橫屏)
int screenNum = getResources().getConfiguration().orientation;
configChanges屬性

  1. orientation 屏幕在縱向和橫向間旋轉
    2.keyboardHidden 鍵盤顯示或隱藏
    3.screenSize 屏幕大小改變了
    4.fontScale 用戶變更了首選的字體大小
    5.locale 用戶選擇了不同的語言設定
    6.keyboard 鍵盤類型變更,例如手機從12鍵盤切換到全鍵盤
    7.touchscreen或navigation 鍵盤或導航方式變化,一般不會發生這樣的事件
    常用的包括:orientation keyboardHidden screenSize,設置這三項界面不會走Activity的生命週期,只會回調onConfigurationChanged方法。
    screenOrientation屬性
    1.unspecified 默認值,由系統判斷狀態自動切換
    2.landscape 橫屏
  2. portrait 豎屏
    4.user 用戶當前設置的orientation值
  3. behind 下一個要顯示的Activity的orientation值
  4. sensor 使用傳感器 傳感器的方向
  5. nosensor 不使用傳感器 基本等同於unspecified
    僅landscape和portrait常用,代表界面默認是橫屏或者豎屏,還可以再代碼中更改。

75.• Activity與Fragment之間生命週期比較

在這裏插入圖片描述
Fragment生命週期
onAttach
onCreate
onCreateView
onActivityCreate ______以上相當於Activity的onCreate方法

onStart ______相當於Activity的onStart方法
onResume ______相當於Activity的onResume方法
onPause ______相當於Activity的onPause方法
onStop ______相當於Activity的onStop方法

onDestroyView
onDestroy
onDetach ______以上相當於Activity的onDestroy方法

當Activity包含一個Fragment的時候,Activity和Fragment生命週期的變化:
Activity(onCreate)—> Fragment(onAttach onCreate onCreateView onActivityCreate)—>
Activity(onStart)—> Fragment(onStart)—>
Activity(onResume)—> Fragment(onResume)—>
Fragment(onPause)—> Activity(onPause)—>
Fragment(onStop)—> Activity(onStop)—>
Fragment(onDestroyView onDestroy onDetach)—> Activity(onDestroy)
由於Fragment依附於Activity,所以啓動的時候Activity的方法肯定在前面,Fragment的方法在後面,但是在要銷燬的時候,Fragment的方法先執行,再執行Activity的方法。

在宿主Activity中使用hide、show方式切換Fragment的時候,Fragment的生命週期是:
a 初始化
Fragment1(onAttach onCreate onCreateView onActivityCreate) —> Fragment1(onStart)—> Fragment1(onResume)
Fragment2(onAttach onCreate onCreateView onActivityCreate) —> Fragment2(onStart)—> Fragment2(onResume)
b Fragment1和Fragment2來回切換都沒有回調生命週期
c 當某一個Fragment調用了跳轉到另一個Activity的時候(或者按HOME鍵的時候)
Fragment1(onPause)—> Fragment1(onStop)
Fragment2(onPause)—> Fragment2(onStop)
d 當在一個透明的Activity中彈出一個Dialog時(與Activity的情況相同)
Fragment1(onPause)
Fragment2(onPause)
e 當宿主Activity被銷燬的時候
Fragment1(onPause)—> Fragment1(onStop)—> Fragment1(onDestroyView onDestroy onDetach)
Fragment2(onPause)—> Fragment2(onStop)—> Fragment2(onDestroyView onDestroy onDetach)

當採用FragmentStatePagerAdapter適配器加載‘Fragment的時候,Fragment的生命週期同上面的情況相同。

76.• Activity上有Dialog的時候按Home鍵時的生命週期

Dialog與 DialogFragment 都不會出發activity的生命週期變動。
也就是說,Dialog的show與dismiss不會引發activity的onPause()和onResume的執行。

77.• 兩個Activity 之間跳轉時必然會執行的是哪幾個方法?

一般情況下比如說有兩個activity,分別叫A,B。
當在A 裏面激活B 組件的時候, A會調用onPause()方法,然後B調用onCreate() ,onStart(), onResume()。
這個時候B覆蓋了A的窗體, A會調用onStop()方法。
如果B是個透明的窗口,或者是對話框的樣式, 就不會調用A的onStop()方法。
如果B已經存在於Activity棧中,B就不會調用onCreate()方法。

78.• Activity的四種啓動模式對比

standard:標準模式:如果在mainfest中不設置就默認standard;standard就是新建一個Activity就在棧中新建一個activity實例;
singleTop:棧頂複用模式:與standard相比棧頂複用可以有效減少activity重複創建對資源的消耗,但是這要根據具體情況而定,不能一概而論;
singleTask:棧內單例模式,棧內只有一個activity實例,棧內已存activity實例,在其他activity中start這個activity,Android直接把這個實例上面其他activity實例踢出棧GC掉;
singleInstance :堆內單例:整個手機操作系統裏面只有一個實例存在就是內存單例;
Activity四種啓動模式常見使用場景:
LauchMode Instance
standard mainfest中沒有配置就默認標準模式
singleTop 登錄頁面、WXPayEntryActivity、WXEntryActivity 、推送通知欄
singleTask 程序模塊邏輯入口:主頁面(Fragment的containerActivity)、WebView頁面、掃一掃頁面
singleInstance 系統Launcher、鎖屏鍵、來電顯示等系統應用

79.• Activity狀態保存與恢復

第一: 有哪些狀態是需要保存的?
有哪些狀態是需要保存的呢?最簡單明瞭的就是對一些數據的保存,比如你正在操作一些數據,當面臨突發情況,你的數據還沒有操作完,這時候你就需要將數據進行保存,以便我們再次回到這個頁面的時候不用重頭再來。

第二: 什麼情況下需要Activity狀態的保存與恢復?
有些設備配置可能會在運行時發生變化(例如屏幕方向、鍵盤可用性及語言)。 發生這種變化時,Android 會重啓正在運行的 Activity(先後調用 onDestroy() 和 onCreate())。重啓行爲旨在通過利用與新設備配置匹配的備用資源自動重新加載您的應用,來幫助它適應新配置。
要妥善處理重啓行爲,Activity 必須通過常規的Activity 生命週期恢復其以前的狀態,在 Activity 生命週期中,Android 會在銷燬 Activity 之前調用 onSaveInstanceState(),以便您保存有關應用狀態的數據。 然後,您可以在 onCreate() 或 onRestoreInstanceState() 期間恢復 Activity 狀態。

首先是在onSaveInstanceState中保存數據

   @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        //保存銷燬之前的數據
        outState.putString("number",mMNumber.getText().toString());
        Log.d(TAG, "onSaveInstanceState");

    }

在onRestoreInstanceState對數據進行恢復

   @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.d(TAG, "onRestoreInstanceState");
        //恢復數據
       String s = savedInstanceState.getString("number");
       mMNumber.setText(s);
    }

到這裏我們總結一下這個方法的調用過程,當運行時配置發生變更的時候,程序總的會銷燬當前的Activity,然後重新創建一個新的Activity,在這個過程中,銷燬當前Activity之前會先調用onSaveInstanceState讓我們來保存數據,然後重建Activity在調用onCreat方發之後會調用onRestoreInstanceState讓我們來對數據進行恢復,當然也可以在onCret中進行數據恢復
第五: 這個知識點你需要注意的地方?

通過以上的講訴,相信你對Activity狀態的保存與恢復已經掌握的差不多了,在這裏我再補充幾點

  1. 關於onSaveInstanceState
    這個方法默認情況下會自動保存有關Activity的視圖層次結構的狀態信息,簡單舉個例子,我們以系統控件EditText來說,系統默認會保存有關這個控件的一個信息,也就是當你在這個控件中輸入內容的時候,即使旋轉屏幕內容也不會丟失,因爲系統已經默認爲其實現了我們說的那兩個方法,但是有個前提,這個控件必須設置id,否則數據依舊會丟失,另外如果你重寫了onRestoreInstanceState也要保證必須有這行代碼

super.onRestoreInstanceState(savedInstanceState);
關於旋轉屏幕無法調用onSaveInstanceState的問題。
出現這種問題你複寫的肯定以下方法
public void onSaveInstanceState (Bundle outState, PersistableBundle outPersistentState);
改成以下方法即可
public void onSaveInstanceState (Bundle outState);

80.fragment各種情況下的生命週期

Activity中調用replace()方法時的生命週期
新替換的Fragment:onAttach > onCreate > onCreateView > onViewCreated > onActivityCreated > onStart > onResume
被替換的Fragment:onPause > onStop > onDestroyView > onDestroy > onDetach

Activity中調用replace()方法和addToBackStack()方法時的生命週期
新替換的Fragment(沒有在BackStack中):onAttach > onCreate > onCreateView > onViewCreated > onActivityCreated > onStart > onResume
新替換的Fragment(已經在BackStack中):onCreateView > onViewCreated > onActivityCreated > onStart > onResume
被替換的Fragment:onPause > onStop > onDestroyView

Fragment在運行狀態後跟隨Activity的生命週期
Fragment在上述的各種情況下進入了onResume後,則進入了運行狀態,以下4個生命週期方法將跟隨所屬的Activity一起被調用:
onPause > onStop > onStart > onResume

81. fragment之間傳遞數據的方式?

Intent傳值
廣播傳值
靜態調用
本地化存儲傳值
暴露接口/方法調用
eventBus等之類的

82.• 怎麼在Activity 中啓動自己對應的Service?

第一種方式:通過StartService啓動Service
通過startService啓動後,service會一直無限期運行下去,只有外部調用了stopService()或stopSelf()方法時,該Service纔會停止運行並銷燬。
第二種方式:通過bindService啓動Service
bindService啓動服務特點:
1.bindService啓動的服務和調用者之間是典型的client-server模式。調用者是client,service則是server端。service只有一個,但綁定到service上面的client可以有一個或很多個。這裏所提到的client指的是組件,比如某個Activity。
2.client可以通過IBinder接口獲取Service實例,從而實現在client端直接調用Service中的方法以實現靈活交互,這在通過startService方法啓動中是無法實現的。
3.bindService啓動服務的生命週期與其綁定的client息息相關。當client銷燬時,client會自動與Service解除綁定。當然,client也可以明確調用Context的unbindService()方法與Service解除綁定。當沒有任何client與Service綁定時,Service會自行銷燬。

83.• 談談你對ContentProvider的理解

特點
Android四大組件之一,需要進行註冊,一般有name,authorities,export等屬性
是一種定義數據共享的接口,並是不android數據存儲的方式之一
跨進程數據訪問
android系統很多系統應用都是使用ContentProvider方式進行儲存的(圖片,通訊錄,視頻,音頻等)
數據更新監聽方便
優缺點
優點:
爲數據訪問提供統一的接口,解決了不同儲存方式需要不同的API的訪問所帶來的繁瑣
跨進程數據的訪問,實現了不同App數據訪問提供了很大的便利
缺點:
不能單獨使用,必須需要和其他的儲存方式結合使用

84.android廣播分類

a.標準廣播
標準廣播發出以後,所有的廣播接收器,可以幾乎在同一時刻同時接受到這條廣播。
優點:效率高
缺點:不能被截斷。
b.有序廣播
有序廣播發出以後,同一時刻只能有一個廣播接收器收到這條廣播。優先級高的廣播先接受到這條
廣播。在當前廣播接收器處理完自己的邏輯以後,可以執行兩種動作:
1.繼續傳遞廣播
2.將廣播截斷

85.• 廣播使用的方式

(1)靜態註冊(常駐型廣播):在清單文件中註冊, 常見的有監聽設備啓動,常駐註冊不會隨程序生命週期改變,適用於長期監聽,這種常駐型廣播當應用程序關閉後,如果有信息廣播來,程序也會被系統調用自動運行。

(2)動態註冊(非常駐型廣播):在代碼中註冊,這種方式註冊的廣播會跟隨程序的生命週期。隨着程序的結束,也就停止接受廣播了。使用與一些需要與生命週期同步的監聽。

86.本地廣播和全局廣播有什麼差別?

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

1、本地廣播:發送的廣播事件不被其他應用程序獲取,也不能響應其他應用程序發送的廣播事件。本地廣播只能被動態註冊,不能靜態註冊。動態註冊或方法時需要用到LocalBroadcastManager。
實現原理:單例LocalBroadcastManager

2、全局廣播:發送的廣播事件可被其他應用程序獲取,也能響應其他應用程序發送的廣播事件(可以通過 exported–是否監聽其他應用程序發送的廣播 在清單文件中控制) 全局廣播既可以動態註冊,也可以靜態註冊。
(1)不讓別的應用收到自己發送的廣播
在Androidmanifest.xml中爲BroadcastReceiver添加權限,如果是自定義權限記得先聲明:

發送廣播是傳入:
public void sendOrderedBroadcast(Intent intent, String receiverPermission)
sendBroadcast(intent, "com.android.permission.quanxian ");
只有具有permission權限的Receiver才能接收此廣播要接收該廣播,在Receiver應用的AndroidManifest.xml中要添加對應的相應的權限。

(2)過濾掉自己不願接收的廣播。
android:exported 此broadcastReceiver能否接收其他App的發出的廣播,這個屬性默認值有點意思,其默認值是由receiver中有無intent-filter決定的,如果有intent-filter,默認值爲true,否則爲false。
android:permission設置之後,只有具有相應權限的廣播發送方發送的廣播才能被此broadcastReceiver所接收;
上述設置也可以達到接收或者不接收的目的,但是全局廣播其實原理是利用binder機制和AMS進行交互如果只是應用內使用,資源耗費或者說延時還是可以優化的。

87.• AlertDialog,popupWindow,Activity區別

AlertDialog :用來提示用戶一些信息,用起來也比較簡單,設置標題類容 和按鈕即可,如果是加載的自定義的view ,用 dialog.setView(layout);加載佈局即可(其實就是一個佈滿全屏的空間)
popupWindow:就是一個懸浮在Activity之上的窗口,可以用展示任意佈局文件
activity:是Android系統中的四大組件之一,可以用於顯示View。Activity是一個與用戶交互的系統模塊,幾乎所有的Activity都是和用戶進行交互的
區別:

AlertDialog是非阻塞式對話框:AlertDialog彈出時,後臺還可以做事情;

而PopupWindow是阻塞式對話框:PopupWindow彈出時,程序會等待,在PopupWindow退出前,程序一直等待,只有當我們調用了dismiss方法的後,PopupWindow退出,程序纔會向下執行。

88• Application 和 Activity 的 Context 對象的區別

在需要傳遞Context參數的時候,如果是在Activity中,我們可以傳遞this(這裏的this指的是Activity.this,是當前Activity的上下文)或者Activity.this。這個時候如果我們傳入getApplicationContext(),我們會發現這樣也是可以用的。可是大家有沒有想過傳入Activity.this和傳入getApplicationContext()的區別呢?首先Activity.this和getApplicationContext()返回的不是同一個對象,一個是當前Activity的實例,一個是項目的Application的實例,這兩者的生命週期是不同的,它們各自的使用場景不同,this.getApplicationContext()取的是這個應用程序的Context,它的生命週期伴隨應用程序的存在而存在;而Activity.this取的是當前Activity的Context,它的生命週期則只能存活於當前Activity,這兩者的生命週期是不同的。getApplicationContext() 生命週期是整個應用,當應用程序摧毀的時候,它纔會摧毀;Activity.this的context是屬於當前Activity的,當前Activity摧毀的時候,它就摧毀。

Android源碼相關分析

89.• invalidate和postInvalidate的區別及使用

Android中實現view的更新有兩組方法,一組是invalidate,另一組是postInvalidate,其中前者是在UI線程自身中使用,而後者在非UI線程中使用。
1,利用invalidate()刷新界面
  實例化一個Handler對象,並重寫handleMessage方法調用invalidate()實現界面刷新;而在線程中通過sendMessage發送界面更新消息。
2,使用postInvalidate()刷新界面
使用postInvalidate則比較簡單,不需要handler,直接在線程中調用postInvalidate即可。

90.• Activity-Window-View三者的差別

Activity是安卓四大組件之一,負責界面展示、用戶交互與業務邏輯處理;
Window就是負責界面展示以及交互的職能部門,就相當於Activity的下屬,Activity的生命週期方法負責業務的處理;
View就是放在Window容器的元素,Window是View的載體,View是Window的具體展示。
然後一句話介紹下三者的關係:
Activity通過Window來實現視圖元素的展示,window可以理解爲一個容器,盛放着一個個的view,用來執行具體的展示工作。

91.• 如何優化自定義View

(1)減少不必要的代碼:對於頻繁調用的方法,需要儘量減少不必要的代碼。

(2)不在 onDraw 中做內存分配的事:先從 onDraw 開始,需要特別注意不應該在這裏做內存分配的事情,因爲它會導致 GC,從而導致卡頓。在初始化或者動畫間隙期間做分配內存的動作。不要在動畫正在執行的時候做內存分配的事情。

(3)減少 onDraw 被調用的次數:大多數時候導致 onDraw 都是因爲調用了 invalidate().因此請儘量減少調用 invaildate()的次數。如果可能的話,儘量調用含有 4 個參數的 invalidate()方法而不是沒有參數的 invalidate()。沒有參數的 invalidate 會強制重繪整個 view。

(4)減少 layout 的次數:一個非常耗時的操作是請求 layout。任何時候執行 requestLayout(),會使得 Android UI 系統去遍歷整個View 的層級來計算出每一個 view 的大小。如果找到有衝突的值,它會需要重新計算好幾次。

(5)選用扁平化的 View:另外需要儘量保持 View 的層級是扁平化的(去除冗餘、厚重和繁雜的裝飾效果),這樣對提高效率很有幫助。

(6)複雜的 UI 使用 ViewGroup:如果你有一個複雜的 UI,你應該考慮寫一個自定義的 ViewGroup 來執行他的 layout 操作。(與內置的view 不同,自定義的 view 可以使得程序僅僅測量這一部分,這避免了遍歷整個 view 的層級結構來計算大小。)繼承 ViewGroup作爲自定義 view 的一部分,有子 views,但是它從來不測量它們。而是根據他自身的 layout 法則,直接設置它們的大小。

92 • 低版本SDK如何實現高版本api?

在低版本的手機系統中,如果直接使用高版本的API肯定會報:“NoSuchMethod”的Crash的。
所以正確的做法應該是在註解的方法中,做版本判斷,在低版本中依然使用老的方式處理。版本判斷時我們需要判斷具體的版本號

93 • 描述一次網絡請求的流程

,大概是經歷了域名解析、TCP的三次握手、建立TCP連接後發起HTTP請求、服務器響應HTTP請求、解析服務器數據
1.域名解析:會解析域名對應的IP地址。
2. TCP的三次握手
第一次握手,客戶端發了個連接請求消息到服務端,服務端收到信息後知道自己與客戶端是可以連接成功的,但此時客戶端並不知道服務端是否已經接收到了它的請求,所以服務端接收到消息後的應答,客戶端得到服務端的反饋後,才確定自己與服務端是可以連接上的,這就是第二次握手。
客戶端只有確定了自己能與服務端連接上才能開始發數據。所以兩次握手肯定是最基本的。
3.建立TCP連接後發起HTTP請求
TCP三次握手建立連接成功後,客戶端按照指定的格式開始向服務端發送HTTP請求,服務端接收請求後,解析HTTP請求,處理完業務邏輯,最後返回一個具有標準格式的HTTP響應給客戶端。
4.客戶端得到服務器響應的數據,從而開始對應的解析流程。

94.HttpUrlConnection 和 okhttp關係

我們最熟悉的肯定是HttpUrlConnection,這是google官方提供的用來訪問網絡,但是HttpUrlConnection實現的比較簡單,只支持1.0/1.1,並沒有上面講的多路複用,如果碰到app大量網絡請求的時候,性能比較差,而且HttpUrlConnection底層也是用Socket來實現的。

OkHttp 與 HttpUrlConnection一樣,實現了一個網絡連接的過程( OkHttp 和 HttpUrlConnection是一級的)。OkHttp使用的是sink和source,這兩個是在Okio這個開源庫裏的,sink相當於outputStream,source相當於是inputStream。Sink和source比InputSrteam和OutputSrewam更加強大(其子類有緩衝BufferedSink、支持Gzip壓縮GzipSink、服務於GzipSink的ForwardingSink和InflaterSink)

95.• Bitmap對象的理解

1.Bitmap在Android中指的是一張圖片。
2.通過BitmapFactory類提供的四類方法:
1)decodeFile(從文件系統加載出一個Bitmap對象)
2)decodeResource(從資源中加載出一個Bitmap對象)
3)decodeStream(從輸入流中加載出一個Bitmap對象)
4)decodeByteArray(從字節數組中加載出一個Bitmap對象)
其中decodeFile , decodeResource又間接調用了decodeStream方法,這四類方法最終是在Android的底層實現的,對應着BitmapFactory類的幾個native方法。
3.BitmapFactory.Options的參數
①inSampleSize參數
上述四類方法都支持BitmapFactory.Options參數,而Bitmap的按一定採樣率進行縮放就是通過BitmapFactory.Options參數實現的,主要用到了inSampleSize參數,即採樣率。通過對inSampleSize的設置,對圖片的像素的高和寬進行縮放。
當inSampleSize=1,即採樣後的圖片大小爲圖片的原始大小。小於1,也按照1來計算。
當inSampleSize>1,即採樣後的圖片將會縮小,縮放比例爲1/(inSampleSize的二次方)。
關於inSampleSize取值的注意事項:
通常是根據圖片寬高實際的大小/需要的寬高大小,分別計算出寬和高的縮放比。但應該取其中最小的縮放比,避免縮放圖片太小,到達指定控件中不能鋪滿,需要拉伸從而導致模糊。
②inJustDecodeBounds參數
我們需要獲取加載的圖片的寬高信息,然後交給inSampleSize參數選擇縮放比縮放。
想先不加載圖片卻能獲得圖片的寬高信息,可通過inJustDecodeBounds=true,然後加載圖片就可以實現只解析圖片的寬高信息,並不會真正的加載圖片,所以這個操作是輕量級的。
當獲取了寬高信息,計算出縮放比後,然後在將inJustDecodeBounds=false,再重新加載圖片,就可以加載縮放後的圖片。
4.高效加載Bitmap的流程
①將BitmapFactory.Options的inJustDecodeBounds參數設爲true並加載圖片。這裏要設置Options.inJustDecodeBounds=true,這時候decode的bitmap爲null,只是把圖片的寬高放在Options裏,
②從BitmapFactory.Options中取出圖片的原始寬高信息,它們對應於outWidth和outHeight參數。
③根據採樣率的規則並結合目標View的所需大小計算出採樣率inSampleSize。
④將BitmapFactory.Options的inJustDecodeBounds參數設爲false,然後重新加載圖片。

96.• looper架構(handler的運行機制)

Handler裏面有一個重要的成員變量Looper,Looper裏面維護了一個MessageQueue(消息隊列),當我們使用handler.post或者sendMessage相關的方法都是將消息Message放入到消息隊列中。每一個線程都將擁有一個自己的Looper,是通過:

1static final ThreadLocal sThreadLocal
實現的,顧名思義ThreadLocal是和線程綁定的。當我們有一個線程A使用sThreadLocal.set(Looper a),線程B使用sThreadLocal.set(Looper b)的方式存儲,如果我們在線程B中使用sThreadLocal.get()將會得到Looper b的實例。所以我們每個線程可以擁有獨立的Looper,Looper裏面維護了一個消息隊列,也就是每一個線程維護自己的消息隊列。

當在主線程中時,在你的應用啓動時系統便給我們創建了一個MainLooper存入了sThreadLocal中,所以平時我們使用Handler時,如果是在主線程中創建的,我們是不需再去創建一個Looper給Handler的,因爲系統已經做了,所以當我們new Handler時,系統便將之前存入的Looper通過sThreadLoca中get出來,然後再去從對應的消息隊列中讀取執行。

而當我們在子線程中創建Handler時,如果直接new Handler運行時肯定會報錯的,提示我們必須先調用Looper.prepare()方法,爲什麼呢?因爲我們沒有創建子線程對應的Looper放入sThreadLocal當中,而prepare方法就是new了一個Looper的實例通過sThreadLocal.set設置到當前線程的。整個建立過程類似於下圖:

也就是說,Handler創建的時候肯定會在一個線程當中(主線程或者子線程),並且創建一個Looper實例與此線程綁定(無論是系統幫我們創建或者通過prepare自己綁定),在Looper中維護一個消息隊列,然後looper循環的從消息隊列中讀取消息執行(在消息隊列所在線程執行)。這就是整個Handler的運行機制了。

97.• ActivityThread,AMS,WMS的工作原理

AMS統一調度所有應用程序的Activity
WMS控制所有Window的顯示與隱藏以及要顯示的位置
在視圖層次中,Activity在WIndow之上,如下圖
在這裏插入圖片描述
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
查詢系統當前運行狀態

啓動流程:

第一階段:啓動ActivityManagerService。
第二階段:調用setSystemProcess。
第三階段:調用installSystemProviders方法。
第四階段:調用systemReady方法。

工作流程:

AMS的工作流程,其實就是Activity的啓動和調度的過程,所有的啓動方式,最終都是通過Binder機制的Client端,調用Server端的AMS的startActivityXXX()系列方法。所以可見,工作流程又包括Client端和Server端兩個。

Client端流程:

Launcher主線程捕獲onClick()點擊事件後,調用Launcher.startActivitySafely()方法。Launcher.startActivitySafely()內部調用了Launcher.startActivity()方法,Launcher.startActivity()內部調用了Launcher的父類Activity的startActivity()方法。

Activity.startActivity()調用Activity.startActivityForResult()方法,傳入該方法的requestCode參數若爲-1,則表示Activity啓動成功後,不需要執行Launcher.onActivityResult()方法處理返回結果。

啓動Activity需要與系統ActivityManagerService交互,必須納入Instrumentation的監控,因此需要將啓動請求轉交instrumentation,即調用Instrumentation.execStartActivity()方法。

Instrumentation.execStartActivity()首先通過ActivityMonitor檢查啓動請求,然後調用ActivityManagerNative.getDefault()得到ActivityManagerProxy代理對象,進而調用該代理對象的startActivity()方法。

ActivityManagerProxy是ActivityManagerService的代理對象,因此其內部存儲的是BinderProxy,調用ActivityManagerProxy.startActivity()實質是調用BinderProxy.transact()向Binder驅動發送START_ACTIVITY_TRANSACTION命令。Binder驅動將處理邏輯從Launcher所在進程切換到ActivityManagerService所在進程。

Server端流程:

啓動Activity的請求從Client端傳遞給Server端後,便進入了啓動應用的七個階段,這裏也是整理出具體流程。

1)預啓動

ActivityManagerService.startActivity()
ActivityStack.startActivityMayWait()
ActivityStack.startActivityLocked()
ActivityStack.startActivityUncheckedLocked()
ActivityStack.startActivityLocked()(重載)
ActivityStack.resumeTopActivityLocked()

2)暫停

ActivityStack.startPausingLocked()
ApplicationThreadProxy.schedulePauseActivity()
ActivityThread.handlePauseActivity()
ActivityThread.performPauseActivity()
ActivityManagerProxy.activityPaused()
completePausedLocked()

3)啓動應用程序進程

第二次進入ActivityStack.resumeTopActivityLocked()
ActivityStack.startSpecificActivityLocked()
startProcessLocked()
startProcessLocked()(重載)
Process.start()

4)加載應用程序Activity

ActivityThread.main()
ActivityThread.attach()
ActivityManagerService.attachApplication()
ApplicationThread.bindApplication()
ActivityThread.handleBindApplication()

5)顯示Activity

ActivityStack.realStartActivityLocked()
ApplicationThread.scheduleLaunchActivity()
ActivityThead.handleLaunchActivity()
ActivityThread.performLaunchActivity()
ActivityThread.handleResumeActivity()
ActivityThread.performResumeActivity()
Activity.performResume()
ActivityStack.completeResumeLocked()

6)Activity Idle狀態的處理

7)停止源Activity
ActivityStack.stopActivityLocked()
ApplicationThreadProxy.scheduleStopActivity()
ActivityThread.handleStopActivity()
ActivityThread.performStopActivityInner()

98.• 自定義View如何考慮機型適配

佈局類建議:

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

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

99.• LaunchMode應用場景

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。

100.•請介紹下ContentProvider 是如何實現數據共享的?

ContentProvider是以Uri的形式對外提供數據,ContenrResolver是根據Uri來訪問數據。
** 步驟:**

定義自己的ContentProvider類,該類需要繼承Android系統提供的ContentProvider基類。

在Manifest.xml 文件中註冊ContentProvider,(四大組件的使用都需要在Manifest文件中註冊) 註冊時需要綁定一個URL。

例如: android:authorities=“com.myit.providers.MyProvider”
說明:authorities就相當於爲該ContentProvider指定URL。 註冊後,其他應用程序就可以通過該Uri來訪問MyProvider所暴露的數據了。
其他程序使用ContentResolver來操作。

調用Activity的ContentResolver獲取ContentResolver對象
調用ContentResolver的insert(),delete(),update(),query()進行增刪改查。
一般來說,ContentProvider是單例模式,也就是說,當多個應用程序通過ContentResolver來操作ContentProvider提供的數據時,ContentResolver調用的數據操作將會委託給同一個ContentResolver

101.•IntentService原理及作用是什麼?

在Android開發中,我們或許會碰到這麼一種業務需求,一項任務分成幾個子任務,子任務按順序先後執行,子任務全部執行完後,這項任務纔算成功。那麼,利用幾個子線程順序執行是可以達到這個目的的,但是每個線程必須去手動控制,而且得在一個子線程執行完後,再開啓另一個子線程。或者,全部放到一個線程中讓其順序執行。這樣都可以做到,但是,如果這是一個後臺任務,就得放到Service裏面,由於Service和Activity是同級的,所以,要執行耗時任務,就得在Service裏面開子線程來執行。那麼,有沒有一種簡單的方法來處理這個過程呢,答案就是IntentService。

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

   所有請求都在一個單線程中,不會阻塞應用程序的主線程(UI Thread),同一時間只處理一個請求。

那麼,用IntentService有什麼好處呢?首先,我們省去了在Service中手動開線程的麻煩,第二,當操作完成時,我們不用手動停止Service,第三,it’s so easy to use!

102.• ApplicationContext和ActivityContext的區別

ApplicationContext的生命週期與Application的生命週期相關的,ApplicationContext隨着Application的銷燬而銷燬,伴隨application的一生,與activity的生命週期無關.

ActivityContext跟Activity的生命週期是相關的,但是對一個Application來說,Activity可以銷燬幾次,那麼屬於Activity的context就會銷燬多次

Context一共有Application、Activity和Service三種類型,因此一個應用程序中Context數量的計算公式就可以這樣寫:

Context數量 = Activity數量 + Service數量 + 1

上面的1代表着Application的數量,因爲一個應用程序中可以有多個Activity和多個Service,但是只能有一個Application。

和UI相關的方法基本都不建議或者不可使用Application,並且,前三個操作基本不可能在Application中出現。實際上,只要把握住一點,凡是跟UI相關的,都應該使用Activity做爲Context來處理;其他的一些操作,Service,Activity,Application等實例都可以,當然了,注意Context引用的持有,防止內存泄漏。
還有就是,在使用context的時候,爲防止內存泄露,注意一下幾個方面:

不要讓生命週期長的對象引用activity context,即保證引用activity的對象要與activity本身生命週期是一樣的。
對於生命週期長的對象,可以使用application context。
避免非靜態的內部類,儘量使用靜態類,避免生命週期問題,注意內部類對外部對象引用導致的生命週期變化。

103.• SP是進程同步的嗎?有什麼方法做到同步?

  1. SharedPreferences不支持進程同步

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

在getSharedPreferences的時候, 會強制讓SP進行一次讀取操作,從而保證數據是最新的. 但是若頻繁多進程進行讀寫 . 若某個進程持有了一個外部sp對象, 那麼不能保證數據是最新的. 因爲剛剛被別的進程更新了.

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

104• 談談多線程在Android中的使用

.Android提供了四種常用的操作多線程的方式,分別是:
Handler+Thread
AsyncTask
ThreadPoolExecutor
IntentService

105.• AndroidManifest的作用與理解

標籤層:
1.package=“應用包名”
整個應用的包名。這裏有個坑,當我們通過ComponentName來啓動某個Activity時,所用的包名一定是這個應用的包名,而不是當前Activity的包名。

2.xmlns:android=“http://schemas.android.com/apk/res/android”
命名空間的聲明,使得各種Android系統級的屬性能讓我們使用。當我們需要使用自定義屬性時,可以將其修改爲res-auto,編譯時會爲我們自動去找到該自定義屬性。

3.android:sharedUserId=“android.uid.system”
將當前應用進程設置爲系統級進程。

4.uses-permission
爲我們的應用添加必須的權限。同時我們也可以該層聲明自定義的權限。

標籤層:
應用層標籤,用來配置我們的apk的整體屬性,也可以統一指定所有界面的主題。

1.“android:name”、“android:icon”、“android:label”
顧名思義,用來指定應用的名稱、在桌面的啓動圖標、應用的標籤名

2.“android:theme”
爲當前應用的每個界面都默認設置一個主題,可以後續在activity標籤層單獨覆蓋此Theme。

3.“android:allowBackup”
關閉應用程序數據的備份和恢復功能,注意該屬性值默認爲true,如果你不需要你的應用被恢復導致隱私數據暴露,必須手動設置此屬性。

4.android:hardwareAccelerated=“true”
開啓硬件加速,一般應用不推介使用。就算非要使用也最好在某個Activity單獨開啓,避免過大的內存開銷。

5.android:taskAffinity
設置Activity任務棧的名稱,可忽略。

<具體組件/>標籤層:
因爲、在實際開發中接觸得不多,這部分主要講解 、標籤。

關於Activity標籤的屬性,個人最覺得繞和難掌握的就是Intent-filter的匹配規則了,每次使用錯了都要去查資料修改,所以這邊總結得儘可能仔細。

1.android:configChanges 當我們的界面大小,方向,字體等config參數改變時,我們的Activity就會重新執行onCreate的生命週期。而當我們設置此屬性後,就可以強制讓Activity不重新啓動,而是隻會調用一次onConfigurationChanged方法,所以我們可以在這裏做一些相關參數改變的操作。

2.“android.intent.category.LAUNCHER”、“android.intent.action.MAIN”
這兩個屬性共同將當前Activity聲明爲了我們應用的入口,將應用註冊至系統的應用列表中,缺一不可。

注:這裏還有一點需要注意,如果希望我們的應用有多個入口,每個入口能進入到app的不同Activity中時,光設置這兩個屬性還不夠,還要爲它指定一個進程和啓動模式。

android:process=".otherProcess"

android:launchMode =“singleInstance”

1.android:exported=“true”
將當前組件暴露給外部。屬性決定它是否可以被另一個Application的組件啓動。

當我們通過intent去隱式調用一個Activity時,需要同時匹配註冊activity中的action、category、data才能正常啓動,而這三個屬性的匹配規則也略有不同。

2.action
action是最簡單的匹配項,我們將其理解爲一個區分大小寫的字符串即可。

一般用來代表某一種特定的動作,隱式調用時intent必須setAction。一個過濾器中可以有多個action屬性,只要我們的itent和其中任意一項equal則就算匹配成功。

3.category
category屬性也是一個字符串,匹配時也必須和過濾器中定義的值相同。

當我們沒有爲intent設置addCategory時,系統爲幫我們默認添加一個值爲"android.intent.category.DEFAULT"的category。

反過來說,如果我們需要我們自己寫的Activity能接受隱式intent啓動,我們就必須在它的過濾器中添加"android.intent.category.DEFAULT",否則無法成功啓動。

3.data
data比較複雜,幸運地是我們幾乎用不到它。

額外擴展一些關於activity的屬性:

標籤:
標籤是提供組件額外的數據用的,它本身是一個鍵值對,寫在清單文件中之後,可以在代碼中獲取。

android:excludeFromRecents=“true”
設置爲true後,當用戶按了“最近任務列表”時候,該activity不會出現在最近任務列表中,可達到隱藏應用的目的。

關於receiver,廣播接收器,也可以給他設置接收權限。一個permission問題:
<receiver

    android:name="com.android.settings.AliAgeModeReceiver"

    android:permission="com.android.settings.permission.SWITH_SETTING">

    <intent-filter>

        <action android:name="com.android.settings.action.SWITH_AGED_MODE"/>

    </intent-filter>

可在receiver標籤中添加權限, 發廣播時可以設置相應權限的應用接收

常見的一些原理性問題

106.• 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的數據結構是基於鏈表的

107.• HandlerThread

HandlerThread本質上就是一個普通Thread,只不過內部建立了Looper.
HandlerThread的特點
HandlerThread將loop轉到子線程中處理,說白了就是將分擔MainLooper的工作量,降低了主線程的壓力,使主界面更流暢。
開啓一個線程起到多個線程的作用。處理任務是串行執行,按消息發送順序進行處理。HandlerThread本質是一個線程,在線程內部,代碼是串行處理的。
但是由於每一個任務都將以隊列的方式逐個被執行到,一旦隊列中有某個任務執行時間過長,那麼就會導致後續的任務都會被延遲處理。
HandlerThread擁有自己的消息隊列,它不會干擾或阻塞UI線程。
對於網絡IO操作,HandlerThread並不適合,因爲它只有一個線程,還得排隊一個一個等着

108.• 關於Handler,在任何地方new Handler 都是什麼線程下?

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

public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}
// 第一個參數是 looper 對象,第二個 callback 對象,第三個消息處理方式(是否異步)
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

可以看出來傳遞 Looper 對象 Handler 就直接使用了。所以對於傳遞 Looper 對象創建 Handler 的情況下,傳遞的 Looper 是哪個線程的,Handler 綁定的就是該線程。

109• ThreadLocal原理,實現及如何保證Local屬性?

當需要使用多線程時,有個變量恰巧不需要共享,此時就不必使用synchronized這麼麻煩的關鍵字來鎖住,每個線程都相當於在堆內存中開闢一個空間,線程中帶有對共享變量的緩衝區,通過緩衝區將堆內存中的共享變量進行讀取和操作,ThreadLocal相當於線程內的內存,一個局部變量。每次可以對線程自身的數據讀取和操作,並不需要通過緩衝區與 主內存中的變量進行交互。並不會像synchronized那樣修改主內存的數據,再將主內存的數據複製到線程內的工作內存。ThreadLocal可以讓線程獨佔資源,存儲於線程內部,避免線程堵塞造成CPU吞吐下降。

在每個Thread中包含一個ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的對象,value是獨享數據。

110.• 請解釋下在單線程模型中Message、Handler、Message Queue、Looper之間的關係

簡單的說,Handler獲取當前線程中的looper對象,looper用來從存放Message的MessageQueue中取出Message,再有Handler進行Message的分發和處理.

Message Queue(消息隊列):用來存放通過Handler發佈的消息,通常附屬於某一個創建它的線程,可以通過Looper.myQueue()得到當前線程的消息隊列

Handler:可以發佈或者處理一個消息或者操作一個Runnable,通過Handler發佈消息,消息將只會發送到與它關聯的消息隊列,然也只能處理該消息隊列中的消息

Looper:是Handler和消息隊列之間通訊橋樑,程序組件首先通過Handler把消息傳遞給Looper,Looper把消息放入隊列。Looper也把消息隊列裏的消息廣播給所有的

Handler:Handler接受到消息後調用handleMessage進行處理

Message:消息的類型,在Handler類中的handleMessage方法中得到單個的消息進行處理

在單線程模型下,爲了線程通信問題,Android設計了一個Message Queue(消息隊列), 線程間可以通過該Message Queue並結合Handler和Looper組件進行信息交換。下面將對它們進行分別介紹:

  1. Message

    Message消息,理解爲線程間交流的信息,處理數據後臺線程需要更新UI,則發送Message內含一些數據給UI線程。

  2. Handler

    Handler處理者,是Message的主要處理者,負責Message的發送,Message內容的執行處理。後臺線程就是通過傳進來的 Handler對象引用來sendMessage(Message)。而使用Handler,需要implement 該類的 handleMessage(Message)方法,它是處理這些Message的操作內容,例如Update UI。通常需要子類化Handler來實現handleMessage方法。

  3. Message Queue

    Message Queue消息隊列,用來存放通過Handler發佈的消息,按照先進先出執行。

    每個message queue都會有一個對應的Handler。Handler會向message queue通過兩種方法發送消息:sendMessage或post。這兩種消息都會插在message queue隊尾並按先進先出執行。但通過這兩種方法發送的消息執行的方式略有不同:通過sendMessage發送的是一個message對象,會被 Handler的handleMessage()函數處理;而通過post方法發送的是一個runnable對象,則會自己執行。

  4. Looper

    Looper是每條線程裏的Message Queue的管家。Android沒有Global的Message Queue,而Android會自動替主線程(UI線程)建立Message Queue,但在子線程裏並沒有建立Message Queue。所以調用Looper.getMainLooper()得到的主線程的Looper不爲NULL,但調用Looper.myLooper() 得到當前線程的Looper就有可能爲NULL。對於子線程使用Looper,API Doc提供了正確的使用方法:這個Message機制的大概流程:

    1. 在Looper.loop()方法運行開始後,循環地按照接收順序取出Message Queue裏面的非NULL的Message。

    2. 一開始Message Queue裏面的Message都是NULL的。當Handler.sendMessage(Message)到Message Queue,該函數裏面設置了那個Message對象的target屬性是當前的Handler對象。隨後Looper取出了那個Message,則調用 該Message的target指向的Hander的dispatchMessage函數對Message進行處理。在dispatchMessage方法裏,如何處理Message則由用戶指定,三個判斷,優先級從高到低:

    1. Message裏面的Callback,一個實現了Runnable接口的對象,其中run函數做處理工作;

    2. Handler裏面的mCallback指向的一個實現了Callback接口的對象,由其handleMessage進行處理;

    3. 處理消息Handler對象對應的類繼承並實現了其中handleMessage函數,通過這個實現的handleMessage函數處理消息。

    由此可見,我們實現的handleMessage方法是優先級最低的!

    1. Handler處理完該Message (update UI) 後,Looper則設置該Message爲NULL,以便回收!

    在網上有很多文章講述主線程和其他子線程如何交互,傳送信息,最終誰來執行處理信息之類的,個人理解是最簡單的方法——判斷Handler對象裏面的Looper對象是屬於哪條線程的,則由該線程來執行!

    1. 當Handler對象的構造函數的參數爲空,則爲當前所在線程的Looper;
  5. Looper.getMainLooper()得到的是主線程的Looper對象,Looper.myLooper()得到的是當前線程的Looper對象。

111.• 請描述一下View事件傳遞分發機制

事件分發原理: 責任鏈模式,事件層層傳遞,直到被消費。
View 的 dispatchTouchEvent 主要用於調度自身的監聽器和 onTouchEvent。
View的事件的調度順序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener 。
不論 View 自身是否註冊點擊事件,只要 View 是可點擊的就會消費事件。
事件是否被消費由返回值決定,true 表示消費,false 表示不消費,與是否使用了事件無關。
ViewGroup 中可能有多個 ChildView 時,將事件分配給包含點擊位置的 ChildView。
ViewGroup 和 ChildView 同時註冊了事件監聽器(onClick等),由 ChildView 消費。
一次觸摸流程中產生事件應被同一 View 消費,全部接收或者全部拒絕。
只要接受 ACTION_DOWN 就意味着接受所有的事件,拒絕 ACTION_DOWN 則不會收到後續內容。
如果當前正在處理的事件被上層 View 攔截,會收到一個 ACTION_CANCEL,後續事件不會再傳遞過來。

112.• View刷新機制

在Android的View刷新機制中,父View負責刷新(invalidateChild)、佈局(layoutChild)顯示子View。而當子View需要刷新時,則是通知父View刷新子view來完成。

invalidate()和postInvalidate() 的區別及使用
當Invalidate()被調用的時候,View的OnDraw()就會被調用;Invalidate()是刷新UI,UI更新必須在主線程,所以invalidate必須在UI線程中被調用,如果在子線程中更新視圖的就調用postInvalidate()。

postInvalidate()實際調用的方法,mHandler.sendMessageDelayed,在子線程中用handler發送消息,所以才能在子線程中使用。

113• 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子視圖,如果沒有就不用;

114.• 爲什麼不能在子線程更新UI?

表象
我們先從表象上分析一下,假設可以在子線程更新UI,會產生那些後果呢?
如果不同的線程控制同一塊UI,因爲時間的延時性,網絡的延遲性,很有可能界面圖像會亂套,會花掉。而且出了問題也非常不容易排查問題出在了哪裏。從硬件上考慮,每個手機只有一個顯示芯片,根本上不可能同時處理多個繪製請求),減少更新線程數,其實是提高了更新效率。

本質
如果可以併發的更新UI,事實上是 “is not thread safe”的,也就是線程不安全。我們都知道,線程安全問題其實就是,不同的線程對同一塊資源的調用。在更新UI的同時,會涉及context資源的調用,所以產生了線程安全問題。

115.ANR產生的原因是什麼?

1.只有主線程纔會產生ANR,主線程就是UI線程;

2.必須發生某些輸入事件或特定操作,比如按鍵或觸屏等輸入事件,在BroadcastReceiver或Service的各個生命週期調用函數;

3.上述事件響應超時,不同的context規定的上限時間不同

a.主線程對輸入事件5秒內沒有處理完畢

b.主線程在執行BroadcastReceiver的onReceive()函數時10秒內沒有處理完畢

c.主線程在Service的各個生命週期函數時20秒內沒有處理完畢。

那麼導致ANR的根本原因是什麼呢?簡單的總結有以下兩點:

1.主線程執行了耗時操作,比如數據庫操作或網絡編程

2.其他進程(就是其他程序)佔用CPU導致本進程得不到CPU時間片,比如其他進程的頻繁讀寫操作可能會導致這個問題。

細分的話,導致ANR的原因有如下幾點:

1.耗時的網絡訪問

2.大量的數據讀寫

3.數據庫操作

4.硬件操作(比如camera)

5.調用thread的join()方法、sleep()方法、wait()方法或者等待線程鎖的時候

6.service binder的數量達到上限

7.system server中發生WatchDog ANR

8.service忙導致超時無響應

9.其他線程持有鎖,導致主線程等待超時

10.其它線程終止或崩潰導致主線程一直等待

那麼如何避免ANR的發生呢或者說ANR的解決辦法是什麼呢?

1.避免在主線程執行耗時操作,所有耗時操作應新開一個子線程完成,然後再在主線程更新UI。

2.BroadcastReceiver要執行耗時操作時應啓動一個service,將耗時操作交給service來完成。

3.避免在Intent Receiver裏啓動一個Activity,因爲它會創建一個新的畫面,並從當前用戶正在運行的程序上搶奪焦點。如果你的應用程序在響應Intent廣 播時需要向用戶展示什麼,你應該使用Notification Manager來實現。

116.• Oom 是否可以try catch?

只有在一種情況下,這樣做是可行的:

在try語句中聲明瞭很大的對象,導致OOM,並且可以確認OOM是由try語句中的對象聲明導致的,那麼在catch語句中,可以釋放掉這些對象,解決OOM的問題,繼續執行剩餘語句。

但是這通常不是合適的做法。

Java中管理內存除了顯式地catch OOM之外還有更多有效的方法:比如SoftReference, WeakReference, 硬盤緩存等。
在JVM用光內存之前,會多次觸發GC,這些GC會降低程序運行的效率。
如果OOM的原因不是try語句中的對象(比如內存泄漏),那麼在catch語句中會繼續拋出OOM

什麼情況導致OOM?
OOM常見情況:

1)Acitivity沒有對棧進行管理,如果開啓過多,就容易造成內存溢出
2)加載大的圖片或者同時數量過多的圖片的時候
3)程序存在內存泄漏問題,導致系統可用內存越來越小。
4)遞歸次數過多,也會導致內存溢出.
5)頻繁的內存抖動,也會造成OOM異常的發生,大量小的對象被頻繁的創建,導致內存碎片,從而當需要分配內存的時候,雖然總體上還有內存分配,但是由於這些內存不是連續的,導致無法分配,系統就直接返回OOM了

有什麼解決方法可以避免OOM?
1)減小對象的內存佔用,避免OOM的第一步就是要儘量減少新分配出來的對象佔用內存的大小,儘量使用更加輕量的對象。
2)內存對象的重複利用,大多數對象的複用,最終實施的方案都是利用對象池技術,要麼是在編寫代碼時顯式地在程序裏創建對象池,然後處理好複用的實現邏輯。要麼就是利用系統框架既有的某些複用特性,減少對象的重複創建,從而降低內存的分配與回收。
3)避免對象的內存泄露,內存對象的泄漏,會導致一些不再使用的對象無法及時釋放,這樣一方面佔用了寶貴的內存空間,很容易導致後續需要分 配內存的時候,空閒空間不足而出現OOM。
4)內存使用策略優化。

117.• 內存泄漏?

什麼是內存泄漏?
內存泄漏是當程序不再使用到的內存時,釋放內存失敗而產生了無用的內存消耗。內存泄漏並不是指物理上的內存消失,這裏的內存泄漏是值由程序分配的內存但是由於程序邏輯錯誤而導致程序失去了對該內存的控制,使得內存浪費。

怎樣會導致內存泄漏?
資源對象沒關閉造成的內存泄漏,如查詢數據庫後沒有關閉遊標cursor
構造Adapter時,沒有使用 convertView 重用
Bitmap對象不在使用時調用recycle()釋放內存
對象被生命週期長的對象引用,如activity被靜態集合引用導致activity不能釋放

內存泄漏有什麼危害?
內存泄漏對於app沒有直接的危害,即使app有發生內存泄漏的情況,也不一定會引起app崩潰,但是會增加app內存的佔用。內存得不到釋放,慢慢的會造成app內存溢出。所以我們解決內存泄漏的目的就是防止app發生內存溢出。

118• LruCache默認緩存大小

設置LruCache緩存的大小,一般爲當前進程可用容量的1/8。

一、Android中的緩存策略
一般來說,緩存策略主要包含緩存的添加、獲取和刪除這三類操作。如何添加和獲取緩存這個比較好理解,那麼爲什麼還要刪除緩存呢?這是因爲不管是內存緩存還是硬盤緩存,它們的緩存大小都是有限的。當緩存滿了之後,再想其添加緩存,這個時候就需要刪除一些舊的緩存並添加新的緩存。

因此LRU(Least Recently Used)緩存算法便應運而生,LRU是近期最少使用的算法,它的核心思想是當緩存滿時,會優先淘汰那些近期最少使用的緩存對象。採用LRU算法的緩存有兩種:LrhCache和DisLruCache,分別用於實現內存緩存和硬盤緩存,其核心思想都是LRU緩存算法。

二、LruCache的使用
LruCache是Android 3.1所提供的一個緩存類,所以在Android中可以直接使用LruCache實現內存緩存。而DisLruCache目前在Android 還不是Android SDK的一部分,但Android官方文檔推薦使用該算法來實現硬盤緩存。

1.LruCache的介紹
LruCache是個泛型類,主要算法原理是把最近使用的對象用強引用(即我們平常使用的對象引用方式)存儲在 LinkedHashMap 中。當緩存滿時,把最近最少使用的對象從內存中移除,並提供了get和put方法來完成緩存的獲取和添加操作。

2.LruCache的使用
LruCache的使用非常簡單,我們就已圖片緩存爲例。

int maxMemory = (int) (Runtime.getRuntime().totalMemory()/1024);
int cacheSize = maxMemory/8;
mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes()*value.getHeight()/1024;
}
};
①設置LruCache緩存的大小,一般爲當前進程可用容量的1/8。
②重寫sizeOf方法,計算出要緩存的每張圖片的大小。

注意:緩存的總容量和每個緩存對象的大小所用單位要一致。

三、LruCache的實現原理
LruCache的核心思想很好理解,就是要維護一個緩存對象列表,其中對象列表的排列方式是按照訪問順序實現的,即一直沒訪問的對象,將放在隊尾,即將被淘汰。而最近訪問的對象將放在隊頭,最後被淘汰。

119• ContentProvider的權限管理

ContentProvider可以理解爲一個Android應用對外開放的接口,只要是符合它所定義的Uri格式的請求,均可以正常訪問執行操作。其他的Android應用可以使用ContentResolver對象通過與ContentProvider同名的方法請求執行,被執行的就是ContentProvider中的同名方法。所以ContentProvider很多對外可以訪問的方法,在ContentResolver中均有同名的方法,是一一對應的
訪問權限

對於ContentProvider暴露出來的數據,應該是存儲在自己應用內存中的數據,對於一些存儲在外部存儲器上的數據,並不能限制訪問權限,使用ContentProvider就沒有意義了。對於ContentProvider而言,有很多權限控制,可以在AndroidManifest.xml文件中對節點的屬性進行配置,一般使用如下一些屬性設置:

android:grantUriPermssions:臨時許可標誌。
android:permission:Provider讀寫權限。
android:readPermission:Provider的讀權限。
android:writePermission:Provider的寫權限。
android:enabled:標記允許系統啓動Provider。
android:exported:標記允許其他應用程序使用這個Provider。
android:multiProcess:標記允許系統啓動Provider相同的進程中調用客戶端。

120• 如何通過廣播攔截和abort一條短信

BroadcastReceiver還可以監聽系統進程,比如android的收短信,電量低,電量改變,系統啓動,等……只要BroadcastReceiver監聽了這些進程,就可以實現很多有趣的功能,比如,接收短信的這條廣播是一條有序廣播,所以我們可以監聽這條信號,在傳遞給真正的接收程序時,我們將自定義的廣播接收程序的優先級大於它,並且取消廣播的傳播,這樣就可以實現攔截短信的功能了。

public class SmsReceiver extends BroadcastReceiver {
        // 當接收到短信時被觸發
        @Override
        public void onReceive(Context context, Intent intent) {
            // 如果是接收到短信
            if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
                // 取消廣播(這行代碼將會讓系統收不到短信)
                abortBroadcast();
                StringBuilder sb = new StringBuilder();
                // 接收由SMS傳過來的數據
                Bundle bundle = intent.getExtras();
                // 判斷是否有數據
                if (bundle != null) {
                    // 通過pdus可以獲得接收到的所有短信消息
                    Object[] pdus = (Object[]) bundle.get("pdus");
                    // 構建短信對象array,並依據收到的對象長度來創建array的大小
                    SmsMessage[] messages = new SmsMessage[pdus.length];
                    for (int i = 0; i < pdus.length; i++) {
                        messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
                    }
                    // 將送來的短信合併自定義信息於StringBuilder當中
                    for (SmsMessage message : messages) {
                        sb.append("短信來源:");
                        // 獲得接收短信的電話號碼
                        sb.append(message.getDisplayOriginatingAddress());
                        sb.append("\n------短信內容------\n");
                        // 獲得短信的內容
                        sb.append(message.getDisplayMessageBody());
                    }
                }
                Toast.makeText(context, sb.toString(), 5000).show();
            }
        }
    }

121.廣播是否可以請求網絡?

可以,不過需要開啓線程操作

122.• 廣播引起anr的時間限制是多少

當 onReceive() 方法在 10 秒內沒有執行完畢, Android 會認爲該程序無響應

123.• Android爲什麼引入Parcelable?

Parcelable是Android提供一套序列化機制,它將序列化後的字節流寫入到一個共性內存中,其他對象可以從這塊共享內存中讀出字節流,並反序列化成對象。因此效率比較高,適合在對象間或者進程間傳遞信息。

124.• 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。

關鍵代碼:

// 給 ImageView 設置一個 tag
holder.img.setTag(imgUrl);
// 預設一個圖片
holder.img.setImageResource(R.drawable.ic_launcher);

// 通過 tag 來防止圖片錯位
if (imageView.getTag() != null && imageView.getTag().equals(imageUrl)) {
imageView.setImageBitmap(result);
}

125.• 屏幕適配的處理技巧都有哪些?

在這裏插入圖片描述

126.• Recycleview和ListView的區別

一、 緩存機制對比

  1. 層級不同:

RecyclerView比ListView多兩級緩存,支持多個離ItemView緩存,支持開發者自定義緩存處理邏輯,支持所有RecyclerView共用同一個RecyclerViewPool(緩存池)。
具體來說:
ListView(兩級緩存):
在這裏插入圖片描述
RecyclerView(四級緩存):
在這裏插入圖片描述
ListView和RecyclerView緩存機制基本一致:

1). mActiveViews和mAttachedScrap功能相似,意義在於快速重用屏幕上可見的列表項ItemView,而不需要重新createView和bindView;

2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意義在於緩存離開屏幕的ItemView,目的是讓即將進入屏幕的ItemView重用.

3). RecyclerView的優勢在於a.mCacheViews的使用,可以做到屏幕外的列表項ItemView進入屏幕內時也無須bindView快速重用;b.mRecyclerPool可以供多個RecyclerView共同使用,在特定場景下,如viewpaper+多個列表頁下有優勢.客觀來說,RecyclerView在特定場景下對ListView的緩存機制做了補強和完善。

  1. 緩存不同:

1). RecyclerView緩存RecyclerView.ViewHolder,抽象可理解爲:

View + ViewHolder(避免每次createView時調用findViewById) + flag(標識狀態);

RecyclerView中mCacheViews(屏幕外)獲取緩存時,是通過匹配pos獲取目標位置的緩存,這樣做的好處是,當數據源數據不變的情況下,無須重新bindView:

2). ListView緩存View。而同樣是離屏緩存,ListView從mScrapViews根據pos獲取相應的緩存,但是並沒有直接使用,而是重新getView(即必定會重新bindView)。

二、 局部刷新

RecyclerView更大的亮點在於提供了局部刷新的接口,通過局部刷新,就能避免調用許多無用的bindView。ListView和RecyclerView最大的區別在於數據源改變時的緩存的處理邏輯,ListView是"一鍋端",將所有的mActiveViews都移入了二級緩存mScrapViews,而RecyclerView則是更加靈活地對每個View修改標誌位,區分是否重新bindView。

ListView獲取緩存的流程:

RecyclerView獲取緩存的流程:

結合RecyclerView的緩存機制,看看局部刷新是如何實現的:

以RecyclerView中notifyItemRemoved(1)爲例,最終會調用requestLayout(),使整個RecyclerView重新繪製,過程爲:

onMeasure()–>onLayout()–>onDraw()

其中,onLayout()爲重點,分爲三步:

dispathLayoutStep1():記錄RecyclerView刷新前列表項ItemView的各種信息,如Top,Left,Bottom,Right,用於動畫的相關計算;

dispathLayoutStep2():真正測量佈局大小,位置,核心函數爲layoutChildren();

dispathLayoutStep3():計算佈局前後各個ItemView的狀態,如Remove,Add,Move,Update等,如有必要執行相應的動畫.

其中,layoutChildren()流程圖:
在這裏插入圖片描述

在這裏插入圖片描述

當調用notifyItemRemoved時,會對屏幕內ItemView做預處理,修改ItemView相應的pos以及flag(流程圖中紅色部分):
在這裏插入圖片描述

當調用fill()中RecyclerView.getViewForPosition(pos)時,RecyclerView通過對pos和flag的預處理,使得bindview只調用一次.

127.• 動態權限適配方案,權限組的概念

在這裏插入圖片描述
具體方法:

使用Context.checkSelfPermission()接口先檢查權限是否授權。
使用Activity.shouldShowRequestPermissionRationale()接口檢查用戶是否勾選不再提醒。
第2步返回爲true時,表示用戶並未勾選不再提醒選項,使用Activity.requestPermissions()接口向系統請求權限。
第2步返回爲false時,表示用戶已勾選不再提醒選項,則應用該彈框提示用戶。
第3步執行後,不論用戶是否授予權限,都會回調Activity.onRequestPermissionsResult()的函數。在Activity中重載onRequestPermissionsResult()函數,在接收授權結果,根據不同的授權結果做相應的處理。

動態權限申請的適配方案:

對於低於M版本(安卓6.0)的Android系統,沒有動態權限的申請問題,動態權限的申請流程對於低於M版本的Android系統也不再適用。所以適配方案,首先要考慮低於M版本的Android系統,因此對於Android版本小於M版本時,在檢查權限時,直接返回true就OK,直接屏蔽後續流程。

對於M版本(安卓6.0)的Android系統,動態權限的申請流程會產生好幾個分支處理邏輯,這樣不善於管理和維護。所以對於此處,爲了將寫得代碼更加整潔和更易於維護,我們可以將動態權限申請進行一次封裝,

新建一個空白的activity用戶權限申請和回調,然後在activity外包裝一層管理內,限制一個調用的入口和出口,對於外部暴露唯一的入口和出口,這樣對於外部邏輯代碼需要調用權限時,將變得異常簡單,並且由於將權限申請封裝在了管理類中,
對於低於M版本的Android系統也將沒有任何引用,在管理類中直接對於低於M版本的權限申請請求直接回調全部已授權即可。

實際App中動態權限申請代碼如下:

由於權限的動態申請與管理應該是伴隨着整個App的生命週期的,所以PermissionsManager設計爲單例的,且初始化保存着applicationContext作爲默認跳轉方式。
在App代碼中,應在自定義的Application中調用PermissionsManager中的initContext()方法。
在App代碼中,在需要申請權限的地方調用PermissionsManager中的newRequestPermissions()方法即可,不論Android版本是低於M版本還是高於M版本,根據實際App是否擁有對應權限,回調傳入的該次權限申請的PermissionsResultsCallback接口的onRequestPermissionsResult()方法。這樣就將動態權限的申請結果處理流程給唯一化,將權限的申請的入口和出口都唯一化。
這樣將權限申請的細節和Android系統版本適配的細節都封裝在了PermissionsManager內部,對於應用外部只需要知道申請權限的入口以及申請權限接口的出口即可。

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

Android下拉通知欄不會影響Activity的生命週期方法

129.• 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(){}回調方法中,將拍照拿到的圖片進行展示

130.• 微信主頁面的實現方式

viewpager + fragment

131.• 微信上消息小紅點的原理

自定義view畫小紅點和數字

132.• 圖片庫對比

https://www.jianshu.com/p/44a4ee648ab4

133.• 圖片框架緩存實現

內存緩存
Glide內存緩存的實現自然也是使用的LruCache算法。不過除了LruCache算法之外,Glide還結合了一種弱引用的機制,共同完成了內存緩存功能。

Glide的圖片加載過程中會調用兩個方法來獲取內存緩存,loadFromCache()和loadFromActiveResources()。這兩個方法中一個使用的就是LruCache算法,另一個使用的就是弱引用。

當acquired變量大於0的時候,說明圖片正在使用中,也就應該放到activeResources弱引用緩存當中。而經過release()之後, 如果acquired變量等於0了,說明圖片已經不再被使用了,那麼此時會調用listener的onResourceReleased()方法來釋放資源

當圖片不再使用時,首先會將緩存圖片從activeResources中移除,然後再將它put到LruResourceCache當中。這樣也就實現了正在使用中的圖片使用弱引用來進行緩存,不在使用中的圖片使用LruCache來進行緩存的功能。

硬盤緩存
Glide是使用的自己編寫的DiskLruCache工具類,但是基本的實現原理都是差不多的。

一種是調用decodeFromCache()方法從硬盤緩存當中讀取圖片,一種是調用decodeFromSource()來讀取原始圖片。默認情況下Glide會優先從緩存當中讀取,只有緩存中不存在要讀取的圖片時,纔會去讀取原始圖片。

調用transform()方法來對圖片進行轉換,然後在writeTransformedToCache()方法中將轉換過後的圖片寫入到硬盤緩存中,調用的同樣是DiskLruCache實例的put()方法,不過這裏用的緩存Key是resultKey(也就是調整圖片大小後進行緩存)。

134.• LRUCache原理

緩存保存了一個強引用(Android 2.3開始,垃圾回收器更傾向於回收弱引用和軟引用,軟引用和弱引用變得不可靠,Android 3.0中,圖片的數據會存儲在本地的內存當中,因而無法用一種可預見的方式將其釋放)限制值的數量. 每當值被訪問的時候,它會被移動到隊列的頭部. 當緩存已滿的時候加入新的值時,隊列中最後的值會出隊,可能被回收

LRUCache內部維護主要是通過LinkedHashMap實現

這是一個安全的線程,多線程緩存通過同步實現

135.• 自己去實現圖片庫,怎麼做

Glide原理
應該具備以下功能:

圖片的同步加載
圖片的異步加載
圖片壓縮
內存緩存
磁盤緩存
網絡拉取

136.• Glide源碼解析

https://blog.csdn.net/rjgcszlc/article/details/81843072

137.• Glide使用什麼緩存?

Glide緩存非常先進,很靈活,很全面,總體上來講有內存緩存和磁盤文件緩存。緩衝機制概括來講就是讀緩存以及是寫入緩存的機制。而Glide讀緩存時機就是先內存緩存查找再到磁盤緩存查找最後網絡,寫入緩存則就是在獲取到原始source圖片之後,先寫入磁盤緩存,再加入內存緩存。

138.網絡框架對比

網絡請求框架總結

1.xutils

此框架龐大而周全,這個框架可以網絡請求,同時可以圖片加載,又可以數據存儲,又可以 View 註解,使用這種框架很方便,這樣會使得你整個項目對它依賴性太強,萬一以後這個庫不維護了,或者中間某個模塊出問題了,這個影響非常大,所以在項目開發時,一般會更喜歡選擇專注某一領域的框架。

2.OkHttp

Android 開發中是可以直接使用現成的api進行網絡請求的,就是使用HttpClient、HttpUrlConnection 進行操作,目前HttpClient 已經被廢棄,而 android-async-http 是基於HttpClient的,可能也是因爲這個原因作者放棄維護。 而OkHttp是Square公司開源的針對Java和Android程序,封裝的一個高性能http請求庫,它的職責跟HttpUrlConnection 是一樣的,支持 spdy、http 2.0、websocket ,支持同步、異步,而且 OkHttp 又封裝了線程池,封裝了數據轉換,封裝了參數使用、錯誤處理等,api使用起來更加方便。可以把它理解成是一個封裝之後的類似HttpUrlConnection的東西,但是在使用的時候仍然需要自己再做一層封裝,這樣才能像使用一個框架一樣更加順手。

3.Volley

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

4.Retrofit

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

5.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 本身封裝的也更易用,擴展性更好些。

6.OkHttp VS Retrofit

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

7.Volley VS Retrofit

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

8.總結

綜上,如果以上三種網絡庫你都能熟練掌握,那麼優先推薦使用Retrofit,前提是最好你們的後臺api也能遵循RESTful的風格, 其次如果不想使用或者沒能力掌握Retrofit ,那麼推薦使用Volley ,畢竟Volley不需要做過多的封裝,如果需要上傳大數據, 那麼不建議使用 Volley,該採用 OkHttp 。

139.HTTP與HTTPS的區別以及如何實現安全性

1.Url開頭:HTTP 的 URL 以 HTTP:// 開頭,而 HTTPS 的 URL 以 HTTPs:// 開頭;

2.安全性:HTTP 是不安全的,而 HTTPS 是安全的。HTTP協議運行在TCP之上,所有傳輸的內容都是明文,HTTPS運行在SSL/TLS之上,SSL/TLS運行在TCP之上,所有傳輸的內容都經過加密的。
3.傳輸效率:傳輸效率上 HTTP 要高於 HTTPS ,因爲 HTTPS 需要經過加密過程,過程相比於 HTTP 要繁瑣一點,效率上低一些也很正常;

4.費用:HTTP 無需證書,而 HTTPS 必需要認證證書;相比於 HTTP 不需要證書來說,使用 HTTPS 需要證書,申請證書是要費用的,HTTPS 這筆費用是無法避免的

5.端口:HTTP和HTTPS使用的是完全不同的連接方式,用的端口也不一樣,前者是80,後者是443。

6.防劫持性:HTTPS可以有效的防止運營商劫持,解決了防劫持的一個大問題。

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