2016校招,Android開發,常見面試問題彙總

NoobZhang發表於2016-11-11

J2EE 部分:

1.Switch能否用string做引數?

在 Java 7 之前, switch 只能支援 byte 、 short 、 char 、 int 或者其對應的封裝類以及 Enum 型別。在 Java 7 中, String 支援被加上了。

2. equals與==的區別:

==是判斷兩個變數或例項是不是指向同一個記憶體空間 equals是判斷兩個變數或例項所指向的記憶體空間的值是不是相同

3. Object有哪些公用方法?

  • 方法equals測試的是兩個物件是否相等
  • 方法clone進行物件拷貝
  • 方法getClass返回和當前物件相關的Class物件
  • 方法notify,notifyall,wait都是用來對給定物件進行執行緒同步的

5. 實際開發中軟引用或者弱引用的使用場景:

利用軟引用和弱引用解決OOM問題:用一個HashMap來儲存圖片的路徑和相應圖片物件關聯的軟引用之間的對映關係,在記憶體不足時,JVM會自動回收這些快取圖片物件所佔用的空間,從而有效地避免了OOM的問題 通過軟可及物件重獲方法實現Java物件的快取記憶體:比如我們建立了一Employee的類,如果每次需要查詢一個僱員的資訊。哪怕是幾秒中之前剛剛查詢過的,都要重新構建一個例項,這是需要消耗很多時間的。我們可以通過軟引用和 HashMap 的結合,先是儲存引用方面:以軟引用的方式對一個Employee物件的例項進行引用並儲存該引用到HashMap 上,key 為此僱員的 id,value為這個物件的軟引用,另一方面是取出引用,快取中是否有該Employee例項的軟引用,如果有,從軟引用中取得。如果沒有軟引用,或者從軟引用中得到的例項是null,重新構建一個例項,並儲存對這個新建例項的軟引用

6. Hashcode的作用,與 equal 有什麼區別

同樣用於鑑定2個物件是否相等的,java集合中有 list 和 set 兩類,其中 set不允許元素重複實現,那個這個不允許重複實現的方法,如果用 equal 去比較的話,如果存在1000個元素,你 new 一個新的元素出來,需要去呼叫1000次 equal 去逐個和他們比較是否是同一個物件,這樣會大大降低效率。hashcode實際上是返回物件的儲存地址,如果這個位置上沒有元素,就把元素直接儲存在上面,如果這個位置上已經存在元素,這個時候才去呼叫equal方法與新元素進行比較,相同的話就不存了,雜湊到其他地址上

7. String、StringBuffer與StringBuilder的區別

String 型別和 StringBuffer 型別的主要效能區別其實在於 String 是不可變的物件 StringBuffer和StringBuilder底層是 char[]陣列實現的 StringBuffer是執行緒安全的,而StringBuilder是執行緒不安全的

8. Override和Overload的含義去區別

Overload顧名思義是重新載入,它可以表現類的多型性,可以是函式裡面可以有相同的函式名但是引數名、返回值、型別不能相同;或者說可以改變引數、型別、返回值但是函式名字依然不變。 Override顧名思義就是ride(重寫)的意思,在子類繼承父類的時候子類中可以定義某方法與其父類有相同的名稱和引數,當子類在呼叫這一函式時自動呼叫子類的方法,而父類相當於被覆蓋(重寫)了。

9. 抽象類和介面的區別

一個類只能繼承單個類,但是可以實現多個介面 介面強調特定功能的實現,而抽象類強調所屬關係 抽象類中的所有方法並不一定要是抽象的,你可以選擇在抽象類中實現一些基本的方法。而介面要求所有的方法都必須是抽象的

10.解析XML的幾種方式的原理與特點:DOM、SAX、PULL

  • DOM:消耗記憶體:先把xml文件都讀到記憶體中,然後再用DOM API來訪問樹形結構,並獲取資料。這個寫起來很簡單,但是很消耗記憶體。要是資料過大,手機不夠牛逼,可能手機直接當機
  • SAX:解析效率高,佔用記憶體少,基於事件驅動的:更加簡單地說就是對文件進行順序掃描,當掃描到文件(document)開始與結束、元素(element)開始與結束、文件(document)結束等地方時通知事件處理函式,由事件處理函式做相應動作,然後繼續同樣的掃描,直至文件結束。
  • PULL:與 SAX 類似,也是基於事件驅動,我們可以呼叫它的next()方法,來獲取下一個解析事件(就是開始文件,結束文件,開始標籤,結束標籤),當處於某個元素時可以呼叫XmlPullParser的getAttributte()方法來獲取屬性的值,也可呼叫它的nextText()獲取本節點的值。

    11.wait()和sleep()的區別

  • sleep來自Thread類,和wait來自Object類
  • 呼叫sleep()方法的過程中,執行緒不會釋放物件鎖。而 呼叫 wait 方法執行緒會釋放物件鎖
  • sleep睡眠後不出讓系統資源,wait讓出系統資源其他執行緒可以佔用CPU
  • sleep(milliseconds)需要指定一個睡眠時間,時間一到會自動喚醒

13.JAVA多型的實現原理

抽象的來講,多型的意思就是同一訊息可以根據傳送物件的不同而採用多種不同的行為方式。(傳送訊息就是函式呼叫) 實現的原理是動態繫結,程式呼叫的方法在執行期才動態繫結,追溯原始碼可以發現,JVM 通過引數的自動轉型來找到合適的辦法。

14.JAVA 垃圾回收與記憶體分配策略

14.1 垃圾回收是什麼?

