2019Android面試總結

Java

equals和==、hashCode的區別

  • == 比較棧中存儲的值是否相同
  • equals 如果不重寫equals方法時,其和==作用相同,Object類默認實現就是通過==來實現equals的,重寫后按照會根據equals返回結果比較.
  1. 重寫equals方法時需要重寫hashCode方法,主要是針對Map、Set等集合類型的使用;
    a: Map、Set等集合類型存放的對象必須是唯一的;
    b: 集合類判斷兩個對象是否相等,是先判斷equals是否相等,如果equals返回TRUE,還要再判斷HashCode返回值是否ture,只有兩者都返回ture,才認為該兩個對象是相等的。
  2. 由于Object的hashCode返回的是對象的hash值,所以即使equals返回TRUE,集合也可能判定兩個對象不等,所以必須重寫hashCode方法,以保證當equals返回TRUE時,hashCode也返回Ture,這樣才能使得集合中存放的對象唯一。

JVM內存模型

image.png

JVM 的內存區域可以分為兩類:線程私有和區域和線程共有的區域。 線程私有的區域:程序計數器、JVM 虛擬機棧、本地方法棧;線程共有的區域:堆、方法區

  • 程序計數器(Program Counter Register)也叫做PC寄存器,是一塊較小的內存空間。在虛擬機概念模型中,字節碼解釋器工作時就是通過改變程序計數器來選取下一條需要執行的字節碼指令,Java虛擬機的多線程是通過輪流切換并分配處理器執行時間的方式來實現的,在一個確定的時刻只有一個處理器執行一條線程中的指令,為了在線程切換后能恢復到正確的執行位置,每個線程都會有一個獨立的程序計數器,因此,程序計數器是線程私有的。如果線程執行的方法不是Native方法,則程序計數器保存正在執行的字節碼指令地址,如果是Native方法則程序計數器的值則為空(Undefined)。程序計數器是Java虛擬機規范中唯一沒有規定任何OutOfMemoryError情況的數據區域
  • Java虛擬機棧(Java Virtual Machine Stacks)
    它的生命周期與線程相同,與線程是同時創建的。Java虛擬機棧存儲線程中Java方法調用的狀態,包括局部變量、參數、返回值以及運算的中間結果等。一個Java虛擬機棧包含了多個棧幀,一個棧幀用來存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。當線程調用一個Java方法時,虛擬機壓入一個新的棧幀到該線程的Java棧中,當該方法執行完成,這個棧幀就從Java棧中彈出。我們平常所說的棧內存(Stack)指的就是Java虛擬機棧。
    1.如果線程請求分配的棧容量超過Java虛擬機所允許的的最大容量,Java虛擬機會拋出StackOverflowError
    2.Java虛擬機棧動態擴展內存時,無法申請到足夠內存,則會報OutOfMemoryError
  • 本地方法棧(Native Method Stack)
    與Java虛擬機棧類似,只不過其執行的是native方法,該區域Java規范沒有強制要求支持,HotSpot VM將本地方法棧和Java虛擬機棧合二為一.本地方法棧也會拋出StackOverflowError或OutOfMemoryError
  • Java堆(Java Heap) 是被所有線程共享的運行時內存區域。Java堆用來存放對象實例,幾乎所有的對象實例都在這里分配內存。Java堆存儲的對象被垃圾收集器管理,這些受管理的對象無需也無法顯示的銷毀。從內存回收的角度,Java堆可以粗略的分為新生代和老年代。Java堆的容量可以是固定的,也可以動態的擴展。Java堆的所使用的內存在物理上不需要連續,邏輯上連續即可。堆中沒有足夠的內存來完成實例分配,并且堆也無法進行擴展時,則會拋出OutOfMemoryError異常。
  • 方法區(Method Area),其是被所有線程共享的運行時內存區域。用來存儲已經被Java虛擬機加載的類的結構信息
    image.png

    每一個Class文件中,都維護著一個常量池(這個保存在類文件里面,不要與方法區的運行時常量池搞混),里面存放著編譯時期生成的各種字面值和符號引用;這個常量池的內容,在類加載的時候,被復制到方法區的運行時常量池 ;
    如果方法區的內存空間不滿足內存分配需求時,Java虛擬機會拋出OutOfMemoryError異常

類的加載過程

類的生命周期


image.png

