面試題總結-Android部分

LiuJian-Android發表於2019-03-12

1、Handler

1.1 簡單介紹:

Handler是用來結合執行緒的訊息佇列來傳送、處理“Message物件”和“Runnable物件”的工具。每一個Handler例項之後會關聯一個執行緒和該執行緒的訊息佇列。當你建立一個Handler的時候,從這時開始,它就會自動關聯到所在的執行緒/訊息佇列,然後它就會陸續把Message/Runnalbe分發到訊息佇列,並在它們出隊的時候處理掉。
Looper
每一個執行緒只有一個Looper,每個執行緒在初始化Looper之後,然後Looper會維護好該執行緒的訊息佇列,用來存放Handler傳送的Message,並處理訊息佇列出隊的Message。它的特點是它跟它的執行緒是繫結的,處理訊息也是在Looper所在的執行緒去處理,所以當我們在主執行緒建立Handler時,它就會跟主執行緒唯一的Looper繫結,從而我們使用Handler在子執行緒發訊息時,最終也是在主執行緒處理,達到了非同步的效果。
MessageQueue
MessageQueue是一個訊息佇列,用來存放Handler傳送的訊息。每個執行緒最多隻有一個MessageQueue。MessageQueue通常都是由Looper來管理,而主執行緒建立時,會建立一個預設的Looper物件,而Looper物件的建立,將自動建立一個MessageQueue。其他非主執行緒,不會自動建立Looper。
Message: 訊息物件,就是MessageQueue裡面存放的物件,一個MessageQueu可以包括多個Message。當我們需要傳送一個Message時,我們一般不建議使用new Message()的形式來建立,更推薦使用Message.obtain()來獲取Message例項,因為在Message類裡面定義了一個訊息池,當訊息池裡存在未使用的訊息時,便返回,如果沒有未使用的訊息,則通過new的方式建立返回,所以使用Message.obtain()的方式來獲取例項可以大大減少當有大量Message物件而產生的垃圾回收問題。

1.2 記憶體洩漏

原因
(1)非靜態內部類是會隱式持有外部類的引用,所以當其他執行緒持有了該Handler,執行緒沒有被銷燬,則意味著Activity會一直被Handler持有引用而無法導致回收。
(2)MessageQueue中如果存在未處理完的Message,Message的target也是對Activity等的持有引用,也會造成記憶體洩漏。
解決辦法
(1)使用靜態內部類+弱引用的方式
(2)在外部類物件被銷燬時,將MessageQueue中的訊息清空。例如,在Activity的onDestroy時將訊息清空

  • 在使用Handler時,通常是通過Handler.obtainMessage()來獲取Message物件的,而其內部呼叫的是Message.obtain()方法,那麼問題來了,為什麼不直接new一個Message,而是通過Message的靜態方法obtain()來得到的呢?
    使用obtain獲取Message物件是因為Message內部維護了一個資料快取池,回收的Message不會被立馬銷燬,而是放入了快取池, 在獲取Message時會先從快取池中去獲取,快取池為null才會去建立新的Message。
    ThreadLocal
    ThreadLocal 為解決多執行緒程式的併發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多執行緒程式。當使用ThreadLocal 維護變數時,ThreadLocal 為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。

面試題總結-Android部分

1.3 handler.postdelay方法中設定延時時間 這個時間準確麼?為什麼

當上一個訊息存在耗時任務的時候,會佔用延時任務執行的時機,此時是不準確的。由於Loop.loop裡面訊息是序列取出併發給handler.dispatchMessage的,那麼輪到處理第二個延時runnable的時候,MessageQueue類的next方法再執行到if(now < msg.when)的時候,就立刻return了該msg,然後由handler.dispatchMessage處理,執行到該runnable的run方法

1.4 主執行緒中什麼時候建立looper 為什麼主執行緒中looper的死迴圈不會阻塞主執行緒