就是釋放那些不再持有引用的物件的記憶體

14.2怎麼判斷一個物件是否需要收集?

  • 引用計數(最簡單古老的方法):指將資源(可以是物件、記憶體或磁碟空間等等)的被引用次數儲存起來,當被引用次數變為零時就將其釋放的過程
    • 物件引用遍歷(現在大多數 jvm 使用的方法):物件引用遍歷從一組物件開始,沿著整個物件圖上的每條連結,遞迴確定可到達(reachable)的物件。如果某物件不能從這些根物件的一個(至少一個)到達,則將它作為垃圾收集
    • 引用計數缺陷:引用計數無法解決迴圈引用問題:假設物件A,B都已經被例項化,讓A=B,B=A,除此之外這兩個物件再無任何引用,此時計數器的值就永遠不可能為0,但是引用計數器無法通知gc回收他們

14.3 Java的四種引用的區別

  • 強引用:如果一個物件具有強引用,它就不會被垃圾回收器回收。即使當前記憶體空間不足,JVM 也不會回收它,而是丟擲 OutOfMemoryError 錯誤,使程式異常終止。如果想中斷強引用和某個物件之間的關聯,可以顯式地將引用賦值為null,這樣一來的話,JVM在合適的時間就會回收該物件
  • 軟引用:在使用軟引用時,如果記憶體的空間足夠,軟引用就能繼續被使用,而不會被垃圾回收器回收,只有在記憶體不足時,軟引用才會被垃圾回收器回收。
  • 弱引用:具有弱引用的物件擁有的生命週期更短暫。因為當 JVM 進行垃圾回收,一旦發現弱引用物件,無論當前記憶體空間是否充足,都會將弱引用回收。不過由於垃圾回收器是一個優先順序較低的執行緒,所以並不一定能迅速發現弱引用物件
  • 虛引用:顧名思義,就是形同虛設,如果一個物件僅持有虛引用,那麼它相當於沒有引用,在任何時候都可能被垃圾回收器回收。

14.4 介紹垃圾回收機制

  • 標記回收法:遍歷物件圖並且記錄可到達的物件,以便刪除不可到達的物件,一般使用單執行緒工作並且可能產生記憶體碎片
  • 標記-壓縮回收法:前期與第一種方法相同,只是多了一步,將所有的存活物件壓縮到記憶體的一端,這樣記憶體碎片就可以合成一大塊可再利用的記憶體區域,提高了記憶體利用率
  • 複製回收法:把現有記憶體空間分成兩部分,gc執行時,它把可到達物件複製到另一半空間,再清空正在使用的空間的全部物件。這種方法適用於短生存期的物件,持續複製長生存期的物件則導致效率降低。
  • 分代回收發:把記憶體空間分為兩個或者多個域,如年輕代和老年代,年輕代的特點是物件會很快被回收,因此在年輕代使用效率比較高的演算法。當一個物件經過幾次回收後依然存活,物件就會被放入稱為老年的記憶體空間,老年代則採取標記-壓縮演算法

14.5 JAVA 中堆和棧的區別

  • 基本資料型別比變數和物件的引用都是在棧分配的
  • 堆記憶體用來存放由new建立的物件和陣列
  • 類變數(static修飾的變數),程式在一載入的時候就在堆中為類變數分配記憶體,堆中的記憶體地址存放在棧中
  • 例項變數:當你使用java關鍵字new的時候,系統在堆中開闢並不一定是連續的空間分配給變數,是根據零散的堆記憶體地址,通過雜湊演算法換算為一長串數字以表徵這個變數在堆中的"物理位置”,例項變數的生命週期--當例項變數的引用丟失後,將被GC(垃圾回收器)列入可回收“名單”中,但並不是馬上就釋放堆中記憶體
  • 區域性變數: 由宣告在某方法,或某程式碼段裡(比如for迴圈),執行到它的時候在棧中開闢記憶體,當區域性變數一但脫離作用域,記憶體立即釋放

15. Java 集合系列問題

15.1 ArrayList、LinkedList、Vector的區別

  • ArrayList 和Vector底層是採用陣列方式儲存資料,Vector由於使用了synchronized方法(執行緒安全)所以效能上比ArrayList要差
  • LinkedList使用雙向連結串列實現儲存,隨機存取比較慢
  • HashMap的底層原始碼實現:當我們往HashMap中put元素的時候,先根據key的hashCode重新計算hash值,根據hash值得到這個元素在陣列中的位置(即下標),如果陣列該位置上已經存放有其他元素了,那麼在這個位置上的元素將以連結串列的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。如果陣列該位置上沒有元素,就直接將該元素放到此陣列中的該位置上。
  • Fail-Fast機制:在使用迭代器的過程中有其他執行緒修改了map,那麼將丟擲ConcurrentModificationException,這就是所謂fail-fast機制。這一機制在原始碼中的實現是通過modCount域,modCount顧名思義就是修改次數,對HashMap內容的修改都將增加這個值,那麼在迭代器初始化過程中會將這個值賦給迭代器的expectedModCount。在迭代過程中,判斷modCount跟expectedModCount是否相等,如果不相等就表示已經有其他執行緒修改了Map

    15.2 HashMap和 HashTable 的區別

    HashTable比較老,是基於Dictionary 類實現的,HashTable 則是基於 Map介面實現的 HashTable 是執行緒安全的, HashMap 則是執行緒不安全的 HashMap可以讓你將空值作為一個表的條目的key或value

17.什麼事反射,在哪裡需要用到?

反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

