Android面試題


Java基礎


1.HashMap實現原理,若是hashCode衝突怎麼辦,爲何線程不安全,與Hashtable有什麼區別

  • 主要經過計算數據的hashCode來插入
  • hashCode相同的元素插入同一個鏈表,才用數組+鏈表方式存儲
  • 可能會有多個線程同時put數據,若同時push了hashCode相同數據,後面的數據可能會將上一條數據覆蓋掉 Hashtable幾乎在每一個方法上都加上synchronized(同步鎖),實現線程安全

2.synchronized 修飾實例方法和修飾靜態方法有什麼不同

public synchronized void run() {
    System.out.println(1);
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
    }
    System.out.println(2);
}
複製代碼
  • synchronized修飾普通方法時鎖對象是this對象,而使用兩個對象去訪問,不是同一把鎖
Demo demo = new Demo();
new Thread(() -> demo.run()).start();
Demo demo2 = new Demo();
new Thread(() -> demo2.run()).start();
複製代碼

結果爲: 1 1 2 2java

不一樣步。但若是使用同一對象訪問,結果纔是同步的node

Demo demo = new Demo();
new Thread(() -> demo.run()).start();
new Thread(() -> demo.run()).start();
複製代碼

輸出結果:1 2 1 2android

  • 當synchronized修飾靜態方法時,鎖對象爲當前類的字節碼文件對象。使用不一樣的對象訪問,結果是同步的,由於當修飾靜態方法時,鎖對象是class字節碼文件對象,而兩個對象是同一個class文件,因此使用的是一個鎖

3.final 、finally、finalize 區別

  1. final關鍵字用於基本數據類型前:這時代表該關鍵字修飾的變量是一個常量,在定義後該變量的值就不能被修改。
    final關鍵字用於方法聲明前:這時意味着該方法時最終方法,只能被調用,不能被覆蓋,可是能夠被重載。
    final關鍵字用於類名前:此時該類被稱爲最終類,該類不能被其餘類繼承。算法

  2. 用try{ }catch(){} 捕獲異常時,不管室友有異常,finally代碼塊中代碼都會執行。sql

  3. finalize方法來自於java.lang.Object,用於回收資源。 能夠爲任何一個類添加finalize方法。finalize方法將在垃圾回收器清除對象以前調用數據庫

4. Java中成靜態內部類和非靜態內部類特色

  • 靜態內部類:和外部類沒有什麼"強依賴"上的關係,耦合程度不高,能夠單首創建實例。因爲靜態內部類與外部類並不會保存相互之間的引用,所以在必定程度上,還會節省那麼一點內存資源
  • 內部類中須要訪問有關外部類的全部屬性及方法

5.強引用、弱引用、軟引用和虛引用

  • 強引用:當內存空間不足時,Java虛擬機寧願拋出OutOfMemoryError錯誤也不會回收,直接new出來的就是強引用
  • 軟引用:內存空間充足時,垃圾回收器不會回收它;若是內存空間不足了,就會回收這些對象的內存。
    當內存不足時,JVM首先將軟引用中的對象引用置爲null,而後通知垃圾回收器進行回收
if(JVM內存不足) {
        // 將軟引用中的對象引用置爲null
        str = null;
        // 通知垃圾回收器進行回收
        System.gc();
   }
複製代碼
  • 弱引用:在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象
  • 虛引用:虛引用顧名思義,就是形同虛設。與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收

6.原子變量Atomic

還在用Synchronized?Atomic你瞭解不?apache

7.多線程併發問題

多線程併發問題編程

8.主線程是否能夠直接捕獲子線程的異常?

try{
    new Thread(){
        public void run(){
            if (...) throw new RuntimeException(); 
        }
    }.start();
}catch(Exception e){
}
複製代碼

答案不能。設計模式

  • 線程代碼不能拋出任何checked異常。全部的線程中的checked異常都只能被線程自己消化掉
  • 子線程代碼拋出運行級別異常以後,線程會中斷。主線程不受這個影響,不會處理這個RuntimeException,並且根本不能catch到這個異常。會繼續執行本身的代碼

9.Java內存模型

因此線程方法的異常只能本身來處理,線程的問題應該線程本身自己來解決,而不要委託到外部數組


Android基礎知識


1.Looper總結

  • Looper經過prepare方法進行實例化,先從他的成員變量sThreadLocal中拿取,沒有的話就new 一個Looper,而後放到sThreadLocal中緩存。每一個線程只能建立一個Looper實例
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
複製代碼
  • Looper經過loop方法開啓循環隊列,裏面開啓了死循環,沒有msg時候會阻塞
  • 在ActivityThread的main方法中也就是Activity啓動的時候,已經調用了Looper.prepareMainLopper()方法

2.ThreadLocal在Looper中的使用

爲了解決多個線程訪問同一個數據問題,同步鎖的思路是線程不能同時訪問一片內存區域.而ThreadLocal的思路是,乾脆給每一個線程Copy一份一摸同樣的對象,線程之間各自玩本身的,互相不影響對方 常見ThreadLocal應用場景:確保在每個線程中只有一個Looper的實例對象

  • ThreadLocal的set方法
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}
複製代碼
  • ThreadLocal的get方法
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
 }
