2019年Android面試題及答案收集

前言

找工作、招人必備之良品。後期不斷完善中……

如何招聘人,蒐集了一些知識點。如何做好應聘準備,也收集了一些主要知識點,供你參考。

  • Android基礎知識:基本涵蓋Android所有知識體系,四大組件,Fragment,WebView,事件分發,View繪製…
  • Java基礎知識&高階知識點:基礎部分不談了,高階部分:泛型,反射,Java虛擬機…
  • 算法與數據結構:鏈表,堆,棧,樹…
  • Android常用框架:異步,網絡,圖片加載,內存優化,依賴注入,數據庫等框架
  • Android前沿技術:Android組件化,熱更新,插件化,消息推送,AOP面向切面編程,Flutter(谷歌的移動UI框架)…
  • 源碼分析:Android源碼分析,啓動一個app的源碼分析,常用框架源碼分析,Java源碼分析,集合源碼分析…
  • 網絡基礎:五層網絡模型,三次握手&四次揮手,請求頭&響應頭,Socket&WebSocket…

文章目錄

1.Java中的==、equals和hashCode的區別

(1)“==”運算符用來比較兩個變量的值是否相等,即該運算符用於比較變量之間對應的內存中的地址是否相同,
要比較兩個基本類型的數據或兩個引用變量是否相等,只能使用“=.=“(注:編譯器格式顯示問題,是雙等號)運算符

(2)equals是Object類提供的方法之一,每個java類都集成自Object類,即每個對象都有equals方法,equals與“==”一樣,比較的都是引用,相比運 算符,equals(Object)方法的特殊之處在於其可以被覆蓋,所以可以通過覆蓋的方法讓他比較的不是引用而是數據內容,即堆中的內容是否相等。

(3)hashCode()方法是從Object類繼承過來的,他也是用來比較兩個對象是否相等,Object類中的hashCode方法,返回對象在內存中地址轉換成的一個Int值,所以如果未重寫hashCode方法,任何對象的hashCode方法返回的值都是不相等的。

綜上而言,==和equals判斷的是(基本類型數據)引用是否相等,但equals可以通過覆蓋方法使其比較的是數據內容,事實上,Java中很多類庫中的類已經覆蓋了此方法,而hashCode判斷的是對象在內存中地址是否相等。

2.int和integer的區別

Integer是int提供的封裝類,而int是java的基本數據類型,Integer的默認值是null,而int的默認值是0,聲明Integer的變量需要實例化,而int不需要,Integer是對象,是一個引用指向這個對象,而int是基本數據類型,直接存儲數據。

3.String、StringBuffer和StringBuilder的區別

他們的主要區別在於運行速度和線程安全兩個方面,運行速度:StringBuilder>StringBuffer>String,String最慢的原因在於String是字符串常量,一旦創建是不可以再更改的,但後兩者的對象是變量,是可以更改的,Java中對String對象的操作實際上是一個不斷創建新的對象而將舊的對象回收的過程,而後兩者因爲是變量,所以可以直接進行更改,在線程安全上,StringBuilder是線程不安全的,而StringBuffer是線程安全的,因爲在StringBuffer對象在字符串緩衝區被多個線程使用時,StringBuffer中很多方法可以帶有synchronized關鍵字,所以可以保證線程是安全的,而StringBuilder不存在該關鍵字,所以在線程中並不安全。

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

內部類是定義在另一個類裏面的類,與之相對應,包含內部類的類被稱爲外部類,

內部類的作用有:(1)內部類提供了更好的封裝,可以把內部類隱藏在外部類之內,不允許同一包中的其他類訪問,(2)內部類的方法可以直接訪問外部類的所有數據,包括私有的數據,(3)內部類的種類:成員內部類、靜態內部類、方法內部類、匿名內部類

5.進程與線程的區別

進程是CPU資源分配的最小單位,而線程是CPU調度的最小單位,進程之間不能共享資源,而線程共享所在進程的地址空間和其他資源,一個進程內可以擁有多個線程,進程可以開啓進程、也可以開啓線程,一個線程只能屬於一個進程,線程可直接使用同進程的資源,線程依賴於進程而存在。

6.final、finally、finalize的區別

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

7.Serializable和Parcelable的區別

Serlizable是java序列化接口,在硬盤上讀寫,讀寫的過程中有大量臨時變量產生,內部執行大量的I/O操作,效率很低;Parcelable是Android序列化接口,效率高,在內存中讀寫,但使用麻煩,對象不能保存到磁盤中。

8.靜態屬性和靜態方法是否可以被繼承?是否可以被重寫?

可以繼承,但不可以被重寫,而是被隱藏,如果子類裏面定義了靜態方法或屬性,那麼這時候父類的靜態方法或屬性稱之爲隱藏。

9.成員內部類、靜態內部類、局部內部類、和匿名內部類的理解

Java中內部類主要分爲成員內部類、局部內部類(嵌套在方法和作用域中)、匿名內部類(無構造方法)、靜態內部類(由static修飾的類、不能使用任何外圍類的非static成員變量和方法、不依賴外圍類),每個內部類都能獨立的繼承一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類均無影響,因爲Java支持實現多個接口,而不支持多繼承,我們可以使用內部類提供的、可以繼承多個具體的或抽象的類的能力來解決使用接口難以解決的問題,接口只是解決了部分問題,而內部類使得多繼承的解決方案變得更加完整。

10.Java的垃圾回收機制及其在何時會被觸發

內存回收機制:就是釋放掉在內存中已經沒有用的對象,要判斷怎樣的對象是沒用的,有兩種方法:(1)採用標記數的方法,在給內存中的對象打上標記,對象被引用一次,計數加一,引用被釋放,計數就減一,當這個計數爲零時,這個對象就可以被回收,但是,此種方法,對於循環引用的對象是無法識別出來並加以回收的,(2)採用根搜索的方法,從一個根出發,搜索所有的可達對象,則剩下的對象就是可被回收的,垃圾回收是在虛擬機空閒的時候或者內存緊張的時候執行的,什麼時候回收並不是由程序員控制的,可達與不可達的概念:分配對象使用new關鍵字,釋放對象時,只需將對象的引用賦值爲null,讓程序不能夠在訪問到這個對象,則稱該對象不可達。

在以下情況中垃圾回收機制會被觸發:

(1)所有實例都沒有活動線程訪問 ;(2)沒有其他任何實例訪問的循環引用實例;(3)Java中有不同的引用類型。判斷實例是否符合垃圾收集的條件都依賴於它的引用類型。

11.Java中的代理是什麼?靜態代理和動態代理的區別是什麼?

代理模式:在某些情況下,一個用戶不想或不能直接引用一個對象,此時可以通過一個稱之爲“代理”的第三者來實現間接引用,代理對象可以在客戶端和目標對象之間起中介的作用,並且可以通過中介對象去掉用戶不能看到的內容和服務,或者添加用戶需要的額外服務。

靜態代理:即在程序運行前代理類就已經存在,也就是編寫代碼的時候已經將代理類的代碼寫好。

動態代理:在程序運行時,通過反射機制動態創建代理類。

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

方法的重寫Overriding和重載Overloading是Java多態性的不同表現

重寫Overriding 是父類與子類之間多態的一種表現;

重載Overloading是同一個類中多態性的一種表現;

13.Java中反射的相關理解

Java的反射機制是在運行狀態中,對於任意一個類,都能夠獲取到這個類的所有屬性和方法,對於任意一個對象,能夠調用它的任意一個方法和屬性,包括私有的方法和屬性,這種動態地獲取信息以及動態的調用對象的方法的功能就稱之爲Java的反射機制。