反射機制主要提供了以下功能: 

  • 在執行時判斷任意一個物件所屬的類;

  • 在執行時構造任意一個類的物件;

  • 在執行時判斷任意一個類所具有的成員變數和方法;

  • 在執行時呼叫任意一個物件的方法;

  • 生成動態代理。

18. 什麼是執行緒池,執行緒池的作用是什麼

答:執行緒池的基本思想還是一種物件池的思想,開闢一塊記憶體空間,裡面存放了眾多(未死亡)的執行緒,池中執行緒執行排程由池管理器來處理。當有執行緒任務時,從池中取一個,執行完成後執行緒物件歸池,這樣可以避免反覆建立執行緒物件所帶來的效能開銷,節省了系統的資源。就好比原來去食堂打飯是每個人看誰搶的贏,誰先搶到誰先吃,有了執行緒吃之後,就是排好隊形,今天我跟你關係好,你先來吃飯。比如:一個應用要和網路打交道,有很多步驟需要訪問網路,為了不阻塞主執行緒,每個步驟都建立個執行緒,線上程中和網路互動,用執行緒池就變的簡單,執行緒池是對執行緒的一種封裝,讓執行緒用起來更加簡便,只需要創一個執行緒池,把這些步驟像任務一樣放進執行緒池,在程式銷燬時只要呼叫執行緒池的銷燬函式即可。

單個執行緒的弊端:a. 每次new Thread新建物件效能差b. 執行緒缺乏統一管理,可能無限制新建執行緒,相互之間競爭,及可能佔用過多系統資源導致當機或者OOM,c. 缺乏更多功能,如定時執行、定期執行、執行緒中斷。

java提供的四種執行緒池的好處在於:a. 重用存在的執行緒,減少物件建立、消亡的開銷,效能佳。b. 可有效控制最大併發執行緒數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。c. 提供定時執行、定期執行、單執行緒、併發數控制等功能。

2、Java 執行緒池

Java通過Executors提供四種執行緒池,分別為:

newCachedThreadPool建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。

newFixedThreadPool 建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。

newScheduledThreadPool 建立一個定長執行緒池,支援定時及週期性任務執行。

newSingleThreadExecutor 建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。

(1). newCachedThreadPool

建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。執行緒池為無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的執行緒,而不用每次新建執行緒。

(2). newFixedThreadPool

建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。

(3) newScheduledThreadPool

建立一個定長執行緒池,支援定時及週期性任務執行。ScheduledExecutorService比Timer更安全,功能更強大

(4)、newSingleThreadExecutor

建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行

Android部分(給出重點部分):

1. Activity 系列問題

1.1 繪製Activity生命週期流程圖

enter image description here

1.2 介紹下不同場景下Activity生命週期的變化過程

  • 啟動Activity: onCreate()--->onStart()--->onResume(),Activity進入執行狀態。
  • Activity退居後臺: 當前Activity轉到新的Activity介面或按Home鍵回到主屏: onPause()--->onStop(),進入停滯狀態。
  • Activity返回前臺: onRestart()--->onStart()--->onResume(),再次回到執行狀態。
  • Activity退居後臺,且系統記憶體不足, 系統會殺死這個後臺狀態的Activity,若再次回到這個Activity,則會走onCreate()-->onStart()--->onResume()
  • 鎖定屏與解鎖螢幕 只會呼叫onPause(),而不會呼叫onStop方法,開屏後則呼叫onResume()

1.3 記憶體不足時系統會殺掉後臺的Activity,若需要進行一些臨時狀態的儲存,在哪個方法進行?

Activity的 onSaveInstanceState() 和 onRestoreInstanceState()並不是生命週期方法,它們不同於 onCreate()、onPause()等生命週期方法,它們並不一定會被觸發。當應用遇到意外情況(如:記憶體不足、使用者直接按Home鍵)由系統銷燬一個Activity,onSaveInstanceState() 會被呼叫。但是當使用者主動去銷燬一個Activity時,例如在應用中按返回鍵,onSaveInstanceState()就不會被呼叫。除非該activity是被使用者主動銷燬的,通常onSaveInstanceState()只適合用於儲存一些臨時性的狀態,而onPause()適合用於資料的持久化儲存。

1.4 onSaveInstanceState()被執行的場景有哪些:

系統不知道你按下HOME後要執行多少其他的程式,自然也不知道activity A是否會被銷燬,因此係統都會呼叫onSaveInstanceState(),讓使用者有機會儲存某些非永久性的資料。以下幾種情況的分析都遵循該原則

  1. 當使用者按下HOME鍵時
  2. 長按HOME鍵,選擇執行其他的程式時
  3. 鎖屏時
  4. 從activity A中啟動一個新的activity時
  5. 螢幕方向切換時

1.5 介紹Activity的幾中啟動模式,並簡單說說自己的理解或者使用場景

  1. Activity的四種啟動模式:  
  2. . standard  
  3.   
  4.         模式啟動模式,每次啟用Activity時都會建立Activity,並放入任務棧中。  
  5. . singleTop  
  6.   
  7.         如果在任務的棧頂正好存在該Activity的例項, 就重用該例項,否者就會建立新的例項並放入棧頂(即使棧中已經存在該Activity例項,只要不在棧頂,都會建立例項)。  
  8. . singleTask  
  9.   
  10.         如果在棧中已經有該Activity的例項,就重用該例項(會呼叫例項的onNewIntent())。重用時,會讓該例項回到棧頂,因此在它上面的例項將會被移除棧。如果棧中不存在該例項,將會建立新的例項放入棧中。   
  11. . singleInstance  
  12.   
  13.         在一個新棧中建立該Activity例項,並讓多個應用共享改棧中的該Activity例項。一旦改模式的Activity的例項存在於某個棧中,任何應用再啟用改Activity時都會重用該棧中的例項,其效果相當於多個應用程式共享一個應用,不管誰啟用該Activity都會進入同一個應用中。 