複製代碼

簡而言之:先拿到當前線程,再從當前線程中拿到ThreadLocalMap,經過ThreadLocalMap來存儲數據。(ThreadLocalMap是Thread的成員變量)

3.Service 和 IntentService

Activity對事件響應不超過5秒,BroadcastReceiver執行不超過10秒,Service耗時操做爲20秒。不然系統會報ANR

  • 使用startService()方法啓用服務後,調用者與服務之間沒有關連。調用者直接退出而沒有調用stopService的話,Service會一直在後臺運行
  • 使用bindService()方法啓用服務,調用者與服務綁定在一塊兒了,調用者一旦退出,服務也就自動終止
  • IntentService是Service的子類,會建立子線程來處理全部的Intent請求,其onHandleIntent()方法實現的代碼,無需處理多線程問題

4.FragmentPageAdapter和FragmentPageStateAdapter的區別

  • FragmentPageAdapter在每次切換頁面的的時候,沒有徹底銷燬Fragment,適用於固定的,少許的Fragment狀況。默認notifyDataSetChanged()刷新是無效的

  • FragmentPageStateAdapter在每次切換頁面的時候,是將Fragment進行回收,適合頁面較多的Fragment使用,這樣就不會消耗更多的內存

5.Sqlite數據庫,什麼是事務

事務是由一個或多個sql語句組成的一個總體,若是全部語句執行成功那麼修改將會所有生效,若是一條sql語句將銷量+1,下一條再+1,假若第二條失敗,那麼銷量將撤銷第一條sql語句的+1操做,只有在該事務中全部的語句都執行成功纔會將修改加入數據庫中
sqlite數據庫相關操做,主要包括建立和增刪改查,事務

6.怎麼作Sqlite數據庫升級

  1. 直接刪除老數據庫,但會形成數據丟失,通常不採用
  2. 對數據庫進行升級,參考SQLite數據庫版本升級

7.invalidate與requestLayout區別

  • view調用invalidate將致使當前view的重繪,viewGroup調用invalidate會使viewGroup的子view調用draw
  • requestLayout方法只會致使當前view的measure和layout,而draw不必定被執行。只有當view的位置發生改變纔會執行draw方法

8.View和ViewGroup區別

  • ViewGrouponInterceptTouchEvent默認返回false,即不攔截事件,View沒有攔截事件方法,View默認時消耗事件的
  • ViewGroup默認不會調用onDraw方法,View默認會調用onDraw方法。能夠經過setWillNotDraw(boolean willNotDraw)來指定是否調用onDraw方法
/** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */
    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }
複製代碼

9.android版本新特性

  • 5.0
    • 引入Material Design主題
  • 6.0
    • 運行時權限
  • 7.0
    • 文件讀寫權限適配FileProvider
    • 移除了對 Apache HTTP 客戶端的支持,建議使用 HttpURLConnection 代替。繼續使用 Apache HTTP API,必須先在 build.gradle 文件中配置:
    android {
            useLibrary 'org.apache.http.legacy'
        }
    複製代碼
  • 8.0
    • 廣播限制。不能在AndroidManifest文件對有些廣播進行靜態註冊
  • 9.0

Android框架知識


1.buttnife實現原理

經過註解處理器動態生成java文件,在java文件中進行findViewById和setOnClickListener操做

2. EventBus實現原理

經過觀察者設計模式,先經過註冊的方式將指定的類加到一個表裏面,等發送消息時輪訓那個表,依據註解和註解的value找到匹配的方法,而後執行該方法

3.LiveData原理

LiveData通知其餘組件原理主要是觀察者設計模式。其優勢有

  • 聽從應用程序的生命週期,如在Activity中若是數據更新了但Activity已是destroy狀態,LivaeData就不會通知Activity(observer)
  • 不會形成內存泄漏

4.Lifecycle

簡單來講,就是可讓你本身的類擁有像 activity 或 fragment 同樣生命週期的功能。繼承Lifecycle 的組件將生命週期脫離出 activity 而轉到本身身上
使用步驟:
(1) 繼承DefaultLifecycleObserver

class TestObserver implements DefaultLifecycleObserver {
     @Override
     public void onCreate(LifecycleOwner owner) {
         // your code
     }
 }
複製代碼

(2) LifecycleOwner是隻有一個方法getLifecycle()的接口,是讓擁有生命週期的東西實現好比(activity)用來獲取Lifecycle。在Android Support Library 26.1.0 及其以後已經activity 和 fragment 已經默認實現了LifecycleOwner 因此在 activity 裏咱們能夠直接:

getLifecycle().addObserver(new MyObserver());
複製代碼

這樣咱們的MyObserver就會感知 activity 的生命週期了

5.FlowableObservable