其中前五步為加載階段,而驗證、準備、解析又可概括為鏈接,所以類加載大致的一個流程為加載、鏈接、初始化,下面我將對這三部分逐個進行詳細講解

  1. 加載
    將類class文件內容加載到內存中,然后把靜態數據轉換為方法區需要的數據結構 (是轉換,并不是將靜態數據加載到方法區) ,最后在堆中生成一個類的Class對象用來訪問方法區數據。其中class文件的表現形式就是字節數組,所以class文件的來源可以是本地文件、網絡、jar包等等。另外加載過程中需要有類加載器參與,在java中類ClassLoader就是類加載器。
  2. 鏈接
    1.驗證: 驗證加載進來的class文件各種格式是否符合JVM的要求
    2.準備:為靜態變量分配內存,并賦予初始值。這個階段開發者定義的值不會賦予靜態變量并且也不會執行靜態代碼塊。但如果為final修飾的變量會直接賦予開發者定義的值。
    3.解析:將常量池中的符號引用轉換為直接引用

符號引用:符號引用是以一組符號來描述所引用的目標,符號可以是任何的字面形式的字面量。
直接引用:是指向目標的指針,偏移量或者能夠直接定位的句柄。該引用是和內存中的而布局有關的,并且一定加載進來的。

  1. 初始化
    初始化為類加載過程的最后一個階段,這個階段會為靜態變量進行賦值,并且順序執行靜態代碼塊

Java類執行過程

Java類執行過程

類初始化的4種情況

  1. 遇到new,getstatic,putstatic,invokestatic這失調字節碼指令時,如果類沒有進行過初始化,則需要先觸發其初始化。生成這4條指令的最常見的Java代碼場景是:使用new關鍵字實例化對象的時候、讀取或設置一個類的靜態字段(被final修飾、已在編譯器把結果放入常量池的靜態字段除外)的時候,以及調用一個類的靜態方法的時候。
  2. 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
  3. 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
  4. 當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。

Java引用及GC垃圾回收機制

Java引用及GC垃圾回收機制

Java Exception和Error 異常

JAVA基礎——異常詳解

Java I/O

Java IO面試題

Java泛型

Java泛型

Java反射

Java反射

Jni介紹

Java多線程

Android

Android 系統架構

android_platform_architecture.png

Android系統架構分為五層

  1. 應用層
  2. 應用框架層(Java API Framework),向應用層提供api,例如:ActivityManager,LocationManager,PackageManager,ContentProvider等
  3. 系統運行庫層(Native)
    1.C++程序庫 SQLite輕型關系型數據庫引擎
    2.Android運行時庫 包括核心庫和ART虛擬機(Android5.0后,Dalvik虛擬機被ART取代)。核心庫提供了Java語言核心庫大多數功能,這樣開發者才可以使用Java語言編寫Android應用。Dalvik虛擬機(DVM)中應用每次運行,字節碼都需要通過即時編譯器(Just In Time,JIT)轉化為機器碼,這會使應用運行效率降低。而ART,系統在安裝應用時會進行一次預編譯(Ahead Of Time,AOT),將字節碼預先編譯為機器碼并存儲在本地,這樣應用每次運行時就無需編譯了,運行效率大大提高。
  4. 硬件抽象層(HAL) 隱藏了特定平臺的硬件接口細節,為操作系統提供虛擬硬件平臺。
  5. Linux 內核層(Linux Kernel) 系統的內存管理,驅動模型都依賴于該內核。

JVM&DVM&ART區別

Android系統啟動過程

Android系統啟動流程
  1. 啟動電源以及系統啟動
    當電源按下時引導芯片代碼開始從預定義的地方(固化在ROM)開始執行。加載引導程序Bootloader到RAM,然后執行。
  2. 引導程序Bootloader
    引導程序是在Android操作系統開始運行前的一個小程序,它的主要作用是把系統OS拉起來并運行。
  3. linux內核啟動 (Linux 內核層)
    內核啟動時,設置緩存、被保護存儲器、計劃列表,加載驅動。當內核完成系統設置,它首先在系統文件中尋找”init”文件,然后啟動root進程或者系統的第一個進程。
  4. init進程啟動(Native層)
    init進程解析init.rc文件孵化出Zygote進程,Zygote進程是Android系統第一個Java進程(虛擬機進程),zygote進程是所有Java進程的父進程。
  5. Zygote進程啟動(Framework層)
    創建Java虛擬機并為Java虛擬機注冊JNI方法,創建服務端Socket,啟動SystemServer進程
  6. SystemServer進程啟動
    啟動各種系統服務,如ActivityManagerService,PackageManagerService,WindowManagerService等。
  7. Launcher啟動
    AMS會啟動Launcher。

Apk打包流程