2. Service系列問題

2.1 註冊Service需要注意什麼

Service還是執行在主執行緒當中的,所以如果需要執行一些複雜的邏輯操作,最好在服務的內部手動建立子執行緒進行處理,否則會出現UI執行緒被阻塞的問題

2.2 Service與Activity怎麼實現通訊

方法一:

  1. 新增一個繼承Binder的內部類,並新增相應的邏輯方法
  2. 重寫Service的onBind方法,返回我們剛剛定義的那個內部類例項
  3. Activity中建立一個ServiceConnection的匿名內部類,並且重寫裡面的onServiceConnected方法和onServiceDisconnected方法,這兩個方法分別會在活動與服務成功繫結以及解除繫結的時候呼叫,在onServiceConnected方法中,我們可以得到一個剛才那個service的binder物件,通過對這個binder物件進行向下轉型,得到我們那個自定義的Binder例項,有了這個例項,做可以呼叫這個例項裡面的具體方法進行需要的操作了

方法二 通過BroadCast(廣播)的形式 當我們的進度發生變化的時候我們傳送一條廣播,然後在Activity的註冊廣播接收器,接收到廣播之後更新檢視

2.3 介紹原始碼中binder機制

1. 直觀來說,Binder是Android中的一個類,它繼承了IBinder介面

2. 從IPC角度來說,Binder是Android中的一種跨程式通訊方式,Binder還可以理解為一種虛擬的物理裝置,它的裝置驅動是/dev/binder,該通訊方式在linux中沒有

3. 從Android Framework角度來說,Binder是ServiceManager連線各種Manager(ActivityManager、WindowManager,etc)和相應ManagerService的橋樑

4. 從Android應用層來說,Binder是客戶端和服務端進行通訊的媒介,當你bindService的時候,服務端會返回一個包含了服務端業務呼叫的 Binder物件,通過這個Binder物件,客戶端就可以獲取服務端提供的服務或者資料,這裡的服務包括普通服務和基於AIDL的服務


2.4 IntentService與Service的區別

IntentService是Service的子類,是一個非同步的,會自動停止的服務,很好解決了傳統的Service中處理完耗時操作忘記停止並銷燬Service的問題

  • 會建立獨立的worker執行緒來處理所有的Intent請求;
  • 會建立獨立的worker執行緒來處理onHandleIntent()方法實現的程式碼,無需處理多執行緒問題;
  • 所有請求處理完成後,IntentService會自動停止,無需呼叫stopSelf()方法停止Service;
  • 為Service的onBind()提供預設實現,返回null;
  • 為Service的onStartCommand提供預設實現,將請求Intent新增到佇列中;
  • IntentService不會阻塞UI執行緒,而普通Serveice會導致ANR異常
  • Intentservice若未執行完成上一次的任務,將不會新開一個執行緒,是等待之前的任務完成後,再執行新的任務,等任務完成後再次呼叫stopSelf()

3. Handle系列問題

3.1 介紹Handle的機制

  • Handler通過呼叫sendmessage方法把訊息放在訊息佇列MessageQueue中,Looper負責把訊息從訊息佇列中取出來,重新再交給Handler進行處理,三者形成一個迴圈
  • 通過構建一個訊息佇列,把所有的Message進行統一的管理,當Message不用了,並不作為垃圾回收,而是放入訊息佇列中,供下次handler建立訊息時候使用,提高了訊息物件的複用,減少系統垃圾回收的次數
  • 每一個執行緒,都會單獨對應的一個looper,這個looper通過ThreadLocal來建立,保證每個執行緒只建立一個looper,looper初始化後就會呼叫looper.loop建立一個MessageQueue,這個方法在UI執行緒初始化的時候就會完成,我們不需要手動建立

3.2 談談對HandlerThread的理解

4. ListView系列問題

4.1 ListView卡頓的原因與效能優化,越多越好

  1. 重用converView: 通過複用converview來減少不必要的view的建立,另外Infalte操作會把xml檔案例項化成相應的View例項,屬於IO操作,是耗時操作。

  2. 減少findViewById()操作: 將xml檔案中的元素封裝成viewholder靜態類,通過converview的setTag和getTag方法將view與相應的holder物件繫結在一起,避免不必要的findviewbyid操作

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

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

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

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

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

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

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

4.2 怎麼實現一個部分更新的 ListView?

4.3 怎麼實現ListView多種佈局?

4.4 ListView與資料庫繫結的實現

5. JNI系列問題

5.1 如何使用JNI

  1. JAVA中宣告native 方法如private native String printJNI(String inputStr);

  2. 使用javah工具生成.h標頭檔案這時候標頭檔案中就會自動生成對應的函式JNIEXPORT jstring JNICALL Java_com_wenming_HelloWorld_printJNI

  3. 實現JNI原生函式原始檔,新建HelloWorld.c檔案,對剛才自動生成的函式進行具體的邏輯書寫,例如返回一個java叫做HelloWorld的字串等

  4. 編譯生成動態連結so檔案**

  5. Java中呼叫Sysytem.load方法把剛才的so庫載入進來,就可以呼叫native方法了

5.2 如何通過JNI傳遞String物件

Java的String和C++的string是不能對等起來的,所以當我們拿到.h檔案下面的jstring物件,會做一次轉換我們把jstring轉換為C下面的char*型別, 獲取值