RxJava1中使用ObservableObserver創建起訂閱關係,但會產生背壓問題。Rxjava2使用FlowableSubscriber替換RaJava1的ObservableObserverFlowable是在Observable的基礎上優化後的產物,Observable能解決的問題Flowable也都能解決。可是並不表明Flowable能夠徹底取代Observable,Flowable運行效率要比Observable慢得多。 只有在須要處理背壓問題時,才須要使用Flowable

  • 當上下游在不一樣的線程中,經過Observable發射,處理,響應數據流時,若是上游發射數據的速度快於下游接收處理數據的速度,這樣對於那些沒來得及處理的數據就會形成積壓,這些數據既不會丟失,也不會被垃圾回收機制回收,而是存放在一個異步緩存池中,若是緩存池中的數據一直得不處處理,越積越多,最後就會形成內存溢出,這即是響應式編程中的背壓(backpressure)問題
  • 若是可以肯定:
    1.上下游運行在同一個線程中
    2.上下游工做在不一樣的線程中,可是下游處理數據的速度不慢於上游發射數據的速度
    3.上下游工做在不一樣的線程中,可是數據流中只有一條數, 則不會產生背壓問題,就沒有必要使用Flowable,以避免影響性能。

6.app優化

  • 內存優化:使用leakcanary抓取內存泄露,或者使用android studio抓取內存信息,經過Profiler分析內存泄露狀況
  • 體積優化
    • 不復雜圖片使用svg代替png。換膚時使用着色器,可減小圖片資源
    • build文件配置
      • 保留指定語言
      • 保留指定so庫架構
      • 開啓混淆壓縮

7.Rxjava中關於Disposable

Rxjava容易遭層內存泄漏。在訂閱了事件後沒有及時取閱,致使在activity或者fragment銷燬後仍然佔用着內存,沒法釋放。而disposable,能夠用來取消訂閱
參考Rxjava關於Disposable你應該知道的事


設計模式


1.裝飾設計模式

  • 當不適合採用生成子類的方式對已有類進行擴充時,能夠採用裝飾設計模式
  • 不適合採用生成子類的方式對已有類進行擴充緣由:會使類更加臃腫。子類會繼承父類全部非private的變量和方法,而後再進行擴充。而使用裝飾設計模式擴充的類,只須要增長擴種那部分功能便可
  • 使用場景:RecyclerView自己是不支持添加底部和頭部的,那麼採用裝飾設計模式能夠對其進行功能擴展。裝飾設計模式 RecyclerView添加頭部和底部

2.MVC、MCP、MVVP 的區別

1.MVC Android傳統就是用MVC模式,Modle(邏輯)和V(View)直接交互,耦合度過高,MVC中是容許Model和View進行交互的,而MVP中很明顯,Model與View之間的交互由Presenter完成。還有一點就是Presenter與View之間的交互是經過接口的

2.MVP 當View 須要更新數據時,首先去找 Presenter,而後 Presenter 去找 Model 請求數據,Model 獲取到數據以後通知 Presenter,Presenter 再通知 View 更新數據,這樣 Model 和 View就不會直接交互了,全部的交互都由 Presenter 進行,Presenter 充當了橋樑的角色。很顯然,Presenter 必須同時持有 View 和 Model 的對象的引用,才能在它們之間進行通訊

存在問題:

  • 內存泄露:因爲Presenter常常性的須要執行一些耗時操做那麼當咱們在操做未完成時候關閉了Activity,會致使Presenter一直持有Activity的對象,形成內存泄漏
  • 隨着業務邏輯的增長,UI的改變多的狀況下,這樣就會形成View的接口會很龐大。而MVVM就解決了這個問題

解決辦法: 在Presenter中使用弱引用,將view的引用加到弱引用中去 每一個Activity都有BaseActivity,BaseActivity中

3.MVVM經過雙向綁定的機制

  • 問題: 看起來MVVM很好的解決了MVC和MVP的不足,可是因爲數據和視圖的雙向綁定,致使出現問題時不太好定位來源,有可能數據問題致使,也有可能業務邏輯中對視圖屬性的修改致使

算法


1.反轉單鏈表

Node node4 = new Node(4, null);
Node node3 = new Node(3, node4);
Node node2 = new Node(2, node3);
Node node1 = new Node(1, node2);
Node pHead = node1;//頭結點
複製代碼

這組鏈表從1到4排序,要求反轉後4到1

public static Node reverseList(Node pHead) {
        Node pReversedHead = null; //反轉事後的單鏈表存儲頭結點
        Node pNode = pHead; //當前節點
        Node pPrev = null; //前一結點
        while (pNode != null) {
            //1.記錄next,下一步:更新當前節點的上一節點和自己。最後移動一位
            Node pNext = pNode.next;
            if (pNext == null) {
                //到了尾節點
                pReversedHead = pNode;
            }
            pNode.next = pPrev;
            pPrev = pNode;
            pNode = pNext;
        }

        return pReversedHead;
}
複製代碼

輸出

pHead = reverseList(pHead);//反轉以後頭結點
while (pHead != null) {
    System.out.println(pHead.key);
    pHead = pHead.next;
}
複製代碼
相關文章
相關標籤/搜索