//ActivityThread.main()
public static void main(String[] args) {
        .... 
        //建立Looper和MessageQueue物件,用於處理主執行緒的訊息
        Looper.prepareMainLooper(); 
        //建立ActivityThread物件
        ActivityThread thread = new ActivityThread();  
        //建立Binder通道 (建立新執行緒)
        thread.attach(false); 
        Looper.loop(); //訊息迴圈執行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
複製程式碼

ActivityThread的main方法主要就是做訊息迴圈,一旦退出訊息迴圈,那麼你的應用也就退出了。Activity的生命週期都是依靠主執行緒的Looper.loop,當收到不同Message時則採用相應措施。我們的程式碼其實就是在這個迴圈裡面去執行的,當然不會阻塞了。而且主執行緒Looper從訊息佇列讀取訊息,當讀完所有訊息時,主執行緒阻塞。子執行緒往訊息佇列傳送訊息,並且往管道檔案寫資料,主執行緒即被喚醒,從管道檔案讀取資料,主執行緒被喚醒只是為了讀取訊息,當訊息讀取完畢,再次睡眠。因此loop的迴圈並不會對CPU效能有過多的消耗。

1.5 handler記憶體洩漏持有的引用鏈

當使用內部類(包括匿名類)來建立Handler的時候,Handler物件會隱式地持有一個外部類物件(通常是一個Activity)的引用(不然你怎麼可能通過Handler來操作Activity中的View?)。而Handler通常會伴隨著一個耗時的後臺執行緒(例如從網路拉取圖片)一起出現,這個後臺執行緒在任務執行完畢(例如圖片下載完畢)之後,通過訊息機制通知Handler,然後Handler把圖片更新到介面。然而,如果使用者在網路請求過程中關閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時被回收掉,但由於這時執行緒尚未執行完,而該執行緒持有Handler的引用(不然它怎麼發訊息給Handler?),這個Handler又持有Activity的引用,就導致該Activity無法被回收(即記憶體洩露),直到網路請求結束(例如圖片下載完畢)。另外,如果你執行了Handler的postDelayed()方法,該方法會將你的Handler裝入一個Message,並把這條Message推到MessageQueue中,那麼在你設定的delay到達之前,會有一條MessageQueue -> Message -> Handler -> Activity的鏈,導致你的Activity被持有引用而無法被回收。處理方法:靜態內部類+弱引用,還有一個就是handler.removeCallbacksAndMessages(null);,就是移除所有的訊息和回撥,簡單一句話就是清空了訊息佇列。

2 優化

2.1 lru演算法原理

LRU(Least Recently Used)是近期最少使用的演算法,它的核心思想是當快取滿時,會優先淘汰那些近期最少使用的快取物件。LruCache中維護了一個集合LinkedHashMap,該LinkedHashMap是以訪問順序排序的。當呼叫put()方法時,就會在結合中新增元素,並呼叫trimToSize()判斷快取是否已滿,如果滿了就用LinkedHashMap的迭代器刪除隊尾元素,即近期最少訪問的元素。當呼叫get()方法訪問快取物件時,就會呼叫LinkedHashMap的get()方法獲得對應集合元素,同時會更新該元素到隊頭。

2.2 記憶體優化

  • 記憶體洩露

    • 單例(主要原因還是因為一般情況下單例都是全域性的,有時候會引用一些實際生命週期比較短的變數,導致其無法釋放) 靜態變數(同樣也是因為生命週期比較長)
    • Handler記憶體洩露
    • 非靜態內部類、匿名內部類(會引用外部類,導致無法釋放,比如各種回撥)
    • 資源使用完未關閉(BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap)
    • 集合中的物件未清理
  • 圖片Bitmap相關

    • 使用完畢後釋放圖片資源
      • bitmap recycle()
      • 採用軟引用(softreference)
    • 根據解析度適配&縮放圖片
      • 設定多套圖片資源
      • BitmapFactory.decodeResource()
      • BitmapFactory.inSampleSize
  • 記憶體抖動 大量臨時小物件頻繁建立會導致記憶體碎片,程式頻繁分配記憶體&垃圾收集器頻繁回收記憶體。導致卡頓甚至記憶體溢位。

  • 程式碼質量 & 數量
    程式碼本身的質量(如 資料結構、資料型別等) & 數量(程式碼量的大小)可能會導致大量的記憶體問題,如佔用記憶體大、記憶體利用率低等 日常不正確使用

面試題總結-Android部分

  • 記憶體洩漏原因: 沒有用的物件無法回收的現象就是記憶體洩露。當一個物件已經不需要再使用了,本該被回收時,而有另外一個正在使用的物件持有它的引用從而就導致,物件不能被回收。這種導致了本該被回收的物件不能被回收而停留在堆記憶體中,就產生了記憶體洩漏。

2.2 檢視優化

  • 避免複雜的View層級。佈局越複雜就越臃腫,就越容易出現效能問題,尋找最節省資源的方式去展示巢狀的內容;

  • 儘量避免在檢視層級的頂層使用相對佈局 RelativeLayout 。相對佈局 RelativeLayout 比較耗資源,因為一個相對佈局 RelativeLayout 需要兩次度量來確保自己處理了所有的佈局關係

  • 佈局層級一樣的情況建議使用線性佈局 LinearLayout 代替相對佈局 RelativeLayout,因為線性佈局 LinearLayout 效能要更高一些

  • 不要使用絕對佈局 AbsoluteLayout

  • 將可重複使用的元件抽取出來並用include標籤進行重用。如果應用多個地方的 UI用到某個佈局,就將其寫成一個佈區域性件,便於各個 UI 重用

  • 使用 merge 標籤減少佈局的巢狀層次
    幫助include標籤排除多餘的一層ViewGroup容器,減少view hierarchy的結構,提升UI渲染的效能

    • 根佈局是FrameLayout且不需要設定background或padding等屬性,可以用merge代替,因為Activity的ContentView父元素就是FrameLayout,所以可以用merge消除只剩一個.
    • 因為merge標籤並不是View,所以在通過LayoutInflate.inflate()方法渲染的時候,第二個引數必須指定一個父容器,且第三個引數必須為true,也就是必須為merge下的檢視指定一個父親節點.由於merge不是View所以對merge標籤設定的所有屬性都是無效的.
  • 使用 ViewStub 標籤來載入一些不常用的佈局
    ViewStub也可以用來載入佈局檔案,但與include標籤完全不同。ViewStub是一個不可見的View類,用於在執行時按需懶載入資源,只有在程式碼中呼叫了viewStub.inflate()或者viewStub.setVisible(View.visible)方法時才內容才變得可見。

    • ViewStub標籤不支援merge標籤。因此這有可能導致載入出來的佈局存在著多餘的巢狀結構,具體如何去取捨就要根據各自的實際情況來決定了。
    • ViewStub的inflate只能被呼叫一次,第二次呼叫會丟擲異常。
    • 雖然ViewStub是不佔用任何空間的,但是每個佈局都必須要指定layout_width和layout_height屬性,否則執行就會報錯。
  • 優化應用的啟動速度。當應用啟動一個應用時,介面的儘快反饋顯示可以給使用者一個良好的體驗。為了啟動更快,可以延遲載入一些 UI 以及避免在應用 Application 層級初始化程式碼。

  • 使用include標籤提高佈局重用性
    將一些通用的檢視提取到一個單獨的layout檔案中,然後使用標籤在需要使用的其他layout佈局檔案中載入進來,比如我們自己App導航欄等。這樣,便於對相同檢視內容進行統一的控制管理,提高佈局重用性。

    *標籤當中,可以重寫所有layout屬性的,如上面include中指定的layout屬性將會覆蓋掉titlebar中指定的layout屬性。 而非layout屬性則無法在標籤當中進行覆寫。另外需要注意的是,如果我們想要在標籤當中覆寫layout屬性, 必須要將layout_width和layout_height這兩個屬性也進行覆寫,否則覆寫效果將不會生效

    • 一個xml佈局檔案有多個include標籤需要設定ID,才能找到相應子View的控制元件,否則只能找到第一個include的layout佈局,以及該佈局的控制元件。

3 View

3.1 如何阻止父view攔截子view

  • 外部攔截法 重寫父容器的onInterceptTouchEvent方法

  • 內部攔截法

 //重寫這個方法,並且在方法裡面請求所有的父控制元件都不要攔截他的事件
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(ev);
    }
