Android進階面試題

1. Android系統啓動流程是什麼?

==Android系統核心流程==:

  • ==啓動電源以及系統啓動==:當電源按下時引導芯片從預定義的地方(固化在ROM)開始執行,加載引導程序BootLoader到RAM,然後執行。
  • ==引導程序BootLoader==:BootLoader是在Android系統開始運行前的一個小程序,主要用於把系統OS拉起來並運行。
  • ==Linux內核啓動==:當內核啓動時,設置緩存、被保護存儲器、計劃列表、加載驅動。當其完成系統設置時,會先在系統文件中尋找init.rc文件,並啓動init進程
  • ==init進程啓動==:初始化和啓動屬性服務,並且啓動Zygote進程
  • ==Zygote進程啓動==:創建JVM併爲其註冊JNI方法,創建服務器端Socket,啓動SystemServer進程
  • ==SystemServer進程啓動==:啓動Binder線程池和SystemServiceManager,並且啓動各種系統服務
  • ==Launcher啓動==:被SystemServer進程啓動的AMS會啓動Launcher,Launcher啓動後會將已安裝應用的快捷圖標顯示到系統桌面上。

進程啓動過程:init進程 -> Zygote進程 –> SystemServer進程 –> 各種系統服務 –> 應用進程。

2. 啓動一個程序,可以主界面點擊圖標進入,也可以從一個程序中跳轉過去,二者有什麼區別?

點擊圖標是因爲啓動程序(主界面也是一個app),發現了在這個程序中存在一個設置爲入口的activity, 所以這個launcher會把icon提出來,放在主界面上。當用戶點擊icon的時候,發出一個Intent:

Intent intent = mActivity.getPackageManager().getLaunchIntentForPackage(packageName);
mActivity.startActivity(intent); 

從另一個程序跳過去,系統會根據第三方程序向系統註冊的功能,爲你的Intent選擇可以打開的程序或者頁面。

所以唯一的一點不同的是從icon的點擊啓動的intent的action是相對單一的,從程序中跳轉或者啓動可能樣式更多一些。本質是相同的。

3. AMS家族重要術語解釋

  • ActivityManagerServices,簡稱AMS,服務端對象,負責系統中所有Activity的生命週期

  • ==ActivityThread==,App的真正入口。當開啓App之後,調用main()開始運行,開啓消息循環隊列,這就是傳說的UI線程或者叫主線程。與ActivityManagerService一起完成Activity的管理工作。

  • ApplicationThread,用來實現ActivityManagerServie與ActivityThread之間的交互。在ActivityManagerSevice需要管理相關Application中的Activity的生命週期時,通過ApplicationThread的代理對象與ActivityThread通信。

  • ApplicationThreadProxy,是ApplicationThread在服務器端的代理,負責和客戶端的ApplicationThread通信。AMS就是通過該代理與ActivityThread進行通信的

  • Instrumentation,每一個應用程序只有一個Instrumetation對象,每個Activity內都有一個對該對象的引用,Instrumentation可以理解爲應用進程的管家,ActivityThread要創建或暫停某個Activity時,都需要通過Instrumentation來進行具體的操作。

  • ActivityStackActivity在AMS的棧管理,用來記錄經啓動的Activity的先後關係,狀態信息等。通過ActivtyStack決定是否需要啓動新的進程。

  • ActivityRecordActivityStack的管理對象,每個Acivity在AMS對應一個ActivityRecord,來記錄Activity狀態以及其他的管理信息。其實就是服務器端的Activity對象的映像

  • TaskRecord,AMS抽象出來的一個“任務”的概念,是記錄ActivityRecord的棧,一個“Task”包含若干個ActivityRecord。AMS用TaskRecord確保Activity啓動和退出的順序。如果你清楚Activity的4種launchMode,那麼對這概念應該不陌生。

4. ==App啓動流程==

點擊應用圖標後會去啓動應用的Launcher Activity,如果Launcer Activity所在的進程沒有創建,還會創建新進程,整體的流程就是一個Activity的啓動流程。

image.png

