🔥 A awesome android expert interview questions and answers(continuous updating ...)java
從幾十份頂級面試倉庫和300多篇高質量面經中總結出一份全面成體系化的Android高級面試題集。android
歡迎來到2020年中高級Android大廠面試祕籍,爲你保駕護航金三銀四,直通大廠的Android高級篇(上)。git
隨着項目的逐漸成熟,用戶基數逐漸增多,DAU持續升高,咱們遇到了不少穩定性方面的問題,對於咱們技術同窗遇到了不少的挑戰,用戶常用咱們的App卡頓或者是功能不可用,所以咱們就針對穩定性開啓了專項的優化,咱們主要優化了三項:github
經過這三方面的優化咱們搭建了移動端的高可用平臺。同時,也作了不少的措施來讓App真正地實現了高可用。面試
咱們針對啓動速度,內存、佈局加載、卡頓、瘦身、流量、電量等多個方面作了多維的優化。算法
咱們的優化主要分爲了兩個層次,即線上和線下,針對於線下呢,咱們側重於發現問題,直接解決,將問題儘量在上線以前解決爲目的。而真正到了線上呢,咱們最主要的目的就是爲了監控,對於各個性能緯度的監控呢,可讓咱們儘量早地獲取到異常狀況的報警。編程
同時呢,對於線上最嚴重的性能問題性問題:Crash,咱們作了專項的優化,不只優化了Crash的具體指標,並且也儘量地獲取了Crash發生時的詳細信息,結合後端的聚合、報警等功能,便於咱們快速地定位問題。json
移動端業務高可用它側重於用戶功能完整可用,主要是爲了解決一些線上一些異常狀況致使用戶他雖然沒有崩潰,也沒有性能問題,可是呢,只是單純的功能不可用的狀況,咱們須要對項目的主流程、核心路徑進行埋點監控,來計算每一步它真實的轉換率是多少,同時呢,還須要知道在每一步到底發生了多少異常。這樣咱們就知道了全部業務流程的轉換率以及相應界面的轉換率,有了大盤的數據呢,咱們就知道了,若是轉換率或者是某些監控的成功率低於某個值,那頗有可能就是出現了線上異常,結合了相應的報警功能,咱們就不須要等用戶來反饋了,這個就是業務穩定性保障的基礎。canvas
同時呢,對於一些特殊狀況,好比說,開發過程中或代碼中出現了一些catch代碼塊,捕獲住了異常,讓程序不崩潰,這實際上是不合理的,程序雖然沒有崩潰,當時程序的功能已經變得不可用,因此呢,這些被catch的異常咱們也須要上報上來,這樣咱們才能知道用戶到底出現了什麼問題而致使的異常。此外,線上還有一些單點問題,好比說用戶點擊登陸一直進不去,這種就屬於單點問題,其實咱們是沒法找出其和其它問題的共性之處的,因此呢,咱們就必需要找到它對應的詳細信息。小程序
最後,若是發生了異常狀況,咱們還採起了一系列措施進行快速止損。(=>4)
首先,須要讓App具有一些高級的能力,咱們對於任何要上線的新功能,要加上一個功能的開關,經過配置中心下發的開關呢,來決定是否要顯示新功能的入口。若是有異常狀況,能夠緊急關閉新功能的入口,那就可讓這個App處於可控的狀態了。
而後,咱們須要給App設立路由跳轉,全部的界面跳轉都須要經過路由來分發,若是咱們匹配到須要跳轉到有bug的這樣一個新功能時,那咱們就不跳轉了,或者是跳轉到統一的異常正處理中的界面。若是這兩種方式都不能夠,那就能夠考慮經過熱修復的方式來動態修復,目前熱修復的方案其實已經比較成熟了,咱們徹底能夠低成本地在咱們的項目中添加熱修復的能力,固然,若是有些功能是由RN或WeeX來實現就更好了,那就能夠經過更新資源包的方式來實現動態更新。而這些若是都不能夠的話呢,那就能夠考慮本身去給應用加上一個自主修復的能力,若是App啓動屢次的話,那就能夠考慮清空全部的緩存數據,將App重置到安裝的狀態,到了最嚴重的等級呢,能夠阻塞主線程,此時必定要等App熱修復成功以後才容許用戶進入。
須要更全面更深刻的理解請查看深刻探索Android穩定性優化
在某一個版本以後呢,咱們會發現這個啓動速度變得特別慢,同時用戶給咱們的反饋也愈來愈多,因此,咱們開始考慮對應用的啓動速度來進行優化。而後,咱們就對啓動的代碼進行了代碼層面的梳理,咱們發現應用的啓動流程已經很是複雜,接着,咱們經過一系列的工具來確認是否在主線程中執行了太多的耗時操做。
咱們通過了細查代碼以後,發現應用主線程中的任務太多,咱們就想了一個方案去針對性地解決,也就是進行異步初始化。(引導=>第2題) 而後,咱們還發現了另一個問題,也能夠進行鍼對性的優化,就是在咱們的初始化代碼當中有些的優先級並非那麼高,它能夠不放在Application的onCreate中執行,而徹底能夠放在以後延遲執行的,由於咱們對這些代碼進行了延遲初始化,最後,咱們還結合了idealHandler作了一個更優的延遲初始化的方案,利用它能夠在主線程的空閒時間進行初始化,以減小啓動耗時致使的卡頓現象。作完這些以後,咱們的啓動速度就變得很快了。
最後,我簡單說下咱們是怎麼長期來保持啓動優化的效果的。首先,咱們作了咱們的啓動器,而且結合了咱們的CI,在線上加上了不少方面的監控。(引導=> 第4題)
咱們最初是採用的普通的一個異步的方案,即new Thread + 設置線程優先級爲後臺線程的方式在Application的onCreate方法中進行異步初始化,後來,咱們使用了線程池、IntentService的方式,可是,在咱們應用的演進過程中,發現代碼會變得不夠優雅,而且有些場景很是很差處理,好比說多個初始化任務直接的依賴關係,好比說某一個初始化任務須要在某一個特定的生命週期中初始化完成,這些都是使用線程池、IntentService沒法實現的。因此說,咱們就開始思考一個新的解決方案,它可以完美地解決咱們剛剛所遇到的這些問題。
這個方案就是咱們目前所使用的啓動器,在啓動器的概念中,咱們將每個初始化代碼抽象成了一個Task,而後,對它們進行了一個排序,根據它們之間的依賴關係排了一個有向無環圖,接着,使用一個異步隊列進行執行,而且這個異步隊列它和CPU的核心數是強烈相關的,它可以最大程度地保證咱們的主線程和別的線程都可以執行咱們的任務,也就是你們幾乎均可以同時完成。
首先,在CPU Profiler和Systrace中有兩個很重要的指標,即cpu time與wall time,咱們必須清楚cpu time與wall time之間的區別,wall time指的是代碼執行的時間,而cpu time指的是代碼消耗CPU的時間,鎖衝突會形成二者時間差距過大。咱們須要以cpu time來做爲咱們優化的一個方向。
其次,咱們不只只追求啓動速度上的一個提高,也須要注意延遲初始化的一個優化,對於延遲初始化,一般的作法是在界面顯示以後纔去進行加載,可是若是此時界面須要進行滑動等與用戶交互的一系列操做,就會有很嚴重的卡頓現象,所以咱們使用了idealHandler來實現cpu空閒時間來執行耗時任務,這極大地提高了用戶的體驗,避免了因啓動耗時任務而致使的頁面卡頓現象。
最後,對於啓動優化,還有一些黑科技,首先,就是咱們採用了類預先加載的方式,咱們在MultiDex.install方法以後起了一個線程,而後用Class.forName的方式來預先觸發類的加載,而後當咱們這個類真正被使用的時候,就不用再進行類加載的過程了。同時,咱們再看Systrace圖的時候,有一部分手機其實並無給咱們應用去跑滿cpu,好比說它有8核,可是卻只給了咱們4核等這些狀況,而後,有些應用對此作了一些黑科技,它會將cpu的核心數以及cpu的頻率在啓動的時候去進行一個暴力的提高。
這種問題其實咱們以前也遇到過,這的確很是難以解決。可是,咱們後面對此進行了反覆的思考與嘗試,終於找到了一個比較好的解決方式。
首先,咱們使用了啓動器去管理每個初始化任務,而且啓動器中每個任務的執行都是被其自動進行分配的,也就是說這些自動分配的task咱們會盡可能保證它會平均分配在咱們每個線程當中的,這和咱們普通的異步是不同的,它能夠很好地緩解咱們應用的啓動變慢。
其次,咱們還結合了CI,好比說,咱們如今限制了一些類,如Application,若是有人修改了它,咱們不會讓這部分代碼合併到主幹分支或者是修改以後會有一些內部的工具如郵件的形式發送到我,而後,我就會和他確認他加的這些代碼究竟是耗時多少,可否異步初始化,不能異步的話就考慮延遲初始化,若是初始化時間太長,則能夠考慮是否能進行懶加載,等用到的時候再去使用等等。
而後,咱們會將問題儘量地暴露在上線以前。同時,咱們真正已經到了線上的一個環境下時,咱們進行了監控的一個完善,咱們不只是監控了App的整個的啓動時間,同時呢,咱們也將每個生命週期都進行了一個監控。好比說Application的onCreate與onAttachBaseContext方法的耗時,以及這兩個生命週期之間間隔的時間,咱們都進行了一個監控,若是說下一次咱們發現了這個啓動速度變慢了,咱們就能夠去查找究竟是哪個環節變慢了,咱們會和之前的版本進行對比,對比完成以後呢,咱們就能夠來找這一段新加的代碼。
須要更全面更深刻的理解請查看深刻探索Android啓動速度優化
一、分析現狀、確認問題
咱們發現咱們的APP在內存方面可能存在很大的問題,第一方面的緣由是咱們的線上的OOM率比較高。第二點呢,咱們常常會看到在咱們的Android Studio的Profiler工具中內存的抖動比較頻繁。這是我一個初步的現狀,而後在咱們知道了這個初步的現狀以後,進行了問題的確認,咱們通過一系列的調研以及深刻研究,咱們最終發現咱們的項目中存在如下幾點大問題,好比說:內存抖動、內存溢出、內存泄漏,還有咱們的Bitmap使用很是粗獷。
二、針對性優化
好比內存抖動的解決 -> Memory Profiler工具的使用(呈現了鋸齒張圖形) -> 分析到具體代碼存在的問題(頻繁被調用的方法中出現了日誌字符串的拼接),也能夠說說內存泄漏或內存溢出的解決。
三、效率提高
爲了避免增長業務同窗的工做量,咱們使用了一些工具類或ARTHook這樣的大圖檢測方案,沒有任何的侵入性,同時,咱們將這些技術教給了你們,而後讓你們一塊兒進行工做效率上的提高。
咱們對內存優化工具Memory Profiler、MAT的使用比較熟悉,所以針對一系列不一樣問題的狀況,咱們寫了一系列解決方案的文檔,分享給你們。這樣,咱們整個團隊成員的內存優化意識就變強了。
一、磨刀不誤砍柴工
咱們一開始並無直接去分析項目中代碼哪些地方存在內存問題,而是先去學習了Google官方的一些文檔,好比說學習了Memory Profiler工具的使用、學習了MAT工具的使用,在咱們將這些工具學習熟練以後,當在咱們的項目中遇到內存問題時,咱們就可以很快地進行排查定位問題進行解決。
二、技術優化必須結合業務代碼
一開始,咱們作了總體APP運行階段的一個內存上報,而後,咱們在一些重點的內存消耗模塊進行了一些監控,可是後面發現這些監控並無緊密地結合咱們的業務代碼,好比說在梳理完項目以後,發現咱們項目中存在使用多個圖片庫的狀況,多個圖片庫的內存緩存確定是不公用的,因此致使咱們整個項目的內存使用量很是高。因此進行技術優化時必須結合咱們的業務代碼。
三、系統化完善解決方案
咱們在作內存優化的過程當中,不只作了Android端的優化工做,還將咱們Android端一些數據的採集上報到了咱們的服務器,而後傳到咱們的後臺,這樣,方便咱們的不管是Bug跟蹤人員或者是Crash跟蹤人員進行一系列問題的解決。
好比說大圖片的檢測,咱們最初的一個方案是經過繼承ImageView,重寫它的onDraw方法來實現。可是,咱們在推廣它的過程當中,發現不少開發人員並不接受,由於不少ImageView以前已經寫過了,你如今讓他去替換,工做成本是比較高的。因此說,後來咱們就想,有沒有一種方案能夠免替換,最終咱們就找到了ARTHook這樣一個Hook的方案。
內存抖動是因爲短期內有大量對象進出新生區致使的,它伴隨着頻繁的GC,gc會大量佔用ui線程和cpu資源,會致使app總體卡頓。
避免發生內存抖動的幾點建議:
須要更全面更深刻的理解請查看Android性能優化以內存優化、深刻探索Android內存優化
我在作佈局優化的過程當中,用到了不少的工具,可是每個工具都有它不一樣的使用場景,不一樣的場景應該使用不一樣的工具。下面我從線上和線下兩個角度來進行分析。
好比說,我要統計線上的FPS,我使用的就是Choreographer這個類,它具備如下特性:
同時,在線下,若是要去優化佈局加載帶來的時間消耗,那就須要檢測每個佈局的耗時,對此我使用的是AOP的方式,它沒有侵入性,同時也不須要別的開發同窗進行接入,就能夠方便地獲取每個佈局加載的耗時。若是還要更細粒度地去檢測每個控件的加載耗時,那麼就須要使用LayoutInflaterCompat.setFactory2這個方法去進行Hook。
此外,我還使用了LayoutInspector和Systrace這兩個工具,Systrace能夠很方便地看到每幀的具體耗時以及這一幀在佈局當中它真正作了什麼。而LayoutInspector能夠很方便地看到每個界面的佈局層級,幫助咱們對層級進行優化。
分析完佈局的加載流程以後,咱們發現有以下四點可能會致使佈局卡頓:
對此,咱們的優化方式有以下幾種:
從項目的初期到壯大期,最後再到成熟期,每個階段都針對卡頓優化作了不一樣的處理。各個階段所作的事情以下所示:
我作卡頓優化也是經歷了一些階段,最初咱們的項目當中的一些模塊出現了卡頓以後,我是經過系統工具進行了定位,我使用了Systrace,而後看了卡頓週期內的CPU情況,同時結合代碼,對這個模塊進行了重構,將部分代碼進行了異步和延遲,在項目初期就是這樣解決了問題。可是呢,隨着咱們項目的擴大,線下卡頓的問題也愈來愈多,同時,在線上,也有卡頓的反饋,可是線上的反饋卡頓,咱們在線下難以復現,因而咱們開始尋找自動化的卡頓監測方案,其思路是來自於Android的消息處理機制,主線程執行任何代碼都會回到Looper.loop方法當中,而這個方法中有一個mLogging對象,它會在每一個message的執行先後都會被調用,咱們就是利用這個先後處理的時機來作到的自動化監測方案的。同時,在這個階段,咱們也完善了線上ANR的上報,咱們採起的方式就是監控ANR的信息,同時結合了ANR-WatchDog,做爲高版本沒有文件權限的一個補充方案。在作完這個卡頓檢測方案以後呢,咱們還作了線上監控及線下檢測工具的建設,最終實現了一整套完善,多維度的解決方案。
咱們的思路是來自於Android的消息處理機制,主線程執行任何代碼它都會走到Looper.loop方法當中,而這個函數當中有一個mLogging對象,它會在每一個message處理先後都會被調用,而主線程發生了卡頓,那就必定會在dispatchMessage方法中執行了耗時的代碼,那咱們在這個message執行以前呢,咱們能夠在子線程當中去postDelayed一個任務,這個Delayed的時間就是咱們設定的閾值,若是主線程的messaege在這個閾值以內完成了,那就取消掉這個子線程當中的任務,若是主線程的message在閾值以內沒有被完成,那子線程當中的任務就會被執行,它會獲取到當前主線程執行的一個堆棧,那咱們就能夠知道哪裏發生了卡頓。
通過實踐,咱們發現這種方案獲取的堆棧信息它不必定是準確的,由於獲取到的堆棧信息它極可能是主線程最終執行的一個位置,而真正耗時的地方其實已經執行完成了,因而呢,咱們就對這個方案作了一些優化,咱們採起了高頻採集的方案,也就是在一個週期內咱們會屢次採集主線程的堆棧信息,若是發生了卡頓,那咱們就將這些卡頓信息壓縮以後上報給APM後臺,而後找出重複的堆棧信息,這些重複發生的堆棧大機率就是卡頓發生的一個位置,這樣就提升了獲取卡頓信息的一個準確性。
首先,針對卡頓,咱們採用了線上、線下工具相結合的方式,線下工具咱們冊中醫藥儘量早地去暴露問題,而針對於線上工具呢,咱們側重於監控的全面性、自動化以及異常感知的靈敏度。
同時呢,卡頓問題還有不少的難題。好比說有的代碼呢,它不到你卡頓的一個閾值,可是執行過多,或者它錯誤地執行了不少次,它也會致使用戶感官上的一個卡頓,因此咱們在線下經過AOP的方式對常見的耗時代碼進行了Hook,而後對一段時間內獲取到的數據進行分析,咱們就能夠知道這些耗時的代碼發生的時機和次數以及耗時狀況。而後,看它是否是知足咱們的一個預期,不知足預期的話,咱們就能夠直接到線下進行修改。同時,卡頓監控它還有不少容易被忽略的一個盲區,好比說生命週期的一個間隔,那對於這種特定的問題呢,咱們就採用了編譯時註解的方式修改了項目當中全部Handler的父類,對於其中的兩個方法進行了監控,咱們就能夠知道主線程message的執行時間以及它們的調用堆棧。
對於線上卡頓,咱們除了計算App的卡頓率、ANR率等常規指標以外呢,咱們還計算了頁面的秒開率、生命週期的執行時間等等。並且,在卡頓發生的時刻,咱們也儘量多地保存下來了當前的一個場景信息,這爲咱們以後解決或者復現這個卡頓留下了依據。
須要更全面更深刻的理解請查看Android性能優化之繪製優化、深刻探索Android佈局優化(上)、深刻探索Android佈局優化(下)
一、鏈接複用:節省鏈接創建時間,如開啓 keep-alive。於Android來講默認狀況下HttpURLConnection和HttpClient都開啓了keep-alive。只是2.2以前HttpURLConnection存在影響鏈接池的Bug。
二、請求合併:即將多個請求合併爲一個進行請求,比較常見的就是網頁中的CSS Image Sprites。若是某個頁面內請求過多,也能夠考慮作必定的請求合併。
三、減小請求數據的大小:對於post請求,body能夠作gzip壓縮的,header也能夠作數據壓縮(不過只支持http 2.0)。 返回數據的body也能夠作gzip壓縮,body數據體積能夠縮小到原來的30%左右(也能夠考慮壓縮返回的json數據的key數據的體積,尤爲是針對返回數據格式變化不大的狀況,支付寶聊天返回的數據用到了)。
四、根據用戶的當前的網絡質量來判斷下載什麼質量的圖片(電商用的比較多)。
五、使用HttpDNS優化DNS:DNS存在解析慢和DNS劫持等問題,DNS 不只支持 UDP,它還支持 TCP,可是大部分標準的 DNS 都是基於 UDP 與 DNS 服務器的 53 端口進行交互。HTTPDNS 則不一樣,顧名思義它是利用 HTTP 協議與 DNS 服務器的 80 端口進行交互。不走傳統的 DNS 解析,從而繞過運營商的 LocalDNS 服務器,有效的防止了域名劫持,提升域名解析的效率。
這是由於在客戶端中,加載H5頁面以前,須要先初始化WebView,在WebView徹底初始化完成以前,後續的界面加載過程都是被阻塞的。
優化手段圍繞着如下兩個點進行:
所以常見的方法是:
除此以外還有一些其餘的優化手段:
爲了加速你的view,對於頻繁調用的方法,須要儘可能減小沒必要要的代碼。先從onDraw開始,須要特別注意不該該在這裏作內存分配的事情,由於它會致使GC,從而致使卡頓。在初始化或者動畫間隙期間作分配內存的動做。不要在動畫正在執行的時候作內存分配的事情。
你還須要儘量的減小onDraw被調用的次數,大多數時候致使onDraw都是由於調用了invalidate().所以請儘可能減小調用invaildate()的次數。若是可能的話,儘可能調用含有4個參數的invalidate()方法而不是沒有參數的invalidate()。沒有參數的invalidate會強制重繪整個view。
另一個很是耗時的操做是請求layout。任什麼時候候執行requestLayout(),會使得Android UI系統去遍歷整個View的層級來計算出每個view的大小。若是找到有衝突的值,它會須要從新計算好幾回。另外須要儘可能保持View的層級是扁平化的,這樣對提升效率頗有幫助。
若是你有一個複雜的UI,你應該考慮寫一個自定義的ViewGroup來執行他的layout操做。與內置的view不一樣,自定義的view能夠使得程序僅僅測量這一部分,這避免了遍歷整個view的層級結構來計算大小。
Error、OOM,StackOverFlowError、Runtime,好比說空指針異常
解決的辦法:
傳統日誌打印有兩個性能問題,一個是反覆操做文件描述符表,一個是反覆進入內核態。因此須要使用mmap的方式去直接讀寫內存。
Android 是一種基於 Linux 的開放源代碼軟件棧,爲普遍的設備和機型而建立。下圖所示爲 Android 平臺的五大組件:
1.應用程序
Android 隨附一套用於電子郵件、短信、日曆、互聯網瀏覽和聯繫人等的核心應用。平臺隨附的應用與用戶能夠選擇安裝的應用同樣,沒有特殊狀態。所以第三方應用可成爲用戶的默認網絡瀏覽器、短信 Messenger 甚至默認鍵盤(有一些例外,例如系統的「設置」應用)。
系統應用可用做用戶的應用,以及提供開發者可從其本身的應用訪問的主要功能。例如,若是您的應用要發短信,您無需本身構建該功能,能夠改成調用已安裝的短信應用向您指定的接收者發送消息。
二、Java API 框架
您可經過以 Java 語言編寫的 API 使用 Android OS 的整個功能集。這些 API 造成建立 Android 應用所需的構建塊,它們可簡化核心模塊化系統組件和服務的重複使用,包括如下組件和服務:
開發者能夠徹底訪問 Android 系統應用使用的框架 API。
三、系統運行庫
1)原生 C/C++ 庫
許多核心 Android 系統組件和服務(例如 ART 和 HAL)構建自原生代碼,須要以 C 和 C++ 編寫的原生庫。Android 平臺提供 Java 框架 API 以嚮應用顯示其中部分原生庫的功能。例如,您能夠經過 Android 框架的 Java OpenGL API 訪問 OpenGL ES,以支持在應用中繪製和操做 2D 和 3D 圖形。若是開發的是須要 C 或 C++ 代碼的應用,能夠使用 Android NDK 直接從原生代碼訪問某些原平生臺庫。
2)Android Runtime
對於運行 Android 5.0(API 級別 21)或更高版本的設備,每一個應用都在其本身的進程中運行,而且有其本身的 Android Runtime (ART) 實例。ART 編寫爲經過執行 DEX 文件在低內存設備上運行多個虛擬機,DEX 文件是一種專爲 Android 設計的字節碼格式,通過優化,使用的內存不多。編譯工具鏈(例如 Jack)將 Java 源代碼編譯爲 DEX 字節碼,使其可在 Android 平臺上運行。
ART 的部分主要功能包括:
在 Android 版本 5.0(API 級別 21)以前,Dalvik 是 Android Runtime。若是您的應用在 ART 上運行效果很好,那麼它應該也可在 Dalvik 上運行,但反過來不必定。
Android 還包含一套核心運行時庫,可提供 Java API 框架使用的 Java 編程語言大部分功能,包括一些 Java 8 語言功能。
四、硬件抽象層 (HAL)
硬件抽象層 (HAL) 提供標準界面,向更高級別的 Java API 框架顯示設備硬件功能。HAL 包含多個庫模塊,其中每一個模塊都爲特定類型的硬件組件實現一個界面,例如相機或藍牙模塊。當框架 API 要求訪問設備硬件時,Android 系統將爲該硬件組件加載庫模塊。
五、Linux 內核
Android 平臺的基礎是 Linux 內核。例如,Android Runtime (ART) 依靠 Linux 內核來執行底層功能,例如線程和低層內存管理。使用 Linux 內核可以讓 Android 利用主要安全功能,而且容許設備製造商爲著名的內核開發硬件驅動程序。
一個Activity包含了一個Window對象,這個對象是由PhoneWindow來實現的。PhoneWindow將DecorView做爲整個應用窗口的根View,而這個DecorView又將屏幕劃分爲兩個區域:一個是TitleView,另外一個是ContentView,而咱們平時所寫的就是展現在ContentView中的。
觸摸事件對應的是MotionEvent類,事件的類型主要有以下三種:
View事件分發本質就是對MotionEvent事件分發的過程。即當一個MotionEvent發生後,系統將這個點擊事件傳遞到一個具體的View上。
事件分發過程由三個方法共同完成:
dispatchTouchEvent:方法返回值爲true表示事件被當前視圖消費掉;返回爲super.dispatchTouchEvent表示繼續分發該事件,返回爲false表示交給父類的onTouchEvent處理。
onInterceptTouchEvent:方法返回值爲true表示攔截這個事件並交由自身的onTouchEvent方法進行消費;返回false表示不攔截,須要繼續傳遞給子視圖。若是return super.onInterceptTouchEvent(ev), 事件攔截分兩種狀況:
注意:通常的LinearLayout、 RelativeLayout、FrameLayout等ViewGroup默認不攔截, 而 ScrollView、ListView等ViewGroup則可能攔截,得看具體狀況。
onTouchEvent:方法返回值爲true表示當前視圖能夠處理對應的事件;返回值爲false表示當前視圖不處理這個事件,它會被傳遞給父視圖的onTouchEvent方法進行處理。若是return super.onTouchEvent(ev),事件處理分爲兩種狀況:
注意:在Android系統中,擁有事件傳遞處理能力的類有如下三種:
三個方法的關係用僞代碼表示以下:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
coonsume = child.dispatchTouchEvent(ev);
}
return consume;
}
複製代碼
經過上面的僞代碼,咱們能夠大體瞭解點擊事件的傳遞規則:對應一個根ViewGroup來講,點擊事件產生後,首先會傳遞給它,這是它的dispatchTouchEvent就會被調用,若是這個ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當前事件,接着事件就會交給這個ViewGroup處理,這時若是它的mOnTouchListener被設置,則onTouch會被調用,不然onTouchEvent會被調用。在onTouchEvent中,若是設置了mOnCLickListener,則onClick會被調用。只要View的CLICKABLE和LONG_CLICKABLE有一個爲true,onTouchEvent()就會返回true消耗這個事件。若是這個ViewGroup的onInterceptTouchEvent方法返回false就表示它不攔截當前事件,這時當前事件就會繼續傳遞給它的子元素,接着子元素的dispatchTouchEvent方法就會被調用,如此反覆直到事件被最終處理。
一、事件傳遞優先級:onTouchListener.onTouch > onTouchEvent > onClickListener.onClick。
二、正常狀況下,一個時間序列只能被一個View攔截且消耗。由於一旦一個元素攔截了此事件,那麼同一個事件序列內的全部事件都會直接交給它處理(即不會再調用這個View的攔截方法去詢問它是否要攔截了,而是把剩餘的ACTION_MOVE、ACTION_DOWN等事件直接交給它來處理)。特例:經過將重寫View的onTouchEvent返回false可強行將事件轉交給其餘View處理。
三、若是View不消耗除ACTION_DOWN之外的其餘事件,那麼這個點擊事件會消失,此時父元素的onTouchEvent並不會被調用,而且當前View能夠持續收到後續的事件,最終這些消失的點擊事件會傳遞給Activity處理。
四、ViewGroup默認不攔截任何事件(返回false)。
五、View的onTouchEvent默認都會消耗事件(返回true),除非它是不可點擊的(clickable和longClickable同時爲false)。View的longClickable屬性默認都爲false,clickable屬性要分狀況,好比Button的clickable屬性默認爲true,而TextView的clickable默認爲false。
六、View的enable屬性不影響onTouchEvent的默認返回值。
七、經過requestDisallowInterceptTouchEvent方法能夠在子元素中干預父元素的事件分發過程,可是ACTION_DOWN事件除外。
記住這個圖的傳遞順序,面試的時候可以畫出來,就很詳細了:
重寫子類的requestDisallowInterceptTouchEvent()方法返回true就不會執行父類的onInterceptTouchEvent(),便可將點擊事件傳到下面的View。
常見開發中事件衝突的有ScrollView與RecyclerView的滑動衝突、RecyclerView內嵌同時滑動同一方向。
滑動衝突的處理規則:
滑動衝突的實現方法:
繪製會從根視圖ViewRoot的performTraversals()方法開始,從上到下遍歷整個視圖樹,每一個View控件負責繪製本身,而ViewGroup還須要負責通知本身的子View進行繪製操做。
MeasureSpec表示的是一個32位的整形值,它的高2位表示測量模式SpecMode,低30位表示某種測量模式下的規格大小SpecSize。MeasureSpec是View類的一個靜態內部類,用來講明應該如何測量這個View。它由三種測量模式,以下:
MeasureSpec經過將SpecMode和SpecSize打包成一個int值來避免過多的對象內存分配,爲了方便操做,其提供了打包和解包的方法,打包方法爲makeMeasureSpec,解包方法爲getMode和getSize。
普通View的MeasureSpec的建立規則以下:
對於DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同決定;對於普通的View,它的MeasureSpec由父視圖的MeasureSpec和其自身的LayoutParams共同決定。
若是View沒有設置背景,那麼返回android:minWidth這個屬性所指定的值,這個值能夠爲0;若是View設置了背景,則返回android:minWidth和背景的最小寬度這二者中的最大值。
直接繼承View的控件須要重寫onMeasure方法並設置wrap_content時的自身大小,不然在佈局中使用wrap_content就至關於使用match_parent。此時,能夠在wrap_content的狀況下(對應MeasureSpec.AT_MOST)指定內部寬/高(mWidth和mHeight)。
系統會遍歷子元素並對每一個子元素執行measureChildBeforeLayout方法,這個方法內部會調用子元素的measure方法,這樣各個子元素就開始依次進入measure過程,而且系統會經過mTotalLength這個變量來存儲LinearLayout在豎直方向的初步高度。每測量一個子元素,mTotalLength就會增長,增長的部分主要包括了子元素的高度以及子元素在豎直方向上的margin等。
因爲View的measure過程和Activity的生命週期方法不是同步執行的,若是View尚未測量完畢,那麼得到的寬/高就是0。因此在onCreate、onStart、onResume中均沒法正確獲得某個View的寬高信息。解決方式以下:
首先,會經過setFrame方法來設定View的四個頂點的位置,即View在父容器中的位置。而後,會執行到onLayout空方法,子類若是是ViewGroup類型,則重寫這個方法,實現ViewGroup中全部View控件佈局流程。
其中會遍歷調用每一個子View的setChildFrame方法爲子元素肯定對應的位置。其中的childTop會逐漸增大,意味着後面的子元素會被放置在靠下的位置。
注意:在View的默認實現中,View的測量寬/高和最終寬/高是相等的,只不過測量寬/高造成於View的measure過程,而最終寬/高造成於View的layout過程,即二者的賦值時機不一樣,測量寬/高的賦值時機稍微早一些。在一些特殊的狀況下則二者不相等:
繪製基本上能夠分爲六個步驟:
若是一個View不須要繪製任何內容,那麼設置這個標記位爲true之後,系統會進行相應的優化。
requestLayout()方法 :會致使調用 measure()過程 和 layout()過程,將會根據標誌位判斷是否須要ondraw。
onLayout()方法:若是該View是ViewGroup對象,須要實現該方法,對每一個子視圖進行佈局。
onDraw()方法:繪製視圖自己 (每一個View都須要重載該方法,ViewGroup不須要實現該方法)。
drawChild():去從新回調每一個子視圖的draw()方法。
invalidate()與postInvalidate()都用於刷新View,主要區別是invalidate()在主線程中調用,若在子線程中使用須要配合handler;而postInvalidate()可在子線程中直接調用。
在AndroidManifest中給四大組件指定屬性android:process開啓多進程模式,在內存容許的條件下能夠開啓N個進程。
全部運行在不一樣進程的四大組件(Activity、Service、Receiver、ContentProvider)共享數據都會失敗,這是因爲Android爲每一個應用分配了獨立的虛擬機,不一樣的虛擬機在內存分配上有不一樣的地址空間,這會致使在不一樣的虛擬機中訪問同一個類的對象會產生多份副本。好比經常使用例子(經過開啓多進程獲取更大內存空間、兩個或者多個應用之間共享數據、微信全家桶)。
通常來講,使用多進程通訊會形成以下幾方面的問題:
AIDL(Android Interface Definition Language,Android接口定義語言):若是在一個進程中要調用另外一個進程中對象的方法,可以使用AIDL生成可序列化的參數,AIDL會生成一個服務端對象的代理類,經過它客戶端能夠實現間接調用服務端對象的方法。
AIDL的本質是系統提供了一套可快速實現Binder的工具。關鍵類和方法:
當有多個業務模塊都須要AIDL來進行IPC,此時須要爲每一個模塊建立特定的aidl文件,那麼相應的Service就會不少。必然會出現系統資源耗費嚴重、應用過分重量級的問題。解決辦法是創建Binder鏈接池,即將每一個業務模塊的Binder請求統一轉發到一個遠程Service中去執行,從而避免重複建立Service。
工做原理:每一個業務模塊建立本身的AIDL接口並實現此接口,而後向服務端提供本身的惟一標識和其對應的Binder對象。服務端只須要一個Service並提供一個queryBinder接口,它會根據業務模塊的特徵來返回相應的Binder對象,不一樣的業務模塊拿到所需的Binder對象後就能夠進行遠程方法的調用了。
爲何選用Binder,在討論這個問題以前,咱們知道Android也是基於Linux內核,Linux現有的進程通訊手段有如下幾種:
既然有現有的IPC方式,爲何從新設計一套Binder機制呢。主要是出於以上三個方面的考量:
而對於Binder來講,數據從發送方的緩存區拷貝到內核的緩存區,而接收方的緩存區與內核的緩存區是映射到同一塊物理地址的,節省了一次數據拷貝的過程,如圖:
共享內存不須要拷貝,Binder的性能僅次於共享內存。
Linux系統將一個進程分爲用戶空間和內核空間。對於進程之間來講,用戶空間的數據不可共享,內核空間的數據可共享,爲了保證安全性和獨立性,一個進程不能直接操做或者訪問另外一個進程,即Android的進程是相互獨立、隔離的,這就須要跨進程之間的數據通訊方式。普通的跨進程通訊方式通常須要2次內存拷貝,以下圖所示:
一次完整的 Binder IPC 通訊過程一般是這樣:
Binder框架 是基於 C/S 架構的。由一系列的組件組成,包括 Client、Server、ServiceManager、Binder驅動,其中 Client、Server、Service Manager 運行在用戶空間,Binder 驅動運行在內核空間。以下圖所示:
最後,結合Android跨進程通訊:圖文詳解 Binder機制 的總結圖來綜合理解一下:
與Binder相關的幾個類的職責:
aidl文件只是用來定義C/S交互的接口,Android在編譯時會自動生成相應的Java類,生成的類中包含了Stub和Proxy靜態內部類,用來封裝數據轉換的過程,實際使用時只關心具體的Java接口類便可。爲何Stub和Proxy是靜態內部類呢?這其實只是爲了將三個類放在一個文件中,提升代碼的聚合性。經過上面的分析,咱們其實徹底能夠不經過aidl,手動編碼來實現Binder的通訊,下面咱們經過編碼來實現ActivityManagerService:
一、首先定義IActivityManager接口:
public interface IActivityManager extends IInterface {
//binder描述符
String DESCRIPTOR = "android.app.IActivityManager";
//方法編號
int TRANSACTION_startActivity = IBinder.FIRST_CALL_TRANSACTION + 0;
//聲明一個啓動activity的方法,爲了簡化,這裏只傳入intent參數
int startActivity(Intent intent) throws RemoteException;
}
複製代碼
二、而後,實現ActivityManagerService側的本地Binder對象基類:
// 名稱隨意,不必定叫Stub
public abstract class ActivityManagerNative extends Binder implements IActivityManager {
public static IActivityManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IActivityManager in = (IActivityManager) obj.queryLocalInterface(IActivityManager.DESCRIPTOR);
if (in != null) {
return in;
}
//代理對象,見下面的代碼
return new ActivityManagerProxy(obj);
}
@Override
public IBinder asBinder() {
return this;
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
// 獲取binder描述符
case INTERFACE_TRANSACTION:
reply.writeString(IActivityManager.DESCRIPTOR);
return true;
// 啓動activity,從data中反序列化出intent參數後,直接調用子類startActivity方法啓動activity。
case IActivityManager.TRANSACTION_startActivity:
data.enforceInterface(IActivityManager.DESCRIPTOR);
Intent intent = Intent.CREATOR.createFromParcel(data);
int result = this.startActivity(intent);
reply.writeNoException();
reply.writeInt(result);
return true;
}
return super.onTransact(code, data, reply, flags);
}
}
複製代碼
三、接着,實現Client側的代理對象:
public class ActivityManagerProxy implements IActivityManager {
private IBinder mRemote;
public ActivityManagerProxy(IBinder remote) {
mRemote = remote;
}
@Override
public IBinder asBinder() {
return mRemote;
}
@Override
public int startActivity(Intent intent) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
int result;
try {
// 將intent參數序列化,寫入data中
intent.writeToParcel(data, 0);
// 調用BinderProxy對象的transact方法,交由Binder驅動處理。
mRemote.transact(IActivityManager.TRANSACTION_startActivity, data, reply, 0);
reply.readException();
// 等待server執行結束後,讀取執行結果
result = reply.readInt();
} finally {
data.recycle();
reply.recycle();
}
return result;
}
}
複製代碼
四、最後,實現Binder本地對象(IActivityManager接口):
public class ActivityManagerService extends ActivityManagerNative {
@Override
public int startActivity(Intent intent) throws RemoteException {
// 啓動activity
return 0;
}
}
複製代碼
簡化版的ActivityManagerService到這裏就已經實現了,剩下就是Client只須要獲取到AMS的代理對象IActivityManager就能夠通訊了。
從 Java 層來看就像訪問本地接口同樣,客戶端基於 BinderProxy 服務端基於 IBinder 對象,從 native 層來看來看客戶端基於 BpBinder 到 ICPThreadState 到 binder 驅動,服務端由 binder 驅動喚醒 IPCThreadSate 到 BbBinder 。跨進程通訊的原理最終是要基於內核的,因此最會會涉及到 binder_open 、binder_mmap 和 binder_ioctl這三種系統調用。
binder 確定是不行的,由於映射的最大內存只有 1M-8K,能夠採用 binder + 匿名共享內存的形式,像跨進程傳遞大的 bitmap 須要打開系統底層的 ashmem 機制。
請按順序仔細閱讀下列文章提高對Binder機制的理解程度:
老羅Binder機制分析系列或Android系統源代碼情景分析Binder章節
Android系統啓動的核心流程以下:
Android系統啓動流程之SystemServer進程啓動
經過意圖,PMS 會解析全部 apk 的 AndroidManifest.xml ,若是解析過會存到 package.xml 中不會反覆解析,PMS 有了它就能找到了。
是由於啓動程序(主界面也是一個app),發現了在這個程序中存在一個設置爲的activity, 因此這個launcher會把icon提出來,放在主界面上。當用戶點擊icon的時候,發出一個Intent:
Intent intent = mActivity.getPackageManager().getLaunchIntentForPackage(packageName);
mActivity.startActivity(intent);
複製代碼
跳過去能夠跳到任意容許的頁面,如一個程序能夠下載,那麼真正下載的頁面可能不是首頁(也有多是首頁),這時仍是構造一個Intent,startActivity。這個intent中的action可能有多種view,download都有可能。系統會根據第三方程序向系統註冊的功能,爲你的Intent選擇能夠打開的程序或者頁面。因此惟一的一點 不一樣的是從icon的點擊啓動的intent的action是相對單一的,從程序中跳轉或者啓動可能樣式更多一些。本質是相同的。
1.ActivityManagerServices,簡稱AMS,服務端對象,負責系統中全部Activity的生命週期。
2.ActivityThread,App的真正入口。當開啓App以後,調用main()開始運行,開啓消息循環隊列,這就是傳說的UI線程或者叫主線程。與ActivityManagerService一塊兒完成Activity的管理工做。
3.ApplicationThread,用來實現ActivityManagerServie與ActivityThread之間的交互。在ActivityManagerSevice須要管理相關Application中的Activity的生命週期時,經過ApplicationThread的代理對象與ActivityThread通訊。
4.ApplicationThreadProxy,是ApplicationThread在服務器端的代理,負責和客戶端的ApplicationThread通訊。AMS就是經過該代理與ActivityThread進行通訊的。
5.Instrumentation,每個應用程序只有一個Instrumetation對象,每一個Activity內都有一個對該對象的引用,Instrumentation能夠理解爲應用進程的管家,ActivityThread要建立或暫停某個Activity時,都須要經過Instrumentation來進行具體的操做。
6.ActivityStack,Activity在AMS的棧管理,用來記錄經啓動的Activity的前後關係,狀態信息等。經過ActivtyStack決定是否須要啓動新的進程。
7.ActivityRecord,ActivityStack的管理對象,每一個Acivity在AMS對應一個ActivityRecord,來記錄Activity狀態以及其餘的管理信息。其實就是服務器端的Activit對象的映像。
8.TaskRecord,AMS抽象出來的一個「任務」的概念,是記錄ActivityRecord的棧,一個「Task」包含若干個ActivityRecord。AMS用TaskRecord確保Activity啓動和退出的順序。若是你清楚Activity的4種launchMode,那麼對這概念應該不陌生。
點擊應用圖標後會去啓動應用的Launcher Activity,若是Launcer Activity所在的進程沒有建立,還會建立新進程,總體的流程就是一個Activity的啓動流程。
Activity的啓動流程圖(放大可查看)以下所示:
整個流程涉及的主要角色有:
注:這裏單獨提一下ActivityStackSupervisior,這是高版本纔有的類,它用來管理多個ActivityStack,早期的版本只有一個ActivityStack對應着手機屏幕,後來高版本支持多屏之後,就有了多個ActivityStack,因而就引入了ActivityStackSupervisior用來管理多個ActivityStack。
整個流程主要涉及四個進程:
有了以上的理解,整個流程能夠歸納以下:
最後,再看看另外一幅啓動流程圖來加深理解:
1.Window用於顯示View和接收各類事件,Window有三種型:應用Window(每一個Activity對應一個Window)、子Widow(不能單獨存在,附屬於特定Window)、系統window(toast和狀態欄)
2.Window分層級,應用Window在1-9九、子Window在1000-199九、系統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。
APK的安裝流程以下所示:
複製APK到/data/app目錄下,解壓並掃描安裝包。
資源管理器解析APK裏的資源文件。
解析AndroidManifest文件,並在/data/data/目錄下建立對應的應用數據目錄。
而後對dex文件進行優化,並保存在dalvik-cache目錄下。
將AndroidManifest文件解析出的四大組件信息註冊到PackageManagerService中。
安裝完成後,發送廣播。
Android的包文件APK分爲兩個部分:代碼和資源,因此打包方面也分爲資源打包和代碼打包兩個方面,下面就來分析資源和代碼的編譯打包原理。
APK總體的的打包流程以下圖所示:
具體說來:
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文件)進行對比,若是相符,則代表內容沒有被修改。
在Apk中寫入一個「指紋」。指紋寫入之後,Apk中有任何修改,都會致使這個指紋無效,Android系統在安裝Apk進行簽名校驗時就會不經過,從而保證了安全性。
對一個任意長度的數據,經過一個Hash算法計算後,均可以獲得一個固定長度的二進制數據,這個數據就稱爲「摘要」。
補充:
特徵:
簽名就是在摘要的基礎上再進行一次加密,對摘要加密後的數據就能夠看成數字簽名。
如何保證公鑰的可靠性呢?答案是數字證書,數字證書是身份認證機構(Certificate Authority)頒發的,包含了如下信息:
接收方收到消息後,先向CA驗證證書的合法性,再進行簽名校驗。
注意:Apk的證書一般是自簽名的,也就是由開發者本身製做,沒有向CA機構申請。Android在安裝Apk時並無校驗證書自己的合法性,只是從證書中提取公鑰和加密算法,這也正是對第三方Apk從新簽名後,還可以繼續在沒有安裝這個Apk的系統中繼續安裝的緣由。
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格式。
base64編碼,內容以-----BEGIN xxx----- 開頭,以-----END xxx----- 結尾。
Android提供了兩種對Apk的簽名方式,一種是基於JAR的簽名方式,另外一種是基於Apk的簽名方式,它們的主要區別在於使用的簽名文件不同:jarsigner使用keystore文件進行簽名;apksigner除了支持使用keystore文件進行簽名外,還支持直接指定pem證書文件和私鑰進行簽名。
keystore是一個密鑰庫,也就是說它能夠存儲多對密鑰和證書,keystore的密碼是用於保護keystore自己的,一對密鑰和證書是經過alias來區分的。因此jarsigner是支持使用多個證書對Apk進行簽名的,apksigner也一樣支持。
JVM:.java -> javac -> .class -> jar -> .jar
架構: 堆和棧的架構.
DVM:.java -> javac -> .class -> dx.bat -> .dex
架構: 寄存器(cpu上的一塊高速緩存)
什麼是Dalvik:Dalvik是Google公司本身設計用於Android平臺的Java虛擬機。Dalvik虛擬機是Google等廠商合做開發的Android移動設備平臺的核心組成部分之一,它能夠支持已轉換爲.dex(即Dalvik Executable)格式的Java應用程序的運行,.dex格式是專爲Dalvik應用設計的一種壓縮格式,適合內存和處理器速度有限的系統。Dalvik通過優化,容許在有限的內存中同時運行多個虛擬機的實例,而且每個Dalvik應用做爲獨立的Linux進程執行。獨立的進程能夠防止在虛擬機崩潰的時候全部程序都被關閉。
什麼是ART:Android操做系統已經成熟,Google的Android團隊開始將注意力轉向一些底層組件,其中之一是負責應用程序運行的Dalvik運行時。Google開發者已經花了兩年時間開發更快執行效率更高更省電的替代ART運行時。ART表明Android Runtime,其處理應用程序執行的方式徹底不一樣於Dalvik,Dalvik是依靠一個Just-In-Time(JIT)編譯器去解釋字節碼。開發者編譯後的應用代碼須要經過一個解釋器在用戶的設備上運行,這一機制並不高效,但讓應用能更容易在不一樣硬件和架構上運行。ART則徹底改變了這套作法,在應用安裝的時候就預編譯字節碼爲機器語言,這一機制叫Ahead-Of-Time(AOT)編譯。在移除解釋代碼這一過程後,應用程序執行將更有效率,啓動更快。
ART優勢:
ART缺點:
從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);
}
複製代碼
這個要從 java 層去看源碼分析,是從 ClassLoader 的 PathList 中去找到目標路徑加載的,同時 so 是經過 mmap 加載映射到虛擬空間的。生命週期加載庫和卸載庫時分別調用 JNI_OnLoad 和 JNI_OnUnload() 方法。
若是這個庫對您有很大幫助,您願意支持這個項目的進一步開發和這個項目的持續維護。你能夠掃描下面的二維碼,讓我喝一杯咖啡或啤酒。很是感謝您的捐贈。謝謝!
歡迎關注個人微信:
bcce5360
。因爲微信羣人數太多沒法生成羣邀二維碼,因此麻煩你們想進微信羣的朋友們,加我微信拉你進羣(PS:微信羣的學習氛圍與各項福利將會超乎你的想象)。
2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~