複製程式碼

3.2 surfaceView textureView區別

            SurfaceView        TexttureView
層級        單獨               普通view
能否動畫    否                 能                       
硬體加速                       必須
能否覆蓋    否
消耗記憶體    少                 多
使用版本                       4.0以後
複製程式碼

3.3 onTouch裡面處理了onClick還能接收到嗎

onTouchListener的onTouch方法優先順序比onTouchEvent高,會先觸發。假如onTouch方法返回false會接著觸發onTouchEvent,反之onTouchEvent方法不會被呼叫。只要view的CLICKABLE和LONG_CLICKABLE有一個為true,那麼他就會消耗這個事件,即onTouchEvent方法返回true,不管它是不是DISABLE狀態。然後就是當ACTION_UP事件發生時,會出發performClick方法,如果View設定了OnClickListener,那麼performClick方法內部會呼叫它的onClick方法。總結:onTouch—–>onTouchEvent—>onclick

3.4 卡頓問題分析

  • xml 寫的一個佈局是如何載入到Acitivty/Fragment中並最終 display

面試題總結-Android部分
CPU 會先把 Layout 中的 UI 元件計算成 polygons(多邊形)和 textures(紋理),然後經過 OpenGL ES 處理。OpenGL ES處理完後再交給 GPU 進行柵格化渲染,渲染後 GPU 再將資料傳送給螢幕,由螢幕進行繪製顯示。

  • 關於VSYNC
    Vertical Synchronization,就是所謂的“垂直同步”。我們也可以把它理解為“幀同步”。就是為了保證 CPU、GPU 生成幀的速度和 Display 重新整理的速度保持一致。在 VSYNC 開始發出訊號時,CPU和GPU已經就開始準備下一幀的資料了,趕在下個 VSYNC 訊號到來時,GPU 渲染完成,及時傳送資料給螢幕,Display 繪製顯示完成。

  • 雙緩衝機制
    雙緩衝技術一直貫穿整個 Android 系統。因為實際上幀的資料就是儲存在兩個 Buffer 緩衝區中,A 緩衝用來顯示當前幀,那麼 B 緩衝就用來快取下一幀的資料,同理,B顯示時,A就用來緩衝!這樣就可以做到一邊顯示一邊處理下一幀的資料。

  • 丟幀
    由於某些原因,比如我們應用程式碼上邏輯處理過於負責或者過於複雜的佈局,過度繪製(Overdraw),UI執行緒的複雜運算,頻繁的GC等,導致下一幀繪製的時間超過了16ms,使用者很明顯感知到了卡頓的出現,也就是所謂的丟幀情況。

    • 1、當 Display 顯示第 0 幀資料時,此時 CPU 和 GPU 已經開始渲染第 1 幀畫面,並將資料快取在緩衝 B 中。但是由於某些原因,就好像上面說的,導致系統處理該幀資料耗時過長或者未能及時處理該幀資料。

    • 2、當 VSYNC 訊號來時,Display 向 B 緩衝要資料,因為緩衝 B 的資料還沒準備好,B緩衝區這時候是被鎖定的,Display 表示你沒準備好,只能繼續顯示之前緩衝 A 的那一幀,此時緩衝 A 的資料也不能被清空和交換資料。這種情況就是所謂的“丟幀”,也被稱作“廢幀”;當第 1 幀資料(即緩衝 B 資料)準備完成後,它並不會馬上被顯示,而是要等待下一個 VSYNC,Display 重新整理後,這時使用者才看到畫面的更新。

    • 3、當某一處丟幀後,大概率會影響後面的繪製也出現丟幀,最後給使用者感覺就是卡頓了。最嚴重的直接造成ANR。

  • 卡頓處理

    • 過於複雜的佈局
    • 過度繪製( Overdraw )
    • UI 執行緒的複雜運算
    • 頻繁的 GC