從對象出發,通過反射(.class類)可以獲取到類的完整信息,(類名、class類型、所在包、具有的所有方法Method[]類型、某個方法的完整信息,包括修飾符、返回值類型、異常、參數類型、所有屬性Field[ ]、某個屬性的完整信息,構造器Constructors,調用類的屬性或方法。

14.Java中註解的相關理解

Java註解,英文名爲Annotation,一種代碼級別的說明,是JDK1.5及以後版本引入的一個特性,與類、接口、枚舉在同一個層次,他可以聲明在包、類、字段、方法、局部變量、方法參數等的前面,用來對這些元素進行說明、註釋、作用分類;註解是Java提供的一種元程序中元素關聯任何信息和任何元數據(metadata)的途徑和方法,Annotion(註解)是一個接口,程序可以通過反射來獲取指定程序元素的Annotion對象,然後通過Annotion對象來獲取註解裏面的元數據

註解的一般格式爲:[修飾符]@interface[名稱]{元素},元素是無方法體的方法聲明,可以有默認值。

註解的作用:

編寫文檔:通過代碼裏標識的元數據生成文檔[生成文檔doc]

代碼分析:通過代碼裏的元數據對代碼進行分析[使用反射]

編譯檢查:通過代碼裏標識的元數據讓編譯器能夠實現基本的編譯檢查[Override]

15.對Java中String類的理解

通過對String類的源代碼分析可知,(1)String類是final類,即意味着String類不能被繼承,並且他的成員方法都默認爲final方法;(2)String類在源代碼中實際上是通過char[ ]數組來保存字符串的;(3)String對象一旦創建就是固定不便的,對String對象的任何change操作都會生成新的對象。

16.對Java中字符串常量池的理解

字符串的分配和其他對象分配一樣,是需要消耗高昂的時間和空間成本的,而且字符串在程序中使用得非常多,JVM爲了提高性能和減少內存的開銷,在實例化字符串的時候會進行一些優化;每當我們創建字符串常量時,JVM首先會檢查字符串常量池,如果該字符牀已經存在於常量池中,那麼就直接返回常量池中的實例引用,如果該字符串不存在,就會實例化該字符串,並將其放在常量池中,由於字符串的不可變性,我們可以十分肯定常量池中一定不存在兩個相同的字符串,Java中的常量池實際上分爲兩種形態:靜態常量池和運行時常量池

靜態常量池:即*.class文件的常量池,.class文件的常量池不僅僅包括字符串(數字)字面量,還包含類、方法的信息,佔用class文件的絕大部分空間。

運行時常量池:常量存入到內存中,並保存在方法區中,我們常說的常量池,就是指方法區中的運行時常量池。

17.Java中爲什麼String類要設計成不可變的

在java中將String類設計成不可變的是綜合考慮到各種因素的結果,需要綜合內存、同步、數據結構、以及安全等方面的考慮。

字符串常量池的需要:字符串常量池是Java堆內存中一個特殊的存儲區域,當創建一個String對象時,假如此字符串值已經存在於常量池中,則不會創建一個新的對象,而是引用已經存在的對象,假若字符串對象允許改變,那麼將會導致各種邏輯的錯誤,比如改變一個引用的字符串將會導致另一個引用出現髒數據。

允許String對象緩存HashCode:Java中String對象的哈希碼被頻繁的使用,比如在HashMap等容器中,字符串不變性保證了哈希碼的唯一性,因此可以放心地進行緩存,這也是一種性能優化的手段,意味着不必每次都去計算新的哈希碼。

安全性:String被許多Java類庫用來當作參數,如:網絡連接(network connection)、打開文件(opening files)等等,如果String不是不可變的,網絡連接、打開文件將會被改變——這將導致一系列的安全威脅,操作的方法本以爲連接上一臺機器,其實不是,優於反射的參數都是字符串,同樣也會引起一系列的安全問題。

18.Java中Hash碼(哈希碼)的理解

在Java中,哈希碼代表了對象的一種特徵,例如我們判斷某兩個字符串是否==,如果其哈希碼相等,則這兩個字符串是相等的,其次,哈希碼是一種數據結構的算法,常見的哈希碼的算法有:

Object類的HashCode,返回對象的內存地址經過處理後的結構,由於每個對象的內存地址都不一樣,所以哈希碼也不一樣。

String類的HashCode,根據String類包含的字符串的內容,根據一種特殊的算法返回哈希碼,只要字符串的內容相同,返回的哈希碼也相同。

Integer類:返回的哈希碼就是integer對象裏所包含的那個整數的數值。例如

Integer i1=new Integer(100) i1.hashCode的值就是100,由此可見兩個一樣大小的Integer對象返回的哈希碼也一樣。

19.Object類的equal方法和hashcode方法的重寫

equal和hashCode的關係是這樣的:(1)如果兩個對象相同(即用equal比較返回true),那麼他們的hashcode值一定要相同;(2)如果兩個對象的hashcode相同,他們並不一定相同(即用equal比較返回false),因爲hashcode的方法是可以重載的,如果不重載,會用Java.long.Object的hashcode方法,只要是不同的對象,hashcode必然不同。

由於爲了提高程序的效率才實現了hashcode方法,先進行hashcode的比較,如果不同,就沒有必要再進行equal的比較了,這樣就大大減少了equals比較的次數,這對需要比較數量很大的運算效率特稿是很多的。

附加:一旦new對象就會在內存中開闢空間,==比較的是對象的地址值,返回的是boolean型,equal方法默認比較的對象的地址值,但是Integer等基本類型包裝類以及String類中已經重寫了equal()方法,比較的是對象內存中的內容,返回值是boolean型。

20.Java常用集合List與Set,以及Map的區別

Java中的集合主要分爲三種類型:Set(集)、List(列表)、Map(映射);

數組是大小固定的,並且同一個數組只能存放類型一樣的數據(基本類型/引用類型),而Java集合是可以存儲和操作數目不固定的一組數據,所有的Java集合都位於java.util包中,Java集合只能存放引用類型的數據,不能存放基本數據類型。

Collection是最基本的集合接口,聲明瞭適用於Java集合(只包括Set和List)的通用方法,Set和List都繼承了Collection接口。

Set是最簡單的一種集合,集合中的對象不按特定的方式排序,並且沒有重複對象,Set接口主要實現了兩種實現類

TreeSet:TreeSet類實現了SortedSet接口,能夠對集合中的對象進行排序;

HashSet:HashSet類按照哈希算法來存取集合中的對象,存取速度比較快;

Set具有與Collection完全一樣的接口,因此沒有任何額外的功能,實際上Set就是Collection,只是行爲不同(這是繼承與多態思想的典型應用,表現不同的行爲),Set不保存重複的元素,Set接口不保證維護元素的次序。

(2) List列表的特徵是其他元素以線性表的方式存儲,集合中可以存放重複的對象,其接口主要實現類:

ArrayList( ):代表長度可以改變的數組,可以對元素進行隨機的訪問,向ArrayList( )中插入與刪除元素的速度慢。

LinkedList( ):在實現類中採用鏈表數據結構,插入和刪除的速度快,但訪問的速度慢。

對於List的隨機訪問來說,就是隻是隨機來檢索位於特定位置的元素,List的get(int index)方法返回集合中由參數index指定的索引位置的對象,索引下標從0開始。

Map映射是一種把關鍵字對象映射的集合,他的每一個元素都包括一堆鍵對象和值對象,Map沒有繼承Collection接口,從Map集合中檢索元素時只要給出鍵對象,就會返回對應的值對象。

HashMap:Map基於散列表的實現,插入和查詢“鍵值對”的開銷是固定的,可以通過構造器設置容量capacity和負載因子load factor ,以調整容器的性能;

LinkedHashMap:類似於HashMap,但在迭代遍歷時,取得“鍵值對”的順序是其插入次序,只比HashMap慢一點,而在迭代訪問時反而更快,因爲它使用鏈表維護內部次序。

TreeMap:基於紅黑樹數據結構的實現,查看“鍵”或“鍵值對”時,他們會對其排序(次序由Comparabel和Comparator決定)

21.ArrayMap和HashMap的區別

ArrayMap相比傳統的HashMap速度更慢,因爲其查找方法是二分法,並且當刪除或添加數據時,會對空間重新調整,可以說ArrayMap是犧牲了時間來換空間,ArrayMap與HashMap的區別主要在:

存儲方式不同:HashMap內部有一個HashMapEntry<K,V>[ ]對象,而ArrayMap是一個<key,value>映射的數據結構,內部使用兩個數組進行數據存儲,一個數組記錄key的hash值,另一個數組記錄value值。

添加數據時擴容的處理不一樣:HashMap進行了new操作,重新創建對象,開銷很大,而ArrayMap用的是copy數據,效率相對高很多。

ArrayMap提供了數組收縮的功能,在clear或remove之後,會重新收縮數組,釋放空間。

ArrayMap採用的是二分法查找。

22.HashMap和HashTable的區別

HashMap是基於哈希表實現的,每一個元素是一個key—value對,其內部通過單鏈表解決衝突的問題HashMap是非線程安全的,只適用於單線程的環境下。多線程的環境下可以採用concurrent併發包下的concurrentHashMap,HsahMap實現了serializable接口,支持序列化,實現了cloneable接口,能被克隆。HashMap內部維持了一個存儲數據的Entry數組,HashMap採用鏈表解決衝突,HashMap中的key和value都允許爲null,key爲null的鍵值對永遠都放在以table[0]爲節點的鏈表中。

HashTable同樣是基於哈希表實現的,同樣每個元素是一個key-value對,其內部也是通過單鏈表解決衝突問題,容量不足時,同樣會自動增大,HashTble是線程安全的,能用在多線程的環境下,HashTable實現了serializable接口,它支持序列化,實現了cloneable接口,能被克隆。

HashMap和HashTable之間的區別有以下幾點

繼承的父類不同,hashTable繼承自Dictionary類,而HashMap繼承自AbstractMap類,但二者都實現了Map接口。

線程安全性不同,HashTable中的方法是synchronized的,而HashMap中的方法在缺省的情況下是非ynchronized的,在多線程的環境下,可以直接使用HsahTable,不需要爲他的方法實現同步,但使用HashMap時就必須自己增加同步處理。

key和value是否允許爲null值:關於其中的key和value都是對象,並且不能包含重複的key,但可以包含重複的value,hashtable中,key和value都不允許出現null值,但在hashmap中,null可以作爲鍵,這樣的鍵只有一個,可以有多個鍵對應的值爲null.

23.HashMap和HashSet的區別

HashMap:其實現了Map接口,HashMap存儲鍵值對,使用put( )方法將元素放入到Map中,HashMap使用鍵對象來計算hashcode值,HashMap比較快,因爲是使用唯一的鍵來獲取對象。

HashSet:實現了Set接口,hashSet僅僅存儲對象,使用add()方法將元素放入到set中,hashset使用成員對象來計算hashcode值,對於兩個對象來說,hashcode可能相同,所以equal方法用來判斷對象的相等性,如果兩個對象不同的話,那麼返回false,hashSet較hashMap來說較慢。

24.ArrayList和LinkedList的區別

ArrayList和LinkedList,前者是Array(動態數組)的數據結構,後者是Link(鏈表)的數據結構,此外他們兩個都是對List接口的實現

當隨機訪問List時(get和set操作),ArrayList和LinkedList的效率更高,因爲LinkedList是線性的數據存儲方式,所以需要移動指針從前往後查找。

當對數據進行增刪的操作時(add和remove),LinkedList比ArrayList的效率更高,因爲ArrayList是數組,所以在其中進行增刪操作時,會對操作點之後的所有數據的下標索引造成影響,需要進行數據的移動

從利用效率來看,ArrayList自由性較低,因爲需要手動的設置固定大小的容量,但是他的使用比較方便,只需要創建,然後添加數據,通過調用下標進行使用,而LinkedList自由性交給,能夠動態的隨數據量的變化而變化,但是它不便於使用。

25.數組和鏈表的區別

數組:是將元素在內存中連續的存儲的,因爲數據是連續存儲的,內存地址連續,所以在查找數據的時候效率比較高,但在存儲之前,需要申請一塊連續的內存空間,並且在編譯的時候就必須確定好他的空間大小。在運行的時候空間的大小是無法隨着需要進行增加和減少的,當數據比較大時,有可能出現越界的情況,當數據比較小時,有可能浪費內存空間,在改變數據個數時,增加、插入、刪除數據效率比較低。

鏈表:是動態申請內存空間的,不需要像數組需要提前申請好內存的大小,鏈表只需在使用的時候申請就可以了,根據需要動態的申請或刪除內存空間,對於數據增加和刪除以及插入比數組靈活,鏈表中數據在內存中可以在任意的位置,通過應用來關聯數據。

26.Java中多線程實現的三種方式

Java中多線程實現的方式主要有三種:繼承Thread類、實現Runnable接口、使用ExecutorService、Callable、Future實現有返回結果的多線程,其中前兩種方式線程執行完沒有返回值,只有最後一種是帶返回值的。

繼承Thread類實現多線程:繼承Thread類本質上也是實現Tunnable接口的一個實例,他代表一個線程的實例,並且啓動線程的唯一方法是通過Thread類的start()方法,start()方法是一個native方法,他將啓動一個新線程,並執行run( )方法。

實現Runnable接口方式實現多線程:實例化一個Thread對象,並傳入實現的Runnable接口,當傳入一個Runnable target參數給Thread後,Thraed的run()方法就會調用target.run( );

使用ExecutorService、Callable、Future實現有返回結果的多線程:可返回值的任務必須實現Callable接口,類似的無返回值的任務必須實現Runnable接口,執行Callable任務後,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了,在結合線程池接口ExecutorService就可以實現有返回結果的多線程。

27.Java中創建線程的三種方式

Java中使用Thread類代表線程,所有的線程對象都必須時Thread類或其子類的實例,Java中可以用三種方式來創建線程

繼承Java中的Thread類創建線程:定義Thread類的子類,並重寫其run( )方法,run( )方法也稱爲線程執行體,創建該子類的實例,調用線程的start()方法啓動線程。

實現Runnable接口創建線程:定義Runnable接口的實現類,重寫run()方法,run方法是線程的執行體,創建Runnable實現類的實例,並用這個實例作爲Thread的target來創建Thread對象,這個Thread對象纔是真正的線程對象,調用線程對象的Start方法啓動線程。

使用Callable和Future創建線程:Callable接口提供了一個call( )方法,作爲線程的執行體,call( )方法可以有返回值,call( )方法可以聲明拋出異常,其創建線程並啓動的步驟,創建Callable接口的實現類,並實現call( )方法,創建該實現類的實例,使用FutureTask類來包裝Callable對象,該FuutureTask對象封裝了callable對象的call( )方法的返回值,使用FutureTask對象作爲Thread對象的target創建並啓動線程,調用FutureTask對象的get( )方法來獲得子線程執行結束後的返回值。

28.線程和進程的區別

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

29.Java中的線程的run( )方法和start()方法的區別

start()方法被用來啓動新創建的線程,而且start( )內部調用了run ( )方法,這和直接調用run( )方法的效果不同,當調用run( )方法時,只會是在原來的線程中調用,沒有新的線程啓動,只有start( )方法纔會啓動新線程。

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

在Java中常使用Semaphore(信號量)來進行併發編程,Semaphore控制的是線程併發的數量,實例化一個Semaphore對象,如Semaphore semaphore = newSemaphore(5,true) ,其創建了對象semaphore,並初始化了5個信號量,即最多允許5個線程併發訪問,在執行的任務中,調用semaphore的acquire()方法請求一個信號量,這時信號量個數就減1,(一旦沒有可使用的信號量,再次請求就會阻塞),來執行任務,執行完任務,調用semaphore的release()方法釋放一個信號量此時信號量的個數就會加1 。

31.Java中wait和sleep方法的不同

Java程序中wait和sleep都會造成某種形式的暫停,sleep()方法屬於Thread類中,而wait( )方法屬於Object類中,sleep( )方法是讓程序暫停執行指定的時間,釋放CPU資源,但不會釋放鎖,線程的監控狀態依然保持着,當指定的時間到了又會自動恢復運行狀態,而當調用wait( )方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後線程才進入對象鎖定池準備,獲取對象鎖進入運行狀態。

32.對Java中wait/notify關鍵字的理解

wait()、notify()、notifyAll( )都不屬於Thread類,而是屬於Object基礎類,也就是每個對象都有wait( )、notify()、notifyAll( )的功能,因爲每個對象都有鎖,鎖是每個對象的基礎。

wait():會把持有該對象線程的對象控制權交出去,然後處於等待狀態。

notify():會通知某個正在等待這個對象的控制權的線程可以運行。

notifyAll( ):會通知所有等待這個對象的控制權的線程繼續運行,如果有多個正在等待該對象控制權時,具體喚醒哪個線程,就由操作系統進行調度。

33.什麼是線程阻塞?線程該如何關閉?

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

線程關閉的方法有如下兩種:

一種是調用線程的stop( )方法;

另一種是自己自行設計一個停止線程的標記;

34.如何保證線程的安全

使用Synchronized關鍵字:

調用Object類的wait很notify;

通過ThreadLocal機制實現;

35.實現線程同步的方式

Java允許線程併發控制,當多個線程同時操作一個可共享的資源變量時(如數據的增刪改查),將會導致數據的不準確,相互之間產生衝突,因此在加入同步鎖以避免在該線程沒有完成操作之前,被其他線程調用,從而保證該變量的唯一性和準確性,同步的方法有以下幾種:

Synchronized關鍵字修飾的方法:由於Java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法;

Synchronized關鍵字修飾語句塊:被Synchronized關鍵字修飾的語句塊會自動加上內置鎖,從而實現同步;

使用特殊域變量(volatile)實現線程同步:當一個共享變量被volatile修飾時,他會保證修改的值立即被更新到主存中,volatile的特殊性在於,內存可見性,就是一個線程對於volatile變量的修改,對於其他線程來說是可見的,即線程每次獲取volatile變量的值都是最新的。

36.Java中Synchronized關鍵字的用法,以及對象鎖、方法鎖、類鎖的理解

Java的內置鎖:每個Java對象都可以用作一個實現同步的鎖,這些鎖稱爲內置鎖,線程進入同步代碼塊或方法時,會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖,獲得內置鎖的唯一途徑就是進入這個鎖的保護的同步代碼塊或方法。

Java的內置鎖是一個互斥鎖,這即意味着最多隻有一個線程獲得該鎖,當線程A嘗試去獲得線程B持有的內置鎖時,線程A必須等待或阻塞,直到線程B釋放該鎖,如果B線程不釋放該鎖,那麼A線程將一直等待下去。

Java的對象鎖與類鎖,對象鎖是用於實例方法的,或者一個對象實例上的,類鎖是用於類的靜態方法或者一個類的class對象上的,類的對象實例可以有很多個,但每個類只有一個class對象,所以不同對象的實例的對象鎖是互不干擾,但是每個類只有一個類鎖。

Synchronized的用法:Synchronized修飾方法和修飾代碼塊。

37.Java中鎖與同步的相關知識

鎖提供了兩種主要的特性:互斥和可見性

互斥即一次只允許一個線程持有某個特定的鎖,因此可使用該特性實現對共享數據的協調訪問協議,這樣,一次就只有一個線程能夠使用該共享的數據;可見性在必須確保鎖釋放之前對共享對象做出的更改對於隨後獲得該鎖的另一個線程是可見的。

在Java中,爲了確保多線程讀寫數據時的一致性,可以採用兩種方式

同步:如使用synchronized關鍵字,或者使用鎖對象;

使用volatile關鍵字:使變量的值發生改變時儘快通知其他線程;

Volatile關鍵字詳解

編譯器爲了加快程序的運行的速度,對一些變量的寫操作會先在寄存器或者CPU緩存上進行,最後寫入內存中,而在這個過程中,變量的新值對於其他線程是不可見的,當對使用volatile標記的變量進行修改時,會將其它緩存中存儲的修改前的變量清除,然後重新讀取。

38.Synchronized和volatile關鍵字的區別

Volatile在本質上是告訴JVM當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取;Synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程會被阻塞。

Volatile僅能使用在變量級別,synchronized則可以使用在變量、方法和類級別。

Volatile僅能修改變量的可見性,不能保證原子性,而synchronized則可以保證變量的修改可見性和原子性。

Volatile不會造成線程的阻塞,synchronized可能會造成線程的阻塞。

Volatile標記的變量不會被編譯器優化,synchronized標記的變量可以被編譯器優化。

39.Java的原子性、可見性、有序性的理解

原子性:原子是世界上最小的物質單位,具有不可分割性,比如a=0,這個操作是不可分割的,那麼我們就會說這個操作是原子操作,再如a++,這個操作實際上是a=a+1,是可以分割的,所以他不是一個原子操作,非原子操作都會存在線程安全的問題,需要使用synchronized同步技術來使其變成一個原子操作,一個操作是原子操作,那麼我麼稱它具有原子性。

可見性:是指線程之間的可見性,一個線程修改的狀態對於另一個線程是可見的,比如用volatile修飾的變量就具有可見性,volatile修飾的變量不允許線程內部緩存和重排序,即會直接修改內存,所以對其他線程是可見的,但volatile只能讓其被修飾的內容具有可見性,並不能保證它具有原子性,因爲volatile僅能使用在變量級別,並不能對方法進行修飾,

有序性:即線程執行的順序按代碼的先後順序執行,在Java內存模型中,允許編譯器和處理器對指令進行重排序,但重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性,在Java裏面可以通過volatile關鍵字來保證一定的“有序性”,另外還可以通過synchronized和Lock來保證有序性。

40.ReentrantLock、Synchronized、Volatile關鍵字

Synchronized:即互斥鎖,即操作互斥,併發線程,串行得到鎖,串行執行代碼,就像一個房間一把鑰匙,一個人進去後,下一個人必須等到第一個人出來得到鑰匙才能進去;

ReetrantLock:可重入鎖,和同步鎖功能類似,不過需要顯性的創建和銷燬,其特點在於①ReentrantLock有try Lock方法,如果鎖被其他線程持有,返回false,可以避免形成死鎖,②創建時可自定義是可搶佔的,③ReentrantReadWriteLock,用於讀多寫少,且不需要互斥的場景大大提高性能;

Volatile:只保證同意變量在多線程中的可見,他會強制將對緩存的修改操作立即寫入主存,如果是寫操作,會導致其他CPU對應的緩存無效;

41.Java中死鎖的概念,其產生的四個必要條件

死鎖是一種情形,多個線程被阻塞,他們中的一個或者全部都在等待某個資源被釋放,由於線程被無限期的阻塞,因此程序不能正常運行,簡單的說就是線程死鎖時,第一個線程等待第二個線程釋放資源,而同時第二個線程又在等待第一個線程釋放資源;

死鎖產生的四個必要條件:

互斥條件:一個資源每次只能被一個進程使用,即在一段時間內某資源僅爲一個進程所佔有,此時若有其他請求該資源,則請求進程只能等待;

請求與保持條件:進程已經持有了至少一個資源,但又提出了新的資源請求,而該資源已經被其他進程所佔有,此時請求會被阻塞,但對自己獲得的資源又保持不放;

不可剝奪條件:進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,只能由獲得該資源的進程自己來釋放(只能時主動釋放);

循環等待條件:即若干進程形成首尾相接的循環等待資源的關係,即形成了一個進程等待環路,環路中每一個進程所佔有的資源同時被另一個進程所申請,也就是前一個進程佔有後一個進程所申請的資源

這四個條件是死鎖產生必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖;

死鎖的避免:避免死鎖的基本思想是系統對進程發出的每一個系統能夠滿足的資源申請進行動態檢查,並根據檢查結果決定是否分配資源,如果分配資源後系統可能發生死鎖,則不分配,否則予以分配,這是一種保證系統不進入死鎖狀態的動態策略。

42.Java中堆和棧的理解

在Java中內存分爲兩種,一種是棧內存,另一種是堆內存

堆內存:用於存儲Java中的對象和數組,當我們new一個對象或創建一個數組的時候,就會在堆內存中開闢一段空間給它,用於存放,堆內存的特點:先進先出,後今後出,②可以動態的分配內存的大小,生存期不必告訴編譯器,但存取速度較慢;

棧內存:主要用來執行程序用,比如基本類型的變量和對象的引用變量,其特點:①先進後出,後進後出,②存取速度比堆快,僅次於寄存器,棧數據可以共享,但其在棧中的數據大小和生存期必須是確定的;

棧內存和堆內存都屬於Java內存的一種,系統會自動去回收它,但對於堆內存開發人員一般會自動回收。

棧是一塊和線程緊密相關的內存區域,每個線程都有自己的棧內存,用於存儲本地變量、方法參數和棧調用一個線程中存儲的變量,對於其他線程是不可見的,而堆是所有線程共享的一個公用內存區域,對象都在堆裏創建,但爲了提升效率,線程會從堆中拷貝一個緩存到自己的棧中,如果多個線程使用該變量,就可能引發問題,這是volatile修飾變量就可以發揮作用,他要求線程從主存中讀取變量的值。

43.理解線程間通信

線程是CPU調度的最小單位(進程是CPU分配資源的最小單位),在Android中主線程是不能夠做耗時的操作的,子線程是不能更新UI的,而線程間的通信方式有很多,比如廣播、接口回掉等,在Android中主要使用handler,handler通過調用sendMessage方法,將保存好的消息發送到MessageQueue中,而Looper對象不斷地調用loop方法,從MessageQueue中取出message,交給handler處理,從而完成線程間通信。

44.線程中的join()方法的理解,及如何讓多個線程按順序執行

Thread類的join()方法主要作用是同步,它可以使線程之間的並行執行變爲串行執行,join()方法把指定的線程加入到當前線程中,可以將兩個交替執行的線程合併爲順序執行的線程,比如在線程B中調用了線程A的join()方法,則會等到A線程執行完,纔會繼續執行線程B,join方法還可以指定時長,表示等待該線程執行指定時長後再執行其他線程,如A.join(1000),表示等待A線程執行1000毫秒後,再執行其他線程,當有多個線程、要使他們案順序執行時,可以使用join()方法在一個線程中啓動另一個線程,另外一個線程在該線程執行完之後再繼續執行。

45.工作者線程(workerThread)與主線程(UI線程)的理解

Android應用的主線程(UI線程)肩負着繪製用戶界面,和及時響應用戶操作的重任,爲避免“用戶點擊按鈕後沒有反應”的狀況,就要確保主線程時刻保持着較高的響應性,把耗時的任務移除主線程,交予工作者線程(即子線程)完成,常見的工作者線程有AsyncTask(異步任務)、IntentService、HandlerThread,他們本質上都是對線程或線程池的封裝。

46.AsyncTask(異步任務)的工作原理

AsyncTask是對Handler和線程池的封裝,使用它可以更新用戶界面,當然,這裏的更新操作還是在主線程中完成的,但由於AsyncTask內部包含了一個Handler,所以可以發送消息給主線程,讓他更新UI,另外AsyncTask內還包含了一個線程池,避免了不必要的創建和銷燬線程的開銷。

47.併發和並行的區別及理解

在單核的機器上,“多進程”並不是真正多個進程同時執行,而是通過CPU時間分片,操作系統快速在進程間切換而模擬出來的多進程,我們通常將這種情況稱爲併發,也就是多個進程的運行行爲是“一併發生”的,但不是同時執行的,因爲CPU核數的限制(CPU和通用寄存器只有有一套),嚴格來說,同一時刻只能存在一個進程的上下文。

但在多核CPU上,能真正實現多個進程並行執行,這種情況叫做並行,因爲多個進程是真正“一併執行的”(具體多少個進程可以並行執行取決於CPU的核數),所以可知,併發是一個比並行更加寬泛的概念,在單核情況下,併發只是併發,而在多核情況下,併發就變爲並行了。

48.同步和異步的區別、阻塞和非阻塞的區別的理解

同步:所謂同步是一個服務的完成需要依賴其他服務,只有等待被依賴的服務完成後,依賴的服務才能算完成,這是一種可靠的服務序列,要麼都成功,要麼都失敗服務的狀態可以保持一致;

異步:所謂異步是一個服務的完成需要依賴其他的服務時,只要通知其依賴的服務開始執行,而不需要等待被依賴的服務完成,此時該服務就算完成了,至於被依賴的服務是否真正完成並不關心,所以這是不可靠的服務序列;

阻塞:阻塞調用是指調用結果返回之前,當前線程會被掛起,一直處於等待消息通知,不能夠執行其他業務,函數只有在得到結果之後纔會返回;

非阻塞:和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立即返回;

阻塞調用和同步調用是不同的,對於同步調用來說,很多時候當前線程可能還是激活的,只是從邏輯上當前函數沒有返回值而已,此時這個線程可能也會處理其他消息,所以如下總結:

如果這個線程在等待當前函數返回時,仍在執行其他消息處理,這種情況叫做同步非阻塞;

如果這個線程在等待當前函數返回時,沒有執行其他的消息處理,而是處於掛起等待的狀態,這種情況叫做同步阻塞;

如果這個線程當前的函數已經返回,並且仍在執行其他的消息處理,這種情況叫做異步非阻塞;

如果這個線程當前的函數已經返回,但沒有執行其他的消息處理,而是處於被掛起的等待狀態,這種情況叫做異步阻塞;

同步與異步的重點在於的等待依賴的服務是否返回結果(即使沒有執行完),也就是結果通知的方式,而不管其依賴的服務是否完成,阻塞與非阻塞的重點在於當前線程等待消息返回時的行爲,是否執行其他的消息處理,當前線程是否被掛起;

49.Java中任務調度的理解

大部分操作系統(如windows、Linux)的任務調度採用時間片輪轉的搶佔式調度方式,也就是說一個任務執行一小段時間後強制暫停去執行下一個任務,每個任務輪流執行,任務執行的一段時間叫做時間片,任務正在執行的狀態叫做運行狀態,任務執行一段時間後強制暫停去執行下一個任務,被暫停的任務就處於就緒狀態,等待下一個屬於他的時間片的到來,這樣每個任務都可以得到執行,由於CPU的執行效率非常高,時間片非常短,在各個任務之間快速的切換,給人的感覺是多個任務在“同時執行”,這也即我們所說的併發;

50.Java中進程的詳細概念

計算機的額核心是CPU,它承擔了所有的計算任務,操作系統是計算機的管理者,它負責任務的調度、資源的分配和管理,統領整個計算機的硬件,應用程序是具有某種功能的程序,其運行在操作系統上,而進程則是一個具有一定獨立功能的程序在一個數據集上一次動態執行的過程,是操作系統進行資源分配和調度的一個獨立單位,是應用程序運行的載體,進程是CPU資源分配的最小單位(線程是CPU執行的最小的單元),各個進程之間內存地址相互隔離。

51.線程的詳細概念

線程是程序執行中一個單一的順序控制流程,是程序執行流的最小單元,是處理器調度和分派的基本單位,一個進程可以有多個線程,各個線程之間共享程序的內存空間(也就是所在進程的內存空間),線程是一個進程中代碼的不同執行路線,線程上下文切換比進程上下文切換要快得多。

52.Android中的性能優化相關問題

由於手機硬件的限制,在Android手機中過多的使用內存,會容易導致oom(out of memory 內存溢出),過多的使用CPU資源,會導致手機卡頓,甚至導致ANR(Application Not Responding 應用程序無響應),Android主要從以下幾部分對此進行優化:

佈局優化,使用herarchyviewer(視圖層次窗口)工具刪除無用的空間和層級;

選擇性能較低的viewgroup,比如在可以選擇RelativeLayout也可以使用LinearLayout的情況下,優先使用LinearLayout,因爲相對來說RelativeLayout功能較爲複雜,會佔用更多的CPU資源;

使用標籤重用佈局、減少層級、進行預加載(用的時候才加載);

繪製優化:指view在ondraw方法中避免大量的耗時的操作,由於onDraw方法可能會被頻繁的調用;

ondraw方法中不要創建新的局部變量,ondraw方法被頻繁的調用,很容易引起GC;

ondraw方法不要做耗時的操作;

線程優化:使用線程池來管理和複用線程,避免程序中出現大量的Thread,同時可以控制線 的併發數,避免相互搶佔資源,而導致線程阻塞;

53.內存泄漏的相關原因

如果一個無用的對象(不需要再使用的對象)仍然被其他對象持有引用,造成該對象無法被系統回收,以致該對象在堆中所佔有的內存單元無法被釋放而造成內存空間浪費,這種情況就是內存泄漏,常見的內存泄露的場景有:

單例模式:因爲單例的靜態特性使得他的生命週期同應用的生命週期一樣長,如果一個對象已經沒有用處了,但是單例還持有他的引用,那麼在某個應用程序的生命週期他都不能正常回收,從而導致內存泄漏;

靜態變量導致內存泄漏:靜態變量存儲在方法區,他的生命週期從類加載開始,到整個進程結束,一旦靜態變量初始化,他所持有的引用只有等到進程結束纔會釋放;

非靜態內部類導致內存泄漏:非靜態內部類(包括匿名內部類)默認就會持有外部類的引用,當非靜態內部類對象的生命週期比外部對象的生命週期長時,就會導致內存泄漏,通常在Android開發中,如果要使用內部類,但又要規避內存泄漏,一般會採用靜態內部類+弱引用的方式;

未取消註冊或回掉導致內存泄漏:比如在Activity中註冊廣播,如果在Activity銷燬後不取消註冊,那麼這個廣播會一直存在於系統中,同非靜態內部類一樣持有Activity引用,導致內存泄漏,因此在註冊廣播後一定要取消註冊;

54.通過Handler在線程間通信的原理

Android中主線程是不能進行耗時操作的,子線程不能進行更新UI,所以就有了Handler,其作用就是實現線程之間的通信。

Handler在整個線程通信的過程中,主要有四個對象,Handler、Message、MessageQueue、Looper等,當應用創建時,就會在主線程中創建Handler對象,將要傳遞的信息保存到Message中,Handler通過調用sendMessage()方法將Message發送到MessageQueue中,Looper對象不斷調用Loop( )方法從MessageQueue中取出Message交給handler進行處理,從而實現線程之間的通信。

55.Android中動畫的類型:

幀動畫:通過指定每一幀圖片和播放的時間,有序地進行播放而形成動畫效果;

補間動畫:通過指定的view的初始狀態、變化時間、方式等,通過一系列的算法去進行圖形變換從而形成動畫效果,主要有Alpha、Scale、Translate、Rotate四種效果;

屬性動畫:在Android3.0開始支持,通過不斷改變view的屬性,不斷的重繪而形成動畫效果;

55.理解Activity、View、Window三者之間的關係

使用一個比喻形容他們的關係,Activity像一個工匠(控制單元),Window像窗戶(承載模型)、View像窗花(顯示視圖)、LayoutInflater像剪刀、XML配置像窗花圖紙:

Activity構造的時候會初始化一個Window,準確的說應該是PhoneWindow;

這個PhoneWindow有一個”ViewRoot“,這個”ViewRoot”是一個View或者說是ViewGroup,是最初的根視圖;

“ViewRoot”是通過addView方法來添加一個個View的,比如TextView、Button等;

這些view的事件的監聽,是由WindowManagerService來接收消息,並且回調Activity函數,比如onClickListener、onKeyDown等;

56.Android中Context詳解:

Context是一個抽象基類,譯爲上下文,也可理解爲環境,是用戶操作操作系統的一個過程,這個對象描述的是一個應用程序環境的全局信息,通過它可以訪問應用程序的資源和相關的權限,簡單的說Context負責Activity、Service、Intent、資源、Package和權限等操作。Context層次如下圖:

第一層:Context抽象類;

第二層:一個ContextImpl的實現類,裏面擁有一個PackageInfo類,PackageInfo類是關於整個包信息的類,一個ContextWraper是一個Context的包裝類,裏面有一個ContextImpl類的實例,通過整個實例去調用ContextImpl裏面的方法;

第三層:Service和Application直接繼承ContextWrapper,但是Activity需要引入主題,所以有了ContextThemeImpl類;

總結:在使用Context對象獲取靜態資源,創建單例對象或者靜態方法時,多考慮Context的生命週期,不要使用Activity的Context,要使用生命週期較長的Application的Context對象,但並不是所有的情況都使用Application的Context,在創建Dialog、view等控件的時候,必須使用Activity的Context對象。

57.Java中double和float類型的區別

float是單精度類型,精度是8位有效數字,取值範圍是10的-38次方到10的38次方,float佔用4個字節的存儲空間;

double是雙精度類型,精度是10的-308次方到10的308次方,double類型佔用8個字節的存儲空間;

默認情況下都用double來表示,所以如果要用float,則應在數字對象後加字符f。

58.Android常用的數據存儲方式(4種)

使用SharedPreference存儲:保存基於xml文件存儲的key-value鍵值對數據,通常用來存儲一些簡單的配置信息;

文件存儲方式:Context提供了兩個方法來打開數據文件的文件IO流;

SQLite存儲數據:SQLite是輕量級的嵌入式數據庫引擎,支持SQL語言;

網絡存儲數據:通過網絡存儲數據;

59.ANR的瞭解及優化

ANR全名Appication NotResponding ,也就是“應用程序無反應”,當操作在一段時間內無法得到系統迴應時就會出現ANR錯誤,造成ANR的主要原因是由於線程的任務在規定的時間內沒有完成造成的;

60.Android垃圾回收機制和程序優化System.gc( )

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

JVM進行次GC的頻率很高,但因爲這種GC佔用的時間極短,所以對系統產生的影響不大,但主GC對系統的影響很明顯,觸發主GC的條件主要有下:

當應用程序空閒時,即沒有應用線程在運行時,GC會被調用,因爲GC在優先級別最低的線程中進行,所以當應用繁忙時,GC就不會被調用;

Java堆內存不足時,主GC會被調用,當應用線程正在運行,並在運行過程中創建新對象時,若這時內存空間不足,JVM會強制調用主GC,以便回收內存用於新的分配,若GC一次之後仍無法滿足,則會繼續進行兩次,若仍無法滿足,則會報OOM錯誤;

System.gc( )函數的作用只是提醒虛擬機希望進行一次垃圾回收,但並不一定保證會進行,具體什麼時候進行取決於虛擬機;

61.Android平臺的優勢和不足

Android平臺手機 5大優勢:

開放性:Android平臺首先就是其開放性,開發的平臺允許任何移動終端廠商加入到Android聯盟中來。顯著的開放性可以使其擁有更多的開發者;

掙脫運營商的束縛:在過去很長的一段時間,手機應用往往受到運營商制約,使用什麼功能接入什麼網絡,幾乎都受到運營商的控制,而Android用戶可以更加方便地連接網絡,運營商的制約減少;

豐富的硬件選擇:由於Android的開放性,衆多的廠商會推出千奇百怪,功能特色各具的多種產品。功能上的差異和特色,卻不會影響到數據同步、甚至軟件的兼容;

開發商不受任何限制:Android平臺提供給第三方開發商一個十分寬泛、自由的環境,不會受到各種條條框框的阻擾;

無縫結合的Google應用: Android平臺手機將無縫結合這些優秀的Google服務如地圖、郵件、搜索等;

Android平臺手機幾大不足:

安全和隱私:由於手機與互聯網的緊密聯繫,個人隱私很難得到保守。除了上網過程中經意或不經意留下的個人足跡,Google這個巨人也時時站在你的身後,洞穿一切;

過分依賴開發商缺少標準配置:在Android平臺中,由於其開放性,軟件更多依賴第三方廠商,比如Android系統的SDK中就沒有內置音樂播放器,全部依賴第三方開發,缺少了產品的統一性;

同類機型用戶很少:在不少手機論壇都會有針對某一型號的子論壇,對一款手機的使用心得交流,並分享軟件資源。而對於Android平臺手機,由於廠商豐富,產品類型多樣,這樣使用同一款機型的用戶越來越少,缺少統一機型的程序強化。

62.其他講解:

Activity組件

在Activity的生命週期中,可以將Activity表現爲3種狀態:

激活態:當Acitivity位於屏幕前端,並可以獲得用戶焦點、收用戶輸入時,這種狀態稱爲激活態,也可以稱爲運行態;

暫停態:當Activity在運行時被另一個Activity所遮擋並獲取焦點,此時Activity仍然可見,也就是說另一個Activity是部分遮擋的,或者另一個Activity是透明的或半透明的,此時Activity處於暫停狀態;

停止態:當Activity被另一個Activity完全遮擋不可見時處於停止狀態,這個Activity仍然存在,它保留在內存中並保持所有狀態和成員信息,但是當該設備內存不足時,該Activity可能會被系統殺掉以釋放其佔用的內存空間,當再次打開該Activity時,它會被重新創建;

Activity生命週期中的7個方法:

onCreate( ):當Activity被創建時調用;

onStart( ):當Activity被創建後將可見時調用;

onResume( ):(繼續開始)當Activity位於設備最前端,對用戶可見時調用;

onPause( ):(暫停)當另一個Activity遮擋當前Activity,當前Activity被切換到後臺時調用;

onRestart( ):(重新啓動)當另一個Activity執行完onStop()方法,又被用戶打開時調用;

onStop( ):如果另一個Activity完全遮擋了當前Activity時,該方法被調用;

onDestory( ):當Activity被銷燬時調用;

Activity的四種啓動模式:standard、singleTop、singleTask和singleInstance,他們是在配置文件中通過android:LauchMode屬性配置;

standard:默認的啓動模式,每次啓動會在任務棧中新建一個啓動的Activity的實例;

SingleTop:如果要啓動的Activity實例已位於棧頂,則不會重新創建該Activity的實例,否則會產生一個新的運行實例;

SingleTask:如果棧中有該Activity實例,則直接啓動,中間的Activity實例將會被關閉,關閉的順序與啓動的順序相同;

SingleInstance:該啓動模式會在啓動一個Activity時,啓動一個新的任務棧,將該Activity實例放置在這個任務棧中,並且該任務棧中不會再保存其他的Activity實例;

Activity任務棧:即存放Activity任務的棧,每打開一個Activity時就會往Activity棧中壓入一個Activity

任務,每當銷燬一個Activity的時候,就會從Activity任務棧中彈出一個Activity任務,

由於安卓手機的限制,只能從手機屏幕獲取當前一個Activity的焦點,即棧頂元素(

最上面的Activity),其餘的Activity會暫居後臺等待系統的調用;

關於任務棧的更多概念:

當程序打開時就創建了一個任務棧,用於存儲當前程序的Activity,當前程序(包括被當前程序所調用的)所有的Activity都屬於一個任務棧;

一個任務棧包含了一個Activity的集合,可以有序的選擇哪個Activity和用戶進行交互,只有任務棧頂的Activity纔可以與用戶進行交互;

任務棧可以移動到後臺,並保留每一個Activity的狀態,並且有序的給用戶列出他們的任務,而且還不會丟失他們的狀態信息;

退出應用程序時,當把所有的任務棧中所有的Activity清除出棧時,任務棧會被銷燬,程序退出;

默認Acctivity啓動方式的缺點:

每開啓一次頁面都會在任務棧中添加一個Activity,而只有任務棧中的Activity全部清除出棧時,任務棧被銷燬,程序纔會退出,這樣就造成了用戶體驗差,需要點擊多次返回纔可以把程序退出了。

每開啓一次頁面都會在任務棧中添加一個Activity還會造成數據冗餘重複數據太多,會導致內存溢出的問題(OOM)。

Service組件

Service組件主要用於處理不干擾用戶交互的後臺操作,如更新應用、播放音樂等,Service組件既不會開啓新的進程,也不會開啓新的線程,它運行在應用程序的主線程中,Service實現的邏輯代碼不能阻塞整個應用程序的運行,否則會引起應用程序拋出ANR異常。

Service組件常被用於實現以下兩種功能(分別對應兩種啓動模式):

使用startService()方法啓動Service組件,運行在系統的後臺在不需要用戶交互的前提下,實現某些功能;

使用bindService()方法啓動Service組件,啓動者與服務者之間建立“綁定關係”,應用程序可以與Service組件交互;

Service中常用的方法:

onBind(Intent intent):抽象方法綁定模式時執行的方法,通過Ibinder對象訪問Service;

onCreate():Service組件創建時執行;

onDestroy():Service組件銷燬時執行;

onStartCommand( Intent intent ,int flags ,int startId ):開始模式時執行的方法,每次執行startService()方法,該方法都會執行;

onUnBind( ):解除綁定時執行;

stop Self():停止Service組件;

Service組件的生命週期:Service有兩種啓動模式,startService方法或bindServce方法,啓動方法決定了Service組件的生命週期和他所執行的生命週期方法:

通過statrtService方法啓動Service組件,如果已經存在目標組件,則會直接調用onStartCommand方法,否則回調哦那Create()方法,創建Service對象,啓動後的Service組件會一直運行在後臺,與“啓動者”無關聯,其狀態不會受“啓動者”的影響,即使“啓動者被銷燬,Service組件還會繼續運行,直到調用StopService()方法,或執行stopSelf()方法;

通過bindService()方法,啓動Service組件,如果組件對象已經存在,則與之綁定,否則創建性的Service對象後再與之綁定,這種啓動方法把Service組件與“啓動者“綁定,Service返回Ibinder對象,啓動者藉助ServiceConnection對象實例實現與之交互,這種啓動方式會將Service組件與”啓動者“相關聯,Service的狀態會受到啓動者的影響;

Service的啓動模式詳解

啓動和停止Service組件的方法都位於context類中,再Activity中可以直接調用;

startService(Intent service):以Start模式啓動Service組件,參數service是目標組件;

stopService(Intent service):停止start模式啓動的Service組件,參數service是目標組件;

bindService(Intent service ,serviceConnection conn ,int flags):以Bind模式啓動Service組件,參數service是目標組件,conn是與目標鏈接的對象,不可爲NULL,flags是綁定模式;

unbindService(ServiceConnection conn):解除綁定模式啓動的Service組件,conn是綁定時的鏈接對象;

BoradcastReceiver組件

廣播機制是安卓系統中的通信機制,可以實現在應用程序內部或應用程序之間傳遞消息的作用,發出廣播(或稱廣播)和接收廣播是兩個不同的動作,Android系統主動發出的廣播稱爲系統廣播,應用程序發出廣播稱爲自定義廣播,廣播實質上是一個Intent對象,接收廣播實質上是接收Intent對象。

接收廣播需要自定義廣播接收器類,繼承自BoradcastReceiver類,BoradcastReceiver的生命週期很短,BoradcastReceiver對象在執行完onReceiver()方法後就會被銷燬,在onReceiver方法中不能執行比較耗時的操作,也不能在onReceiver方法中開啓子線程,因爲當父線程被殺死後,他的子線程也會被殺死,從而導致線程不安全。

廣播分爲有序廣播和無序廣播

無序廣播:通過sendBoradcast()方法發送的廣播,普通廣播對於接收器來說是無序的,沒有優先級,每個接收器都無需等待即可以接收到廣播,接收器之間相互是沒有影響的,這種廣播無法被終止,即無法阻止其他接收器的接收動作。

有序廣播:通過sendOrderedBroadcast()方法發送的廣播,有序廣播對於接收器來說是有序的,有優先級區分的,接收者有權終止廣播,使後續接收者無法接收到廣播,並且接收者接收到廣播後可以對結果對象進行操作。

註冊廣播接收器的方式:配置文件靜態註冊和在代碼中動態註冊。

配置文件中靜態註冊:BoradcastReceiver組件使用標籤配置,寫在application標籤內部,receiver標籤內的標籤用於設置廣播接收器能夠響應的廣播動作。

使用代碼動態註冊:使用context中的registerReceiver(BoradcastReceiver receiver ,IntentFileter filter)方法,參數receiver是需要註冊的廣播接收器,參數filter是用於選擇相匹配的廣播接收器,使用這種方法註冊廣播接收器,最後必須解除註冊,解除註冊使用context的unregisterReceiver(BoradcastReceiverreceiver)方法,參數receiver爲要解除註冊的廣播接收器。

配置文件靜態註冊和在代碼中動態註冊兩種方式的區別

靜態註冊(也稱常駐型註冊):這種廣播接收器需要在Androidmainfest.xml中進行註冊,這種方式註冊的廣播,不受頁面生命週期的影響,即使退出了頁面,也可以收到廣播,這種廣播一般用於開機自啓動,由於這種註冊方式的廣播是常駐型廣播,所以會佔用CPU資源。

動態註冊:是在代碼中註冊的,這種註冊方式也叫非常駐型註冊,會受到頁面生命週期的影響,退出頁面後就不會收到廣播,我們通常將其運用在更新UI方面,這種註冊方式優先級較高,最後需要解綁(解除註冊),否則會導致內存泄漏)。