整個流程涉及的主要角色有:

  • Instrumentation: 監控應用與系統相關的交互行爲。
  • AMS:組件管理調度中心,什麼都不幹,但是什麼都管。
  • ActivityStarter:Activity啓動的控制器,處理Intent與Flag對Activity啓動的影響,具體說來有:
    1、尋找符合啓動條件的Activity,如果有多個,讓用戶選擇;
    2、校驗啓動參數的合法性;
    3、 返回int參數,代表Activity是否啓動成功。
  • ActivityStackSupervisior:這個類的作用從它的名字就可以看出來,它用來管理任務棧。
  • ActivityStack:用來管理任務棧裏的Activity。
  • ActivityThread:最終幹活的人,Activity、Service、BroadcastReceiver的啓動、切換、調度等各種操作都在這個類裏完成。

注:這裏單獨提一下ActivityStackSupervisior,這是高版本纔有的類,它用來管理多個ActivityStack,早期的版本只有一個ActivityStack對應着手機屏幕,後來高版本支持多屏以後,就有了多個ActivityStack,於是就引入了ActivityStackSupervisior用來管理多個ActivityStack。

==整個流程主要涉及四個進程==:

  • ==調用者進程==,如果是在桌面啓動應用就是Launcher應用進程。
  • ActivityManagerService等所在的==System Server進程==,該進程主要運行着系統服務組件。
  • ==Zygote進程==,該進程主要用來fork新進程。
  • ==新啓動的應用進程==,該進程就是用來承載應用運行的進程了,它也是應用的主線程(新創建的進程就是主線程),處理組件生命週期、界面繪製等相關事情。

有了以上的理解,==整個流程可以概括如下==:

  • 1、點擊桌面應用圖標,==Launcher進程將啓動Activity(MainActivity)的請求以Binder的方式發送給了AMS==。
  • 2、==AMS接收到啓動請求後,交付ActivityStarter處理Intent和Flag等信息==,然後再交給ActivityStackSupervisior/ActivityStack 處理Activity進棧相關流程。==同時以Socket方式請求Zygote進程fork新進程==。
  • 3、==Zygote接收到新進程創建請求後fork出新進程==。
  • 4、==在新進程裏創建ActivityThread對象==,新創建的ActivityThread就是應用的主線程,==在主線程裏開啓Looper消息循環,開始處理創建Activity==。
  • 5、==ActivityThread利用ClassLoader去加載Activity、創建Activity實例,並回調Activity的onCreate()方法==,這樣便完成了Activity的啓動。
image.png

5. ==理解Window和WindowManager==

  • 1.Window用於顯示View和接收各種事件,Window有三種型:應用Window(每個Activity對應一個Window)、子Widow(不能單獨存在,附屬於特定Window)、系統window(toast和狀態欄)

  • 2.Window分層級,應用Window在1-99、子Window在1000-1999、系統Window在2000-2999. WindowManager提供了增改View的三個功能

  • 3.Window是個抽象概念:每一個Window對應着一個ViewRootImpl,Window通過ViewRootImpl來和View建立聯繫,View是Window存在的實體,只能通過WindowManager來訪問Window

  • 4.WindowManager的實現是WindowManagerImpl,其再委託WindowManagerGlobal來對Window進行操作,其中有四種List分別儲存對應的View、ViewRootImpl、WindowManger.LayoutParams和正在被刪除的View。

  • 5.Window的實體是存在於遠端的WindowMangerService,所以增刪改Window在本端是修改上面的幾個List然後通過ViewRootImpl重繪View,通過WindowSession(每Window個對應一個)在遠端修改Window。

  • 6.Activity創建Window:Activity會在attach()中創建Window並設置其回調(onAttachedToWindow()、dispatchTouchEvent()),Activity的Window是由Policy類創建PhoneWindow實現的。然後通過Activity#setContentView()調用PhoneWindow的setContentView。

6. 應用程序安裝到手機上時發生了什麼?

image.png
  • 複製APK到/data/app目錄下,解壓並掃描安裝包
  • 資源管理器解析APK裏的資源文件
  • 解析AndroidManifest.xml文件,並在/data/data/目錄下創建對應的應用數據目錄。
  • 然後對dex文件進行優化,並保存在dalvik-cache目錄下。
  • ,將AndroidManifest文件解析出的四大組件信息註冊到PackageManagerService中
  • 安裝完成後,發送廣播