3.5判斷點選還是移動

在up事件裡相應判斷

3.6 Android Y軸旋轉動畫


public class RotateYAnimation extends Animation {
    int centerX, centerY;
    Camera camera = new Camera();
 
    /**
     * 獲取座標,定義動畫時間
     * @param width
     * @param height
     * @param parentWidth
     * @param parentHeight
     */
    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        //獲得中心點座標
        centerX = width / 2;
        centerY = width / 2;
        //動畫執行時間 自行定義
        setInterpolator(new OvershootInterpolator());
    }
 
    /**
     * 旋轉的角度設定
     * @param interpolatedTime
     * @param t
     */
 
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final Matrix matrix = t.getMatrix();
        camera.save();
        //設定camera的位置
        camera.setLocation(0,0,180);
        camera.rotateY(180 * interpolatedTime);
        //把我們的攝像頭加在變換矩陣上
        camera.getMatrix(matrix);
        //設定翻轉中心點
        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX,centerY);
        camera.restore();
    }
 
}
複製程式碼

3.7 列表優化

  • 資料處理
    很多時候從我們要對伺服器上獲取下來的列表資料進行一次二次加工,以便轉化為我們介面上要顯示的資料,這些操作可能會比較耗時。比如字串拼接、時間格式化等操作都是比較耗時的操作。比較好的實踐是將列表資料的加工在notifyDataSetChanged()之前在後臺執行緒做好,在Adapter中只做資料的繫結。

  • 介面優化 優化佈局層級減少過渡繪製、優化佈局方式提高佈局效率。

  • 避免非必要的重新整理
    如列表控制元件,在滑動時不要去載入圖片,可以在滑動監聽裡停止圖片的載入。

3.8 安卓中invalidate和requestLayout的實現和區別

面試題總結-Android部分

