作者:guoxiaoxing 鏈接: https://github.com/guoxiaoxing/android-interview 本文基于作者采用的MIT協(xié)議分發(fā),。
手畫一下Android系統(tǒng)架構(gòu)圖,,描述一下各個層次的作用?
Android系統(tǒng)架構(gòu)圖
從上到下依次分為六層: 應(yīng)用框架層 進程通信層 系統(tǒng)服務(wù)層 Android運行時層 硬件抽象層 Linux內(nèi)核層
Activity如與Service通信,?
可以通過bindService的方式,,先在Activity里實現(xiàn)一個ServiceConnection接口,并將該接口傳遞給bindService()方法,,在ServiceConnection接口的onServiceConnected()方法 里執(zhí)行相關(guān)操作,。
Service的生命周期與啟動方法有什么區(qū)別?
startService():開啟Service,,調(diào)用者退出后Service仍然存在,。 bindService():開啟Service,調(diào)用者退出后Service也隨即退出,。
Service生命周期: 只是用startService()啟動服務(wù):onCreate() -> onStartCommand() -> onDestory 只是用bindService()綁定服務(wù):onCreate() -> onBind() -> onUnBind() -> onDestory 同時使用startService()啟動服務(wù)與bindService()綁定服務(wù):onCreate() -> onStartCommnad() -> onBind() -> onUnBind() -> onDestory
廣播分為哪幾種,,應(yīng)用場景是什么?
普通廣播:調(diào)用sendBroadcast()發(fā)送,,最常用的廣播,。 有序廣播:調(diào)用sendOrderedBroadcast(),發(fā)出去的廣播會被廣播接受者按照順序接收,,廣播接收者按照Priority屬性值從大-小排序,,Priority屬性相同者,動態(tài)注冊的廣播優(yōu)先,,廣播接收者還可以 選擇對廣播進行截斷和修改,。
廣播的兩種注冊方式有什么區(qū)別?
靜態(tài)注冊:常駐系統(tǒng),,不受組件生命周期影響,,即便應(yīng)用退出,,廣播還是可以被接收,耗電,、占內(nèi)存,。 動態(tài)注冊:非常駐,跟隨組件的生命變化,,組件結(jié)束,,廣播結(jié)束。在組件結(jié)束前,,需要先移除廣播,,否則容易造成內(nèi)存泄漏。
廣播發(fā)送和接收的原理了解嗎,?
繼承BroadcastReceiver,,重寫onReceive()方法。 通過Binder機制向ActivityManagerService注冊廣播,。 通過Binder機制向ActivityMangerService發(fā)送廣播,。 ActivityManagerService查找符合相應(yīng)條件的廣播(IntentFilter/Permission)的BroadcastReceiver,將廣播發(fā)送到BroadcastReceiver所在的消息隊列中,。 BroadcastReceiver所在消息隊列拿到此廣播后,,回調(diào)它的onReceive()方法。
ContentProvider,、ContentResolver與ContentObserver之間的關(guān)系是什么?
ContentProvider:管理數(shù)據(jù),,提供數(shù)據(jù)的增刪改查操作,,數(shù)據(jù)源可以是數(shù)據(jù)庫、文件,、XML,、網(wǎng)絡(luò)等,ContentProvider為這些數(shù)據(jù)的訪問提供了統(tǒng)一的接口,,可以用來做進程間數(shù)據(jù)共享,。 ContentResolver:ContentResolver可以不同URI操作不同的ContentProvider中的數(shù)據(jù),外部進程可以通過ContentResolver與ContentProvider進行交互,。 ContentObserver:觀察ContentProvider中的數(shù)據(jù)變化,,并將變化通知給外界。
遇到過哪些關(guān)于Fragment的問題,,如何處理的,?
getActivity()空指針:這種情況一般發(fā)生在在異步任務(wù)里調(diào)用getActivity(),而Fragment已經(jīng)onDetach(),,此時就會有空指針,,解決方案是在Fragment里使用 一個全局變量mActivity,,在onAttach()方法里賦值,這樣可能會引起內(nèi)存泄漏,,但是異步任務(wù)沒有停止的情況下本身就已經(jīng)可能內(nèi)存泄漏,,相比直接crash,這種方式 顯得更妥當(dāng)一些,。 Fragment視圖重疊:在類onCreate()的方法加載Fragment,,并且沒有判斷saveInstanceState==null或if(findFragmentByTag(mFragmentTag) == null),導(dǎo)致重復(fù)加載了同一個Fragment導(dǎo)致重疊,。(PS:replace情況下,,如果沒有加入回退棧,則不判斷也不會造成重疊,,但建議還是統(tǒng)一判斷下)
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { // 在頁面重啟時,,F(xiàn)ragment會被保存恢復(fù),而此時再加載Fragment會重復(fù)加載,,導(dǎo)致重疊 ; if(saveInstanceState == null){ // 或者 if(findFragmentByTag(mFragmentTag) == null) // 正常情況下去 加載根Fragment } }
Android里的Intent傳遞的數(shù)據(jù)有大小限制嗎,,如何解決?
Intent傳遞數(shù)據(jù)大小的限制大概在1M左右,,超過這個限制就會靜默崩潰,。處理方式如下:
進程內(nèi):EventBus,文件緩存,、磁盤緩存,。 進程間:通過ContentProvider進行款進程數(shù)據(jù)共享和傳遞。
描述一下Android的事件分發(fā)機制,?
Android事件分發(fā)機制的本質(zhì):事件從哪個對象發(fā)出,,經(jīng)過哪些對象,最終由哪個對象處理了該事件,。此處對象指的是Activity,、Window與View。 Android事件的分發(fā)順序:Activity(Window) -> ViewGroup -> View Android事件的分發(fā)主要由三個方法來完成,,如下所示:
// 父View調(diào)用dispatchTouchEvent()開始分發(fā)事件 public boolean dispatchTouchEvent(MotionEvent event){ boolean consume = false; // 父View決定是否攔截事件 if(onInterceptTouchEvent(event)){ // 父View調(diào)用onTouchEvent(event)消費事件,,如果該方法返回true,表示 // 該View消費了該事件,,后續(xù)該事件序列的事件(Down,、Move、Up)將不會在傳遞 // 該其他View,。 consume = onTouchEvent(event); }else{ // 調(diào)用子View的dispatchTouchEvent(event)方法繼續(xù)分發(fā)事件 consume = child.dispatchTouchEvent(event); } return consume; }
描述一下View的繪制原理,?
View的繪制流程主要分為三步:
onMeasure:測量視圖的大小,從頂層父View到子View遞歸調(diào)用measure()方法,,measure()調(diào)用onMeasure()方法,,onMeasure()方法完成繪制工作,。 onLayout:確定視圖的位置,從頂層父View到子View遞歸調(diào)用layout()方法,,父View將上一步measure()方法得到的子View的布局大小和布局參數(shù),,將子View放在合適的位置上。 onDraw:繪制最終的視圖,,首先ViewRoot創(chuàng)建一個Canvas對象,,然后調(diào)用onDraw()方法進行繪制。onDraw()方法的繪制流程為:① 繪制視圖背景,。② 繪制畫布的圖層,。 ③ 繪制View內(nèi)容。 ④ 繪制子視圖,,如果有的話,。⑤ 還原圖層。⑥ 繪制滾動條,。
requestLayout(),、invalidate()與postInvalidate()有什么區(qū)別?
requestLayout():該方法會遞歸調(diào)用父窗口的requestLayout()方法,,直到觸發(fā)ViewRootImpl的performTraversals()方法,,此時mLayoutRequestede為true,會觸發(fā)onMesaure()與onLayout()方法,,不一定 會觸發(fā)onDraw()方法,。 invalidate():該方法遞歸調(diào)用父View的invalidateChildInParent()方法,直到調(diào)用ViewRootImpl的invalidateChildInParent()方法,,最終觸發(fā)ViewRootImpl的performTraversals()方法,,此時mLayoutRequestede為false,不會 觸發(fā)onMesaure()與onLayout()方法,,當(dāng)時會觸發(fā)onDraw()方法。 postInvalidate():該方法功能和invalidate()一樣,,只是它可以在非UI線程中調(diào)用,。
一般說來需要重新布局就調(diào)用requestLayout()方法,需要重新繪制就調(diào)用invalidate()方法,。
了解APK的打包流程嗎,,描述一下?
Android的包文件APK分為兩個部分:代碼和資源,,所以打包方面也分為資源打包和代碼打包兩個方面,,這篇文章就來分析資源和代碼的編譯打包原理。
APK整體的的打包流程如下圖所示:
具體說來:
通過AAPT工具進行資源文件(包括AndroidManifest.xml,、布局文件,、各種xml資源等)的打包,,生成R.java文件。 通過AIDL工具處理AIDL文件,,生成相應(yīng)的Java文件,。 通過Javac工具編譯項目源碼,生成Class文件,。 通過DX工具將所有的Class文件轉(zhuǎn)換成DEX文件,,該過程主要完成Java字節(jié)碼轉(zhuǎn)換成Dalvik字節(jié)碼,壓縮常量池以及清除冗余信息等工作,。 通過ApkBuilder工具將資源文件,、DEX文件打包生成APK文件。 利用KeyStore對生成的APK文件進行簽名,。 如果是正式版的APK,,還會利用ZipAlign工具進行對齊處理,對齊的過程就是將APK文件中所有的資源文件舉例文件的起始距離都偏移4字節(jié)的整數(shù)倍,,這樣通過內(nèi)存映射訪問APK文件 的速度會更快,。
了解APK的安裝流程嗎,描述一下,?
APK的安裝流程如下所示:
復(fù)制APK到/data/app目錄下,,解壓并掃描安裝包。
資源管理器解析APK里的資源文件,。 解析AndroidManifest文件,,并在/data/data/目錄下創(chuàng)建對應(yīng)的應(yīng)用數(shù)據(jù)目錄。 然后對dex文件進行優(yōu)化,,并保存在dalvik-cache目錄下,。 將AndroidManifest文件解析出的四大組件信息注冊到PackageManagerService中。 安裝完成后,,發(fā)送廣播,。
當(dāng)點擊一個應(yīng)用圖標(biāo)以后,都發(fā)生了什么,,描述一下這個過程,?
點擊應(yīng)用圖標(biāo)后會去啟動應(yīng)用的LauncherActivity,如果LancerActivity所在的進程沒有創(chuàng)建,,還會創(chuàng)建新進程,,整體的流程就是一個Activity的啟動流程。
Activity的啟動流程圖(放大可查看)如下所示:
整個流程涉及的主要角色有:
Instrumentation: 監(jiān)控應(yīng)用與系統(tǒng)相關(guān)的交互行為,。 AMS:組件管理調(diào)度中心,,什么都不干,但是什么都管。 ActivityStarter:Activity啟動的控制器,,處理Intent與Flag對Activity啟動的影響,,具體說來有:1 尋找符合啟動條件的Activity,如果有多個,,讓用戶選擇,;2 校驗啟動參數(shù)的合法性;3 返回int參數(shù),,代表Activity是否啟動成功,。 ActivityStackSupervisior:這個類的作用你從它的名字就可以看出來,它用來管理任務(wù)棧,。 ActivityStack:用來管理任務(wù)棧里的Activity,。 ActivityThread:最終干活的人,是ActivityThread的內(nèi)部類,,Activity,、Service、BroadcastReceiver的啟動,、切換,、調(diào)度等各種操作都在這個類里完成。
注:這里單獨提一下ActivityStackSupervisior,,這是高版本才有的類,,它用來管理多個ActivityStack,早期的版本只有一個ActivityStack對應(yīng)著手機屏幕,,后來高版本支持多屏以后,,就有了多個ActivityStack,于是就引入了ActivityStackSupervisior用來管理多個ActivityStack,。
整個流程主要涉及四個進程:
調(diào)用者進程,,如果是在桌面啟動應(yīng)用就是Launcher應(yīng)用進程。 ActivityManagerService等所在的System Server進程,,該進程主要運行著系統(tǒng)服務(wù)組件,。 Zygote進程,該進程主要用來fork新進程,。 新啟動的應(yīng)用進程,,該進程就是用來承載應(yīng)用運行的進程了,它也是應(yīng)用的主線程(新創(chuàng)建的進程就是主線程),,處理組件生命周期、界面繪制等相關(guān)事情,。
有了以上的理解,,整個流程可以概括如下:
點擊桌面應(yīng)用圖標(biāo),Launcher進程將啟動Activity(MainActivity)的請求以Binder的方式發(fā)送給了AMS。 AMS接收到啟動請求后,,交付ActivityStarter處理Intent和Flag等信息,,然后再交給ActivityStackSupervisior/ActivityStack 處理Activity進棧相關(guān)流程。同時以Socket方式請求Zygote進程fork新進程,。 Zygote接收到新進程創(chuàng)建請求后fork出新進程,。 在新進程里創(chuàng)建ActivityThread對象,新創(chuàng)建的進程就是應(yīng)用的主線程,,在主線程里開啟Looper消息循環(huán),,開始處理創(chuàng)建Activity。 ActivityThread利用ClassLoader去加載Activity,、創(chuàng)建Activity實例,,并回調(diào)Activity的onCreate()方法。這樣便完成了Activity的啟動,。
BroadcastReceiver與LocalBroadcastReceiver有什么區(qū)別,?
BroadcastReceiver 是跨應(yīng)用廣播,利用Binder機制實現(xiàn),。 LocalBroadcastReceiver 是應(yīng)用內(nèi)廣播,,利用Handler實現(xiàn),利用了IntentFilter的match功能,,提供消息的發(fā)布與接收功能,,實現(xiàn)應(yīng)用內(nèi)通信,效率比較高,。
Android Handler機制是做什么的,,原理了解嗎?
Android消息循環(huán)流程圖如下所示:
主要涉及的角色如下所示:
Message:消息,,分為硬件產(chǎn)生的消息(例如:按鈕,、觸摸)和軟件產(chǎn)生的消息。 MessageQueue:消息隊列,,主要用來向消息池添加消息和取走消息,。 Looper:消息循環(huán)器,主要用來把消息分發(fā)給相應(yīng)的處理者,。 Handler:消息處理器,,主要向消息隊列發(fā)送各種消息以及處理各種消息。
整個消息的循環(huán)流程還是比較清晰的,,具體說來:
Handler通過sendMessage()發(fā)送消息Message到消息隊列MessageQueue,。 Looper通過loop()不斷提取觸發(fā)條件的Message,并將Message交給對應(yīng)的target handler來處理,。 target handler調(diào)用自身的handleMessage()方法來處理Message,。
事實上,,在整個消息循環(huán)的流程中,并不只有Java層參與,,很多重要的工作都是在C 層來完成的,。我們來看下這些類的調(diào)用關(guān)系。
注:虛線表示關(guān)聯(lián)關(guān)系,,實線表示調(diào)用關(guān)系,。
在這些類中MessageQueue是Java層與C 層維系的橋梁,MessageQueue與Looper相關(guān)功能都通過MessageQueue的Native方法來完成,,而其他虛線連接的類只有關(guān)聯(lián)關(guān)系,,并沒有直接調(diào)用的關(guān)系,它們發(fā)生關(guān)聯(lián)的橋梁是MessageQueue,。
Android Binder機制是做什么的,,為什么選用Binder,原理了解嗎,?
Android Binder是用來做進程通信的,,Android的各個應(yīng)用以及系統(tǒng)服務(wù)都運行在獨立的進程中,它們的通信都依賴于Binder,。
為什么選用Binder,,在討論這個問題之前,我們知道Android也是基于Linux內(nèi)核,,Linux現(xiàn)有的進程通信手段有以下幾種:
管道:在創(chuàng)建時分配一個page大小的內(nèi)存,,緩存區(qū)大小比較有限; 消息隊列:信息復(fù)制兩次,,額外的CPU消耗,;不合適頻繁或信息量大的通信; 共享內(nèi)存:無須復(fù)制,,共享緩沖區(qū)直接付附加到進程虛擬地址空間,,速度快;但進程間的同步問題操作系統(tǒng)無法實現(xiàn),,必須各進程利用同步工具解決,; 套接字:作為更通用的接口,傳輸效率低,,主要用于不通機器或跨網(wǎng)絡(luò)的通信,; 信號量:常作為一種鎖機制,防止某進程正在訪問共享資源時,,其他進程也訪問該資源,。因此,主要作為進程間以及同一進程內(nèi)不同線程之間的同步手段,。6. 信號: 不適用于信息交換,,更適用于進程中斷控制,,比如非法內(nèi)存訪問,殺死某個進程等,;
既然有現(xiàn)有的IPC方式,為什么重新設(shè)計一套Binder機制呢,。主要是出于以上三個方面的考量:
高性能:從數(shù)據(jù)拷貝次數(shù)來看Binder只需要進行一次內(nèi)存拷貝,,而管道、消息隊列,、Socket都需要兩次,,共享內(nèi)存不需要拷貝,Binder的性能僅次于共享內(nèi)存,。 穩(wěn)定性:上面說到共享內(nèi)存的性能優(yōu)于Binder,,那為什么不適用共享內(nèi)存呢,因為共享內(nèi)存需要處理并發(fā)同步問題,,控制負(fù)責(zé),,容易出現(xiàn)死鎖和資源競爭,穩(wěn)定性較差,。而Binder基于C/S架構(gòu),,客戶端與服務(wù)端彼此獨立,穩(wěn)定性較好,。 安全性:我們知道Android為每個應(yīng)用分配了UID,,用來作為鑒別進程的重要標(biāo)志,Android內(nèi)部也依賴這個UID進行權(quán)限管理,,包括6.0以前的固定權(quán)限和6.0以后的動態(tài)權(quán)限,,傳榮IPC只能由用戶在數(shù)據(jù)包里填入UID/PID,這個標(biāo)記完全 是在用戶空間控制的,,沒有放在內(nèi)核空間,,因此有被惡意篡改的可能,因此Binder的安全性更高,。
描述一下Activity的生命周期,,這些生命周期是如何管理的?
Activity與Fragment生命周期如下所示:
讀者可以從上圖看出,,Activity有很多種狀態(tài),,狀態(tài)之間的變化也比較復(fù)雜,在眾多狀態(tài)中,,只有三種是常駐狀態(tài):
Resumed(運行狀態(tài)):Activity處于前臺,,用戶可以與其交互。 Paused(暫停狀態(tài)):Activity被其他Activity部分遮擋,,無法接受用戶的輸入,。 Stopped(停止?fàn)顟B(tài)):Activity被完全隱藏,,對用戶不可見,進入后臺,。
其他的狀態(tài)都是中間狀態(tài),。
我們再來看看生命周期變化時的整個調(diào)度流程,生命周期調(diào)度流程圖如下所示:
所以你可以看到,,整個流程是這樣的:
比方說我們點擊跳轉(zhuǎn)一個新Activity,,這個時候Activity會入棧,同時它的生命周期也會從onCreate()到onResume()開始變換,,這個過程是在ActivityStack里完成的,,ActivityStack 是運行在Server進程里的,這個時候Server進程就通過ApplicationThread的代理對象ApplicationThreadProxy向運行在app進程ApplicationThread發(fā)起操作請求,。 ApplicationThread接收到操作請求后,,因為它是運行在app進程里的其他線程里,所以ApplicationThread需要通過Handler向主線程ActivityThread發(fā)送操作消息,。 主線程接收到ApplicationThread發(fā)出的消息后,,調(diào)用主線程ActivityThread執(zhí)行響應(yīng)的操作,并回調(diào)Activity相應(yīng)的周期方法,。
注:這里提到了主線程ActivityThread,,更準(zhǔn)確來說ActivityThread不是線程,因為它沒有繼承Thread類或者實現(xiàn)Runnable接口,,它是運行在應(yīng)用主線程里的對象,,那么應(yīng)用的主線程 到底是什么呢?從本質(zhì)上來講啟動啟動時創(chuàng)建的進程就是主線程,,線程和進程處理是否共享資源外,,沒有其他的區(qū)別,對于Linux來說,,它們都只是一個struct結(jié)構(gòu)體,。
Activity的通信方式有哪些?
startActivityForResult EventBus LocalBroadcastReceiver
Android應(yīng)用里有幾種Context對象?
Context類圖如下所示:
可以發(fā)現(xiàn)Context是個抽象類,,它的具體實現(xiàn)類是ContextImpl,,ContextWrapper是個包裝類,內(nèi)部的成員變量mBase指向的也是個ContextImpl對象,,ContextImpl完成了 實際的功能,,Activity、Service與Application都直接或者間接的繼承ContextWrapper,。
描述一下進程和Application的生命周期,?
一個安裝的應(yīng)用對應(yīng)一個LoadedApk對象,對應(yīng)一個Application對象,,對于四大組件,,Application的創(chuàng)建和獲取方式也是不盡相同的,,具體說來:
Activity:通過LoadedApk的makeApplication()方法創(chuàng)建。 Service:通過LoadedApk的makeApplication()方法創(chuàng)建,。 靜態(tài)廣播:通過其回調(diào)方法onReceive()方法的第一個參數(shù)指向Application,。 ContentProvider:無法獲取Application,因此此時Application不一定已經(jīng)初始化,。
Android哪些情況會導(dǎo)致內(nèi)存泄漏,,如何分析內(nèi)存泄漏?
常見的產(chǎn)生內(nèi)存泄漏的情況如下所示:
持有靜態(tài)的Context(Activity)引用,。 持有靜態(tài)的View引用, 內(nèi)部類&匿名內(nèi)部類實例無法釋放(有延遲時間等等),,而內(nèi)部類又持有外部類的強引用,,導(dǎo)致外部類無法釋放,這種匿名內(nèi)部類常見于監(jiān)聽器,、Handler,、Thread、TimerTask 資源使用完成后沒有關(guān)閉,,例如:BraodcastReceiver,,ContentObserver,F(xiàn)ile,,Cursor,,Stream,Bitmap,。 不正確的單例模式,,比如單例持有Activity。 集合類內(nèi)存泄漏,,如果一個集合類是靜態(tài)的(緩存HashMap),,只有添加方法,沒有對應(yīng)的刪除方法,,會導(dǎo)致引用無法被釋放,,引發(fā)內(nèi)存泄漏。 錯誤的覆寫了finalize()方法,,finalize()方法執(zhí)行執(zhí)行不確定,,可能會導(dǎo)致引用無法被釋放。
查找內(nèi)存泄漏可以使用Android Profiler工具或者利用LeakCanary工具,。
Android有哪幾種進程,,是如何管理的?
Android的進程主要分為以下幾種:
前臺進程
用戶當(dāng)前操作所必需的進程,。如果一個進程滿足以下任一條件,,即視為前臺進程:
托管用戶正在交互的 Activity(已調(diào)用 Activity 的 onResume() 方法) 托管某個 Service,,后者綁定到用戶正在交互的 Activity 托管正在“前臺”運行的 Service(服務(wù)已調(diào)用 startForeground()) 托管正執(zhí)行一個生命周期回調(diào)的 Service(onCreate()、onStart() 或 onDestroy()) 托管正執(zhí)行其 onReceive() 方法的 BroadcastReceiver
通常,,在任意給定時間前臺進程都為數(shù)不多,。只有在內(nèi)存不足以支持它們同時繼續(xù)運行這一萬不得已的情況下,系統(tǒng)才會終止它們,。 此時,,設(shè)備往往已達到內(nèi)存分頁狀態(tài),因此需要終止一些前臺進程來確保用戶界面正常響應(yīng),。
可見進程
沒有任何前臺組件,、但仍會影響用戶在屏幕上所見內(nèi)容的進程。 如果一個進程滿足以下任一條件,,即視為可見進程:
托管不在前臺,、但仍對用戶可見的 Activity(已調(diào)用其 onPause() 方法)。例如,,如果前臺 Activity 啟動了一個對話框,,允許在其后顯示上一 Activity,則有可能會發(fā)生這種情況,。 托管綁定到可見(或前臺)Activity 的 Service,。
可見進程被視為是極其重要的進程,除非為了維持所有前臺進程同時運行而必須終止,,否則系統(tǒng)不會終止這些進程,。
服務(wù)進程
正在運行已使用 startService() 方法啟動的服務(wù)且不屬于上述兩個更高類別進程的進程。盡管服務(wù)進程與用戶所見內(nèi)容沒有直接關(guān)聯(lián),,但是它們通常在執(zhí)行一些用戶關(guān) 心的操作(例如,,在后臺播放音樂或從網(wǎng)絡(luò)下載數(shù)據(jù))。因此,,除非內(nèi)存不足以維持所有前臺進程和可見進程同時運行,,否則系統(tǒng)會讓服務(wù)進程保持運行狀態(tài)。
后臺進程
包含目前對用戶不可見的 Activity 的進程(已調(diào)用 Activity 的 onStop() 方法),。這些進程對用戶體驗沒有直接影響,,系統(tǒng)可能隨時終止它們,以回收內(nèi)存供前臺進程,、可見進程或服務(wù)進程使用,。 通常會有很多后臺進程在運行,因此它們會保存在 LRU (最近最少使用)列表中,,以確保包含用戶最近查看的 Activity 的進程最后一個被終止,。如果某個 Activity 正確實現(xiàn)了生命周期方法,并保存了其當(dāng)前狀態(tài),則終止其進程不會對用戶體驗產(chǎn)生明顯影響,,因為當(dāng)用戶導(dǎo)航回該 Activity 時,,Activity 會恢復(fù)其所有可見狀態(tài)。
空進程
不含任何活動應(yīng)用組件的進程,。保留這種進程的的唯一目的是用作緩存,,以縮短下次在其中運行組件所需的啟動時間。 為使總體系統(tǒng)資源在進程緩存和底層內(nèi)核緩存之間保持平衡,,系統(tǒng)往往會終止這些進程,。
ActivityManagerService負(fù)責(zé)根據(jù)各種策略算法計算進程的adj值,然后交由系統(tǒng)內(nèi)核進行進程的管理,。
SharePreference性能優(yōu)化,,可以做進程同步嗎?
在Android中, SharePreferences是一個輕量級的存儲類,,特別適合用于保存軟件配置參數(shù),。使用SharedPreferences保存數(shù)據(jù),其背后是用xml文件存放數(shù)據(jù),,文件 存放在/data/data/ < package name > /shared_prefs目錄下.
之所以說SharedPreference是一種輕量級的存儲方式,是因為它在創(chuàng)建的時候會把整個文件全部加載進內(nèi)存,,如果SharedPreference文件比較大,,會帶來以下問題:
第一次從sp中獲取值的時候,有可能阻塞主線程,,使界面卡頓,、掉幀。 解析sp的時候會產(chǎn)生大量的臨時對象,,導(dǎo)致頻繁GC,,引起界面卡頓。 這些key和value會永遠存在于內(nèi)存之中,,占用大量內(nèi)存,。
優(yōu)化建議
不要存放大的key和value,會引起界面卡,、頻繁GC,、占用內(nèi)存等等。 毫不相關(guān)的配置項就不要放在在一起,,文件越大讀取越慢,。 讀取頻繁的key和不易變動的key盡量不要放在一起,影響速度,,如果整個文件很小,,那么忽略吧,為了這點性能添加維護成本得不償失,。 不要亂edit和apply,,盡量批量修改一次提交,,多次apply會阻塞主線程。 盡量不要存放JSON和HTML,,這種場景請直接使用JSON,。 SharedPreference無法進行跨進程通信,MODE_MULTI_PROCESS只是保證了在API 11以前的系統(tǒng)上,,如果sp已經(jīng)被讀取進內(nèi)存,,再次獲取這個SharedPreference的時候,如果有這個flag,,會重新讀一遍文件,,僅此而已。
如何做SQLite升級,?
數(shù)據(jù)庫升級增加表和刪除表都不涉及數(shù)據(jù)遷移,,但是修改表涉及到對原有數(shù)據(jù)進行遷移。升級的方法如下所示:
將現(xiàn)有表命名為臨時表,。 創(chuàng)建新表,。 將臨時表的數(shù)據(jù)導(dǎo)入新表。 刪除臨時表,。
如果是跨版本數(shù)據(jù)庫升級,,可以由兩種方式,如下所示:
逐級升級,,確定相鄰版本與現(xiàn)在版本的差別,,V1升級到V2,V2升級到V3,依次類推,。 跨級升級,,確定每個版本與現(xiàn)在數(shù)據(jù)庫的差別,為每個case編寫專門升級大代碼,。
進程保護如何做,,如何喚醒其他進程?
進程?;钪饕袃蓚€思路:
提升進程的優(yōu)先級,,降低進程被殺死的概率。 拉活已經(jīng)被殺死的進程,。
如何提升優(yōu)先級,,如下所示:
監(jiān)控手機鎖屏事件,在屏幕鎖屏?xí)r啟動一個像素的Activity,,在用戶解鎖時將Activity銷毀掉,,前臺Activity可以將進程變成前臺進程,優(yōu)先級升級到最高。
如果拉活 利用廣播拉活A(yù)ctivity,。
理解序列化嗎,,Android為什么引入Parcelable?
所謂序列化就是將對象變成二進制流,,便于存儲和傳輸,。
Serializable是java實現(xiàn)的一套序列化方式,可能會觸發(fā)頻繁的IO操作,,效率比較低,,適合將對象存儲到磁盤上的情況。 Parcelable是Android提供一套序列化機制,,它將序列化后的字節(jié)流寫入到一個共性內(nèi)存中,,其他對象可以從這塊共享內(nèi)存中讀出字節(jié)流,并反序列化成對象,。因此效率比較高,,適合在對象間或者進程間傳遞信息。
如何計算一個Bitmap占用內(nèi)存的大小,,怎么保證加載Bitmap不產(chǎn)生內(nèi)存溢出,?Bitamp 占用內(nèi)存大小 = 寬度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一個像素所占的內(nèi)存.
注:這里inDensity表示目標(biāo)圖片的dpi(放在哪個資源文件夾下),inTargetDensity表示目標(biāo)屏幕的dpi,,所以你可以發(fā)現(xiàn)inDensity和inTargetDensity會對Bitmap的寬高 進行拉伸,,進而改變Bitmap占用內(nèi)存的大小。
在Bitmap里有兩個獲取內(nèi)存占用大小的方法,。 getByteCount():API12 加入,代表存儲 Bitmap 的像素需要的最少內(nèi)存,。 getAllocationByteCount():API19 加入,,代表在內(nèi)存中為 Bitmap 分配的內(nèi)存大小,代替了 getByteCount() 方法,。
在不復(fù)用 Bitmap 時,,getByteCount() 和 getAllocationByteCount 返回的結(jié)果是一樣的。在通過復(fù)用 Bitmap 來解碼圖片時,,那么 getByteCount() 表示新解碼圖片占用內(nèi)存的大 小,,getAllocationByteCount() 表示被復(fù)用 Bitmap真實占用的內(nèi)存大小(即 mBuffer 的長度),。
為了保證在加載Bitmap的時候不產(chǎn)生內(nèi)存溢出,,可以受用BitmapFactory進行圖片壓縮,主要有以下幾個參數(shù):
BitmapFactory.Options.inPreferredConfig:將ARGB_8888改為RGB_565,,改變編碼方式,,節(jié)約內(nèi)存。 BitmapFactory.Options.inSampleSize:縮放比例,可以參考Luban那個庫,,根據(jù)圖片寬高計算出合適的縮放比例,。 BitmapFactory.Options.inPurgeable:讓系統(tǒng)可以內(nèi)存不足時回收內(nèi)存。
Android如何在不壓縮的情況下加載高清大圖,?
使用BitmapRegionDecoder進行布局加載,。
Android里的內(nèi)存緩存和磁盤緩存是怎么實現(xiàn)的?
內(nèi)存緩存基于LruCache實現(xiàn),磁盤緩存基于DiskLruCache實現(xiàn),。這兩個類都基于Lru算法和LinkedHashMap來實現(xiàn),。
LRU算法可以用一句話來描述,如下所示:
LRU是Least Recently Used的縮寫,,最近最久未使用算法,,從它的名字就可以看出,它的核心原則是如果一個數(shù)據(jù)在最近一段時間沒有使用到,,那么它在將來被 訪問到的可能性也很小,,則這類數(shù)據(jù)項會被優(yōu)先淘汰掉。
LruCache的原理是利用LinkedHashMap持有對象的強引用,,按照Lru算法進行對象淘汰,。具體說來假設(shè)我們從表尾訪問數(shù)據(jù),在表頭刪除數(shù)據(jù),,當(dāng)訪問的數(shù)據(jù)項在鏈表中存在時,,則將該數(shù)據(jù)項移動到表尾,否則在表尾新建一個數(shù)據(jù)項,。當(dāng)鏈表容量超過一定閾值,,則移除表頭的數(shù)據(jù)。
為什么會選擇LinkedHashMap呢,?
這跟LinkedHashMap的特性有關(guān),,LinkedHashMap的構(gòu)造函數(shù)里有個布爾參數(shù)accessOrder,當(dāng)它為true時,,LinkedHashMap會以訪問順序為序排列元素,,否則以插入順序為序排序元素。
DiskLruCache與LruCache原理相似,,只是多了一個journal文件來做磁盤文件的管理和迎神,,如下所示:
libcore.io.DiskLruCache 1 1 1 DIRTY 1517126350519 CLEAN 1517126350519 5325928 REMOVE 1517126350519
注:這里的緩存目錄是應(yīng)用的緩存目錄/data/data/pckagename/cache,未root的手機可以通過以下命令進入到該目錄中或者將該目錄整體拷貝出來:
//進入/data/data/pckagename/cache目錄 adb shell run-as com.your.packagename cp /data/data/com.your.packagename/ //將/data/data/pckagename目錄拷貝出來 adb backup -noapk com.your.packagename
我們來分析下這個文件的內(nèi)容:
第一行:libcore.io.DiskLruCache,,固定字符串,。 第二行:1,DiskLruCache源碼版本號,。 第三行:1,,App的版本號,,通過open()方法傳入進去的。 第四行:1,,每個key對應(yīng)幾個文件,,一般為1. 第五行:空行 第六行及后續(xù)行:緩存操作記錄。
第六行及后續(xù)行表示緩存操作記錄,,關(guān)于操作記錄,,我們需要了解以下三點:
DIRTY 表示一個entry正在被寫入。寫入分兩種情況,,如果成功會緊接著寫入一行CLEAN的記錄,;如果失敗,會增加一行REMOVE記錄,。注意單獨只有DIRTY狀態(tài)的記錄是非法的,。 當(dāng)手動調(diào)用remove(key)方法的時候也會寫入一條REMOVE記錄。 READ就是說明有一次讀取的記錄,。 CLEAN的后面還記錄了文件的長度,,注意可能會一個key對應(yīng)多個文件,那么就會有多個數(shù)字,。
PathClassLoader與DexClassLoader有什么區(qū)別,?
PathClassLoader:只能加載已經(jīng)安裝到Android系統(tǒng)的APK文件,即/data/app目錄,,Android默認(rèn)的類加載器,。 DexClassLoader:可以加載任意目錄下的dex、jar,、apk,、zip文件。
WebView優(yōu)化了解嗎,,如何提高WebView的加載速度,?
為什么WebView加載會慢呢?
這是因為在客戶端中,,加載H5頁面之前,需要先初始化WebView,,在WebView完全初始化完成之前,,后續(xù)的界面加載過程都是被阻塞的。
優(yōu)化手段圍繞著以下兩個點進行:
預(yù)加載WebView,。 加載WebView的同時,,請求H5頁面數(shù)據(jù)。
因此常見的方法是:
全局WebView,。 客戶端代理頁面請求,。WebView初始化完成后向客戶端請求數(shù)據(jù),。 asset存放離線包。
除此之外還有一些其他的優(yōu)化手段:
腳本執(zhí)行慢,,可以讓腳本最后運行,,不阻塞頁面解析。 DNS與鏈接慢,,可以讓客戶端復(fù)用使用的域名與鏈接,。 React框架代碼執(zhí)行慢,可以將這部分代碼拆分出來,,提前進行解析,。
Java和JS的相互調(diào)用怎么實現(xiàn),有做過什么優(yōu)化嗎,?
jockeyjs:https://github.com/tcoulter/jockeyjs 對協(xié)議進行統(tǒng)一的封裝和處理,。
JNI了解嗎,Java與C 如何相互調(diào)用,?
Java調(diào)用C
在Java中聲明Native方法(即需要調(diào)用的本地方法) 編譯上述 Java源文件javac(得到 .class文件) 3,。 通過 javah 命令導(dǎo)出JNI的頭文件(.h文件) 使用 Java需要交互的本地代碼 實現(xiàn)在 Java中聲明的Native方法 編譯.so庫文件 通過Java命令執(zhí)行 Java程序,最終實現(xiàn)Java調(diào)用本地代碼
C 調(diào)用Java
從classpath路徑下搜索ClassMethod這個類,,并返回該類的Class對象,。 獲取類的默認(rèn)構(gòu)造方法ID。 查找實例方法的ID,。 創(chuàng)建該類的實例,。 調(diào)用對象的實例方法。
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod (JNIEnv *env, jclass cls) { jclass clazz = NULL; jobject jobj = NULL; jmethodID mid_construct = NULL; jmethodID mid_instance = NULL; jstring str_arg = NULL; // 1,、從classpath路徑下搜索ClassMethod這個類,,并返回該類的Class對象 clazz = (*env)->FindClass(env, 'com/study/jnilearn/ClassMethod'); if (clazz == NULL) { printf('找不到'com.study.jnilearn.ClassMethod'這個類'); return; } // 2、獲取類的默認(rèn)構(gòu)造方法ID mid_construct = (*env)->GetMethodID(env,clazz, '<init>','()V'); if (mid_construct == NULL) { printf('找不到默認(rèn)的構(gòu)造方法'); return; } // 3,、查找實例方法的ID mid_instance = (*env)->GetMethodID(env, clazz, 'callInstanceMethod', '(Ljava/lang/String;I)V'); if (mid_instance == NULL) { return; } // 4,、創(chuàng)建該類的實例 jobj = (*env)->NewObject(env,clazz,mid_construct); if (jobj == NULL) { printf('在com.study.jnilearn.ClassMethod類中找不到callInstanceMethod方法'); return; } // 5、調(diào)用對象的實例方法 str_arg = (*env)->NewStringUTF(env,'我是實例方法'); (*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200); // 刪除局部引用 (*env)->DeleteLocalRef(env,clazz); (*env)->DeleteLocalRef(env,jobj); (*env)->DeleteLocalRef(env,str_arg); }
了解插件化和熱修復(fù)嗎,,它們有什么區(qū)別,,理解它們的原理嗎?
插件化:插件化是體現(xiàn)在功能拆分方面的,,它將某個功能獨立提取出來,,獨立開發(fā),獨立測試,,再插入到主應(yīng)用中,。依次來較少主應(yīng)用的規(guī)模。 熱修復(fù):熱修復(fù)是體現(xiàn)在bug修復(fù)方面的,,它實現(xiàn)的是不需要重新發(fā)版和重新安裝,,就可以去修復(fù)已知的bug,。
利用PathClassLoader和DexClassLoader去加載與bug類同名的類,替換掉bug類,,進而達到修復(fù)bug的目的,,原理是在app打包的時候阻止類打上CLASS_ISPREVERIFIED標(biāo)志,然后在熱修復(fù)的時候動態(tài)改變BaseDexClassLoader對象間接引用的dexElements,,替換掉舊的類,。
如何做性能優(yōu)化?
節(jié)制的使用Service,,當(dāng)啟動一個Service時,,系統(tǒng)總是傾向于保留這個Service依賴的進程,這樣會造成系統(tǒng)資源的浪費,,可以使用IntentService,,執(zhí)行完成任務(wù)后會自動停止。 當(dāng)界面不可見時釋放內(nèi)存,,可以重寫Activity的onTrimMemory()方法,,然后監(jiān)聽TRIM_MEMORY_UI_HIDDEN這個級別,這個級別說明用戶離開了頁面,,可以考慮釋放內(nèi)存和資源,。 避免在Bitmap浪費過多的內(nèi)存,使用壓縮過的圖片,,也可以使用Fresco等庫來優(yōu)化對Bitmap顯示的管理,。 使用優(yōu)化過的數(shù)據(jù)集合SparseArray代替HashMap,HashMap為每個鍵值都提供一個對象入口,,使用SparseArray可以免去基本對象類型轉(zhuǎn)換為引用數(shù)據(jù)類想的時間,。
如果防止過度繪制,如何做布局優(yōu)化,?
使用include復(fù)用布局文件,。 使用merge標(biāo)簽避免嵌套布局。 使用stub標(biāo)簽僅在需要的時候在展示出來,。
如何提交代碼質(zhì)量,?
避免創(chuàng)建不必要的對象,盡可能避免頻繁的創(chuàng)建臨時對象,,例如在for循環(huán)內(nèi),,減少GC的次數(shù)。 盡量使用基本數(shù)據(jù)類型代替引用數(shù)據(jù)類型,。 靜態(tài)方法調(diào)用效率高于動態(tài)方法,也可以避免創(chuàng)建額外對象,。 對于基本數(shù)據(jù)類型和String類型的常量要使用static final修飾,,這樣常量會在dex文件的初始化器中進行初始化,,使用的時候可以直接使用。 多使用系統(tǒng)API,,例如數(shù)組拷貝System.arrayCopy()方法,,要比我們用for循環(huán)效率快9倍以上,因為系統(tǒng)API很多都是通過底層的匯編模式執(zhí)行的,,效率比較高,。
有沒有遇到64k問題,為什么,,如何解決,?
在DEX文件中,method,、field,、class等的個數(shù)使用short類型來做索引,即兩個字節(jié)(65535),,method,、field、class等均有此限制,。 APK在安裝過程中會調(diào)用dexopt將DEX文件優(yōu)化成ODEX文件,,dexopt使用LinearAlloc來存儲應(yīng)用信息,關(guān)于LinearAlloc緩沖區(qū)大小,,不同的版本經(jīng)歷了4M/8M/16M的限制,,超出 緩沖區(qū)時就會拋出INSTALL_FAILED_DEXOPT錯誤。
解決方案是Google的MultiDex方案,,具體參見:配置方法數(shù)超過 64K 的應(yīng)用,。
MVC、MVP與MVVM之間的對比分析,?
MVC:PC時代就有的架構(gòu)方案,,在Android上也是最早的方案,Activity/Fragment這些上帝角色既承擔(dān)了V的角色,,也承擔(dān)了C的角色,,小項目開發(fā)起來十分順手,大項目就會遇到 耦合過重,,Activity/Fragment類過大等問題,。 MVP:為了解決MVC耦合過重的問題,MVP的核心思想就是提供一個Presenter將視圖邏輯I和業(yè)務(wù)邏輯相分離,,達到解耦的目的,。 MVVM:使用ViewModel代替Presenter,實現(xiàn)數(shù)據(jù)與View的雙向綁定,,這套框架最早使用的data-binding將數(shù)據(jù)綁定到xml里,,這么做在大規(guī)模應(yīng)用的時候是不行的,,不過數(shù)據(jù)綁定是
一個很有用的概念,后續(xù)Google又推出了ViewModel組件與LiveData組件,。ViewModel組件規(guī)范了ViewModel所處的地位,、生命周期、生產(chǎn)方式以及一個Activity下多個Fragment共享ViewModel數(shù)據(jù)的問題,。LiveData組件則提供了在Java層面View訂閱ViewModel數(shù)據(jù)源的實現(xiàn)方案,。
本文僅是作者開源項目的一部分,整體包含:
大家可以關(guān)注作者庫一起維護: https://github.com/guoxiaoxing/android-interview
PS: 別人給出的答案始終是參考答案,,一定要自己去親身驗證和總結(jié),。
幫朋友插個內(nèi)推招聘,頭條懂車帝團隊招人,。
職位描述 1,、負(fù)責(zé)公司移動產(chǎn)品的研發(fā), 編寫高質(zhì)量的代碼; 2,、和產(chǎn)品經(jīng)理配合, 深度參與手機產(chǎn)品需求討論, 功能定義等,; 3、設(shè)計良好的代碼結(jié)構(gòu), 不斷迭代重構(gòu) ,; 4,、指導(dǎo)并帶領(lǐng)初級工程師共同完成研發(fā)任務(wù)。
職位要求經(jīng)驗3-5年,,學(xué)歷本科及以上 1,、智能手機愛好者和使用者, 追求良好的用戶體驗; 2,、熱愛移動產(chǎn)品研發(fā), 愿意在移動開發(fā)領(lǐng)域深入鉆研, 并成為專家,; 3、熟練掌握J(rèn)AVA, 熟悉Android SDK,; 4,、兩年以上Android開發(fā)經(jīng)驗, 能獨立開發(fā)Android App; 5,、對軟件產(chǎn)品有強烈的責(zé)任心, 具備良好的溝通能力和優(yōu)秀的團隊協(xié)作能力,。
簡歷直接發(fā)到即可: [email protected]
|