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