myapp.apk
|------assets/   // 原封不動打包
    ---lib/      // 原封不動打包
        --armeabi-v7a
            - libconversation.so
    ---META-INF/    
        --CERT.RSA  // 這個文件保存了簽名和公鑰證書。
        --CERT.SF
        --MANIFEST.MF
    ---res/   // 所有XML文件都是二進制
        --anim/
        --color/
        --drawable/
        --layout/
        --menu/
        --raw/
        --xml/
        ...
    ---AndroidManifest.xml  // 二進制
    ---classes.dex  //  *.java(通過javac)--->*.class(通過dx工具)--->classes.dex
    ---resources.arsc // 記錄了所有的應用程序資源目錄的信息,將其想象成是一個資源索引表,這個資源索引表在給定資源ID和設備配置信息(例如設備分辨率)的情況下,能夠在應用程序的資源目錄中快速地找到最匹配的資源
  1. 通過aapt工具編譯所有資源文件(res目錄但不包括res/raw)生成R.java(但res/raw資源也會在R.java)供代碼引用資源。
    assets和res/raw不參與編譯,會原封不動打包到apk,res目錄下的其他xml資源和AndroidManifest.xml都會編譯為二進制。
    打包工具aapt負責編譯和打包資源,編譯完成之后,會生成一個resources.arsc文件和一個R.java,前者保存的是一個資源索引表,后者定義了各個資源ID常量。res/values會被直接保存在resource.arsc中。
    那為什么要將文本格式的xml轉為二進制格式的xml呢?
    (1) 二進制格式的XML文件占用空間更小。
    (2) 二進制格式的XML文件解析速度更快。
  2. 通過aidl工具將aidl代碼轉化為java代碼
  3. 所有的java文件通過javac編譯為class字節碼文件(包括R.java和aidl生成的java類)
  4. 將生成的class文件和第三方jar包通過dx工具生成classes.dex
  5. 通過apkbuilder(最新的sdk沒有該腳本,是通過sdklib.jar完成)將so文件,classes.dex、resources.arsc、res文件夾(res/raw資源被原裝不動地打包進APK之外,其它的資源都會被編譯或者處理)、Other Resources(assets文件夾)、AndroidManifest.xml打包成apk文件
    1.res/raw和assets的相同點:
    兩者目錄下的文件在打包后會原封不動的保存在apk包中,不會被編譯成二進制。
    2.res/raw和assets的不同點:
    res/raw中的文件會被映射到R.java文件中,訪問的時候直接使用資源ID即R.id.filename;assets文件夾下的文件不會被映射到R.java中,訪問的時候需要AssetManager類。
    res/raw不可以有目錄結構,而assets則可以有目錄結構,也就是assets目錄下可以再建立文件夾
  6. jarsigner對apk進行簽名,可以進行Debug和Release 簽名。
  7. 通過zipalign工具對apk中的未壓縮資源(圖片、視頻)進行“對齊操作”,讓資源按4字節的邊界進行對齊,使得資源訪問速度更快。(優化思想類似結構體變量分配時采用內存對齊)

Activity相關

1. 談談onSaveInstanceState(Bundle outState)

onSaveInstanceState一般在onStop之間調用,與onpause沒有調用時序關系

  1. 用戶行為要Activity關閉,不會執行onSaveInstanceState方法.當用戶按下HOME鍵時或啟動其他程序時都會調用onSaveInstanceState
  2. onSaveInstanceStateonRestoreInstanceState不是成對執行,onRestoreInstanceState轉屏時會執行

2. onRestoreInstanceState(Bundle savedInstanceState)

onRestoreInstanceState在onResume之前調用,onCreate(Bundle)可能沒有數據,但onRestoreInstanceState調用時一定會有.

  1. 其并不是和onSaveInstanceState成對回調,如果Activity并沒有銷毀也不需要恢復,這種情況下不會調用。
  2. 回調onRestoreInstanceState方法也會將保存的bundle傳給onCreate()方法中的bundle

3. onPause和onSaveInstanceState區別

作用不同,onPause適用做數據持久化存儲,onSaveInstanceState適用于臨時狀態數據

4. 4種啟動模式介紹

  • standard:標準模式 每次啟動一個Activity,都會創建一個實例,放入啟動他的Activity(除了啟動它的Activity是SingleInstance情況)棧頂,即使已經存在
  • singleTop:棧頂復用模式 如果新Activity已經位于棧頂,那么activity不會重新創建,同時onNewIntent方法會被回調.
  • singleTask:棧內復用模式 只要該Activity在一個任務棧中存在,都不會重新創建,并回調onNewIntent方法.比如啟動Activity A,先判斷是否存在其想要的任務棧,如果不存在,系統會先重新創建一個任務棧將A放入該任務棧;如果存在,這時會判斷該棧是否有實例存在,如果實例存在則將A調到棧頂并調用onNewIntent方法,如果實例不存在,就創建A實例并將其壓棧.
  • singleInstance:單實例模式 具有此模式的Activity只能單獨位于一個任務棧中,且此任務棧中只有唯一一個實例.
    TaskAffinity可以指定任務棧名稱,默認為包名,其用來和SingleTask配對適用,其他模式沒有意義.
    各自適用場景
    SingleTop 棧頂復用,可能有多個Activity,但SingleTask棧中只有一個.
    FLAG
    FLAG_ACTIVITY_NEW_TASK,FLAG_ACTIVITY_SINGLE_TOP,作用和singleTask,singleTop作用相同,但指定FLAG的優先級高.