要點:使用廣播機制更新UI的思路,在需要更新的Activity內定義一個繼承自BroadcastReceiver的 內部類,在Activty中動態註冊該廣播接收器,通過廣播接收器的的onReceiver()方法來更新UI。

ContentProvider(內容提供者)組件

Android系統將所有的數據都規定爲私有,無法直接訪問應用程序之外的數據,如果需要訪問其他程序的數據或向其他程序提供數據,需要用到ContentProvider抽象類,該抽象類爲存儲和獲取數據提供了統一的接口,ContentProvider對數據進行了封裝,也在使用時不必關心數據的存儲方式。

URI(統一資源標識符):一個Uri一般有三部分組成,分別是訪問協議、唯一性標識字符串(或訪問權限字符串)、資源部份。

ContentProvider實現對外部程序數據操作的思路:

在一個程序中自定義ContentProvider類(繼承自ContentProvider),並實現其中的抽象方法,在配置文件中使用標籤對其進行註冊(標籤中有3個很重要的屬性:name爲指明自定義的ContentProvider的全路徑,authories爲訪問標識,exported是否允許外部應用訪問),在另一個需要操作該程序數據的程序中,通過context類的getContentResolver方法獲得ContentResolver類的實例對象,ContentResolver類執行增、刪、改、查操作的方法與ContentProvider類的幾乎一致,通過調用ContentResolver類實現類的方法來實現數據操作(在這些方法中仍然會用到Uri參數,其應與被操作數據的所在程序中的定義的Uri一致)。