3.9 android自定義view重寫及呼叫的一些方法

  • onMeasure
    測量本質就是測量本身有多大,也就是給mMeasuredWidth和mMeasuredHeight這兩個屬性賦值,也就是呼叫setMeasuredDimension這個方法。另外父view測量子view的時候呼叫的measure方法,還有一些衍生方法如measureChildWithMargins。

  • onLayout
    作用是子view應該怎樣放置,也就是設定子view的mLeft、mTop、mRight、mBottom屬性。該方法在View中是空實現,很顯然主要用於ViewGroup。父view放置子view的時候呼叫layout方法。

  • onDraw
    具體長什麼樣。

4 四大元件(除Activity)

4.1 Broadcast

  • 廣播型別
    • Normal Broadcast:普通廣播
    • System Broadcast: 系統廣播
    • Ordered broadcast:有序廣播
    • Sticky Broadcast:粘性廣播
    • Local Broadcast:App應用內廣播

4.2 contentprovider

  • contentprovider是執行在哪個程式裡面的
    contentprovider的oncreate方法,執行在ui執行緒。但是其他的方法,執行在非ui執行緒,例如call、query、delete、insert、upate等方法

  • 別的主執行緒呼叫它的時候會被阻塞嗎?
    別的主執行緒調contentprovider裡面方法的時候,雖然他的call、query、delete、insert、upate等方法執行在非ui執行緒,但是其他呼叫方法是會被阻塞的。比如你在activity的oncreate方法中呼叫contentprovider的query等方法,oncreate方法會被阻塞

  • 如果不同的其他應用,同時呼叫了這個contentprovider的同一個方法,它們會相互阻塞嗎?比如有三個應用同時都在呼叫這個provider的插入方法,它們會相互阻塞還是併發執行
    不管Provider訪問者是同一個程式的不同執行緒,還是不同的程式,Provider方實際上是同一個Provider物件例項,當併發訪問CP的同一個方法的時候,這個方法執行在不同的執行緒中,不會相互影響

4.3 Service

  • 兩種服務啟動區別

    • 啟動狀態,主要用於執行後臺計算。startservice啟動服務後,程式退出,服務依舊存在
    • 繫結狀態,主要用於其他元件和service互動。bindservice啟動服務後程式退出unbindservice,服務就會銷燬
  • 同時用兩種方式啟動同一個service 會產生幾個service例項?
    同時呼叫兩種方法啟動同一個方法,只會啟動一個服務,但是其生命週期有所不同,取決於兩種方法啟動服務的先後順序。

  • 繫結的service 頁面關閉後未解除繫結 如何銷燬
    bindService時LoadApk將ServiceConnection用map儲存了起來,當Activity被destroy時會執行removeContextRegistrations來清除 該context的相關注冊。所以Activity退出時服務也被解綁。

  • Intentservice原理
    介紹:
    IntentService是繼承於Service並處理非同步請求的一個類,在IntentService內有一個工作執行緒來處理耗時操作,啟動IntentService的方式和啟動傳統Service一樣,同時,當任務執行完後,IntentService會自動停止,而不需要我們去手動控制。另外,可以啟動IntentService多次,而每一個耗時操作會以工作佇列的方式在IntentService的onHandleIntent回撥方法中執行,並且,每次只會執行一個工作執行緒,執行完第一個再執行第二個,以此類推。
    原理:
    IntentService第一次啟動時會呼叫onCreate方法,建立一個HandlerThread物件,並開啟HandlerThread執行緒,然後使用HandlerThread的Looper物件初始化mServiceHandler物件,通過mServiceHandler傳送訊息在HandlerThread中執行操作。每次啟動IntentService都會呼叫onStartCommand()方法,onStartCommand方法會呼叫onStart方法。onStart方法中只是呼叫mServiceHandler傳送了一個訊息,交給HandlerThread處理,HandlerThread在handleMsg方法裡呼叫handleIntent處理訊息,完成後呼叫stop方法停止任務。

5 框架原理

  • OKHttp:使用了責任鏈模式,通過一層一層的攔截器最終將請求處理完全返回響應回撥
  • Retrofit:動態代理
  • Dagger:註解反射
  • RxJava:響應式程式設計+觀察者模式

5.1 okhttp攔截器

面試題總結-Android部分

5.2 okhttp執行流程

面試題總結-Android部分

5.3 Retrofit的本質流程

面試題總結-Android部分

6 多執行緒問題

6.1 IPC 都有哪些方式