IntentFilter匹配規則

  • 一個Intent只有同時匹配某個Activity的intent-filter中的action,category,data才算完全匹配.
  • 一個Activity可以有多個intent-filter,一個intent只要成功匹配任意一組就可以啟動Activity.
    1.action匹配規則: intent-filter可以有多個action,但Intent只能指定一個action,只要與intent-filter中一條action匹配,則action匹配
    2.category匹配規則:intent可以不指定category,則系統會為其添加default的category,intent可以指定多條category,但多條category必須在intent-filter包含,如果包含則category匹配
    3.data匹配規則:intent-filter中有data,則intent中必須指明data,并且該data需要和intent-filter中一個data圈圈匹配即可.

Fragment相關

1. Fragment生命周期

onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()->onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()

  • onAttach():當Fragment和Activity建立關聯是調用
  • onCreateView(): 當Fragment創建視圖時調用
  • onActivityCreated():當與Fragment關聯的Activity執行完onCreate方法后調用
  • onDestroyView(): Fragment中布局被移除的時候
  • onDetach():與Activity解除關聯時調用

2. Fragment和Activity異同

  • 相同點:他們都可包含布局,有自己的生命周期
  • 不同點:Fragment依附在Activity上,聲明周期有宿主Activity而不是操作系統調用

3. 合適采用Fragment

  • Fragment性能高,可以解決Activity切換卡頓
  • Fragment對于大小屏適配非常合適

4. DialogFragment與AlertDialog的優點

  • 轉屏DialogFragment可保持顯示,并可數據恢復
  • 有著和Activity幾乎一致的生命周期,更容易管理
  • 大小屏幕可以定制顯示狀態,例如大屏幕將內容直接顯示在Activity中,小屏幕顯示對話框

5. 同一個Activity不同Fragment的傳值

  1. Activity向Fragment傳值
    通過Activity中在添加Fragment之前調用setArgument傳入Bundle值,Fragment通過getArgument即可得到Bundle
  2. Fragment向Activity傳值
    采用接口回調,Fragment中定義接口,Activity實現該接口,需要傳值時Fragment調用接口方法即可。Fragment中該接口的具體實現可以通過getActivity直接獲得,也可通過set方法傳入實現了該接口的對象。
  3. Fragment向Fragment傳值
    1. 先由Fragment傳值到Activity,再Activity傳至Fragment
    2. 得到另一個Fragment的對象,調用其方法即可,eg:左右兩個Fragment,左Fragment向右Fragment傳值
      LeftFragment
    // 或者調用findFramgentByTag方法
    RightFragment rightFragment =(RightFragment) getFragmentManager().findFragmentById(R.id.right_fragment);
    
    1. 同屬一個Activity,可以直接得到目標Fragment的控件進行操作

Service相關

1. Service生命周期

Service兩種啟動方式生命周期

2. startService和bindService

  1. 啟動Service

    context.startService()->onCreate()->onStartCommand()->Service運行->(如果調用context.stopService)->onDestroy
    
    • 多次調用startService()只會走一次onCreate(),每次都會執行onStartCommand()方法
    • 多次調用stopService之后的stopService不會有任何反應
  2. 綁定Service

    context.bindService()->onCreate()->onBind()->Service綁定->(如果調用context.unbindService())->onUnbind()->onDestory();
    
    • 多次調用bindService(),只有第一次會走onCreate()和onBind(),之后的不會走任何方法
    • 多次調用unbindService(),之后的unbind會報錯Caused by: java.lang.IllegalArgumentException: Service not registered: com.breeze.b.MainActivity$1@bdbf7e8
    • onBind返回null,onServiceConnected不會被回調
    • Service已經被啟動,這時綁定Service解綁后又重新綁定,在onRebind方法返回值為true的情況,這時會執行onRebind方法而不是調用bind
  3. Service可以同時處于綁定狀態和運行狀態,只要Service存在則再start或bind就不會回調onCreate方法,當Service被start并且bind,需要同時stop和unbind才會回調onDestory()

  4. 啟動方式和綁定方式區別

    1. 啟動方式一旦啟動Service,Service就一直運行在后臺,除非調用自身stopSelf()方法,或者context.stopService();綁定方式綁定Service后,如果客戶端調用unbindservice或者客戶端退出后Service都會銷毀,其與客戶端聲明周期有關
    2. 啟動方式,調用方式無法與Service進行交互,只能啟動或關閉Service;綁定方式可以通過返回的IBinder接口調用服務端的代碼,從而實現交互。