constchar* str;
str = env->GetStringUTFChars(prompt,false);

賦予值

char* tmpstr ="return string succeeded";
jstring rtstr = env->NewStringUTF(tmpstr);

6. OOM系列問題

6.1 什麼OOM?

OOM全稱是Out Of Merrory,Android系統的每一個應用程式都設定一個硬性的Dalvik Heap Size最大限制閾值,如果申請的記憶體資源超過這個限制,系統就會丟擲OOM錯誤

6.2 記憶體洩漏有哪些場景以及解決方法

  • 類的靜態變數持有大資料物件 靜態變數長期維持到大資料物件的引用,阻止垃圾回收。

  • 非靜態內部類存在靜態例項 非靜態內部類會維持一個到外部類例項的引用,如果非靜態內部類的例項是靜態的,就會間接長期維持著外部類的引用,阻止被回收掉。

  • 資源物件未關閉 資源性物件比如(Cursor,File檔案等)往往都用了一些緩衝,我們在不使用的時候,應該及時關閉它們, 以便它們的緩衝及時回收記憶體。它們的緩衝不僅存在於java虛擬機器內,還存在於java虛擬機器外。 如果我們僅僅是把它的引用設定為null,而不關閉它們,往往會造成記憶體洩露。解決辦法: 比如SQLiteCursor(在解構函式finalize(),如果我們沒有關閉它,它自己會調close()關閉), 如果我們沒有關閉它,系統在回收它時也會關閉它,但是這樣的效率太低了。 因此對於資源性物件在不使用的時候,應該呼叫它的close()函式,將其關閉掉,然後才置為null. 在我們的程式退出時一定要確保我們的資源性物件已經關閉。 程式中經常會進行查詢資料庫的操作,但是經常會有使用完畢Cursor後沒有關閉的情況。如果我們的查詢結果集比較小, 對記憶體的消耗不容易被發現,只有在常時間大量操作的情況下才會復現記憶體問題,這樣就會給以後的測試和問題排查帶來困難和風險,記得try catch後,在finally方法中關閉連線

  • Handler記憶體洩漏 Handler作為內部類存在於Activity中,但是Handler生命週期與Activity生命週期往往並不是相同的,比如當Handler物件有Message在排隊,則無法釋放,進而導致本該釋放的Acitivity也沒有辦法進行回收。解決辦法

  • 宣告handler為static類,這樣內部類就不再持有外部類的引用了,就不會阻塞Activity的釋放
  • 如果內部類實在需要用到外部類的物件,可在其內部宣告一個弱引用引用外部類。

    public class MainActivity extends Activity {
     private CustomHandler mHandler;
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mHandler = new CustomHandler(this);
     }
    
     static class CustomHandlerextends Handler {
         // 內部宣告一個弱引用,引用外部類
         private WeakReference<MainActivity > activityWeakReference;
         public MyHandler(MyActivity activity) {
             activityWeakReference= new WeakReference<MainActivity >(activity);
         }
                 // ... ...   
     }
    }
    
  • 在Activity onStop或者onDestroy的時候,取消掉該Handler物件的Message和Runnable

    Override
    public void onDestroy() {
     //  If null, all callbacks and messages will be removed.
     mHandler.removeCallbacksAndMessages(null);
    }
    
  • 一些不良程式碼習慣 有些程式碼並不造成記憶體洩露,但是他們的資源沒有得到重用,頻繁的申請記憶體和銷燬記憶體,消耗CPU資源的同時,也引起記憶體抖動解決方案 如果需要頻繁的申請記憶體物件和和釋放物件,可以考慮使用物件池來增加物件的複用。 例如ListView便是採用這種思想,通過複用converview來避免頻繁的GC

6.2 如何避免 OOM 問題的出現

1. 使用更加輕量的資料結構 例如,我們可以考慮使用ArrayMap/SparseArray而不是HashMap等傳統資料結構。通常的HashMap的實現方式更加消耗記憶體,因為它需要一個額外的例項物件來記錄Mapping操作。另外,SparseArray更加高效,在於他們避免了對key與value的自動裝箱(autoboxing),並且避免了裝箱後的解箱。

2. 避免在Android裡面使用Enum Android官方培訓課程提到過“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具體原理請參考《Android效能優化典範(三)》,所以請避免在Android裡面使用到列舉。

3. 減小Bitmap物件的記憶體佔用 Bitmap是一個極容易消耗記憶體的大胖子,減小建立出來的Bitmap的記憶體佔用可謂是重中之重,,通常來說有以下2個措施:inSampleSize:縮放比例,在把圖片載入記憶體之前,我們需要先計算出一個合適的縮放比例,避免不必要的大圖載入。decode format:解碼格式,選擇ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差異

4.Bitmap物件的複用 縮小Bitmap的同時,也需要提高BitMap物件的複用率,避免頻繁建立BitMap物件,複用的方法有以下2個措施LRUCache : “最近最少使用演算法”在Android中有極其普遍的應用。ListView與GridView等顯示大量圖片的控制元件裡,就是使用LRU的機制來快取處理好的Bitmap,把近期最少使用的資料從快取中移除,保留使用最頻繁的資料,inBitMap高階特性:利用inBitmap的高階特性提高Android系統在Bitmap分配與釋放執行效率。使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經存在的記憶體區域,新解碼的Bitmap會嘗試去使用之前那張Bitmap在Heap中所佔據的pixel data記憶體區域,而不是去問記憶體重新申請一塊區域來存放Bitmap。利用這種特性,即使是上千張的圖片,也只會僅僅只需要佔用螢幕所能夠顯示的圖片數量的記憶體大小