Fragment

Android3.0引入Fragment技術,譯爲“碎片、片段”,在Activity中可以通過FragmentManager來添加、移除和管理所加入的Fragment,每個Fragment都有自己的佈局、生命週期、交互事件的處理,由於Fragment是嵌入到Activity中的,所以Fragment的生命週期又和Activity的生命週期有密切的關聯。

Fragment的生命週期的方法:

onAttach( ):當Fagment和Activity產生關聯時被調用;

onCreate( ):當Fragment被創建時調用;

onCreateView():創建並返回與Fragment相關的view界面;

onViewCreate():在onCreateView執行完後立即執行;

onActivityCreated( ):通知Fragment,他所關聯的Activity已經完成了onCreate的調用;

onStart( ):讓Fragment準備可以被用戶所見,該方法和Activity的onStart()方法相關聯;

onResume():Fragment可見,可以和用戶交互,該方法和Activity的onResume方法相關聯;

onPause():當用戶離開Fragment時調用該方法,此操作是由於所在的Activity被遮擋或者是在Activity中的另一個Fragment操作所引起的;

onStop():對用戶而言,Fragment不可見時調用該方法,此操作是由於他所在的Activity不再可見或者是在Activity中的一個Fragment操作所引起的;

onDestroyView():ment清理和它的view相關的資源;