3. Service如何和Activity進行通信

通過bindService可以實現Activity調用Service的方法.通過廣播實現Service向Activity發送消息

4. IntentService

用于后臺執行耗時任務,執行完成并可自動停止.比線程優先級高. 實現原理是通過HandlerThread消息機制.

5. Android8.0不允許啟動后臺Service

切換為前臺Service,但必須有通知.或者通過JobIntentService實現.

6. 如何保證Service不被殺死

  • Service的onStartCommand()中設置flag為START_STICKY,在運行onStartCommand后service進程被kill后,那將保留在開始狀態,但是不保留那些傳入的intent。不久后service就會再次嘗試重新創建,因為保留在開始狀態,在創建 service后將保證調用onstartCommand。
  • 提升Service優先級為前臺Service
  • Service執行onDestory()方法時發送廣播,接收到廣播時重啟Service
  • 系統級應用

Broadcast相關

廣播的類型主要分為5類:

  • 普通廣播(Normal Broadcast)
  • 系統廣播(System Broadcast)
  • 有序廣播(Ordered Broadcast)
  • 粘性廣播(Sticky Broadcast)
  • App應用內廣播(Local Broadcast)

1. 普通廣播(Normal Broadcast)

普通廣播接收沒有先后順序.最常用,分為動態注冊和靜態注冊。但靜態注冊在Android8.0上做了限制,只能接收部分系統廣播.

2. 系統廣播(System Broadcast)

  • Android中內置了多個系統廣播:只要涉及到手機的基本操作(如開機、網絡狀態變化、拍照等等),都會發出相應的廣播
  • 每個廣播都有特定的Intent - Filter(包括具體的action),Android常用系統廣播action如下:
系統操作 action
監聽網絡變化 android.net.conn.CONNECTIVITY_CHANGE
關閉或打開飛行模式 Intent.ACTION_AIRPLANE_MODE_CHANGED
充電時或電量發生變化 Intent.ACTION_BATTERY_CHANGED
系統啟動完成后(僅廣播一次) Intent.ACTION_BOOT_COMPLETED
屏幕被關閉 Intent.ACTION_SCREEN_OFF
屏幕被打開 Intent.ACTION_SCREEN_ON

3. 有序廣播(Ordered Broadcast)

  • 發送出去的廣播被廣播接收者按照先后順序接收,
    廣播接受者接收廣播的順序規則(同時面向靜態和動態注冊的廣播接受者)
    按照Priority屬性值從大-小排序;Priority 取值范圍-1000~+1000,默認為0
    Priority屬性相同者,動態注冊的廣播優先;
  • 先接收的廣播接收者可以對廣播進行截斷通過調用 abortBroadcast();,即后接收的廣播接收者不再接收到此廣播;
    先接收的廣播接收者可以對廣播進行修改通過setResultxxx(),getResultxxx(),那么后接收的廣播接收者將接收到被修改后的廣播
  • 有序廣播的使用過程與普通廣播非常類似,差異僅在于廣播的發送方式:sendOrderedBroadcast(intent);

4. 粘性廣播(Sticky Broadcast)

粘性消息在發送后就一直存在于系統的消息容器里面,等待對應的處理器去處理,如果暫時沒有處理器處理這個消息則一直在消息容器里面處于等待狀態,粘性廣播的Receiver如果被銷毀,那么下次重建時會自動接收到消息數據。(在 android 5.0/api 21中deprecated,不再推薦使用)

耳機插拔廣播Intent.ACTION_HEADSET_PLUG就是粘性廣播

5. App應用內廣播(Local Broadcast)

Android中的廣播可以跨App直接通信(exported對于有intent-filter情況下默認值為true).

存在的問題

  • 其他App針對性發出與當前App intent-filter相匹配的廣播,由此導致當前App不斷接收廣播并處理;
  • 其他App注冊與當前App一致的intent-filter用于接收廣播,獲取廣播具體信息;
    即會出現安全性 & 效率性的問題。

App應用內廣播可理解為一種局部廣播,廣播的發送者和接收者都同屬于一個App。相比于全局廣播(普通廣播),App應用內廣播優勢體現在:安全性高 & 效率高。通常采用如下兩種方式實現本地廣播

  1. 增設相應權限permission或者通過intent.setPackage(packageName)指定包名接收
  2. v4包下LocalBroadcastManager實現localBroadcastManager = LocalBroadcastManager.getInstance(this);獲取其實例,通過其發送廣播和接收廣播。