7. Android的打包流程?(即清點擊 Android Studio 的 build 按鈕後發生了什麼?)apk裏有哪些東西?簽名算法的原理?

apk打包流程

Android的包文件APK分爲兩個部分:代碼和資源,所以==打包方面也分爲資源打包和代碼打包兩個方面==,下面就來分析資源和代碼的編譯打包原理。

image.png
  • 通過AAPT工具進行資源文件(包括AndroidManifest.xml、佈局文件、各種xml資源等)的打包,生成R.java文件
  • 通過AIDL工具處理AIDL文件,生成相應的Java文件。
  • 通過Java Compiler編譯R.java、Java接口文件、Java源文件,生成.class文件
  • 通過dex命令,將.class文件和第三方庫中的.class文件處理生成classes.dex,該過程主要完成Java字節碼轉換成Dalvik字節碼,壓縮常量池以及清除冗餘信息等工作。
  • 通過ApkBuilder工具將資源文件、DEX文件打包生成APK文件
  • 通過Jarsigner工具,利用KeyStore對生成的APK文件進行簽名
  • 如果是正式版的APK,還會利用ZipAlign工具進行對齊處理,對齊的過程就是將APK文件中所有的資源文件距離文件的起始距位置都偏移4字節的整數倍,這樣通過內存映射訪問APK文件的速度會更快,並且會減少其在設備上運行時的內存佔用。

==apk組成==

  • dex:最終生成的Dalvik字節碼。

  • res:存放資源文件的目錄。

  • asserts:額外建立的資源文件夾。

  • lib:如果存在的話,存放的是ndk編出來的so庫。

  • META-INF:存放簽名信息

    MANIFEST.MF(清單文件):其中每一個資源文件都有一個SHA-256-Digest簽名,MANIFEST.MF文件的SHA256(SHA1)並base64編碼的結果即爲CERT.SF中的SHA256-Digest-Manifest值。

    CERT.SF(待簽名文件):除了開頭處定義的SHA256(SHA1)-Digest-Manifest值,後面幾項的值是對MANIFEST.MF文件中的每項再次SHA256並base64編碼後的值。

    CERT.RSA(簽名結果文件):其中包含了公鑰、加密算法等信息。首先對前一步生成的MANIFEST.MF使用了SHA256(SHA1)-RSA算法,用開發者私鑰簽名,然後在安裝時使用公鑰解密。最後,將其與未加密的摘要信息(MANIFEST.MF文件)進行對比,如果相符,則表明內容沒有被修改

  • androidManifest.xml:程序的全局清單配置文件。

  • resources.arsc:編譯後的二進制資源文件。

==爲什麼要簽名==

  • 確保Apk來源的真實性。
  • 確保Apk沒有被第三方篡改。

什麼是簽名

簽名是在Apk中寫入一個“指紋”。指紋寫入以後,Apk中有任何修改,都會導致這個指紋無效,Android系統在安裝Apk進行簽名校驗時就會不通過,從而保證了安全性。

數字摘要

對一個任意長度的數據,通過一個Hash算法計算後,都可以得到一個固定長度的二進制數據,這個數據就稱爲“摘要”。