* Bundle     
四大元件中的三大元件(Activity,BroadcaseReceiver,Service)都是支援在Intent中傳遞Bundle資料的,由於Bundle實現了Parcelable介面,所以可以很方便的在不同程式間傳輸。
* 使用檔案共享     
享檔案也是一種不錯的程式間通訊方式,兩個程式通過讀/寫同一個檔案來交換資料。但有一點要注意:android系統是基於Linux的,使得其併發讀/寫檔案可以沒限制地進行,甚至兩執行緒同時對同一檔案進行寫操作都是允許的,儘管這可能出問題。So,重點:檔案共享方式適合在對資料同步要求不高的程式間進行通訊,並且要妥善處理併發讀/寫問題。
* Messenger     
Messenger譯為信使,顧名思義,主要作用就是傳遞訊息。通過它可在不同程式中傳遞Message物件,在Message中放入要傳遞的資料,即可輕鬆地實現資料的程式間傳遞了。Messenger是一種輕量級的IPC方案,底層實現是AIDL。
* AIDL     
上面說到Messenger,其是以序列的方式處理客戶端發來的訊息,如果有大量的併發請求,那麼使用Messenger就不太合適了。同時Messenger主要作用就是傳遞訊息,很多時候我們可能需要跨程式呼叫服務端的方法,這種情形Messenger就無法做到了,但是我們可以使用AIDL來實現跨程式的方法呼叫。
* ContentProvider     
ContentProvider是Android中提供的專門用於不同應用間進行資料共享的方式,從這一點,它天生就適合程式間通訊。和Messenger一樣,contentProvider的底層實現同樣是Binder,由此可見,Binder在Android中是何等的重要。雖然ContentProvider底層是用Binder,但它的使用過程要比AIDL 簡單許多,因為系統已經做了封裝。系統預置了許多ContentProvider,比如通訊錄資訊,日程變資訊等,要跨程式訪問這些資訊,只需要通過ContentResolver的query、update、insert和delete方法即可。
* Socket     
Socket 也稱為“套接字”。是網路通訊中的概念,它分為流式套接字和使用者資料包套接字兩種,分別對應於網路傳輸控制層中的TCP和UDP協議。TCP是面向連線的協議,提供穩定的雙向通訊功能,TCP連線的建立需要經過“三次握手”才能完成,為了提供穩定的資料傳輸功能,其本身提供了超時重傳機制,因此具有很高的穩定性。而UDP是無連線的,提供不穩定的單向通訊功能,當然UDP也能實現雙向通訊功能。在效能上,UDP 具有更高的效率,缺點是不保證資料一定能夠正確傳輸,尤其是在網路阻塞的情況下。
複製程式碼

6.2 aild 具體實現過程

* 服務端    
服務端首先要建立一個Service用來監聽客戶端的連線請求,然後建立一個AIDL檔案,將暴露給客戶端的介面在這個AIDL檔案中宣告,最後在Service中實現這個介面即可。&emsp;&emsp;&emsp;&emsp;
* 客戶端    
客戶端所要做的事情就稍微簡單一些,首先需要繫結服務Service,繫結成功後,將服務端的Binder物件轉成AIDL藉口所屬的型別,接著就可以呼叫AIDL中的方法了。
* AIDL介面建立     
建立一個字尾為AIDL的檔案,裡面宣告介面和方法。
* 遠端服務端Service的實現    
建立一個Binder物件並在onBind中返回它,這個物件繼承自.Stub並實現了它內部的AIDL方法
* 客戶端的實現    
首先繫結遠端服務,繫結成功後將服務端返回的Binder物件轉換成AIDL介面,然後就可以通過這個介面去呼叫服務端的遠端方法
複製程式碼

6.3 Android 為什麼用Binder實現ipc機制