4. 使用更小的圖片 在涉及給到資源圖片時,我們需要特別留意這張圖片是否存在可以壓縮的空間,是否可以使用更小的圖片。儘量使用更小的圖片不僅可以減少記憶體的使用,還能避免出現大量的InflationException。假設有一張很大的圖片被XML檔案直接引用,很有可能在初始化檢視時會因為記憶體不足而發生InflationException,這個問題的根本原因其實是發生了OOM。

5.StringBuilder 在有些時候,程式碼中會需要使用到大量的字串拼接的操作,這種時候有必要考慮使用StringBuilder來替代頻繁的“+”。

4.避免在onDraw方法裡面執行物件的建立 類似onDraw等頻繁呼叫的方法,一定需要注意避免在這裡做建立物件的操作,因為他會迅速增加記憶體的使用,而且很容易引起頻繁的gc,甚至是記憶體抖動。

5. 避免物件的記憶體洩露 android中記憶體洩漏的場景以及解決辦法,參考上一問

7. ANR 系列問題

7.1 什麼ANR

ANR全稱Application Not Responding,意思就是程式未響應。如果一個應用無法響應使用者的輸入,系統就會彈出一個ANR對話方塊,使用者可以自行選擇繼續等待亦或者是停止當前程式。一旦出現下面兩種情況,則彈出ANR對話方塊

  • 應用在5秒內未響應使用者的輸入事件(如按鍵或者觸控)
  • BroadcastReceiver未在10秒內完成相關的處理

7.2 ANR是怎麼引起的?

  • 主執行緒中存在耗時的計算-
  • 主執行緒被IO操作(從4.0之後網路IO不允許在主執行緒中)阻塞。-
  • 主執行緒中錯誤的操作,比如Thread.wait或者Thread.sleep等

7.3 如何避免ANR問題的出現

基本思路就是把一些耗時操作放到子執行緒中處理

  • 使用AsyncTask處理耗時IO操作。

  • 降低子執行緒優先順序使用Thread或者HandlerThread時,呼叫Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)設定優先順序,否則仍然會降低程式響應,因為預設Thread的優先順序和主執行緒相同。

  • 使用Handler處理子執行緒結果,而不是使用Thread.wait()或者Thread.sleep()來阻塞主執行緒。

  • Activity的onCreate和onResume回撥中儘量避免耗時的程式碼

  • BroadcastReceiver中onReceive程式碼也要儘量減少耗時操作建議使用IntentService處理。IntentService是一個非同步的,會自動停止的服務,很好解決了傳統的Service中處理完耗時操作忘記停止並銷燬Service的問題

8. Asynctask問題

8.1 AsynTask為什麼要設計為只能夠一次任務?

最核心的還是執行緒安全問題,多個子執行緒同時執行,會產生狀態不一致的問題。所以要務必保證只能夠執行一次

8.2 AsynTask造成的記憶體洩露的問題怎麼解決,》比如非靜態內部類AsynTask會隱式地持有外部類的引用,如果其生命週期大於外部activity的生命週期,就會出現記憶體洩漏

  • 注意要複寫AsynTask的onCancel方法,把裡面的socket,file等,該關掉的要及時關掉
  • 在 Activity 的onDestory()方法中呼叫Asyntask.cancal方法
  • Asyntask內部使用弱引用的方式來持有Activity

8.3 若Activity已經銷燬,此時AsynTask執行完並且返回結果,會報異常嗎?

當一個App旋轉時,整個Activity會被銷燬和重建。當Activity重啟時,AsyncTask中對該Activity的引用是無效的,因此onPostExecute()就不會起作用,若AsynTask正在執行,折會報 view not attached to window manager 異常

同樣也是生命週期的問題,在 Activity 的onDestory()方法中呼叫Asyntask.cancal方法,讓二者的生命週期同步

8.4 Activity銷燬但Task如果沒有銷燬掉,當Activity重啟時這個AsyncTask該如何解決?

還是螢幕旋轉這個例子,在重建Activity的時候,會回掉Activity.onRetainNonConfigurationInstance()重新傳遞一個新的物件給AsyncTask,完成引用的更新

9. Android觸控分發機制

9.1 介紹觸控事件的分發機制

enter image description here

(1) 事件從Activity.dispatchTouchEvent()開始傳遞,只要沒有被停止或攔截,從最上層的View(ViewGroup)開始一直往下(子View)傳遞。子View可以通過onTouchEvent()對事件進行處理。

(2) 事件由父View(ViewGroup)傳遞給子View,ViewGroup可以通過onInterceptTouchEvent()對事件做攔截,停止其往下傳遞。

(3) 如果事件從上往下傳遞過程中一直沒有被停止,且最底層子View沒有消費事件,事件會反向往上傳遞,這時父View(ViewGroup)可以進行消費,如果還是沒有被消費的話,最後會到Activity的onTouchEvent()函式。

(4) 如果View沒有對ACTION_DOWN進行消費,之後的其他事件不會傳遞過來。

(5) OnTouchListener優先於onTouchEvent()對事件進行消費。

上面的消費即表示相應函式返回值為true。

9.2 View中 setOnTouchListener的onTouch,onTouchEvent,onClick的執行順序

追溯到View的dispatchTouchEvent原始碼檢視,有這麼一段程式碼

public boolean dispatchTouchEvent(MotionEvent event) {  
        if (!onFilterTouchEventForSecurity(event)) {  
            return false;  
        }  

        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                mOnTouchListener.onTouch(this, event)) {  
            return true;  
        }  
        return onTouchEvent(event);  
    }