ContentProvider相關

ContentProvider四大組件之一,嚴格意義不屬于數據存儲,它實現存儲方式是通過其他存儲方式實現(文件存儲,SharedPrefernces,SQLite數據).ContentProvider主要是用于不同應用之間數據共享.

數據存儲方式有哪些?

  1. File文件存儲: 寫入和讀取文件的方法和Java中實現I/O的程序一樣
  2. SharedPreferences: 輕型數據存儲方式,常用與存儲一些簡單配置信息,本質是通過XML文件存儲鍵值對數據
  3. SQLite數據存儲:輕型關系型數據庫,速度快,占用資源少,適合存儲大量復雜關系型數據
  4. ContentProvider: 實現數據存儲與共享
  5. 網絡存儲 使用較少,暫不記錄
    Android數據存儲的5種方式

1. SharedPreferences適用情形?適用中需要注意什么?

適用情形上面已說明.SharedPreferences底層通過讀/寫XML文件來實現,并發容易出問題.從而可靠性下降

Android進程和線程區別

  • 進程就是一個應用程序的執行過程,它是一個動態的概念
  • 線程是進程中的一部分,起始CPU調度的最小單元
    進程至少包含一個線程,及主線程,Android中稱為UI線程。線程之間可以共享進程資源,但進程與進程彼此獨立。
  • 一般一個應用對應一個進程,但也可以通過process屬性開啟多進程模式
  • 線程是有限個:不可無限制的產生,且線程的產生和銷毀都是要一定開銷。

IPC

1. 為什么要進行IPC,多進程可能出現什么問題

由于每個應用都運行在獨立的虛擬機,不同虛擬機內存地址空間不同,從而不同虛擬機訪問同一個對象會產生多個副本,故涉及到內存共享數據,都會共享失敗,一般會造成如下幾個問題。

  1. 靜態成員和單例模式完全失效
  2. 線程同步完全失效
    不同進程的鎖永遠不會是同一個對象
  3. SharedPreference可靠性下降
    SharedPreference不支持兩個進程同時執行寫操作,否則會引起一定概率的數據丟失
  4. Application多次創建

2. Serializable接口和Parcelable接口的區別

  • Parcelable和Serializable都是實現序列化并且都可以用于Intent間傳遞數據
  • Serializable是Java的實現方式,會頻繁的IO操作,所以消耗比較大,但是實現方式簡單。適用本地存儲和網絡傳輸。
  • Parcelable是Android提供的方式,其是將一個對象進行效率比較高,但是實現起來復雜一些 。
    二者的選取規則是:內存序列化上選擇Parcelable, 存儲到設備或者網絡傳輸上選擇Serializable(當然Parcelable也可以但是稍顯復雜)

3. Android中為何新增Binder來作為主要的IPC方式?

  1. 傳輸效率高、可操作性強:傳輸效率主要影響因素是內存拷貝的次數,拷貝次數越少,傳輸速率越高。從Android進程架構角度分析:對于消息隊列、Socket和管道來說,數據先從發送方的緩存區拷貝到內核開辟的緩存區中,再從內核緩存區拷貝到接收方的緩存區,一共兩次拷貝,共享內存雖然無需拷貝,但控制復雜,難以使用.
  2. 安全性 傳統Linux IPC的接收方無法獲得對方進程可靠的UID/PID,從而無法鑒別對方身份;而Binder機制為每個進程分配了UID/PID且在Binder通信時會根據UID/PID進行有效性檢測。

4. Binder機制?

以AIDL運行機制介紹


  1. 服務端中的Service給與其綁定的客戶端提供Binder對象
  2. 客戶端通過AIDL接口中的asInterface()將這個Binder對象轉換為代理Proxy,通過它發起RPC請求。客戶端發起請求時會掛起當前線程,并將參數寫入data然后調用transact()方法,RPC請求會通過系統底層封裝后由服務端的onTransact()處理,并將結果寫入reply,這個過程運行在服務端中的Binder線程池,最后返回調用結果并喚醒客戶端線程。


    image.png

5. 是否了解AIDL?原理是什么?如何優化多模塊都使用AIDL的情況

當有多個業務模塊都需要AIDL來進行IPC,此時需要為每個模塊創建特定的aidl文件,那么相應的Service就會很多。必然會出現系統資源耗費嚴重、應用過度重量級的問題。解決辦法是建立Binder連接池,即將每個業務模塊的Binder請求統一轉發到一個遠程Service中去執行,從而避免重復創建Service