補充:

  • 散列算法的基礎原理:將數據(如一段文字)運算變爲另一固定長度值。
  • SHA-1:在密碼學中,SHA-1(安全散列算法1)是一種加密散列函數,它接受輸入併產生一個160 位(20 字節)散列值,稱爲消息摘要 。
  • MD5:MD5消息摘要算法(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼散列函數,可以產生出一個128位(16字節)的散列值(hash value),用於確保信息傳輸完整一致。
  • SHA-2:名稱來自於安全散列算法2(英語:Secure Hash Algorithm 2)的縮寫,一種密碼散列函數算法標準,其下又可再分爲六個不同的算法標準,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。

特徵:

  • 唯一性
  • 固定長度:比較常用的Hash算法有MD5和SHA1,MD5的長度是128拉,SHA1的長度是160位。
  • 不可逆性

簽名和校驗的主要過程

簽名就是在摘要的基礎上再進行一次加密,對摘要加密後的數據就可以當作數字簽名。

簽名過程

  • 1、計算摘要:通過Hash算法提取出原始數據的摘要。
  • 2、計算簽名:再通過基於密鑰(私鑰)的非對稱加密算法對提取出的摘要進行加密,加密後的數據就是簽名信息。
  • 3、寫入簽名:將簽名信息寫入原始數據的簽名區塊內。

校驗過程

  • 1、首先用同樣的Hash算法從接收到的數據中提取出摘要。
  • 2、解密簽名:使用發送方的公鑰對數字簽名進行解密,解密出原始摘要。
  • 3、比較摘要:如果解密後的數據和提取的摘要一致,則校驗通過;如果數據被第三方篡改過,解密後的數據和摘要將會不一致,則校驗不通過。

數字證書

如何保證公鑰的可靠性呢?答案是數字證書,數字證書是身份認證機構(Certificate Authority)頒發的,包含了以下信息:

  • 證書頒發機構
  • 證書頒發機構簽名
  • 證書綁定的服務器域名
  • 證書版本、有效期
  • 簽名使用的加密算法(非對稱算法,如RSA)
  • 公鑰等

接收方收到消息後,先向CA驗證證書的合法性,再進行簽名校驗。

注意:Apk的證書通常是自簽名的,也就是由開發者自己製作,沒有向CA機構申請。Android在安裝Apk時並沒有校驗證書本身的合法性,只是從證書中提取公鑰和加密算法,這也正是對第三方Apk重新簽名後,還能夠繼續在沒有安裝這個Apk的系統中繼續安裝的原因。

keystore和證書格式

keystore文件中包含了私鑰、公鑰和數字證書。根據編碼不同,keystore文件分爲很多種,Android使用的是Java標準keystore格式JKS(Java Key Storage),所以通過Android Studio導出的keystore文件是以.jks結尾的。

keystore使用的證書標準是X.509,X.509標準也有多種編碼格式,常用的有兩種:pem(Privacy Enhanced Mail)和der(Distinguished Encoding Rules)。jks使用的是der格式,Android也支持直接使用pem格式的證書進行簽名。

兩種證書編碼格式的區別:

  • DER(Distinguished Encoding Rules)
    二進制格式,所有類型的證書和私鑰都可以存儲爲der格式。
  • PEM(Privacy Enhanced Mail)
    base64編碼,內容以-----BEGIN xxx----- 開頭,以-----END xxx----- 結尾。

jarsigner和apksigner的區別

Android提供了兩種對Apk的簽名方式,一種是基於JAR的簽名方式,另一種是基於Apk的簽名方式,它們的主要區別在於使用的簽名文件不一樣:jarsigner使用keystore文件進行簽名;apksigner除了支持使用keystore文件進行簽名外,還支持直接指定pem證書文件和私鑰進行簽名。

在簽名時,除了要指定keystore文件和密碼外,也要指定alias和key的密碼,這是爲什麼呢?

keystore是一個密鑰庫,也就是說它可以存儲多對密鑰和證書,keystore的密碼是用於保護keystore本身的,一對密鑰和證書是通過alias來區分的。所以jarsigner是支持使用多個證書對Apk進行簽名的,apksigner也同樣支持。

Android Apk V1 簽名原理

  • 1、解析出 CERT.RSA 文件中的證書、公鑰,解密 CERT.RSA 中的加密數據。
  • 2、解密結果和 CERT.SF 的指紋進行對比,保證 CERT.SF 沒有被篡改。
  • 3、而 CERT.SF 中的內容再和 MANIFEST.MF 指紋對比,保證 MANIFEST.MF 文件沒有被篡改。
  • 4、MANIFEST.MF 中的內容和 APK 所有文件指紋逐一對比,保證 APK 沒有被篡改。

==V1簽名和V2簽名==

  • V1簽名靠META_INFO文件夾下的簽名文件
  • V2驗證壓縮文件的所有字節,因此,在簽名後無法再更改
  • 只勾選v1簽名並不會影響什麼,但是在7.0上不會使用更安全的驗證方式
  • 只勾選V2簽名7.0以下會直接安裝完顯示未安裝,7.0以上則使用了V2的方式驗證
  • V1和V2可以同是勾選

==v3簽名key和v2還有v1有什麼區別==?

  • 在v1版本的簽名中,簽名以文件的形式存在於apk包中,這個版本的apk包就是一個標準的zip包,V2和V1的差別是V2是對整個zip包進行簽名,而且在zip包中增加了一個apk signature block,裏面保存簽名信息。
  • v2版本簽名塊(APK Signing Block)本身又主要分成三部分:
  1. SignerData(簽名者數據):主要包括簽名者的證書,整個APK完整性校驗hash,以及一些必要信息
  2. Signature(簽名):開發者對SignerData部分數據的簽名數據
  3. PublicKey(公鑰):用於驗籤的公鑰數據
  • v3版本簽名塊也分成同樣的三部分,與v2不同的是在SignerData部分,v3新增了attr塊,其中是由更小的level塊組成。每個level塊中可以存儲一個證書信息。前一個level塊證書驗證下一個level證書,以此類推。最後一個level塊的證書,要符合SignerData中本身的證書,即用來簽名整個APK的公鑰所屬於的證書。

8. 說下Android虛擬機和Java虛擬機的區別?

JVM和Dalvik虛擬機(DVM)的區別:

  • JVM編譯產物:java --javac-> .class --jar-> .jar
    架構:堆和棧的架構
  • DVM編譯產物:java --javac-> .class -> dx.bat -> .dex
    架構:寄存器

==Android2個虛擬機的區別==(一個5.0之前,一個5.0之後)?

Dalvik:Dalvik是Google公司自己設計用於Android平臺的Java虛擬機,它可以支持已轉換爲.dex(即Dalvik Executable)格式的Java應用程序的運行,.dex格式是專爲Dalvik應用設計的一種壓縮格式,適合內存和處理器速度有限的系統。Dalvik經過優化,允許在有限的內存中同時運行多個虛擬機的實例,並且每一個Dalvik應用作爲獨立的Linux進程執行。獨立的進程可以防止在虛擬機崩潰的時候所有程序都被關閉。

ART:ART代表Android Runtime,其處理應用程序執行的方式完全不同於Dalvik,Dalvik是依靠一個Just-In-Time(JIT)編譯器去解釋字節碼。開發者編譯後的應用代碼需要通過一個解釋器在用戶的設備上運行,這一機制並不高效,但讓應用能更容易在不同硬件和架構上運行。ART則完全改變了這套做法,在應用安裝的時候就預編譯字節碼爲機器語言,這一機制叫Ahead-Of-Time(AOT)編譯。在移除解釋代碼這一過程後,應用程序執行將更有效率,啓動更快。

ART的優點:

  • 系統性能的顯著提升。
  • 應用啓動更快、運行更快、體驗更流暢、觸感反饋更及時。
  • 更長的電池續航能力。
  • 支持更低的硬件。

ART缺點:

  • 更大的存儲空間佔用,可能會增加10%-20%。
  • 更長的應用安裝時間。

9. ==爲什麼系統不建議在子線程訪問UI==?

  • Android的UI控件不是線程安全的,如果在多線程中併發訪問可能會導致UI控件處於不可預期的狀態。

這時你可能會問爲何系統不對UI控件的訪問加上鎖機制呢?

  • 加鎖機制會讓UI訪問邏輯變的複雜
  • 加鎖機制會降低UI的訪問效率,因爲加鎖會阻塞某些線程的執行。

10. ==怎麼保證應用啓動不卡頓? 黑白屏怎麼處理?==

應用啓動速度,取決於你在application裏面時候做了什麼事情,比如你集成了很多sdk,並且sdk的init操作都需要在主線程裏實現所以會有卡頓的感覺。在非必要的情況下可以把加載延後或則開啓子線程處理
另外,影響界面卡頓的兩大因素,分別是界面繪製和數據處理。

  • ==佈局優化==(使用include,merge標籤,複雜佈局推薦使用ConstraintLayout等)
  • ==onCreate() 中不執行耗時操作 把頁面顯示的 View 細分一下,放在 AsyncTask 裏逐步顯示,用 Handler 更好==。這樣用戶的看到的就是有層次有步驟的一個個的 View 的展示,不會是先看到一個黑屏,然後一下顯示所有 View。最好做成動畫,效果更自然。
  • 利用多線程的目的就是==儘可能的減少 onCreate() 和 onReume()的時間==,使得用戶能儘快看到頁面,操作頁面。
  • 減少主線程阻塞時間。
  • 提高 Adapter 和 AdapterView 的效率

黑白屏處理:

  • ==黑白屏產生原因==:當我們在啓動一個應用時,系統會去檢查是否已經存在這樣一個進程,如果不存在,系統的服務會先檢查startActivity中的intent的信息,然後在去創建進程,最後啓動Acitivy,即冷啓動。而啓動出現白黑屏的問題,就是在這段時間內產生的。系統在繪製頁面加載佈局之前,首先會初始化窗口(Window),而在進行這一步操作時,系統會根據我們設置的Theme來指定它的Theme 主題顏色,我們在Style中的設置就決定了顯示的是白屏還是黑屏。
  1. windowIsTranslucent和windowNoTitle,將這兩個屬性都設置成true (會有明顯的卡頓體驗,不推薦)
  2. 如果啓動頁只是是一張圖片,那麼爲啓動頁專一設置一個新的主題,設置主題的android:windowBackground屬性爲啓動頁背景圖即可
  3. 使用layer-list製作一張圖片launcher_layer.xml,將其設置爲啓動頁專一主題的背景,並將其設置爲啓動頁佈局的背景。

11. 如何通過Gradle配置多渠道包?

  • 首先要了解設置多渠道的原因。在安裝包中添加不同的標識,配合自動化埋點,應用在請求網絡的時候攜帶渠道信息,方便後臺做運營統計,比如說統計我們的應用在不同應用市場的下載量等信息。

  • 這裏以友盟統計爲例

  1. 首先在manifest.xml文件中設置動態渠道變量:
image.png
  1. 接着在app目錄下的build.gradle中配置productFlavors,也就是配置打包的渠道:
image.png

最後在編輯器下方的Teminal輸出命令行

  1. 執行./gradlew assembleRelease ,將會打出所有渠道的release包;
    執行./gradlew assembleVIVO,將會打出VIVO渠道的release和debug版的包;
    執行./gradlew assembleVIVORelease將生成VIVO的release包。

12. 對於應用更新這塊是如何做的? (灰度,強制更新、分區域更新)

  • 內部更新:
  1. 通過接口獲取線上版本號,versionCode
    比較線上的versionCode
  2. 和本地的versionCode,彈出更新窗口
  3. 下載APK文件(文件下載)
  4. 安裝APK
  • 灰度更新:
  • 找單一渠道投放特別版本。
  • 做升級平臺的改造,允許針對部分用戶推送升級通知甚至版本強制升級。
  • 開放單獨的下載入口。
  • 是兩個版本的代碼都打到app包裏,然後在app端植入測試框架,用來控制顯示哪個版本。測試框架負責與服務器端api通信,由服務器端控制app上A/B版本的分佈,可以實現指定的一組用戶看到A版本,其它用戶看到B版本。服務端會有相應的報表來顯示A/B版本的數量和效果對比。最後可以由服務端的後臺來控制,全部用戶在線切換到A或者B版本~
  • 無論哪種方法都需要做好版本管理工作,分配特別的版本號以示區別。 當然,既然是做灰度,數據監控(常規數據、新特性數據、主要業務數據)還是要做到位,該打的數據樁要打。 還有,灰度版最好有收回的能力,一般就是強制升級下一個正式版。
  • 強制更新:

一般的處理就是進入應用就彈窗通知用戶有版本更新,彈窗可以沒有取消按鈕並不能取消。這樣用戶就只能選擇更新或者關閉應用了,當然也可以添加取消按鈕,但是如果用戶選擇取消則直接退出應用。

  • 增量更新:

二進制差分工具bsdiff是相應的補丁合成工具,根據兩個不同版本的二進制文件,生成補丁文件.patch文件。通過bspatch使舊的apk文件與不定文件合成新的apk。 注意通過apk文件的md5值進行區分版本。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章