Android系統是基於Linux系統的,理論上應該使用Linux內建的IPC方式。Linux中的IPC方式有管道、訊號量、共享記憶體、訊息佇列、Socket,Android使用的Binder機制不屬於Linux。Android不繼承Linux中原有的IPC方式,而選擇使用Binder,說明Binder具有一定的優勢。 Android系統為了嚮應用開發者提供豐富的功能,廣泛的使用了Client-Server通訊方式,如媒體播放、各種感測器等,Client-Server的通訊方式是Android IPC的核心,應用程式只需要作為Client端,與這些Server建立連線,即可使用這些功能服務。

  下面通過一些功能點,一一排除Linux系統中五種IPC方式,解釋為什麼Android選擇使用Binder:

  • 從通訊方式上說,我們希望得到的是一種Client-Server的通訊方式,但在Linux的五種IPC機制中,只有Socket支援這種通訊方式。雖然我們可以通過在另外四種方式的基礎上架設一些協議來實現Client-Server通訊,但這樣增加了系統的複雜性,在手機這種條件複雜、資源稀缺的環境下,也難以保證可靠性;

  • 從傳輸效能上說,Socket作為一款通用介面,其傳輸效率低,開銷大,主要用在跨網路的程式間通訊和本機上程式間的低速通訊;訊息佇列和管道採用儲存-轉發方式,即資料先從傳送方拷貝到記憶體開闢的快取區中,然後再從核心快取區拷貝到接收方快取區,至少有兩次拷貝過程;共享記憶體雖然無需拷貝,但控制複雜,難以使用;而Binder只需要拷貝一次;

  • 從安全性上說,Android作為一個開放式的平臺,應用程式的來源廣泛,因此確保只能終端的安全是非常重要的。Linux傳統的IPC沒有任何安全措施,完全依賴上層協議來確保,具體有以下兩點表現:

    • 第一,傳統IPC的接收方無法獲得對方可靠的UID/PID(使用者ID/程式ID),從而無法鑑別對方身份,使用傳統IPC時只能由使用者在資料包裡填入UID/PID,但這樣不可靠,容易被惡意程式利用;
    • 第二,傳統IPC的訪問接入點是開放的,無法建立私有通訊,只要知道這些接入點的程式都可以和對端建立連線,這樣無法阻止惡意程式通過猜測接收方的地址獲得連線。   基於以上原因,Android需要建立一套新的IPC機制來滿足系統對通訊方式、傳輸效能和安全性的要求,這就是Binder。

  綜上,Binder是一種基於Client-Server通訊模式的通訊方式,傳輸過程只需要一次拷貝,可以為傳送方新增UID/PID身份,支援實名Binder和匿名Binder,安全性高。

  

6.4 子執行緒回撥主執行緒的方式

  • view.post(Runnable action)
  • activity.runOnUiThread(Runnable action)
  • Handler

7 Activity和Fragment

7.1 生命週期

  • Singletask 生命週期 launchMode為singleTask的時候,通過Intent啟到一個Activity,如果系統已經存在一個例項,系統就會將請求傳送到這個例項上,但這個時候,系統就不會再呼叫通常情況下我們處理請求資料的onCreate方法,而是呼叫onNewIntent方法。
    onNewIntent->onRestart->onStart->onResume

  • Android onPause和onStop的比較

    • onPause():Activity失去焦點,但仍然可見。
    • onStop():Activity在後臺,不可見(完全被另一個Activity擋住,或者程式後臺執行)。

7.2 FragmentManager

  • Fragmentmanager和supportfm 區別

    • 3.0以下:getSupportFragmentManager()

    • 3.0以上:getFragmentManager()

  • 巢狀fragment獲取manager getChildFragmentManager()是fragment中的方法, 返回的是管理當前fragment內部子fragments的manage

7.3 Fragment生命週期

面試題總結-Android部分

7.4 Fragment回退鍵監聽

  • 在Fragment中onResume監聽返回鍵事件
@Override
public void onResume() {
    super.onResume();
    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View view, int i, KeyEvent keyEvent) {
            if(keyEvent.getAction() == KeyEvent.ACTION_DOWN && i == KeyEvent.KEYCODE_BACK){
                Toast.makeText(getActivity(), "按了返回鍵", Toast.LENGTH_SHORT).show();
                return true;
            }
            return false;
        }
    });
}
複製程式碼
  • 優雅的方法 *
// ①先定義介面BackHandleInterface
public interface BackHandleInterface {

    void onSelectedFragment(BackHandleFragment backHandleFragment);

}
複製程式碼
// ②定義公用的Fragment
public abstract class BackHandleFragment extends Fragment {

    private BackHandleInterface backHandleInterface;

    /**
     * 所有繼承BackHandledFragment的子類都將在這個方法中實現物理Back鍵按下後的邏輯
     * FragmentActivity捕捉到物理返回鍵點選事件後會首先詢問Fragment是否消費該事件
     * 如果沒有Fragment訊息時FragmentActivity自己才會消費該事件
     */
    public abstract boolean onBackPressed();

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(getActivity() instanceof BackHandleInterface){
            this.backHandleInterface = (BackHandleInterface)getActivity();
        }else{
            throw new ClassCastException("Hosting Activity must implement BackHandledInterface");
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        backHandleInterface.onSelectedFragment(this);
    }
}
複製程式碼
//需要實現監聽的Fragment的Activity實現介面
//主要的是onSelectedFragment()和onBackPressed()其他方法可以忽略
public class EdittextActivity extends AppCompatActivity implements BackHandleInterface {