當以下三個條件任意一個不成立時,

  • mOnTouchListener不為null
  • view是enable的狀態
  • mOnTouchListener.onTouch(this, event)返回true,

函式會執行到onTouchEvent。在這裡我們可以看到,首先執行的是mOnTouchListener.onTouch的方法,然後是onTouchEvent方法

繼續追溯原始碼,到onTouchEvent()觀察,發現在處理ACTION_UP事件裡有這麼一段程式碼

 if (!post(mPerformClick)) {  
                                    performClick();  
                                }

此時可知,onClick方法也在最後得到了執行

所以三者的順序是:

  1. setOnTouchListener() 的onTouch
  2. onTouchEvent()
  3. onClick()

10. Dalvik虛擬機器系列問題

10.1 什麼是Dalvik虛擬機器?

Dalvik虛擬機器是Android平臺的核心。它可以支援.dex格式的程式的執行,.dex格式是專為Dalvik設計的一種壓縮格式,可以減少整體檔案尺寸,提高I/O操作的速度,適合記憶體和處理器速度有限的系統。

10.2 Dalvik虛擬機器的作用是什麼?

Dalvik虛擬機器主要是完成物件生命週期管理,記憶體回收,堆疊管理,執行緒管理,安全和異常管理等等重要功能。

10.3 Dalvik虛擬機器與JVM有什麼區別

  • Dalvik 基於暫存器,而 JVM 基於棧。基於暫存器的虛擬機器對於更大的程式來說,在它們編譯的時候,花費的時間更短。
  • Dalvik執行.dex格式的位元組碼,而JVM執行.class格式的位元組碼。

10.4 每個應用程式對應多少個Dalvik虛擬機器

  • 每一個Android應用在底層都會對應一個獨立的Dalvik虛擬機器例項,其程式碼在虛擬機器的解釋下得以執行 ,而所有的Android應用的執行緒都對應一個Linux執行緒

11. 註冊廣播接收器有哪幾種方式,有什麼區別

  • 靜態註冊:在AndroidManifest.xml檔案中進行註冊,當App退出後,Receiver仍然可以接收到廣播並且進行相應的處理
  • 動態註冊:在程式碼中動態註冊,當App退出後,也就沒辦法再接受廣播了

12. 顯示Intent與隱式Intent的區別

對明確指出了目標元件名稱的Intent,我們稱之為“顯式Intent”。 對於沒有明確指出目標元件名稱的Intent,則稱之為“隱式 Intent”。