每個業務模塊創建自己的AIDL接口并實現此接口,然后向服務端提供自己的唯一標識和其對應的Binder對象。服務端只需要一個Service,服務器提供一個queryBinder接口,它會根據業務模塊的特征來返回相應的Binder對像,不同的業務模塊拿到所需的Binder對象后就可進行遠程方法的調用了

6. IPC有哪些,各自優缺點適用場景

  1. Intent數據傳遞
    使用Bundle進行傳遞數據在Activity,Service,BroadcastReceiver中,數據支持類型有
    1.8種基本類型及數組
    2.String(實現了Serializable),CharSequence及對應數組
  2. 是實現了Serializable或者Parcelable的對象或數組
    Bundle支持傳輸的最大數據為1M,甚至更小,因為Binder會話緩沖區大小限制為1M,它是被所有處于Binder會話的進程鎖共享的。
  3. 文件共享 (適合對并發要求不高的進程之間使用)
    • ObjectOutputStream/ObjectInputStrem實現讀寫對象到文件 文件可以是各種格式,只要雙方約定好即可
    • 并發讀寫容易引起問題
  4. SharedPreference (不建議使用)
    • 本質上也是文件共享的一種
    • 系統對其有緩存策略,即內存中存在其緩存,多進程對其讀寫不可靠
    • 高并發容易引起數據丟失
  5. 基于Binder的Messager和AIDL
  6. Socket
  7. LocalSocket
    LocalSocket解決的是同一臺主機上不同進程間互相通信的問題。其相對于網絡通信使用的socket不需要經過網絡協議棧,不需要打包拆包、計算校驗,自然的執行效率也高。與大名鼎鼎的binder機制作用一樣,都在Android系統中作為IPC通信手段被廣泛使用

android下使用localsocket可以實現C與C,C與JAVA,JAVA與JAVA進程間通信

IPC優缺點對比

1. MotionEvent是什么?包含幾種事件?什么條件下會產生?

MotionEvent是手指觸摸屏幕鎖產生的一系列事件。包含的事件有:
ACTION_DOWN:手指剛接觸屏幕
ACTION_MOVE:手指在屏幕上滑動
ACTION_UP:手指在屏幕上松開的一瞬間
ACTION_CANCEL:手指保持按下操作,并從當前控件轉移到外層控件時會觸發

2. View的事件分發機制?

觸摸事件傳遞順序是Activity,ViewGroup,View
而在事件分發過程中,涉及到三個最重要的方法:

  • dispatchTouchEvent()
    事件能夠傳遞到當前View,則該方法一定調用,返回結果受當前View的onTouchEvent和子View的dispatchTouchEvent方法影響,表示是否消耗事件.
  • onInterceptTouchEvent()
    判斷是否攔截某個事件,如果當前View攔截了某個事件,那么在同一個事件序列當中,此方法不會被再次調用,返回值表示是否攔截當前事件序列.
  • onTouchEvent
    在dispatchTouchEvent方法中調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗則統一事件序列中,當前View無法再次接收到事件.
    整體分發過程如下
public boolean dispatchTouchEvent(MotionEvent ev) {
    //consume 表示事件是否在該View中消耗
    boolean consume = false;
    //是否當前View攔截事件,如果攔截,則調用本View的onTouchEvent方法,不會再下發事件序列中的其他事件
    if (onInterceptTouchEvent(ev)) {
        consume = onTouchEvent(ev);  
    } else {
        //如果沒有攔截事件,則會遍歷手指按下時接觸的所有子View(不是容器的所有子View,而是該容器中down事件所接觸的子View)進行遍歷派發事件,事件如果有消耗則退出循環,如果所有字View均沒有消耗,則此時consume為false
        for(int i=0; i<childs.length && !consume;i++) {
            consume = childs[i].dispatchTouchEvent(ev);
        }
        
    }
    如果派發給所有字view事件依然沒有消耗,則有本View的onTouchEvent來進行處理,如果依然返回false,則當前View沒有消耗事件,進一步上發事件給父View,如果頂層View依然沒有處理,則Activity的onTouchEvent會被調用.
    if(!consume) {
        consume = onTouchEvent(ev);       
    }
    return consume;
}
  • ViewGroup在ACTION_DOWN事件時,onInterceptTouchEvent方法返回true,則所有子View都無法接收到事件,只在ACTION_MOVE或ACTION_UP時,onInterceptTouchEvent方法返回true,上次接收到事件的子View會接收了ACTION_CANCEL事件
  • ACTION_DOWN時,所有View都不消耗事件時,則Activity不會再繼續下事件給View.
  • View的onTouchListener的onTouch方法>onTouchEvent>onClickListener的onClick方法
  • 子View可通過requestDisallowInterceptTouchEvent干預父View的事件攔截,除了ACTION_DOWN事件.
    具體意思就是如果父View在ACTION_DOWN時已經攔截了事件,那么子View無論如何都不會得到任何事件. 還有一種情況是,子View雖然禁止攔截事件,但自己不消耗事件,那么事件序列依然不會給其下發.

