Ⅰ 【android】Android中的類載入
前文: 【java】ClassLoader與雙親委派機制
Android中的類載入器有三種, DexClassLoader 、 PathClassLoader 、 BootClassLoader 。
其中 BootClassLoader 是系統啟動時預載入常用類的,一般使用不到。 DexClassLoader 、 PathClassLoader 都是繼承自 BaseDexClassLoader 。
但 DexClassLoader 和 PathClassLoader 並沒有重寫 BaseDexClassLoader 中的任何方法,所以源碼只需要看 BaseDexClassLoader 即可。
由於Android SDK並沒有包含 BaseDexClassLoader ,所以需要到源碼查詢網站查詢源碼,如下:
復制這個java文件到對應源碼文件夾下就可以在Android Studio中查看了。
通過調試可以看到,Android中普通類的載入器其實是 PathClassLoader 。追蹤 PathClassLoader.findClass 方法,即可獲取Android的類載入過程:
PathClassLoader.findClass -- 繼承自 --> BaseDexClassLoader.findClass()
-> BaseDexClassLoader.pathList.findClass()
-> DexPathList.dexElements.foreach { element.findClass() }
-> Element.findClass()
-> Element.dexFile.loadClassBinaryName()
-> DexFile.defineClass()
即類載入過程通過 BaseDexClassLoader.findClass 、 DexPathList.findClass 、 Element.findClass 、 DexFile.loadClassBinaryName ,最終會落到 DexFile.defineClass 方法中,然後就交給native層了。
其中需要注意的是,在 BaseDexClassLoader.findClass 的開頭有這么一段:
這段是在Android 10新加入的,據稱是為了實現 shared library 功能的,在之前的版本中沒首枯有這一段。
在上一節中知道了,類載入的流程如下:
BaseDexClassLoader.findClass() ->
BaseDexClassLoader.pathList.findClass() ->
DexPathList.dexElements.foreach { element.findClass() } ->
Element.findClass() -> ...
看 DexPathList.findClass 方法:
可以發現, DexPathList 載入類的方法是遍歷 dexElements 數組依次載入,知道獲取到值為止。所以可迅叢以通過修改這個數組,把新的dex文件放在數組的前面,使其載入修改後的類者昌洞,從而實現熱修復。
根據以上原理,寫下這個工具類,有效性待驗證:
Ⅱ Android啟動優化概述
Android啟動應用, 按 官方說法 分為冷啟動, 溫啟動和熱啟動.
具體的定義可以看官方文檔, 簡單地說
一般我們只需要關注冷啟動即可.
要想啟動快, 硬體性能必然有影響, 在硬體一定的前提下, 我們要盡量 降低啟動應用時CPU的負載 , 讓CPU有更多的算力投入到啟動流程中:
在做好一些基本原則後, 接著看具體的流程優化點
在應用進程創建後, 首先必然是載入類, 此時一些靜態變數就會初始化了, 因此我們應該
類載入完畢後就是創建 Application 實例了, 因此我們應該
之後會先創建 ContentProvider 和執行 ContentProvider.onCreate() , 因此我們應該
跟接著就會執行 Application.onCreate() 等方法, 因此我們應該
接著就進入 Activity 環節.
同樣第一步會是創建實例, 因此我們應該
在 Activity 進程生命周期後, 第一步就是渲染(inflate)布局, 我們應該
在應用啟動的瞬間, 系統服納虧務會先展示一個空白窗口哪茄殲, 等待應用第一幀繪制完畢後, 再從該窗口切換到應用, 如果啟動耗時較長, 就會明顯看到白屏, 對於這一點, 常見的操作有
可以使用IdleHandler, 在主線程空閑時再執行某些不重要的操作
實際上非同步初始化只是不阻塞主線程, 但是子線程一樣會佔用CPU資源, 讓主線程的執行時間變少, 所以不應該盲目地將所有工作放到子線程.
優化做到最後, 就是在系統流程上做文章了
原理是將啟動時載入的類放到主dex,提升了這些類的內聚,讓更多的類滿足pre-verify的條件,在安裝時就做了校驗和優化,以減少首次載入的耗時,從而優化冷啟動耗時。
Redex 初探與 Interdex:Andorid 冷啟動優化
應用啟動過程中會從apk壓縮包中讀取文件, 該優化的原理是利用Linux中的Pagecache機制, 讓啟動過程會用到的文件盡可能進入緩存中, 減少磁碟IO次數
支付寶 App 構建優化解析:通過安裝包重排布優化 Android 端啟動性能
在Dalvik VM(Android5.0以前)載入類的時候會有一個類校驗過程, 它需要校驗方李沖法的每一個指令, 是一個比較耗時的過程, 可以通過Hook去掉類載入過程中的類驗證過程. 不過對於ART(Android5.0之後)來說, 這個過程在安裝時已經做了, 所以用處不大.
不進入冷啟動, 就不用優化了~
這個Android Studio自帶的工具, 可以看到啟動過程中詳細的方法執行流程, 但是採集數據本身會影響方法執行, 所以不能准確判斷每個方法的耗時, 但是仍可以判斷哪個方法相對來說耗時.
這個工具的好處是可以自定義事件, 可以指定需要採集的數據集, 可以看到線程間的狀態等.
啟動優化的一個關鍵點在於定義啟動結束的點, 以及如何測量啟動時間.
在Android4.4以上, 系統進程會提供一個類似 ActivityManager: Displayed ***: +3s534ms 的日誌, 表示從啟動進程到首次繪制完畢所用的時間.
應用可以在任何時候調用該方法, 觸發系統列印類似 system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms 的日誌
應用可以通過 ViewTreeObserver 來監聽繪制前回調來判斷第一幀的繪制時機, 或者直接在控制項樹的末尾加一個簡單的View, 它 onDraw 調用時即表示頁面(差不多)繪制完畢.
應用啟動過程可以參考 Android Vitals Series' Articles 系列文章
Ⅲ Android開發 頁面載入慢的問題
小圖片載入理論上不會影響載入速度的,你們的項目是否在Ui線程進行了很多其他的操作導致了頁面載入慢的結果。
Ⅳ Android 性能優化之啟動加速
當點擊app的啟動圖標時,安卓系統會從Zygote進程中fork創建出一個新的進程分配給該應用,之後會依次創建和初始化Application類、創建MainActivity類、載入主題樣式Theme中的
windowBackground等屬性設置給MainActivity以及配置Activity層級上的一些屬性、再inflate布局、當onCreate/onStart/onResume方法都走完了後最後才進行contentView的measure/layout/draw顯示在界面上,所以直到這里,
應用的第一次啟動才算完成,這時候我們看到的界面也就是所說的第一幀。所以,總結一下,應用的啟動流程如下:
Application的構造器方法——>attachBaseContext()——>onCreate()——>Activity的構造方法——>onCreate()——>配置主題中背景等屬性——>onStart()——>onResume()——>測量布局繪制顯示在界面上。
1、冷啟動:當啟動應用時,後台沒有該應用的進程,這腔滑晌時系統會重新創建一個新的進程分配給該應用,這個啟動方式就是冷啟動。
2、熱啟動:當啟動應用時,後台已有該應用伍鋒的進程(例:按back鍵、在已有進程的情況下,這種啟動會從已有的進程中來啟動應用,這個方式叫熱啟動
1、冷啟動:冷啟動因為系統會重新創建一個新的進程分配給它,所以會先創建和初始化Application類,再創建和初始化MainActivity類(包括一系列的測量、布局、繪制),最後顯示在界面上。
2、熱啟動:熱啟動因為會從已有的進程中來啟動,所以熱啟動就不會走Application這步了,而是直接走MainActivity(包括一系列的測量、布局、繪制),所以熱啟動的過程只需要創建和初始化一個MainActivity就行了,而不必創建和初始化Application
黑白屏產生原因:當我們在啟讓清動一個應用時,系統會去檢查是否已經存在這樣一個進程,如果不存在,系統的服務會先檢查startActivity 中的intent 的信息,然後在去創建進程,最後啟動Acitivy,即冷啟動。
而啟動出現白黑屏的問題,就是在這段時間內產生的。系統在繪制頁面載入布局之前,首先會初始化窗口(Window),而在進行這一步操作時,系統會根據我們設
置的Theme 來指定它的Theme 主題顏色,我們在Style 中的設置就決定了顯示的是白屏還是黑屏。
1.Application 優化(懶載入,延時載入)
2.UI效果,背景圖
3.fragment的懶載入
4.延時載入
Ⅳ 熱更新是什麼意思
問題一:熱更新是什麼意思? 最簡單的解釋就是不關軟體直接更新,更新期間軟體直接用,現在大部分軟體是山臘孝冷更新,要退出才能更新
問題二:ios熱更新是什麼意思 ios為什麼要移除熱更新 iso10的熱度還沒有過去,iOS10.2 Beta3就已經推出來了.那麼大家肯定想要知道ios10.2更新了什麼?ios10.2怎麼樣?下面我給大家帶來的是iOS10.2 Beta3詳細介紹,有興趣的朋友快來看看吧!iOS10.2 Beta3簡介iOS10.2開發者預覽版Beta3固件更新,這是iOS10.2第三個開發者預覽版別,此外,iOS10.2公測版Beta3也同步推送,依照常規,兩個版別更新內容一致,本次更新固件編號為14C5077b.iOS10.2 Beta3功能在 iOS 10.2 beta 3 中,蘋果現已刪除了視頻 App (由於 TV App 的呈現).而 iOS 10.1 中呈現視頻運用的告訴插件也被移除,取而代之的是 TV 運用的告訴插件.而在這一版中,蘋果移除了 SOS 緊迫呼叫功用本來能夠經過接連多次按下電源鍵呼叫緊迫效勞功用.而在蘋果的發布注意事項中說到, SOS 功用現在僅在印度可用.iOS10.2 Beta3更新內容SOS緊迫救助功能移除,該功能僅在印度區域可用.視頻運用移除,視頻運用告訴插件移除.電視運用(國行稱號)中用戶能夠挑選是不是運用數據播映視頻,用戶在采購視頻資本時可挑選HD或許SD畫質,現在國行版無法運用,需求切換到美區才幹進行體會.iMessage新增愛心全屏特效.以上就是我給大家帶來的是iOS10.2 Beta3詳細介紹,沒看我的文章之前還有疑問的網友們,現在看了我的文章還會不懂嗎?我認為這篇文章是對大家有所幫助的,大家有任何疑問可以在下方留言哦!
問題三:蘋果手機的熱更新是什麼意思 請解答的詳細點 就是不經過APP商店,直接在應用內更新,如一些游戲,經常會更新數據包。
問題四:熱更新真的那麼重要嗎 背景 相信使用 Node.js 過 Web 應用的同學一定苦惱過新修改的代碼必須要重啟 Node.js 進程後才能更新的問題。習慣使用 PHP 的同學更會非常的不適用,大呼果然還是我大PHP才是世界上最好的編程語言。手動重啟進程不僅僅是非常惱人的重復勞動,當應用規模稍大以後,啟動時間也逐漸開始不容忽視。 當然作為程序猿,無論使用哪種語言,都不會讓這樣的事情折磨自己。解決這類問題最直接和普適的手段就是監聽文件修改並重啟進程。這個方法也已經有很多成熟的解決方案提供了,比如已經被棄坑的 node-supervisor,以及現在比較火的 PM2 ,或者比較輕量級的 node-dev 等等均是這樣的思路。 本文則提供了另外一種思路,只需要很小的改造,就可以實現真正的0重啟熱更新代碼,解決 Node.js Web 應用時惱人的代碼更新問題。 總體思路 說起代碼熱更新,逗稿當下最有名的當屬 Erlang 語言的熱更新功能,這門語言的特色在於高並發和分布式編程,主要的應用場景則是類似證券交易、游戲服務端等領域。這些場景都或多或少要求服務擁有在運行中運維的手段,而代碼熱更新就是其中非常重要的一環,因此我們可以先簡單的了解一下 Erlang 的做法。 由於我也沒有使用過 Erlang ,以下內容均為道聽途說,如果希望深入和准確的了解 Erlang 的代碼熱更新實現,最好還是查閱官方文檔。 Erlang 的代碼載入由一個名為code_server的模塊管理,除了啟動時的一些必要代碼外,大部分的代碼均是由code_server載入。 當code_server發現模塊代碼被更新後,會重新載入模塊,此後的新請求會使用新模塊執行,而原有還在執行的請求則繼續使用老模塊執行。 老模塊會在新模塊載入後,被打上old標簽,新模塊則是current標簽。當下一次熱更新的時候,Erlang 會掃描還在執行老模塊的進行並殺掉,再繼續按照這個邏輯更新模塊。 Erlang 中並非所有代碼均允許熱更新,如 kernel, stdlib, piler 等基礎模塊默認是不允許更新的 我們可以發現 Node.js 中也有與code_server類似的局擾模塊,即 reuire 體系,因此 Erlang 的做法應該也可以在 Node.js 上做一些嘗試。通過了解 Erlang 的做法,我們可以大概的總結出在 Node.js 中解決代碼熱更新的關鍵問題點 如何更新模塊代碼 如何使用新模塊處理請求 如何釋放老模塊的資源 那麼接下來我們就逐個的解析這些問題點。 如何更新模塊代碼 要解決模塊代碼更新的問題,我們就需要去閱讀 Node.js 的模塊管理器實現,直接上鏈接 mole.js。通過簡單的閱讀,我們可以發現核心的代碼就在於 Mole._load ,稍微精簡一下代碼貼出來。 Check the cache for the reuested file. 1. If a mole already exists in the cache: return its exports object. 2. If the mole is native: call `NativeMole.reuire()` with the filename and return the result. 3. Otherwise, creat......>>
問題五:熱更新 的是什麼 熱更新的時候不需要關閉伺服器,直接重新部署項目就行。冷的自然就是關閉伺服器後再操作
問題六:android熱更新是什麼意思 我們知道Java在運行時載入對應的類是通過ClassLoader來實現的,ClassLoader本身是一個抽象來,Android中使用PathClassLoader類作為Android的默認的類載入器,
PathClassLoader其實實現的就是簡單的從文件系統中載入類文件。PathClassLoade本身繼承自BaseDexClassLoader,BaseDexClassLoader重寫了findClass方法,
該方法是ClassLoader的核心
問題七:熱更新有多重要 應該是有點上火積食了,不知道寶寶多大了,可以給他煮一些白蘿卜水喝一下,衣服別給他穿太多了,食物也是,盡量別吃會上火的食物
問題八:熱更新什麼意思 就是更新的比較熱
問題九:如何實現iOS熱更新 Unity沒有實現iOS平台代碼熱更新是因為:
1 所謂熱更新就是指代碼可以不通過重新打包提交App Store的方式來更新客戶端的執行代碼。
2由於以下幾個原因客戶端更新希望更加輕量和快速: App Store的審核周期比較難控制; 手機網路游戲更新頻繁.。
問題十:熱更新真的那麼重要嗎 日系的不安全,韓系的還不如日系的,當然你要是能到日、韓原產而非中國產的例外,人家對中國市場是特別對待的。
ESP對於安全來說是很必要的,不怕一萬只怕萬一,你說呢?2010大眾的GOLF為標配、大眾斯柯達明銳手自一體的都是標配,手動最低端十二萬多的可以選配。
Ⅵ android 怎麼動態載入jar
核心類 1.1 DexClassLoader類 可以載入jar/apk/dex,可以從SD卡中載入為安裝的apk。 1.2 PathClassLoader類 只能載入已經安裝到Android系統中的apk文件。 一、正文 1.1 類似於eclipse的插件化實現, 首先定義好介面, 用戶實現介面功能後即可通過動態載入的方式載入jar文件, 以實現具體功能。 注意 , 這里的jar包需要經過android dx工具的處理 , 否則不能使用。
Ⅶ Android-類載入
雙親委託機制
類在進行類載入的時候,把載入任務託管給父類載入器,如能載入成功,則返回,否則依次向子類載入器遞歸嘗試類載入。
意義:
①避免類的重復載入,父類載入已載入該類時,子ClassLoader就沒有必要載入一次了。
②安全性,防止核心API被隨意篡改。
ClassLoader
ClassLoader本身是一個抽象方法。它的主要實現類有BootClassLoader、PathClassLoader、DexClassLoader.
BootClassLoader:用於載入Android Framwork層(SDK)的class文件
PathClassLoader:用於Android應用程序載入器,可以載入指定的dex和jar、zip、apk中的classes.dex(系統使用)
DexClassLoader:用於載入指定的dex和jar、zip、apk中的classes.dex。(供開發者使用)
拓展:
在API26之前。
optimizedDirectory 參數就是dexopt的產出目錄(odex)。那 PathClassLoader 創建時,這個目錄為null,就
意味著不進行dexopt?並不是, optimizedDirectory 為null時的默認路徑為:/data/dalvik-cache。
在API26之後DexClassLoader也取消了optimizedDirectory
熱修復相關
LoadClass:
findClass:PathClassLoader和DexClassLoader的父類BaseDexClassLoader中實現findClass。
BaseDexClassLoader中
PathClassLoader載入過後,pathlist 中存在一個Element數組,Element類中存在一個dexFile成員表示dex文件,即:APK中有X個dex,則Element數組就扮叢有X個元素。
總結:
可能看到這里我們比較亂了,理一下。一個類的載入經歷了哪些。我們以PathClassLoader為例。
①載入一個類的時候,首先通過Class緩存尋找是否已經載入過該類。參考抽象類的loadClass方法。
②若在緩存中未找到該類,則交由父載入器載入該類。參考抽象類的loadClass方法。
③調用父載入器PathClassLoader的父類BaseDexClassLoader實現的findClass方法載入該類。
④PathClassLoader在初始化的時候調用棗坦父構造方法實例化DexPathList屬性,DexPathList屬性初始化時構造方法內通過makePathElements(或makeDexElements 不同API可能不同)載入APK內的dex文件生成Element數組。
⑤BaseDexClassLoader實現的findClass方法中順序循環已存在的Element數組,通過Element中的DexFile載入類。。
⑥未找到,拋出類未找廳岩櫻到異常。
熱修復(multide 形式(thinker、qfix))
熱修復的原理。我們只需在應用啟動的時候,一般是在application方法中(因為class載入首先從緩存中載入),在應用啟動後,經過PathClassLoader載入過後所有的類都在 pathList的Element 數組,把生成的Elment數組插入到PathList的Element數組的最前方。在載入類的時候就只會載入到我們需要更新的類了,因為是順序尋找,找到就返回。(先從我們補丁的dex文件生成的element尋找,找不到再從APK的dex生成的element種尋找)。
熱修復基本思路總結:
①獲取到當前引用的PathClassLoader
②反射獲取其中DexPathList屬性:DexPathList pathList.
③獲取到補丁包path.dex文件的Element[]數組 pElements。參考PathClassLoader怎麼把dex文件轉換為Element數組的。於是我們反射執行DexPathList 中的makePathElements方法(視API而定)傳入dex路徑得到補丁包的element數組。
④獲取pathList的dexElements數組。
⑤把補丁包的pElements數組合並到pathList的dexElements數組的前方,即newElements=pElements+dexElements
⑥反射賦值把newElements替換掉pathList的dexElements
熱修復沒這么簡單,還需考慮混淆,API版本不同導致的使用makePathElements方法或makeDexElements方法等因素。
熱修復(InstantRun 形式(Robust))待了解。
Ⅷ android 熱部署是什麼意思
在 Java 開發領域,熱部署一直是一個難以解決的問題,目前的 Java 虛擬機只能實現方法體的修改熱部署,對於整個類的結構修改,仍然需要重啟虛擬機,對類重新載入才能完成更新操作。對於某些大型的應用來說,每次的重啟都需要花費大量的時間成本。雖然 osgi 架構的出現,讓模塊重啟成為可能,但是如果模塊之間有調用關系的話,這樣的操作依然會讓應用出現短暫的功能性休克。本文將探索如何在不破壞 Java 虛擬機現有行為的前提下,實現某個單一類的熱部署,讓系統無需重啟就完成某個類的更新。
類載入的探索
首先談一下何為熱部署(hotswap),熱部署是在不重啟 Java 虛擬機的前提下,能自動偵測到 class 文件的變化,更新運行時 class 的行為。Java 類是通過 Java 虛擬機載入的,某個類的 class 文件在被 classloader 載入後,會生成對應的 Class 對象,之局差巧後就可以創建該類的實例。默認的虛擬機行為只會在啟動時載入類,如果後期有一個類需要更新的話,單純替換編譯的 class 文件,Java 虛擬機是不會更新正在運行的 class。如果要實現熱部署,最根本的方式是修改虛擬機的源代碼,改變 classloader 的載入行為,使虛擬機能監聽 class 文件的更新,重新載入 class 文件,這樣的行為破壞性很大,為後續的 JVM 升級埋下了一個大坑。
另一種友好的方法是創建自己的 classloader 來載入需要監聽的 class,這樣就能控制類載入的時機,從而實現熱部署。本文將具體探索如何實現這個方案。首先需要了解一下 Java 虛擬機現有的載入機制。目前的載入機制,稱為雙親委派,系統在使用一個 classloader 來載入類時,會先詢問當前 classloader 的父類是否有能力載入,如果父類無法實現載入操作,才會將任務下放到該 classloader 來載入。這種自上而下的載入方式的好處是,讓每個 classloader 執行自己的載入任務,不會重復載入類。但是這種方式卻使載入順序非常難改變,讓自定義 classloader 搶先載入需要監聽改變的類成為了一個難題。
不過我們可以換一個思路,雖然無法搶先載入該類,但是仍然可以用自定義 classloader 創建一個功能相同的類,讓每次實例化的對象都指向這個新的類。當這個類的 class 文件發生改變的時候,再次創建一個更新的類,之後如果系統再次發出實例化請求,創建的對象講指向這個全新的類。
下面來簡單列舉一下需要做的工作。
創建自定義的 classloader,載入需要監聽改變的類,在 class 文件發生改變的時候,重新載入該類。
改變創建對象的行為,使他們在創建時使用自定義 classloader 載入的 class。
自定義載入器的實現
自定義載入器仍然慶態需要執行類載入的功能。這里卻存在一個問題,同一個類載入器無法同時載入兩個相同名稱的類,由於不論類的結構如何發生變化,生成的類名不會變,而 classloader 只能在虛擬機停止前銷毀已經載入的類,這樣 classloader 就無法載入更新後的類了。這里有一個小技巧,讓每次載入的類都保存成一個帶有版本信息的 class,比如載入 Test.class 時,保存在內存中的類是 Test_v1.class,當類發生改變時,重新載入的類名是 Test_v2.class。但是真正執行載入 class 文件創建 class 的 defineClass 方法是一個 native 的方法,修改起來又變得很困難。所以面前還剩一條路,那就是直接修改編譯生成的 class 文件。
利用 ASM 修改 class 文件
可以修改位元組碼的框架有很多,比如 ASM,CGLIB。本文使用的是 ASM。先來介紹一下 class 文件的結構,class 文件包含了以下幾類信息,一個是類的基本信息,包含了訪問許可權信息,類名信息,父類信息,介面信息。第二個是類的變數信息。第三個是方法的信息。桐鍵ASM 會先載入一個 class 文件,然後嚴格順序讀取類的各項信息,用戶可以按照自己的意願定義增強組件修改這些信息,最後輸出成一個新的 class。
首先看一下如何利用 ASM 修改類信息。
清單 1. 利用 ASM 修改位元組碼
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassReader cr = null;
String enhancedClassName = classSource.getEnhancedName();
try {
cr = new ClassReader(new FileInputStream(
classSource.getFile()));
} catch (IOException e) {
e.printStackTrace();
return null;
}
ClassVisitor cv = new EnhancedModifier(cw,
className.replace(".", "/"),
enhancedClassName.replace(".", "/"));
cr.accept(cv, 0);
Ⅸ Android Studio Flutter Hot Reload熱載入無效
如題,在Flutter開發中,正常情況下,修改後按保存(ctrl+s),就能自動州虛耐將更新內容熱載入到設備中,但是我早上突然就遇到保存後沒有熱載入的情況。
試了試冊春,有的頁面是沒問題,可以熱更新的,有的頁面不行,那應該就是某些頁面的問題了。在熱更新生效的頁面,每次保存後查看Run裡面輸出的日誌,譽悉發現最後一行是類似:
而熱更新無效的頁面,保存後的日誌是:
也就是AS沒有找到改變的東西,所以沒更新。
聯想到早些時候把幾個dart文件的位置拖動了下,是不是那個操作引起的問題,打開來看了看,發現了問題所在。那些引用被拖動文件的地方,引用語句由
變成了
(***是我脫敏替代了)
導致AS無法載入最新修改的內容。
把引用方式由file的方式改回package的方式就行。
以上。
Ⅹ Android類載入機制
Android手寫熱修復(一)--ClassLoader
我們平時編寫的 .java 文件不是可執行文件,需要先編譯成 .class 文件才可以被虛擬機執行。所謂類載入是指通過 類載入器 把class文件載入到虛擬機的內存空間,具體來說是方法區。類通常是按需載入,即第一次使用該類時才載入。
首先,Java與Android都是把類載入到虛擬機內存中,然後由虛擬機轉換成設備識別的機器碼。但是由於二者使用的虛擬機不同,所以在類載入方面也是有所區別的。Java的虛擬機是JVM,Android的虛擬機是dalvik/art(5.0以後虛擬機是art,是對dalvik的一種升級)。 Java虛擬機運行的是class文件,而Android 虛擬機運行的是dex文件。 dex其實是class文件的集合,是對class文件優化的產物,是為了避免出現重復的class。
從上面的講解中,我們已經知道我們平時寫的類是被 類載入器 載入盡虛擬機內存才能運行。下面就通過Framework源碼來為大家講解Android中最主要的5個類載入器。
在Activity做個簡單驗證:
結果:
可以看出系統類由BootClassLoader載入,apk中的類由PathClassLoader載入,PathClassLoader的父類載入器是BootClassLoader。如果暫時不能理解父類載入器是什麼,沒關系,後面講雙親委託機制的時候會理解的。
下面的源碼解析基於 Android SDK API28 ,這幾個類載入器(除了ClassLoader)沒辦法直接在AS上查看源碼,AS搜索到的是反編譯的class的內容,是不可信的,為大家推薦一個在線工具查看, 在線查看Android Framework源碼 。
用來載入本地文件系統上的文件或目錄,通常是用來載入apk中我們自己寫的類,而像 Activity.class 這種系統的類不是由它載入。注意:這里,並不像很多網上文章說的那樣只能載入apk,本地的其他目錄的文件也是可以的,這一點我會在後面驗證說明。
也是被用來載入 jar 、apk、dex,通常用來載入未安裝到應用中的文件。注意,它需要一個應用私有的可寫的目錄來存放優化後的dex文件。千萬不要選擇外部存儲路徑,因為這樣可能會導致你的應用遭到注入攻擊。
關於dex文件優化,可能很多人還是不理解,水平有限,我簡單解釋一下,
構造器參數解釋:
關於optimizedDirectory:
1、這是dex優化後的路徑,它必須是一個應用私有的可寫的目錄否則會存在注入攻擊的風險;
2、這個參數在API 26(8.0)之前是有值的,之後的話,這個參數已經沒有影響了,因為在調用父構造器的時候這個參數始終為null,也就是說Android 8.0 以後DexClassLoader和PathClassLoader基本一樣的來;
3、在載入app的時候,apk內部的dex已經執行過優化了,優化之後放在系統目錄/data/dalvik-cache下。
這個構造器的關鍵是初始化了一個DexPathList對象,這個是後面載入class的關鍵類。
這個構造方法等關鍵是通過 makeDexElements() 方法來獲取Element數組,這個Element數組非常關鍵,後面查找class就會用到它,也是熱修復的關鍵點之一。
splitDexPath(dexPath) 方法是把dexPath目錄下的所有文件轉換成一個File集合,如果是多個文件的話,會用 : 作為分隔符。
makeDexElements()
小結一下,這個方法就是把指定目錄下的文件apk/jar/zip/dex按不同的方式封裝成Element對象,然後按順序添加到Element[]數組中。
DexPathList#loadDexFile()
可以看到 DexFile 最終是調用了openDexFile、native方法openDexFileNative去打開Dex文件的,如果outputName為空,則自動生成一個緩存目錄,具體來說是 /data/dalvik-cache/[email protected] 。openDexFileNative這個native方法就不具體分析了,主要是對dex文件進行了優化操作,將優化後得odex文件通過mmap映射到內存中。感興趣的同學可以參考:
《DexClassLoader和PathClassLoader載入Dex流程》
現在在回頭看看DexClassLoader與PathClassLoader的區別。DexClassLoader可以指定odex的路徑,而PathClassLoader則採用系統默認的緩存路徑,在8.0以後沒有區別。
ClassLoader是一個抽象類,有3個構造方法,最終調用的還是第一個構造方法,主要功能是保存實現類傳入的parent參數,也就是父類載入器。ClassLoader的實現類主要有2個,一個是前面講過的BaseDexClassLoader,另一個是BootClassLoader。
BootClassLoader是ClassLoader的內部類,而且繼承了ClassLoader。
這是載入一個類的入口,流程如下:
1、 先檢查這個類是否已經被載入,有的話直接返回Class對象;
2、如果沒有載入過,通過父類載入器去載入,可以看出parent是通過遞歸的方式去載入class的;
3、如果所有的父類載入器都沒有載入過,就由當前的類載入器去載入。
通常我們自己寫的類是通過當前類載入器調用 findClass 方法去載入的,但是在 ClassLoader 中這是個空方法,具體的實現在它的子類 BaseDexClassLoader 中。
BaseDexClassLoader # findClass
可以看到是通過pathList去查找class的,這個對象其實之前講過,它是在BaseDexClassLoader 的構造方法中初始化的,它實際上是一個 DexPathList 對象。
DexPathList # findClass()
對Element數組遍歷,再通過Element對象的 findClass 方法去查找class,有的話就直接返回這個class,找不到則返回null。 這里可以看出獲取Class是通過DexFile來實現的,而各種類載入器操作的是Dex。Android虛擬機載入的dex文件,而不是class文件。
1、載入一個類是通過雙親委託機制來實現的。
2、如果是第一次載入class,那是通過 BaseDexClassLoader 中的findClass方法實現的;接著進入 DexPathList 中的findClass方法,內部通過遍歷Element數組,從Element對象中去查找類;Element實際上是對Dex文件的包裝,最終還是從dexfile去查找的class。
3、一般app運行主要用到2個類載入器,一個是PathClassLoader:主要用於載入自己寫的類;另一個是BootClassLoader:用於載入Framework中的類;
4、熱修復和插件化一般是利用DexClassLoader來實現。
5、PathClassLoader和DexClassLoader其實都可以載入apk/jar/dex,區別是 DexClassLoader 可以指定 optimizedDirectory ,也就是 dex2oat 的產物 .odex 存放的位置,而 PathClassLoader 只能使用系統默認位置。但是在8.0 以後二者是沒有區別的,只能使用系統默認的位置了。
這張圖來源於:
Android虛擬機框架:類載入機制
在類載入流程分析中,我們已經知道,查找class是通過DexPathList來完成的,實際上DexPathList最終還是遍歷其Element數組,獲取DexFile對象來載入Class文件。 由於數組是有序的,如果2個dex文件中存在相同類名的class,那麼類載入器就只會載入數組前面的dex中的class。如果apk中出現了有bug的class,那隻要把修復的class打包成dex文件並且放在 DexPathList 中Element數組`的前面,就可以實現bug修復了 。下一篇為大家帶來的手寫熱修復。
Android類載入機制的細枝末節
從JVM到Dalivk再到ART(class,dex,odex,vdex,ELF)
類載入機制系列2——深入理解Android中的類載入器
Android 熱修復核心原理,ClassLoader類載入