對於隱式意圖,在定義Activity時,指定一個intent-filter,當一個隱式意圖物件被一個意圖過濾器進行匹配時,將有三個方面會被參考到:

  • 動作(Action)
  • 類別(Category ['kætɪg(ə)rɪ] )
  • 資料(Data )

13. Android中的動畫有哪些,區別是什麼

  • 逐幀動畫(Drawable Animation): 載入一系列Drawable資源來建立動畫,簡單來說就是播放一系列的圖片來實現動畫效果,可以自定義每張圖片的持續時間

  • 補間動畫(Tween Animation): Tween可以對View物件實現一系列簡單的動畫效果,比如位移,縮放,旋轉,透明度等等。但是它並不會改變View屬性的值,只是改變了View的繪製的位置,比如,一個按鈕在動畫過後,不在原來的位置,但是觸發點選事件的仍然是原來的座標。

  • 屬性動畫(Property Animation): 動畫的物件除了傳統的View物件,還可以是Object物件,動畫結束後,Object物件的屬性值被實實在在的改變了

14. 不使用動畫,怎麼實現一個動態的 View?

15. Postvalidata與Validata有什麼區別?

16. 如何自定義ViewGroup?

17. View的繪製流程

enter image description here measure()方法,layout(),draw()三個方法主要存放了一些識別符號,來判斷每個View是否需要再重新測量,佈局或者繪製,主要的繪製過程還是在onMeasure,onLayout,onDraw這個三個方法中

1.onMesarue() 為整個View樹計算實際的大小,即設定實際的高(對應屬性:mMeasuredHeight)和寬(對應屬性: mMeasureWidth),每個View的控制元件的實際寬高都是由父檢視和本身檢視決定的。

2.onLayout() 為將整個根據子檢視的大小以及佈局引數將View樹放到合適的位置上。

3. onDraw() 開始繪製影象,繪製的流程如下

  1. 首先繪製該View的背景
  2. 呼叫onDraw()方法繪製檢視本身 (每個View都需要過載該方法,ViewGroup不需要實現該方法)
  3. 如果該View是ViewGroup,呼叫dispatchDraw ()方法繪製子檢視
  4. 繪製滾動條

18. 資料持久化的四種方式有哪些?

  1. 檔案儲存: 通過java.io.FileInputStream和java.io.FileOutputStream這兩個類來實現對檔案的讀寫,java.io.File類則用來構造一個具體指向某個檔案或者資料夾的物件。

  2. SharedPreferences: SharedPreferences是一種輕量級的資料儲存機制,他將一些簡單的資料型別的資料,包括boolean型別,int型別,float型別,long型別以及String型別的資料,以鍵值對的形式儲存在應用程式的私有Preferences目錄(/data/data/<包名>/shared_prefs/)中,這種Preferences機制廣泛應用於儲存應用程式中的配置資訊。

  3. SQLite資料庫: 當應用程式需要處理的資料量比較大時,為了更加合理地儲存、管理、查詢資料,我們往往使用關聯式資料庫來儲存資料。Android系統的很多使用者資料,如聯絡人資訊,通話記錄,簡訊息等,都是儲存在SQLite資料庫當中的,所以利用操作SQLite資料庫的API可以同樣方便的訪問和修改這些資料。

  4. ContentProvider: 主要用於在不同的應用程式之間實現資料共享的功能,不同於sharepreference和檔案儲存中的兩種全域性可讀寫操作模式,內容提供其可以選擇只對哪一部分資料進行共享,從而保證我們程式中的隱私資料不會有洩漏的風險

19. fragement裡面可以再巢狀fragment?

20. Socker程式設計的步驟

21. Activity中如何動態的新增Fragment

22. Scrollview怎麼判斷是否滑倒底部

23. 什麼是 MVC 模式?MVC 模式的好處是什麼?

24. 應用常駐後臺,避免被第三方殺掉的方法,講講你用過的奇淫巧技?

  1. Service設定成START_STICKY kill 後會被重啟(等待5秒左右),重傳Intent,保持與重啟前一樣

  2. 通過 startForeground將程式設定為前臺程式, 做前臺服務,優先順序和前臺應用一個級別​,除非在系統記憶體非常缺,否則此程式不會被 kill

  3. 雙程式Service: 讓2個程式互相保護**,其中一個Service被清理後,另外沒被清理的程式可以立即重啟程式

  4. QQ黑科技: 在應用退到後臺後,另起一個只有 1 畫素的頁面停留在桌面上,讓自己保持前臺狀態,保護自己不被後臺清理工具殺死

  5. 在已經root的裝置下,修改相應的許可權檔案,將App偽裝成系統級的應用 Android4.0系列的一個漏洞,已經確認可行

  1. 用C編寫守護程式(即子程式) : Android系統中當前程式(Process)fork出來的子程式,被系統認為是兩個不同的程式。當父程式被殺死的時候,子程式仍然可以存活,並不受影響。鑑於目前提到的在Android->- Service層做雙守護都會失敗,我們可以fork出c程式,多程式守護。死迴圈在那檢查是否還存在,具體的思路如下(Android5.0以上的版本不可行)
  2. 用C編寫守護程式(即子程式),守護程式做的事情就是迴圈檢查目標程式是否存在,不存在則啟動它。
  3. 在NDK環境中將1中編寫的C程式碼編譯打包成可執行檔案(BUILD_EXECUTABLE)。主程式啟動時將守護程式放入私有目錄下,賦予可執行許可權,啟動它即可。

  4. 聯絡廠商,加入白名單

25.Context與ApplicationContext的區別,分別用在什麼情況下

Application的Context是一個全域性靜態變數,SDK的說明是隻有當你引用這個context的生命週期超過了當前activity的生命週期,而和整個應用的生命週期掛鉤時,才去使用這個application的context。

在android中context可以作很多操作,但是最主要的功能是載入和訪問資源。在android中有兩種context,一種是 application context,一種是activity context,通常我們在各種類和方法間傳遞的是activity context。

26. 同一個應用程式的不同Activity可以執行在不同的程式中麼?如果可以,舉例說明;

27. Java中的執行緒同步有哪幾種方式,舉例說明;

28. dp, dip, dpi, px, sp是什麼意思以及他們的換算公式?layout-sw400dp, layout-h400dp分別代表什麼意思;

29. 如何讓兩個TextView在一個RelativeLayout水平居中顯示;

30. 如何畫出一個印章的圖案

31. 如何實現一個字型的描邊與陰影效果

32. 設計一個從網路請求資料,圖片,並載入到列表的系統,畫出客戶端架構並簡單的分析下;

33. 設計一個檔案的斷點續傳系統;

34. 設計一個圖片快取載入機制

資料結構與演算法部分:

  1. 給最外層的rootview,把這個根檢視下的全部button背景設定成紅色,手寫程式碼,不許用遞迴
  2. 給一串字串比如abbbcccd,輸出a1b3c3d1,手寫程式碼(注意有個別字元可能會出現十次以上的情況)
  3. 一個序列,它的形式是12349678,9是最高峰,經歷了一個上升又下降的過程,找出裡面的最大值的位置,要求效率儘可能高
  4. 二叉查詢樹的刪除操作,手寫程式碼
  5. 反轉連結串列,手寫程式碼
  6. 二分查詢,手寫程式碼
  7. 有海量條 url,其中不重複的有300萬條,現在希望挑選出重複出現次數最高的 url,要求效率儘可能的高
  8. 一篇英語文章,去掉字元只留下k個,如何去掉才能使這k個字元字典序最小
  9. 弗洛伊德演算法和 Dijkstra演算法的區別?複雜度是多少?講講 Dijkstra演算法的具體過程
  10. 反轉字串,要求手寫程式碼,優化速度、優化空間
  11. 給出兩個無向圖,找出這2個無向圖中相同的環路。手寫程式碼
  12. 單例模式,手寫程式碼
  13. 生產者與消費者,手寫程式碼
  14. 二叉樹映象,手寫程式碼
  15. 最長不重複子串(最長重複子串),手寫程式碼

作業系統部分:

  1. 分別從作業系統的記憶體角度與程式執行緒角度解釋分析堆,棧二者的區別
  2. 什麼是事務?
  3. OSI七層模型有哪些,各層次的作用
  4. TCP的三次握手過程,四次揮手過程,為什麼需要三次?
  5. 說說作業系統中程式的通訊方式
  6. 瀏覽器輸入地址之後,之後的過程
  7. 談談 HTTP 中Get 和 Post 方法的區別?

相關文章