onDestroy():最終清理Fragment的狀態;

onDetach():Fragment與Activity不再產生關聯;

Fragment加載佈局文件是在onCreateView()方法中使用LayoutInflater(佈局加載器)的inflate( )方法加載佈局文件。

Fragment中傳遞數據:當Activity加載Fragment的時候,往其內部傳遞參數,官方推薦使用Fragment的setArguments(Bundle bundle)方式傳遞參數,具體方法爲在Activity中,對要傳遞參數的Fragment調用setArguments(Bundle bundle)方法,在接收參數的Fragment中調用context的getArguments()方法獲取Bundle參數;

管理Fragment:通過調用context的getFragmentManager( )方法或者getsupportFragmentManager( )方法(這兩個方法需要導入的包不同)來獲取FragmentManager,使用FragmentManager對象,主要可以調用如下方法:

findFragmentById/findFragmentByTag:獲取Activity中已經存在的Fragment;

getFragments( ):獲取所有加入Activity中的Fragment;

begainTransaction( ):獲取一個FragmentTransaction對象,用來執行Fragment的事物;

popBackStack():從Activity的後退棧中彈出Fragment;

addOnBackChagedListerner:註冊一個偵聽器以監視後退棧的變化;

FragmentTransaction:

在Activity中對Fragment進行添加、刪除、替換以及執行其他的動作將引起Fragment的變化,叫做一個事務,事務是通過FragmentTransaction來執行的,可以用add、remove、replace、show、hide( )等方法構成事務,最後使用commit提交事務,參考代碼:

getSupportFragmentManager( ).beginTransaction()

             .add(R.id.main_container,tFragment)

            .add(R.id.main_container,eFragment)

             .hide(eFragment).commit( );

其中R.id.main_container爲佈局中容納Fragment的view的ID。

ViewPager

ViewPager是google SDK自帶的一個附加包(android.support.V4)中的一個類,可視爲一個可以實現一種卡片式左右滑動的View容器,使用該類類似於ListView,需要用到自定義的適配器PageAdapter,區別在於每次要獲取一個view的方式,ViewPager準確的說應該是一個ViewGroup。

PagerAdapter是ViewPager的支持者,ViewPager調用它來獲取所需顯示的頁面,而PagerAdapter也會在數據變化時,通知ViewPager,這個類也是FragmentPagerAdapter和FragmentStatePagerAdapter的基類。

FragmentPageAdapter和FragmentStatePagerAdapter的區別

FragmentPageAdapter:和PagerAdapter一樣,只會緩存當前Fragment以及左邊一個和右邊一個,如果左邊或右邊不存在Fragment則不緩存;

FragmentStatePagerAdapter:當Fragment對用戶不可見時,整個Fragment會被銷燬,只會保存Fragment的狀態,而在頁面需要重新顯示的時候,會生成新的頁面;

綜上,FragmentPagerAdapter適合頁面較少的場合,而FragmentStatePagerAdapter則適合頁面較多或頁面的內容非常複雜(需要佔用大量內存)的情況。

當實現一個PagerAdapter時,需要重寫相關方法:

getCount( ):獲得viewPager中有多少個view ;

destroyItem(viewGroup ,interesting,object):移除一個給定位置的頁面;

instantiateItem(ViewGroup ,int ):將給定位置的view添加到viewgroup(容器中),創建並顯示出來,返回一個代表新增頁面的Object(key),key和每個view要有一一對應的關係;

isviewFromObject( ):判斷instantiateItem(ViewGroup,int )函數所返回的key和每一個頁面視圖是否是代表的同一個視圖;

綜合使用ViewPager、Fragment和FragmentPagerAdapter:

自定義一個繼承FragmentPagerAdapter的子類,重寫其相關方法,當在實例化該子類時,可以傳入Fragmnt集合(即要使用到的全部Fragmnet的集合),將ViewPager與該自定義的適配器實例綁定,爲ViewPager設置OnPagerListener( )監聽事件,重寫OnPagerChangeListener的onPageSelected( )方法,實現頁面的翻轉。

關於Fragment中的控件的事件的監聽:

在Fragment中的onActivityCreated( )生命週期方法中通過context的getActivity()方法獲取到Fragment類相關聯的Activity,並就此Activity通過findViewById( )方法來獲取相關組件,再爲組件添加監聽事件。

Android的事件傳遞(分發)機制

基礎概念:

事件分發的對象:點擊事件(Touch事件),當用戶觸摸屏幕時(view或viewGroup派生的控件),將產生點擊事件(Touch事件),Touch事件的相關細節(發生觸摸的位置、時間等)被封裝程MotionEvent對象;

事件的類型:

MotionEvent . ACTION_DOWN 按下view(所有事件的開始)

MotionEvent .ACTION_UP 擡起view(與DOWN對應)

MotionEvent .ACTION_MOVE 滑動view

MotionEvent .ACTION_CANCEL 結束事件(非人爲的原因)

事件列:從手指觸摸至離開屏幕,這個過程中產生的一系列事件爲事件列,一般情況下,事件列都是以DOWN事件開始,UP事件結束,中間有無數的MOVE事件;

事件分發的本質:將點擊事件(MotionEvent)傳遞到某個具體的View&處理的整個過程,即事件的傳遞過程=分發過程;

事件在哪些對象之間傳遞:Activity、viewGroup、view等對象;

事件分發過程中協作完成的方法:

dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()方法;

Android事件傳遞機制跟佈局的層次密切相關,一個典型的佈局包括根節點ViewGroup、子ViewGroup、以及最末端的view(如下圖):

在這種結構中最上層的是RootViewGroup,下層是子view,當我們點擊子view的時候,點擊事件從上層往下層依次傳遞(即下沉傳遞Activity——ViewGroup——View),傳遞的過程中調用dispatchTouchEvent和onInterceptTouchEvent函數,當事件傳遞到被點擊的子view之後,停止事件的傳遞,開始改變方向(即冒泡響應View ——ViewGroup——Activity),依次向上層響應,響應的過程調用onTouch或onTouchEvent方法。

傳遞過程中的協作方法:

public boolean dispatchTouchEvent(MotionEvent ev):事件分發處理函數,通常會在Activity層根據UI的情況,把事件傳遞給相應的ViewGroup;

public boolean onIntereptTouchEvent(MotionEvent ev):對分發的事件進行攔截(注意攔截ACTION_DOWN和其他ACTION的差異),有兩種情況:

第一種情況:如果ACTION_DOWN的事件沒有被攔截,順利找到了TargetView,那麼後續的MOVE和UP都能夠下發,如果後續的MOVE與UP下發時還有繼續攔截的話,事件只能傳遞到攔截層,並且發出ACTION_CANCEL;

第二種情況:如果ACTION_DOWN的事件下發時被攔截,導致沒有找到TargetView,那麼後續的MOVE和UP都無法向下派發了,在Activity層就終止了傳遞。

public boolean onTouchEvent(MotionEvent ev):響應處理函數,如果設置對應的Listener的話,這裏還會與OnTouch、onClick、onLongClick相關聯,具體的執行順序是:

onTouch()——>onTouchEvent()——>onClick()——>onLongClick(),是否能夠順序執行,取決於每個方法返回值是true還是false。

Bitmap的使用及內存優化

位圖是相對於矢量圖而言的,也稱爲點陣圖,位圖由像素組成,圖像的清晰度由單位長度內的像素的多少來決定的,在Android系統中,位圖使用Bitmap類來表示,該類位於android.graphics包中,被final所修飾,不能被繼承,創建Bitmap對象可使用該類的靜態方法createBitmap,也可以藉助BitmapFactory類來實現。Bitmap可以獲取圖像文件信息,如寬高尺寸等,可以進行圖像剪切、旋轉、縮放等操作.

BitmapFactory是創建Bitmap的工具類,能夠以文件、字節數組、輸入流的形式創建位圖對象,BitmapFactory類提供的都是靜態方法,可以直接調用,BitmapFactory. Options類是BitmapFactory的靜態內部類,主要用於設定位圖的解析參數。在解析位圖時,將位圖進行相應的縮放,當位圖資源不再使用時,強制資源回收,可以有效避免內存溢出。

縮略圖:不加載位圖的原有尺寸,而是根據控件的大小呈現圖像的縮小尺寸,就是縮略圖。

將大尺寸圖片解析爲控件所指的尺寸的思路:

實例化BitmapFactory . Options對象來獲取解析屬性對象,設置BitmapFactory. Options的屬性inJustDecodeBounds爲true後,再解析位圖時並不分配存儲空間,但可以計算出原始圖片的寬度和高度,即outWidth和outHeight,將這兩個數值與控件的寬高尺寸相除,就可以得到縮放比例,即inSampleSize的值,然後重新設置inJustDecodeBounds爲false,inSampleSize爲計算所得的縮放比例,重新解析位圖文件,即可得到原圖的縮略圖。

獲取控件寬高屬性的方法:可以利用控件的getLayoutParams()方法獲得控件的LayoutParams對象,通過LayoutParams的Width和Height屬性來得到控件的寬高,同樣可以利用控件的setLayoutParams( )方法來動態的設置其寬高,其中LayoutParams是繼承於Android.view.viewGroup.LayoutParams,LayoutParams類是用於child view(子視圖)向parent view(父視圖)傳遞佈局(Layout)信息包,它封裝了Layout的位置、寬、高等信息。

Bitmap的內存優化:

及時回收Bitmap的內存:Bitmap類有一個方法recycle( ),用於回收該Bitmap所佔用的內存,當保證某個Bitmap不會再被使用(因爲Bitamap被強制釋放後,再次使用它會拋出異常)後,能夠在Activity的onStop()方法或onDestory()方法中將其回收,回收方法:

if ( bitmap !=null && !bitmap.isRecycle ( ) ){
        bitmap.recycle( );
        bitmap=null ;
        }

   System.gc( ) ;

System.gc()方法可以加快系統回收內存的到來;

捕獲異常:爲了避免應用在分配Bitmap內存時出現OOM異常以後Crash掉,需在對Bitmap實例化的過程中進行OutOfMemory異常的捕獲;

緩存通用的Bitmap對象:緩存分爲硬盤緩存和內存緩存,將常用的Bitmap對象放到內存中緩存起來,或將從網絡上獲取到的數據保存到SD卡中;

壓縮圖片:即以縮略圖的形式顯示圖片。

使用View繪製視圖

View類是Android平臺中各種控件的父類,是UI(用戶界面)的基礎構件,View相當於屏幕上的一塊矩形區域,其重複繪製這個區域和處理事件,View是所有Weight類(組件類)的父類,其子類ViewGroup是所有Layout類的父類,如果需要自定義控件,也要繼承View,實現onDraw方法和事件處理方法。

View繪製的流程:OnMeasure()——>OnLayout()——>OnDraw()

OnMeasure():測量視圖大小,從頂層父View到子View遞歸調用measure方法,measure方法又回調OnMeasure。

OnLayout():確定View的位置,進行頁面的佈局,從頂層父View向子View的遞歸調用View.Layout方法的過程,即父View根據上一步measure子View所得到的佈局大小和佈局參數,將子View放到合適的位置上。

OnDraw():繪製視圖,ViewGroup創建一個Canvas對象,調用OnDraw()方法,但OnDraw方法是個空方法,需要自己根據OnMeasure和OnLayout獲取得到參數進行自定義繪製。

注意:在Activity的生命週期中沒有辦法獲取View的寬高,原因就是View沒有測量完。

Canvas類:Canvas類位於android.graphics包中,它表示一塊畫布,可以使用該類提供的方法,繪製各種圖形圖像,Canvas對象的獲取有兩種方式:一種是重寫View.onDraw方法時,在該方法中Canvas對象會被當作參數傳遞過來,在該Canvas上繪製的圖形圖像會一直顯示在View中,另一種是藉助位圖創建Canvas,參考代碼:

Bitmap  bitmap = Bitmap.CreatBitmap(100,100,Bitmap.Config,ARGB_888);

Canvas  canvas = new Canvas(bitmap);

Android中頁面的橫屏與豎屏操作:

在配置文件中爲Activity設置屬性 android:screenOrientation = “ string ” ; 其中string爲屬性值,landscape(橫屏)或portrait(豎屏);

在代碼中設置:

		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);爲防止切換後重新啓動當前Activity,應在配置文件中添加android:configChanges=“keyboardHidden|orientation”屬性,並在Activity中重寫onConfigurationChanged方法。

獲取手機中屏幕的寬和高的方法:

通過Context的getWindowManager()方法獲得WindowManager對象,再從WindowManager對象中通過getDefaultDisplay獲得Display對象,再從Display對象中獲得屏幕的寬和高。

Android內存泄漏及管理

內存溢出(out of memory):是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of Memory 的錯誤,比如申請了一個integer,但給它存了long才能存下的數,這就是內存溢出,通俗來講,內存溢出就是內存不夠用。

內存泄漏(Memory Leak):是指程序使用new關鍵字向系統申請內存後,無法釋放已經申請的內存空間,一次內存泄漏可以忽略,但多次內存泄漏堆積後會造成很嚴重的後果,會佔用光內存空間。

以發生的方式來看,內存泄漏可以分爲以下幾類:

常發性內存泄漏:發生內存泄漏的代碼會被執行多次,每次執行都會導致一塊內存泄漏;

偶發性內存泄漏:發生內存泄露的代碼只有在某些特定的環境或情況下才會發生;

一次性內存泄漏:發生內存泄漏的代碼只會被執行一次,或是由於算法上的缺陷,導致總會有一塊且僅有一塊內存發生泄漏;

隱式內存泄漏:程序在運行過程中不停的分配內存,但是直到程序結束的時候纔會釋放內存;

常見造成內存泄漏的原因:

單例模式引起的內存泄漏:由於單例的靜態特性使得單例的生命週期和程序的生命週期一樣長,如果一個對象(如Context)已經不再使用,而單例對象還持有對象的引用就會造成這個對象不能正常回收;

Handler引起的內存泄漏:子線程執行網絡任務,使用Handler處理子線程發送的消息,由於Hnadler對象是非靜態匿名內部類的實例對象,持有外部類(Activity)的引用,在Handler Message中,Looper線程不斷輪詢處理消息,當Activity退出時還有未處理或正在處理的消息時,消息隊列中的消息持有Handler對象引用,Handler又持有Activity,導致Activity的內存和資源不能及時回收;

線程造成內存泄漏:匿名內部類Runnable和AsyncTask對象執行異步任務,當前Activity隱式引用,當前Activity銷燬之前,任務還沒有執行完,將導致Activity的內存和資源不能及時回收;

資源對象未關閉造成的內存泄漏:對於使用了BroadcastReceiver、File、Bitmap等資源,應該在Activity銷燬之前及時關閉或註銷它們,否則這些資源將不會回收,造成內存泄漏;

內存泄漏的檢測工具:LeakCanary

LeakCanary是Square公司開源的一個檢測內存泄漏的函數庫,可以在開發的項目中集成,在Debug版本中監控Activity、Fragment等的內存泄漏,LeakCanaty集成到項目之後,在檢測內存泄漏時,會發送消息到系統通知欄,點擊後會打開名稱DisplayLeakActivity的頁面,並顯示泄露的跟蹤信息,Logcat上面也會有對應的日誌輸出。

Android設計模式之MVC

MVC即Model-View-Controller,M是模型,V是視圖,C是控制器,MVC模式下系統框架的類庫被劃分爲模型(Model)、視圖(View)、控制器(Controller),模型對象負責建立數據結構和相應的行爲操作處理,視圖負責在屏幕上渲染出相應的圖形信息,展示給用戶看,控制器對象負責截獲用戶的按鍵和屏幕觸摸事件,協調Model對象和View對象。

用戶與視圖交互,視圖接收並反饋用戶的動作,視圖把用戶的請求傳給相應的控制器,由控制器決定調用哪個模型,然後由模型調用相應的業務邏輯對用戶請求進行加工處理,如果需要返回數據,模型會把相應的數據返回給控制器,由控制器調用相應的視圖,最終由視圖格式化和渲染返回的數據,一個模型可以有多個視圖,一個視圖可以有多個控制器,一個控制器可以有多個模型。

Model(模型):Model是一個應用系統的核心部分,代表了該系統實際要實現的所有功能處理,比如在視頻播放器中,模型代表了一個視頻數據庫及播放視頻的程序和函數代碼,Model在values目錄下通過xml文件格式生成,也可以由代碼動態生成,View和Model是通過橋樑Adapter來連接起來的;

View(視圖):View是軟件應用傳給用戶的一個反饋結果,它代表了軟件應用中的圖形展示,聲音播放、觸覺反饋等,視圖的根節點是應用程序的自身窗口,View在Layout目錄中通過xml文件格式生成,用findViewById()獲取,也可以通過代碼動態生成;

Controller(控制器):Controller在軟件應用中負責對外部事件的響應,包括:鍵盤敲擊、屏幕觸摸、電話呼入等,Controller實現了一個事件隊列,每一個外部事件均在事件隊列中被唯一標識,框架依次將事件從隊列中移出並派發出去;

JVM運行原理詳解

JVM簡析:說起Java,我們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用程序接口(Java API)。它們的關係如下圖所示:

Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫並編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示: 運行期環境代表着Java平臺,開發人員編寫Java代碼(.java文件),然後將之編譯成字節碼(.class文件),再然後字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。

JVM在它的生存週期中有一個明確的任務,那就是運行Java程序,因此當Java程序啓動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟着消失了。在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在覈心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱爲適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因爲有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。

下面我們從JVM的基本概念和運過程程這兩個方面入手來對它進行深入的研究。

2.JVM基本概念

(1)基本概念:JVM是可運行Java代碼的假想計算機 ,包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。JVM是運行在操作系統之上的,它與硬件沒有直接的交互。

(2)運行過程:我們都知道Java源文件,通過編譯器,能夠生產相應的.Class文件,也就是字節碼文件,而字節碼文件又通過Java虛擬機中的解釋器,編譯成特定機器上的機器碼 。也就是如下: ① Java源文件—->編譯器—->字節碼文件;② 字節碼文件—->JVM—->機器碼

每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的,這也就是Java爲什麼能夠跨平臺的原因了 ,當一個程序從開始運行,這時虛擬機就開始實例化了,多個程序啓動就會存在多個虛擬機實例。程序退出或者關閉,則虛擬機實例消亡,多個虛擬機實例之間數據不能共享。

(3)三種JVM:① Sun公司的HotSpot;② BEA公司的JRockit;③ IBM公司的J9JVM;

在JDK1.7及其以前我們所使用的都是Sun公司的HotSpot,但由於Sun公司和BEA公司都被oracle收購,jdk1.8將採用Sun公司的HotSpot和BEA公司的JRockit兩個JVM中精華形成jdk1.8的JVM。

3.JVM的體系結構

(1)Class Loader類加載器: 負責加載 .class文件,class文件在文件開頭有特定的文件標示,並且ClassLoader負責class文件的加載等,至於它是否可以運行,則由Execution Engine決定。①定位和導入二進制class文件;② 驗證導入類的正確性;③ 爲類分配初始化內存;④ 幫助解析符號引用.

(2)Native Interface本地接口:本地接口的作用是融合不同的編程語言爲Java所用,它的初衷是融合C/C++程序,Java誕生的時候C/C++橫行的時候,要想立足,必須有調用C/C++程序,於是就在內存中專門開闢了一塊區域處理標記爲native的代碼,它的具體作法是Native Method Stack中登記native方法,在Execution Engine執行時加載native libraies。

(3)Execution Engine 執行引擎:執行包在裝載類的方法中的指令,也就是方法。

(4)Runtime data area 運行數據區:虛擬機內存或者Jvm內存,衝整個計算機內存中開闢一塊內存存儲Jvm需要用到的對象,變量等,運行區數據有分很多小區,分別爲:方法區,虛擬機棧,本地方法棧,堆,程序計數器。

4.JVM數據運行區詳解(棧管運行,堆管存儲):

說明:JVM調優主要就是優化 Heap堆 和 Method Area 方法區。

(1)Native Method Stack本地方法棧:它的具體做法是Native Method Stack中登記native方法,在Execution Engine執行時加載native libraies。

(2)PC Register程序計數器:每個線程都有一個程序計算器,就是一個指針,指向方法區中的方法字節碼(下一個將要執行的指令代碼),由執行引擎讀取下一條指令,是一個非常小的內存空間,幾乎可以忽略不記。

(3)Method Area方法區:方法區是被所有線程共享,所有字段和方法字節碼,以及一些特殊方法如構造函數,接口代碼也在此定義。簡單說,所有定義的方法的信息都保存在該區域,此區域屬於共享區間。靜態變量+常量+類信息+運行時常量池存在方法區中,實例變量存在堆內存中。

(4)Stack 棧: ① 棧是什麼,棧也叫棧內存,主管Java程序的運行,是在線程創建時創建,它的生命期是跟隨線程的生命期,線程結束棧內存也就釋放,對於棧來說不存在垃圾回收問題,只要線程一結束該棧就Over,生命週期和線程一致,是線程私有的。基本類型的變量和對象的引用變量都是在函數的棧內存中分配。② 棧存儲什麼?棧幀中主要保存3類數據:本地變量(Local Variables):輸入參數和輸出參數以及方法內的變量;棧操作(Operand Stack):記錄出棧、入棧的操作; 棧幀數據(Frame Data):包括類文件、方法等等。③棧運行原理,棧中的數據都是以棧幀(Stack Frame)的格式存在,棧幀是一個內存區塊,是一個數據集,是一個有關方法和運行期數據的數據集,當一個方法A被調用時就產生了一個棧幀F1,並被壓入到棧中,A方法又調用了B方法,於是產生棧幀F2也被壓入棧,B方法又調用了C方法,於是產生棧幀F3也被壓入棧……依次執行完畢後,先彈出後進…F3棧幀,再彈出F2棧幀,再彈出F1棧幀。遵循“先進後出”/“後進先出”原則。

(5) Heap 堆:堆這塊區域是JVM中最大的,應用的對象和數據都是存在這個區域,這塊區域也是線程共享的,也是 gc 主要的回收區,一個 JVM 實例只存在一個堆類存,堆內存的大小是可以調節的。類加載器讀取了類文件後,需要把類、方法、常變量放到堆內存中,以方便執行器執行,堆內存分爲三部分:

① 新生區:新生區是類的誕生、成長、消亡的區域,一個類在這裏產生,應用,最後被垃圾回收器收集,結束生命。新生區又分爲兩部分:伊甸區(Eden space)和倖存者區(Survivor pace),所有的類都是在伊甸區被new出來的。倖存區有兩個:0區(Survivor0 space)和1區(Survivor 1 space)。當伊甸園的空間用完時,程序又需要創建對象,JVM的垃圾回收器將對伊甸園進行垃圾回收(Minor GC),將伊甸園中的剩餘對象移動到倖存0區。若倖存0區也滿了,再對該區進行垃圾回收,然後移動到1區。那如果1去也滿了呢?再移動到養老區。若養老區也滿了,那麼這個時候將產生Major GC(FullGCC),進行養老區的內存清理。若養老區執行Full GC 之後發現依然無法進行對象的保存,就會產生OOM異常“OutOfMemoryError”。如果出現java.lang.OutOfMemoryError: Java heap space異常,說明Java虛擬機的堆內存不夠。原因有二: a.Java虛擬機的堆內存設置不夠,可以通過參數-Xms、-Xmx來調整。b.代碼中創建了大量大對象,並且長時間不能被垃圾收集器收集(存在被引用)。

養老區:養老區用於保存從新生區篩選出來的 JAVA 對象,一般池對象都在這個區域活躍。

③ 永久區:永久存儲區是一個常駐內存區域,用於存放JDK自身所攜帶的 Class,Interface 的元數據,也就是說它存儲的是運行環境必須的類信息,被裝載進此區域的數據是不會被垃圾回收器回收掉的,關閉 JVM 纔會釋放此區域所佔用的內存。 如果出現java.lang.OutOfMemoryError:PermGen space,說明是Java虛擬機對永久代Perm內存設置不夠。原因有二:

a.程序啓動需要加載大量的第三方jar包。例如:在一個Tomcat下部署了太多的應用。

b.大量動態反射生成的類不斷被加載,最終導致Perm區被佔滿。

方法區和堆內存的異議: 實際而言,方法區和堆一樣,是各個線程共享的內存區域,它用於存儲虛擬機加載的:類信息+普通常量+靜態常量+編譯器編譯後的代碼等等,雖然JVM規範將方法區描述爲堆的一個邏輯部分,但它卻還有一個別名叫做Non-Heap(非堆),目的就是要和堆分開。

對於HotSpot虛擬機,很多開發者習慣將方法區稱之爲“永久代(Parmanent Gen)”,但嚴格本質上說兩者不同,或者說使用永久代來實現方法區而已,永久代是方法區的一個實現,jdk1.7的版本中,已經將原本放在永久代的字符串常量池移走。

常量池(Constant Pool)是方法區的一部分,Class文件除了有類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池,這部分內容將在類加載後進入方法區的運行時常量池中存放。

Android平臺的虛擬機Dalvik

