面試題總結-Android部分

一、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對象而產生的垃圾回收問題。android

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 爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。

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()方法得到對應集合元素,同時會更新該元素到隊頭。bash

2.2 內存優化

  • 內存泄露服務器

    • 單例(主要緣由仍是由於通常狀況下單例都是全局的,有時候會引用一些實際生命週期比較短的變量,致使其沒法釋放) 靜態變量(一樣也是由於生命週期比較長)
    • Handler內存泄露
    • 非靜態內部類、匿名內部類(會引用外部類,致使沒法釋放,好比各類回調)
    • 資源使用完未關閉(BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap)
    • 集合中的對象未清理
  • 圖片Bitmap相關網絡

    • 使用完畢後釋放圖片資源
      • bitmap recycle()
      • 採用軟引用(softreference)
    • 根據分辨率適配&縮放圖片
      • 設置多套圖片資源
      • BitmapFactory.decodeResource()
      • BitmapFactory.inSampleSize
  • 內存抖動 大量臨時小對象頻繁建立會致使內存碎片,程序頻繁分配內存&垃圾收集器頻繁回收內存。致使卡頓甚至內存溢出。數據結構

  • 代碼質量 & 數量
    代碼自己的質量(如 數據結構、數據類型等) & 數量(代碼量的大小)可能會致使大量的內存問題,如佔用內存大、內存利用率低等 平常不正確使用多線程

  • 內存泄漏緣由: 沒有用的對象沒法回收的現象就是內存泄露。當一個對象已經不須要再使用了,本該被回收時,而有另一個正在使用的對象持有它的引用從而就致使,對象不能被回收。這種致使了本該被回收的對象不能被回收而停留在堆內存中,就產生了內存泄漏。

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

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,用戶很明顯感知到了卡頓的出現,也就是所謂的丟幀狀況。

    • 一、當 Display 顯示第 0 幀數據時,此時 CPU 和 GPU 已經開始渲染第 1 幀畫面,並將數據緩存在緩衝 B 中。可是因爲某些緣由,就好像上面說的,致使系統處理該幀數據耗時過長或者未能及時處理該幀數據。

    • 二、當 VSYNC 信號來時,Display 向 B 緩衝要數據,由於緩衝 B 的數據還沒準備好,B緩衝區這時候是被鎖定的,Display 表示你沒準備好,只能繼續顯示以前緩衝 A 的那一幀,此時緩衝 A 的數據也不能被清空和交換數據。這種狀況就是所謂的「丟幀」,也被稱做「廢幀」;當第 1 幀數據(即緩衝 B 數據)準備完成後,它並不會立刻被顯示,而是要等待下一個 VSYNC,Display 刷新後,這時用戶纔看到畫面的更新。

    • 三、當某一處丟幀後,大機率會影響後面的繪製也出現丟幀,最後給用戶感受就是卡頓了。最嚴重的直接形成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的實現和區別

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攔截器

5.2 okhttp運行流程

5.3 Retrofit的本質流程

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生命週期

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

8 其餘

8.1 MVP框架模型

  • 優勢:

    • 一、下降耦合度,隱藏數據邏輯,減輕Activity或是Fragment的壓力,讓其更多的只關注處理生命週期任務,使得代碼更加簡潔明
    • 二、模塊職責劃分明顯,視圖邏輯和業務邏輯分別抽象到V層和P層的接口中去,提升代碼可閱讀性,複用度較高,靈活性高
    • 三、方便測試驅動開發
    • 四、減小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同樣提供動態加載。

相關文章
相關標籤/搜索