    private BackHandleFragment backHandleFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_layout);
        addFragment(R.id.fragmentContainer, new EdittextFragment());
    }

    public void addFragment(int containerViewId, Fragment fragment){
        final FragmentTransaction transaction = this.getSupportFragmentManager().beginTransaction();
        transaction.add(containerViewId, fragment);
        transaction.commit();
    }

    @Override
    public void onSelectedFragment(BackHandleFragment backHandleFragment) {
        this.backHandleFragment = backHandleFragment;
    }

    @Override
    public void onBackPressed() {
        //if判斷裡面就呼叫了來自Fragment的onBackPressed()
        //一樣!!,如果onBackPressed是返回false,就會進入條件內進行預設的操作
        if(backHandleFragment == null || !backHandleFragment.onBackPressed()){
            if(getSupportFragmentManager().getBackStackEntryCount() == 0){
                super.onBackPressed();
            }else{
                getSupportFragmentManager().popBackStack();
            }
        }
    }
}
複製程式碼
//需要監聽的Fragment
public class EdittextFragment extends BackHandleFragment {

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_edittext, container, false);
        return view;
    }

    @Override
    public boolean onBackPressed() {
        Toast.makeText(getActivity(), "按了返回鍵", Toast.LENGTH_SHORT).show();
        return true;//因為這裡return true 所以不會返回上一個頁面,方便我截圖
    }
}
複製程式碼

7.5 全域性彈窗怎麼獲取當前activity引用

  • 利用系統對話方塊AlertDialog的簡單實現    

alter為AlertDialog型別物件,注意要在alter.show()語句前加入

alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
複製程式碼

然後在AndroidManifest.xml中加入許可權:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"></uses-permission> 
複製程式碼
  • Application.ActivityLifecycleCallbacks

面試題總結-Android部分

8 其他

8.1 MVP框架模型

  • 優點:

    • 1、降低耦合度,隱藏資料邏輯,減輕Activity或是Fragment的壓力,讓其更多的只關注處理生命週期任務,使得程式碼更加簡潔明
    • 2、模組職責劃分明顯,檢視邏輯和業務邏輯分別抽象到V層和P層的介面中去,提高程式碼可閱讀性,複用度較高,靈活性高
    • 3、方便測試驅動開發
    • 4、減少Activity的記憶體洩露問題
  • 缺點:
    最明顯的建立一個Activity需要配合建立多個介面類和實現類,每個操作都需要通過介面回撥的方式進行,雖然邏輯清晰程式碼,同時也造成了類的增多和程式碼量的加大。

8.2 Sp.commit.apply區別

  • apply沒有返回值而commit返回boolean表明修改是否提交成功
  • apply是將修改資料原子提交到記憶體, 而後非同步真正提交到硬體磁碟, 而commit是同步的提交到硬體磁碟,因此,在多個併發的提交commit的時候,他們會等待正在處理的commit儲存到磁碟後在操作,從而降低了效率。而apply只是原子的提交到內容,後面有呼叫apply的函式的將會直接覆蓋前面的記憶體資料,這樣從一定程度上提高了很多效率。
  • apply方法不會提示任何失敗的提示。 由於在一個程式中,sharedPreference是單例項,一般不會出現併發衝突,如果對提交的結果不關心的話,建議使用apply,當然需要確保提交成功且有後續操作的話,還是需要用commit的。

8.3 sp的程式安全與執行緒安全

官方文件明確指出,SharedPreferences不支援多執行緒,程式也是不安全的 如果想要實現執行緒安全需重新實現其介面,如下:

private static final class SharedPreferencesImpl implements SharedPreferences {
...
    public String getString(String key, String defValue) {
        synchronized (this) {
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
   }
...
    public final class EditorImpl implements Editor {
        public Editor putString(String key, String value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }
    ...
    }
}
複製程式碼

8.4 dexclassloader和pathclassloader 雙親委託模型

PathClassLoader和DexClassLoader都是繼承與BaseDexClassLoader,BaseDexClassLoader繼承與ClassLoader。DexClassLoader:能夠載入自定義的jar/apk/dex PathClassLoader:只能載入系統中已經安裝過的apk 所以Android系統預設的類載入器為PathClassLoader,而DexClassLoader可以像JVM的ClassLoader一樣提供動態載入。

相關文章