Dalvik概述:Dalvik是Google公司自己設計用於Android平臺的Java虛擬機。它可以支持已轉換爲.dex(即Dalvik Executable)格式的Java應用程序的運行,.dex格式是專爲Dalvik設計的一種壓縮格式,可以減少整體文件尺寸,提高I/o操作的類查找速度所以適合內存和處理器速度有限的系統。

Dalvik虛擬機(DVM)和Java 虛擬機(JVM)首要差別:Dalvik 基於寄存器,而JVM 基於棧。性能有很大的提升。基於寄存器的虛擬機對於更大的程序來說,在它們編譯的時候,花費的時間更短。

寄存器的概念:寄存器是中央處理器內的組成部分。寄存器是有限存貯容量的高速存貯部件,它們可用來暫存指令、數據和位址。在中央處理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序計數器(PC),在中央處理器的算術及邏輯部件中,包含的寄存器有累加器(ACC)。

棧的概念:棧是線程獨有的,保存其運行狀態和局部自動變量的(所以多線程中局部變量都是相互獨立的,不同於類變量)。棧在線程開始的時候初始化(線程的Start方法,初始化分配棧),每個線程的棧互相獨立。每個函數都有自己的棧,棧被用來在函數之間傳遞參數。操作系統在切換線程的時候會自動的切換棧,就是切換SS/ESP寄存器。棧空間不需要在高級語言裏面顯式的分配和釋放。

DVM進程的設計規則:

每個應用程序都運行在它自己的Linux空間。在需要執行該應用程序時Android將啓動該進程,當不再需要該應用程序,並且系統資源分配不夠時,則系統終止該進程。

每個應用程序都有自己的(DVM),所以任一應用程序的代碼與其他應用程序的代碼是相互隔離的。

默認情況下,每個應用程序都給分配一個唯一的Linux用戶ID。所以應用程序的文件只能對該應用程序可見。

所以說每個應用程序都擁有一個獨立的DVM,而每個DVM在Linux中又是一個進程,所以說DVM進程和Linux進程可以說是一個概念。

Android 應用程序的編譯:Android所有類都通過JAVA編譯器編譯,然後通過Android SDK的“dex文件轉換工具”轉換爲“dex”的字節文件,再由DVM載入執行。

Android ART模式簡介:Android4.4引入ART模式來代替Dalvik虛擬機。ART是AndroidRuntime的縮寫,它提供了以AOT(Ahead-Of-Time)的方式運行Android應用程序的機制。所謂AOT是指在運行前就把中間代碼靜態編譯成本地代碼,這就節省了JIT運行時的轉換時間。因此,和採用JIT的Dalvik相比,ART模式在總體性能有了很大的提升,應用程序不但運行效率更高,耗電量更低,而且佔用的內存也更少;ART和dalvik相比,系統的性能得到了顯著提升,同時佔用的內存更少,因此能支持配置更低的設備。但是ART模式下編譯出來的文件會比以前增大10%-20%,系統需要更多的存儲空間,同時因爲在安裝時要執行編譯,應用的安裝時間也比以前更長了;ART和dalvik相比,系統的性能得到了顯著提升,同時佔用的內存更少,因此能支持配置更低的設備。但是ART模式下編譯出來的文件會比以前增大10%-20%,系統需要更多的存儲空間,同時因爲在安裝時要執行編譯,應用的安裝時間也比以前更長了。

Java的內存分配

Java內存分配主要包括以下幾個區域: 1. 寄存器:我們在程序中無法控制;2. 棧:存放基本類型的數據和對象的引用,但對象本身不存放在棧中,而是存放在堆中;3. 堆:存放用new產生的數據;4. 靜態域:存放在對象中用static定義的靜態成員;5. 常量池:存放常量;6. 非RAM(隨機存取存儲器)存儲:硬盤等永久存儲空間。

Java內存分配中的棧:在函數中定義的一些基本類型的變量數據和對象的引用變量都在函數的棧內存中分配。當在一段代碼塊定義一個變量時,Java就在棧中爲這個變量分配內存空間,當該變量退出該作用域後,Java會自動釋放掉爲該變量所分配的內存空間,該內存空間可以立即被另作他用。

Java內存分配中的堆:堆內存用來存放由new創建的對象和數組。在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。在堆中產生了一個數組或對象後,還可以 在棧中定義一個特殊的變量,讓棧中這個變量的取值等於數組或對象在堆內存中的首地址,棧中的這個變量就成了數組或對象的引用變量。引用變量就相當於是爲數組或對象起的一個名稱,以後就可以在程序中使用棧中的引用變量來訪問堆中的數組或對象。引用變量就相當於是爲數組或者對象起的一個名稱。引用變量是普通的變量,定義時在棧中分配,引用變量在程序運行到其作用域之外後被釋放。而數組和對象本身在堆中分配,即使程序運行到使用 new 產生數組或者對象的語句所在的代碼塊之外,數組和對象本身佔據的內存不會被釋放,數組和對象在沒有引用變量指向它的時候,才變爲垃圾,不能在被使用,但仍然佔據內存空間不放,在隨後的一個不確定的時間被垃圾回收器收走(釋放掉)。這也是 Java 比較佔內存的原因。實際上,棧中的變量指向堆內存中的變量,這就是Java中的指針!

Java內存分配中的常量池 (constant pool):常量池指的是在編譯期被確定,並被保存在已編譯的.class文件中的一些數據。除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數組)的常量值(final)還包含一些以文本形式出現的符號引用,比如: 1.類和接口的全限定名;2.字段的名稱和描述符; 3.方法和名稱和描述符。虛擬機必須爲每個被裝載的類型維護一個常量池。常量池就是該類型所用到常量的一個有序集和,包括直接常量(string,integer和 floating point常量)和對其他類型,字段和方法的符號引用。對於String常量,它的值是在常量池中的。而JVM中的常量池在內存當中是以表的形式存在的,對於String類型,有一張固定長度的CONSTANT_String_info表用來存儲文字字符串值,注意:該表只存儲文字字符串值,不存儲符號引用。說到這裏,對常量池中的字符串值的存儲位置應該有一個比較明瞭的理解了。在程序執行的時候,常量池會儲存在Method Area,而不是堆中。

堆與棧:Java的堆是一個運行時數據區,類的(對象從中分配空間。這些對象通過new、newarray、 anewarray和multianewarray等指令建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,因爲它是在運行時動態分配內存的,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由於要在運行時動態分配內存,存取速度較慢。 棧的優勢是,存取速度比堆要快,僅次於寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變量數據(int, short, long, byte, float, double, boolean, char)和對象句柄(引用)。棧有一個很重要的特殊性,就是存在棧中的數據可以共享

Android中的Binder機制

Binder是Android系統進程間通信(IPC)方式之一。Linux已經擁有的進程間通信IPC手段包括(Internet Process Connection):管道(Pipe)、信號(Signal)和跟蹤(Trace)、插口(Socket)、報文隊列(Message)、共享內存(Share Memory)和信號量(Semaphore)。Binder可以提供系統中任何程序都可以訪問的全局服務。Android的Binder的框架如下:

上圖中涉及到Binder模型的4類角色:Binder驅動,ServiceManager,Server和Client。 因爲後面章節講解Binder時,都是以MediaPlayerService和MediaPlayer爲代表進行講解的;這裏就使用MediaPlayerService代表了Server,而MediaPlayer則代表了Client。

Binder機制的目的是實現IPC(Inter-ProcessCommunication),即實現進程間通信。在上圖中,由於MediaPlayerService是Server的代表,而MediaPlayer是Client的代表;因此,對於上圖而言,Binder機制則表現爲"實現MediaPlayerService和MediaPlayer之間的通信"。

Android中的緩存機制

移動開發本質上就是手機和服務器之間進行通信,需要從服務端獲取數據。反覆通過網絡獲取數據是比較耗時的,特別是訪問比較多的時候,會極大影響了性能,Android中可通過緩存機制來減少頻繁的網絡操作,減少流量、提升性能。

實現原理:把不需要實時更新的數據緩存下來,通過時間或者其他因素 來判別是讀緩存還是網絡請求,這樣可以緩解服務器壓力,一定程度上提高應用響應速度,並且支持離線閱讀。

Bitmap的緩存:在許多的情況下(像 ListView, GridView 或 ViewPager 之類的組件 )我們需要一次性加載大量的圖片,在屏幕上顯示的圖片和所有待顯示的圖片有可能需要馬上就在屏幕上無限制的進行滾動、切換。像ListView, GridView 這類組件,它們的子項當不可見時,所佔用的內存會被回收以供正在前臺顯示子項使用。垃圾回收器也會釋放你已經加載了的圖片佔用的內存。如果你想讓你的UI運行流暢的話,就不應該每次顯示時都去重新加載圖片。保持一些內存和文件緩存就變得很有必要了。

使用內存緩存:通過預先消耗應用的一點內存來存儲數據,便可快速的爲應用中的組件提供數據,是一種典型的以空間換時間的策略。LruCache 類(Android v4 Support Library 類庫中開始提供)非常適合來做圖片緩存任務,它可以使用一個LinkedHashMap 的強引用來保存最近使用的對象,並且當它保存的對象佔用的內存總和超出了爲它設計的最大內存時會把不經常使用的對象成員踢出以供垃圾回收器回收。給LruCache 設置一個合適的內存大小,需考慮如下因素:

還剩餘多少內存給你的activity或應用使用屏幕上需要一次性顯示多少張圖片和多少圖片在等待顯示 手機的大小和密度是多少(密度越高的設備需要越大的 緩存) 圖片的尺寸(決定了所佔用的內存大小) 圖片的訪問頻率(頻率高的在內存中一直保存)保存圖片的質量(不同像素的在不同情況下顯示);

使用磁盤緩存:內存緩存能夠快速的獲取到最近顯示的圖片,但不一定就能夠獲取到。當數據集過大時很容易把內存緩存填滿(如GridView )。你的應用也有可能被其它的任務(比如來電)中斷進入到後臺,後臺應用有可能會被殺死,那麼相應的內存緩存對象也會被銷燬。當你的應用重新回到前臺顯示時,你的應用又需要一張一張的去加載圖片了。磁盤文件緩存能夠用來處理這些情況,保存處理好的圖片,當內存緩存不可用的時候,直接讀取在硬盤中保存好的圖片,這樣可以有效的減少圖片加載的次數。讀取磁盤文件要比直接從內存緩存中讀取要慢一些,而且需要在一個UI主線程外的線程中進行,因爲磁盤的讀取速度是不能夠保證的,磁盤文件緩存顯然也是一種以空間換時間的策略。

使用SQLite進行緩存:網絡請求數據完成後,把文件的相關信息(如url(一般作爲唯一標示),下載時間,過期時間)等存放到數據庫。下次加載的時候根據url先從數據庫中查詢,如果查詢到並且時間未過期,就根據路徑讀取本地文件,從而實現緩存的效果。

文件緩存:思路和一般緩存一樣,把需要的數據存儲在文件中,下次加載時判斷文件是否存在和過期(使用File.lastModified()方法得到文件的最後修改時間,與當前時間判斷),存在並未過期就加載文件中的數據,否則請求服務器重新下載。

Android 中圖片的三級緩存策略

三級緩存: •內存緩存,優先加載,速度最快; •本地緩存,次優先加載,速度快;•網絡緩存,最後加載,速度慢,浪費流量 ;

三級緩存策略,最實在的意義就是 減少不必要的流量消耗,增加加載速度。

三級緩存的原理:

•首次加載的時候通過網絡加載,獲取圖片,然後保存到內存和 SD 卡中。

•之後運行 APP 時,優先訪問內存中的圖片緩存。

•如果內存沒有,則加載本地 SD 卡中的圖片。

具體的緩存策略可以是這樣的:內存作爲一級緩存,本地作爲二級緩存,網絡加載爲最後。其中,內存使用 LruCache ,其內部通過 LinkedhashMap 來持有外界緩存對象的強引用;對於本地緩存,使用 DiskLruCache。加載圖片的時候,首先使用 LRU 方式進行尋找,找不到指定內容,按照三級緩存的方式,進行本地搜索,還沒有就網絡加載。

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