Android 子線程訪問主線線程更新UI && Handler原理

1. 為什么Android無法在子線程更新UI

實質上是為了避免多線程并發問題,容易引起界面更新錯亂。如果通過加鎖的機制實現同步,則非常消耗性能。

2. 其他線程訪問UI線程的5種方式:

  1. Activity.runOnUiThread(Runnable);
  2. View.post(Runnable);
  3. View.postDelayed(Runnable,long);
  4. Handler;
  5. AsyncTask。
    上述5種方式包括AsyncTask本質都是通過Handler來實現的

3. Handler原理

Handler原理如下:

一個線程通過Looper.prepare會創建唯一的Looper對象,創建Looper中就會創建一個消息隊列MessageQueue(數據結構是單鏈表隊列)。創建Handler時會與當前線程的Looper及MessageQueue綁定,調用sendMessage方法發送消息Message,該消息Message會入隊到MessageQueue。線程調用Looper.loop方法就會循環從MessageQueue中取消息并將該消息通過Handler的dispatchMessage方法將Message交由Handler處理。Looper負責一直從消息隊列拿消息交由Handler來處理消息。

一個線程只能有一個Looper對象,并且有一個消息隊列,但可以有多個Handler對象。

4. 主線程中的Looper.loop()一直無限循環為什么不會造成ANR?

造成ANR的原因一般有兩種:

  1. 當前的事件沒有機會得到處理(即主線程正在處理前一個事件,沒有及時的完成或者looper被某種原因阻塞住了)
  2. 當前的事件正在處理,但沒有及時完成

當只在UI線程延時很長時間,是不會引起ANR,如果按鈕點擊后延時,按鈕則彈起后無法恢復,直到延時結束,如果按鈕彈起,這時還要處理其他事件則會引起ANR

因為Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每一個點擊觸摸或者說Activity的生命周期都是運行在 Looper.loop() 的控制之下,如果它停止了,應用也就停止了。只能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。也就說我們的代碼其實就是在這個循環里面去執行的,當然不會阻塞了。

5. HandlerThread

HandlerThread繼承自Thread,它就是一個線程,不過做了對looper的封裝。

HandlerThread的run方法中開始執行了Looper.prepare方法,在run方法結尾調用了Looper.loop方法,Looper.loop之前回調onLooperPrepared方法供子類可以進行一些設置。當在其他線程創建Handler時,需要傳入HandlerThread的getLooper方法,這樣該Handler才與該線程的Looper綁定了,從而Handler發送的消息才會進入該線程的MessageQueue中,Looper.loop取得消息才可以交由該Handler處理。

當線程無需處理消息時,可通過HandlerThread的quit或者quitSafely方法退出消息循環,從而run方法執行完成,該線程也就終止了。quit和quitSafely區別在于quit消息隊列中的全部消息,而quitSafely只清除消息隊列中的延遲消息。

6. AsyncTask相比Handler有什么優點?不足呢?

  • Handler
    使用的優點:結構清晰,功能定義明確
    對于多個后臺任務時,簡單,清晰
    使用的缺點:在單個后臺異步處理時,顯得代碼過多,結構過于復雜(相對性)
  • AsyncTask
    使用的優點:簡單,快捷,過程可控
    使用的缺點:在使用多個異步操作和并需要進行Ui變更時,就變得復雜起來.

7. 使用AsyncTask需要注意什么?

一個異步對象只能調用一次execute()方法

線程池和阻塞隊列

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 這是我近段時間收集的面試題,獻給打算年后找工作的同學們。文中涉及的知識比較廣也可能比較零散,并且一些較為基礎的知識...
    01_小小魚_01閱讀 918評論 0 35
  • Java部分 1.GC是什么? 為什么要有GC? GC是垃圾收集的意思(Gabage Collection),內存...
    01_小小魚_01閱讀 201評論 0 3
  • Java部分 1.GC是什么? 為什么要有GC? GC是垃圾收集的意思(Gabage Collection),內存...
    Near尼爾閱讀 1,468評論 0 21
  • Java SE 基礎: 封裝、繼承、多態 封裝: 概念:就是把對象的屬性和操作(或服務)結合為一個獨立的整體,并盡...
    Jayden_Cao閱讀 2,134評論 0 8
  • 安裝完成后運行桌面快捷方式,彈出注冊窗口選擇Activate>License Server>輸入http://xi...
    1_ming閱讀 2,174評論 0 0