【建議收藏】2020年中高級Android大廠面試祕籍,爲你保駕護航金三銀四,直通大廠(Android高級篇上)

前言

成爲一名優秀的Android開發,須要一份完備的知識體系,在這裏,讓咱們一塊兒成長爲本身所想的那樣~。

🔥 A awesome android expert interview questions and answers(continuous updating ...)java

從幾十份頂級面試倉庫和300多篇高質量面經中總結出一份全面成體系化的Android高級面試題集。android

歡迎來到2020年中高級Android大廠面試祕籍,爲你保駕護航金三銀四,直通大廠的Android高級篇(上)。git

Android高級面試題 (⭐⭐⭐)


1、性能優化

一、App穩定性優化

一、大家作了哪些穩定性方面的優化?

隨着項目的逐漸成熟,用戶基數逐漸增多,DAU持續升高,咱們遇到了不少穩定性方面的問題,對於咱們技術同窗遇到了不少的挑戰,用戶常用咱們的App卡頓或者是功能不可用,所以咱們就針對穩定性開啓了專項的優化,咱們主要優化了三項:github

  • Crash專項優化(=>2)
  • 性能穩定性優化(=>2)
  • 業務穩定性優化(=>3)

經過這三方面的優化咱們搭建了移動端的高可用平臺。同時,也作了不少的措施來讓App真正地實現了高可用。面試

二、性能穩定性是怎麼作的?

  • 全面的性能優化:啓動速度、內存優化、繪製優化
  • 線下發現問題、優化爲主
  • 線上監控爲主
  • Crash專項優化

咱們針對啓動速度,內存、佈局加載、卡頓、瘦身、流量、電量等多個方面作了多維的優化。算法

咱們的優化主要分爲了兩個層次,即線上和線下,針對於線下呢,咱們側重於發現問題,直接解決,將問題儘量在上線以前解決爲目的。而真正到了線上呢,咱們最主要的目的就是爲了監控,對於各個性能緯度的監控呢,可讓咱們儘量早地獲取到異常狀況的報警。編程

同時呢,對於線上最嚴重的性能問題性問題:Crash,咱們作了專項的優化,不只優化了Crash的具體指標,並且也儘量地獲取了Crash發生時的詳細信息,結合後端的聚合、報警等功能,便於咱們快速地定位問題。json

三、業務穩定性如何保障?

  • 數據採集 + 報警
  • 須要對項目的主流程與核心路徑進行埋點監控,
  • 同時還需知道每一步發生了多少異常,這樣,咱們就知道了全部業務流程的轉換率以及相應界面的轉換率
  • 結合大盤,若是轉換率低於某個值,進行報警
  • 異常監控 + 單點追查
  • 兜底策略

移動端業務高可用它側重於用戶功能完整可用,主要是爲了解決一些線上一些異常狀況致使用戶他雖然沒有崩潰,也沒有性能問題,可是呢,只是單純的功能不可用的狀況,咱們須要對項目的主流程、核心路徑進行埋點監控,來計算每一步它真實的轉換率是多少,同時呢,還須要知道在每一步到底發生了多少異常。這樣咱們就知道了全部業務流程的轉換率以及相應界面的轉換率,有了大盤的數據呢,咱們就知道了,若是轉換率或者是某些監控的成功率低於某個值,那頗有可能就是出現了線上異常,結合了相應的報警功能,咱們就不須要等用戶來反饋了,這個就是業務穩定性保障的基礎。canvas

同時呢,對於一些特殊狀況,好比說,開發過程中或代碼中出現了一些catch代碼塊,捕獲住了異常,讓程序不崩潰,這實際上是不合理的,程序雖然沒有崩潰,當時程序的功能已經變得不可用,因此呢,這些被catch的異常咱們也須要上報上來,這樣咱們才能知道用戶到底出現了什麼問題而致使的異常。此外,線上還有一些單點問題,好比說用戶點擊登陸一直進不去,這種就屬於單點問題,其實咱們是沒法找出其和其它問題的共性之處的,因此呢,咱們就必需要找到它對應的詳細信息。小程序

最後,若是發生了異常狀況,咱們還採起了一系列措施進行快速止損。(=>4)

四、若是發生了異常狀況,怎麼快速止損?

  • 功能開關
  • 統跳中心
  • 動態修復:熱修復、資源包更新
  • 自主修復:安全模式

首先,須要讓App具有一些高級的能力,咱們對於任何要上線的新功能,要加上一個功能的開關,經過配置中心下發的開關呢,來決定是否要顯示新功能的入口。若是有異常狀況,能夠緊急關閉新功能的入口,那就可讓這個App處於可控的狀態了。

而後,咱們須要給App設立路由跳轉,全部的界面跳轉都須要經過路由來分發,若是咱們匹配到須要跳轉到有bug的這樣一個新功能時,那咱們就不跳轉了,或者是跳轉到統一的異常正處理中的界面。若是這兩種方式都不能夠,那就能夠考慮經過熱修復的方式來動態修復,目前熱修復的方案其實已經比較成熟了,咱們徹底能夠低成本地在咱們的項目中添加熱修復的能力,固然,若是有些功能是由RN或WeeX來實現就更好了,那就能夠經過更新資源包的方式來實現動態更新。而這些若是都不能夠的話呢,那就能夠考慮本身去給應用加上一個自主修復的能力,若是App啓動屢次的話,那就能夠考慮清空全部的緩存數據,將App重置到安裝的狀態,到了最嚴重的等級呢,能夠阻塞主線程,此時必定要等App熱修復成功以後才容許用戶進入。

image

須要更全面更深刻的理解請查看深刻探索Android穩定性優化

二、App啓動速度優化

一、啓動優化是怎麼作的?

  • 分析現狀、確認問題
  • 針對性優化(先歸納,引導其深刻)
  • 長期保持優化效果

在某一個版本以後呢,咱們會發現這個啓動速度變得特別慢,同時用戶給咱們的反饋也愈來愈多,因此,咱們開始考慮對應用的啓動速度來進行優化。而後,咱們就對啓動的代碼進行了代碼層面的梳理,咱們發現應用的啓動流程已經很是複雜,接着,咱們經過一系列的工具來確認是否在主線程中執行了太多的耗時操做。

咱們通過了細查代碼以後,發現應用主線程中的任務太多,咱們就想了一個方案去針對性地解決,也就是進行異步初始化。(引導=>第2題) 而後,咱們還發現了另一個問題,也能夠進行鍼對性的優化,就是在咱們的初始化代碼當中有些的優先級並非那麼高,它能夠不放在Application的onCreate中執行,而徹底能夠放在以後延遲執行的,由於咱們對這些代碼進行了延遲初始化,最後,咱們還結合了idealHandler作了一個更優的延遲初始化的方案,利用它能夠在主線程的空閒時間進行初始化,以減小啓動耗時致使的卡頓現象。作完這些以後,咱們的啓動速度就變得很快了。

最後,我簡單說下咱們是怎麼長期來保持啓動優化的效果的。首先,咱們作了咱們的啓動器,而且結合了咱們的CI,在線上加上了不少方面的監控。(引導=> 第4題)

二、是怎麼異步的,異步遇到問題沒有?

  • 體現演進過程
  • 詳細介紹啓動器

咱們最初是採用的普通的一個異步的方案,即new Thread + 設置線程優先級爲後臺線程的方式在Application的onCreate方法中進行異步初始化,後來,咱們使用了線程池、IntentService的方式,可是,在咱們應用的演進過程中,發現代碼會變得不夠優雅,而且有些場景很是很差處理,好比說多個初始化任務直接的依賴關係,好比說某一個初始化任務須要在某一個特定的生命週期中初始化完成,這些都是使用線程池、IntentService沒法實現的。因此說,咱們就開始思考一個新的解決方案,它可以完美地解決咱們剛剛所遇到的這些問題。

這個方案就是咱們目前所使用的啓動器,在啓動器的概念中,咱們將每個初始化代碼抽象成了一個Task,而後,對它們進行了一個排序,根據它們之間的依賴關係排了一個有向無環圖,接着,使用一個異步隊列進行執行,而且這個異步隊列它和CPU的核心數是強烈相關的,它可以最大程度地保證咱們的主線程和別的線程都可以執行咱們的任務,也就是你們幾乎均可以同時完成。

三、啓動優化有哪些容易忽略的注意點?

  • cpu time與wall time
  • 注意延遲初始化的優化
  • 介紹下黑科技

首先,在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的頻率在啓動的時候去進行一個暴力的提高。

四、版本迭代致使的啓動變慢有好的解決方式嗎?

  • 啓動器
  • 結合CI
  • 監控完善

這種問題其實咱們以前也遇到過,這的確很是難以解決。可是,咱們後面對此進行了反覆的思考與嘗試,終於找到了一個比較好的解決方式。

首先,咱們使用了啓動器去管理每個初始化任務,而且啓動器中每個任務的執行都是被其自動進行分配的,也就是說這些自動分配的task咱們會盡可能保證它會平均分配在咱們每個線程當中的,這和咱們普通的異步是不同的,它能夠很好地緩解咱們應用的啓動變慢。

其次,咱們還結合了CI,好比說,咱們如今限制了一些類,如Application,若是有人修改了它,咱們不會讓這部分代碼合併到主幹分支或者是修改以後會有一些內部的工具如郵件的形式發送到我,而後,我就會和他確認他加的這些代碼究竟是耗時多少,可否異步初始化,不能異步的話就考慮延遲初始化,若是初始化時間太長,則能夠考慮是否能進行懶加載,等用到的時候再去使用等等。

而後,咱們會將問題儘量地暴露在上線以前。同時,咱們真正已經到了線上的一個環境下時,咱們進行了監控的一個完善,咱們不只是監控了App的整個的啓動時間,同時呢,咱們也將每個生命週期都進行了一個監控。好比說Application的onCreate與onAttachBaseContext方法的耗時,以及這兩個生命週期之間間隔的時間,咱們都進行了一個監控,若是說下一次咱們發現了這個啓動速度變慢了,咱們就能夠去查找究竟是哪個環節變慢了,咱們會和之前的版本進行對比,對比完成以後呢,咱們就能夠來找這一段新加的代碼。

五、開放問題:若是提升啓動速度,設計一個延遲加載框架或者sdk的方法和注意的問題

image

image

須要更全面更深刻的理解請查看深刻探索Android啓動速度優化

三、App內存優化

一、大家內存優化項目的過程是怎麼作的?

一、分析現狀、確認問題

咱們發現咱們的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總體卡頓。

避免發生內存抖動的幾點建議:

  • 儘可能避免在循環體內建立對象,應該把對象建立移到循環體外。
  • 注意自定義View的onDraw()方法會被頻繁調用,因此在這裏面不該該頻繁的建立對象。
  • 當須要大量使用Bitmap的時候,試着把它們緩存在數組或容器中實現複用。
  • 對於可以複用的對象,同理能夠使用對象池將它們緩存起來。

image

須要更全面更深刻的理解請查看Android性能優化以內存優化深刻探索Android內存優化

四、App繪製優化

一、你在作佈局優化的過程當中用到了哪些工具?

我在作佈局優化的過程當中,用到了不少的工具,可是每個工具都有它不一樣的使用場景,不一樣的場景應該使用不一樣的工具。下面我從線上和線下兩個角度來進行分析。

好比說,我要統計線上的FPS,我使用的就是Choreographer這個類,它具備如下特性:

  • 一、可以獲取總體的幀率。
  • 二、可以帶到線上使用。
  • 三、它獲取的幀率幾乎是實時的,可以知足咱們的需求。

同時,在線下,若是要去優化佈局加載帶來的時間消耗,那就須要檢測每個佈局的耗時,對此我使用的是AOP的方式,它沒有侵入性,同時也不須要別的開發同窗進行接入,就能夠方便地獲取每個佈局加載的耗時。若是還要更細粒度地去檢測每個控件的加載耗時,那麼就須要使用LayoutInflaterCompat.setFactory2這個方法去進行Hook。

此外,我還使用了LayoutInspector和Systrace這兩個工具,Systrace能夠很方便地看到每幀的具體耗時以及這一幀在佈局當中它真正作了什麼。而LayoutInspector能夠很方便地看到每個界面的佈局層級,幫助咱們對層級進行優化。

二、佈局爲何會致使卡頓,你又是如何優化的?

分析完佈局的加載流程以後,咱們發現有以下四點可能會致使佈局卡頓:

  • 一、首先,系統會將咱們的Xml文件經過IO的方式映射的方式加載到咱們的內存當中,而IO的過程可能會致使卡頓。
  • 二、其次,佈局加載的過程是一個反射的過程,而反射的過程也會可能會致使卡頓。
  • 三、同時,這個佈局的層級若是比較深,那麼進行佈局遍歷的過程就會比較耗時。
  • 四、最後,不合理的嵌套RelativeLayout佈局也會致使重繪的次數過多。

對此,咱們的優化方式有以下幾種:

  • 一、針對佈局加載Xml文件的優化,咱們使用了異步Inflate的方式,即AsyncLayoutInflater。它的核心原理是在子線程中對咱們的Layout進行加載,而加載完成以後會將View經過Handler發送到主線程來使用。因此不會阻塞咱們的主線程,加載的時間所有是在異步線程中進行消耗的。而這僅僅是一個從側面緩解的思路。
  • 二、後面,咱們發現了一個從根源解決上述痛點的方式,即便用X2C框架。它的一個核心原理就是在開發過程咱們仍是使用的XML進行編寫佈局,可是在編譯的時候它會使用APT的方式將XML佈局轉換爲Java的方式進行佈局,經過這樣的方式去寫佈局,它有如下優勢:一、它省去了使用IO的方式去加載XML佈局的耗時過程。二、它是採用Java代碼直接new的方式去建立控件對象,因此它也沒有反射帶來的性能損耗。這樣就從根本上解決了佈局加載過程當中帶來的問題。
  • 三、而後,咱們能夠使用ConstraintLayout去減小咱們界面佈局的嵌套層級,若是原始佈局層級越深,它能減小的層級就越多。而使用它也能避免嵌套RelativeLayout佈局致使的重繪次數過多。
  • 四、最後,咱們能夠使用AspectJ框架(即AOP)和LayoutInflaterCompat.setFactory2的方式分別去創建線下全局的佈局加載速度和控件加載速度的監控體系。

三、作完佈局優化有哪些成果產出?

  • 一、首先,咱們創建了一個體系化的監控手段,這裏的體系還指的是線上加線下的一個綜合方案,針對線下,咱們使用AOP或者ARTHook,能夠很方便地獲取到每個佈局的加載耗時以及每個控件的加載耗時。針對線上,咱們經過Choreographer.getInstance().postFrameCallback的方式收集到了FPS,這樣咱們能夠知道用戶在哪些界面出現了丟幀的狀況。
  • 二、而後,對於佈局監控方面,咱們設立了FPS、佈局加載時間、佈局層級等一系列指標。
  • 三、最後,在每個版本上線以前,咱們都會對咱們的核心路徑進行一次Review,確保咱們的FPS、佈局加載時間、佈局層級等達到一個合理的狀態。

四、你是怎麼作卡頓優化的?

從項目的初期到壯大期,最後再到成熟期,每個階段都針對卡頓優化作了不一樣的處理。各個階段所作的事情以下所示:

  • 一、系統工具定位、解決
  • 二、自動化卡頓方案及優化
  • 三、線上監控及線下監測工具的建設

我作卡頓優化也是經歷了一些階段,最初咱們的項目當中的一些模塊出現了卡頓以後,我是經過系統工具進行了定位,我使用了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率等常規指標以外呢,咱們還計算了頁面的秒開率、生命週期的執行時間等等。並且,在卡頓發生的時刻,咱們也儘量多地保存下來了當前的一個場景信息,這爲咱們以後解決或者復現這個卡頓留下了依據。

七、TextView setText耗時的緣由,對TextView繪製層源碼的理解?

八、開放問題:優化一個列表頁面的打開速度和流暢性。

image

須要更全面更深刻的理解請查看Android性能優化之繪製優化深刻探索Android佈局優化(上)深刻探索Android佈局優化(下)

五、App瘦身

image

六、網絡優化

一、移動端獲取網絡數據優化的幾個點

  • 一、鏈接複用:節省鏈接創建時間,如開啓 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 服務器,有效的防止了域名劫持,提升域名解析的效率。

image

參考文章

二、客戶端網絡安全實現

三、設計一個網絡優化方案,針對移動端弱網環境。

七、App電量優化

image

八、安卓的安全優化

一、提升app安全性的方法?

二、安卓的app加固如何作?

三、安卓的混淆原理是什麼?

四、談談你對安卓簽名的理解。

九、爲何WebView加載會慢呢?

這是由於在客戶端中,加載H5頁面以前,須要先初始化WebView,在WebView徹底初始化完成以前,後續的界面加載過程都是被阻塞的。

優化手段圍繞着如下兩個點進行:

  • 預加載WebView。
  • 加載WebView的同時,請求H5頁面數據。

所以常見的方法是:

  • 全局WebView。
  • 客戶端代理頁面請求。WebView初始化完成後向客戶端請求數據。
  • asset存放離線包。

除此以外還有一些其餘的優化手段:

  • 腳本執行慢,可讓腳本最後運行,不阻塞頁面解析。
  • DNS連接慢,可讓客戶端複用使用的域名與連接。
  • React框架代碼執行慢,能夠將這部分代碼拆分出來,提早進行解析。

十、如何優化自定義View

爲了加速你的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的層級結構來計算大小。

十一、FC(Force Close)何時會出現?

Error、OOM,StackOverFlowError、Runtime,好比說空指針異常

解決的辦法:

  • 注意內存的使用和管理
  • 使用Thread.UncaughtExceptionHandler接口

十二、Java多線程引起的性能問題,怎麼解決

1三、TraceView的實現原理,分析數據偏差來源。

1四、是否使用過SysTrace,原理的瞭解?

1五、mmap + native 日誌優化?

傳統日誌打印有兩個性能問題,一個是反覆操做文件描述符表,一個是反覆進入內核態。因此須要使用mmap的方式去直接讀寫內存。

2、Android Framework相關

一、Android系統架構

image

Android 是一種基於 Linux 的開放源代碼軟件棧,爲普遍的設備和機型而建立。下圖所示爲 Android 平臺的五大組件:

1.應用程序

Android 隨附一套用於電子郵件、短信、日曆、互聯網瀏覽和聯繫人等的核心應用。平臺隨附的應用與用戶能夠選擇安裝的應用同樣,沒有特殊狀態。所以第三方應用可成爲用戶的默認網絡瀏覽器、短信 Messenger 甚至默認鍵盤(有一些例外,例如系統的「設置」應用)。

系統應用可用做用戶的應用,以及提供開發者可從其本身的應用訪問的主要功能。例如,若是您的應用要發短信,您無需本身構建該功能,能夠改成調用已安裝的短信應用向您指定的接收者發送消息。

二、Java API 框架

您可經過以 Java 語言編寫的 API 使用 Android OS 的整個功能集。這些 API 造成建立 Android 應用所需的構建塊,它們可簡化核心模塊化系統組件和服務的重複使用,包括如下組件和服務:

  • 豐富、可擴展的視圖系統,可用以構建應用的 UI,包括列表、網格、文本框、按鈕甚至可嵌入的網絡瀏覽器
  • 資源管理器,用於訪問非代碼資源,例如本地化的字符串、圖形和佈局文件
  • 通知管理器,可以讓全部應用在狀態欄中顯示自定義提醒
  • Activity 管理器,用於管理應用的生命週期,提供常見的導航返回棧
  • 內容提供程序,可以讓應用訪問其餘應用(例如「聯繫人」應用)中的數據或者共享其本身的數據

開發者能夠徹底訪問 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 的部分主要功能包括:

  • 預先 (AOT) 和即時 (JIT) 編譯
  • 優化的垃圾回收 (GC)
  • 更好的調試支持,包括專用採樣分析器、詳細的診斷異常和崩潰報告,而且可以設置監視點以監控特定字段

在 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 利用主要安全功能,而且容許設備製造商爲著名的內核開發硬件驅動程序。

對於Android應用開發來講,最好能手繪下面的系統架構圖:

image

二、View的事件分發機制?滑動衝突怎麼解決?

瞭解Activity的構成

一個Activity包含了一個Window對象,這個對象是由PhoneWindow來實現的。PhoneWindow將DecorView做爲整個應用窗口的根View,而這個DecorView又將屏幕劃分爲兩個區域:一個是TitleView,另外一個是ContentView,而咱們平時所寫的就是展現在ContentView中的。

觸摸事件的類型

觸摸事件對應的是MotionEvent類,事件的類型主要有以下三種:

  • ACTION_DOWN
  • ACTION_MOVE(移動的距離超過必定的閾值會被斷定爲ACTION_MOVE操做)
  • ACTION_UP

View事件分發本質就是對MotionEvent事件分發的過程。即當一個MotionEvent發生後,系統將這個點擊事件傳遞到一個具體的View上。

事件分發流程

事件分發過程由三個方法共同完成:

dispatchTouchEvent:方法返回值爲true表示事件被當前視圖消費掉;返回爲super.dispatchTouchEvent表示繼續分發該事件,返回爲false表示交給父類的onTouchEvent處理。

onInterceptTouchEvent:方法返回值爲true表示攔截這個事件並交由自身的onTouchEvent方法進行消費;返回false表示不攔截,須要繼續傳遞給子視圖。若是return super.onInterceptTouchEvent(ev), 事件攔截分兩種狀況:  

  • 1.若是該View存在子View且點擊到了該子View, 則不攔截, 繼續分發 給子View 處理, 此時至關於return false。
  • 2.若是該View沒有子View或者有子View可是沒有點擊中子View(此時ViewGroup 至關於普通View), 則交由該View的onTouchEvent響應,此時至關於return true。

注意:通常的LinearLayout、 RelativeLayout、FrameLayout等ViewGroup默認不攔截, 而 ScrollView、ListView等ViewGroup則可能攔截,得看具體狀況。

onTouchEvent:方法返回值爲true表示當前視圖能夠處理對應的事件;返回值爲false表示當前視圖不處理這個事件,它會被傳遞給父視圖的onTouchEvent方法進行處理。若是return super.onTouchEvent(ev),事件處理分爲兩種狀況:

  • 1.若是該View是clickable或者longclickable的,則會返回true, 表示消費 了該事件, 與返回true同樣;
  • 2.若是該View不是clickable或者longclickable的,則會返回false, 表示不 消費該事件,將會向上傳遞,與返回false同樣。

注意:在Android系統中,擁有事件傳遞處理能力的類有如下三種:

  • Activity:擁有分發和消費兩個方法。
  • ViewGroup:擁有分發、攔截和消費三個方法。
  • View:擁有分發、消費兩個方法。

三個方法的關係用僞代碼表示以下:

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事件除外。

記住這個圖的傳遞順序,面試的時候可以畫出來,就很詳細了:

image

ACTION_CANCEL何時觸發,觸摸button而後滑動到外部擡起會觸發點擊事件嗎,再滑動回去擡起會麼?

  • 通常ACTION_CANCEL和ACTION_UP都做爲View一段事件處理的結束。若是在父View中攔截ACTION_UP或ACTION_MOVE,在第一次父視圖攔截消息的瞬間,父視圖指定子視圖不接受後續消息了,同時子視圖會收到ACTION_CANCEL事件。
  • 若是觸摸某個控件,可是又不是在這個控件的區域上擡起(移動到別的地方了),就會出現action_cancel。
點擊事件被攔截,可是想傳到下面的View,如何操做?

重寫子類的requestDisallowInterceptTouchEvent()方法返回true就不會執行父類的onInterceptTouchEvent(),便可將點擊事件傳到下面的View。

如何解決View的事件衝突?舉個開發中遇到的例子?

常見開發中事件衝突的有ScrollView與RecyclerView的滑動衝突、RecyclerView內嵌同時滑動同一方向。

滑動衝突的處理規則:

  • 對於因爲外部滑動和內部滑動方向不一致致使的滑動衝突,能夠根據滑動的方向判斷誰來攔截事件。
  • 對於因爲外部滑動方向和內部滑動方向一致致使的滑動衝突,能夠根據業務需求,規定什麼時候讓外部View攔截事件,什麼時候由內部View攔截事件。
  • 對於上面兩種狀況的嵌套,相對複雜,可一樣根據需求在業務上找到突破點。

滑動衝突的實現方法:

  • 外部攔截法:指點擊事件都先通過父容器的攔截處理,若是父容器須要此事件就攔截,不然就不攔截。具體方法:須要重寫父容器的onInterceptTouchEvent方法,在內部作出相應的攔截。
  • 內部攔截法:指父容器不攔截任何事件,而將全部的事件都傳遞給子容器,若是子容器須要此事件就直接消耗,不然就交由父容器進行處理。具體方法:須要配合requestDisallowInterceptTouchEvent方法。

加深理解,GOGOGO

三、View的繪製流程?

DecorView被加載到Window中

  • 從Activity的startActivity開始,最終調用到ActivityThread的handleLaunchActivity方法來建立Activity,首先,會調用performLaunchActivity方法,內部會執行Activity的onCreate方法,從而完成DecorView和Activity的建立。而後,會調用handleResumeActivity,裏面首先會調用performResumeActivity去執行Activity的onResume()方法,執行完後會獲得一個ActivityClientRecord對象,而後經過r.window.getDecorView()的方式獲得DecorView,而後會經過a.getWindowManager()獲得WindowManager,最終調用其addView()方法將DecorView加進去。
  • WindowManager的實現類是WindowManagerImpl,它內部會將addView的邏輯委託給WindowManagerGlobal,可見這裏使用了接口隔離和委託模式將實現和抽象充分解耦。在WindowManagerGlobal的addView()方法中不只會將DecorView添加到Window中,同時會建立ViewRootImpl對象,並將ViewRootImpl對象和DecorView經過root.setView()把DecorView加載到Window中。這裏的ViewRootImpl是ViewRoot的實現類,是鏈接WindowManager和DecorView的紐帶。View的三大流程均是經過ViewRoot來完成的。

瞭解繪製的總體流程

繪製會從根視圖ViewRoot的performTraversals()方法開始,從上到下遍歷整個視圖樹,每一個View控件負責繪製本身,而ViewGroup還須要負責通知本身的子View進行繪製操做。

理解MeasureSpec

MeasureSpec表示的是一個32位的整形值,它的高2位表示測量模式SpecMode,低30位表示某種測量模式下的規格大小SpecSize。MeasureSpec是View類的一個靜態內部類,用來講明應該如何測量這個View。它由三種測量模式,以下:

  • EXACTLY:精確測量模式,視圖寬高指定爲match_parent或具體數值時生效,表示父視圖已經決定了子視圖的精確大小,這種模式下View的測量值就是SpecSize的值。
  • AT_MOST:最大值測量模式,當視圖的寬高指定爲wrap_content時生效,此時子視圖的尺寸能夠是不超過父視圖容許的最大尺寸的任何尺寸。
  • UNSPECIFIED:不指定測量模式, 父視圖沒有限制子視圖的大小,子視圖能夠是想要的任何尺寸,一般用於系統內部,應用開發中不多用到。

MeasureSpec經過將SpecMode和SpecSize打包成一個int值來避免過多的對象內存分配,爲了方便操做,其提供了打包和解包的方法,打包方法爲makeMeasureSpec,解包方法爲getMode和getSize。

普通View的MeasureSpec的建立規則以下:

image

對於DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同決定;對於普通的View,它的MeasureSpec由父視圖的MeasureSpec和其自身的LayoutParams共同決定。

如何根據MeasureSpec去實現一個瀑布流的自定義ViewGroup?

View繪製流程之Measure

  • 首先,在ViewGroup中的measureChildren()方法中會遍歷測量ViewGroup中全部的View,當View的可見性處於GONE狀態時,不對其進行測量。
  • 而後,測量某個指定的View時,根據父容器的MeasureSpec和子View的LayoutParams等信息計算子View的MeasureSpec。
  • 最後,將計算出的MeasureSpec傳入View的measure方法,這裏ViewGroup沒有定義測量的具體過程,由於ViewGroup是一個抽象類,其測量過程的onMeasure方法須要各個子類去實現。不一樣的ViewGroup子類有不一樣的佈局特性,這致使它們的測量細節各不相同,若是須要自定義測量過程,則子類能夠重寫這個方法。(setMeasureDimension方法用於設置View的測量寬高,若是View沒有重寫onMeasure方法,則會默認調用getDefaultSize來得到View的寬高)
getSuggestMinimumWidth分析

若是View沒有設置背景,那麼返回android:minWidth這個屬性所指定的值,這個值能夠爲0;若是View設置了背景,則返回android:minWidth和背景的最小寬度這二者中的最大值。

自定義View時手動處理wrap_content時的情形

直接繼承View的控件須要重寫onMeasure方法並設置wrap_content時的自身大小,不然在佈局中使用wrap_content就至關於使用match_parent。此時,能夠在wrap_content的狀況下(對應MeasureSpec.AT_MOST)指定內部寬/高(mWidth和mHeight)。

LinearLayout的onMeasure方法實現解析(這裏僅分析measureVertical核心源碼)

系統會遍歷子元素並對每一個子元素執行measureChildBeforeLayout方法,這個方法內部會調用子元素的measure方法,這樣各個子元素就開始依次進入measure過程,而且系統會經過mTotalLength這個變量來存儲LinearLayout在豎直方向的初步高度。每測量一個子元素,mTotalLength就會增長,增長的部分主要包括了子元素的高度以及子元素在豎直方向上的margin等。

在Activity中獲取某個View的寬高

因爲View的measure過程和Activity的生命週期方法不是同步執行的,若是View尚未測量完畢,那麼得到的寬/高就是0。因此在onCreate、onStart、onResume中均沒法正確獲得某個View的寬高信息。解決方式以下:

  • Activity/View#onWindowFocusChanged:此時View已經初始化完畢,當Activity的窗口獲得焦點和失去焦點時均會被調用一次,若是頻繁地進行onResume和onPause,那麼onWindowFocusChanged也會被頻繁地調用。
  • view.post(runnable): 經過post能夠將一個runnable投遞到消息隊列的尾部,始化好了而後等待Looper調用次runnable的時候,View也已經初始化好了。
  • ViewTreeObserver#addOnGlobalLayoutListener:當View樹的狀態發生改變或者View樹內部的View的可見性發生改變時,onGlobalLayout方法將被回調。
  • View.measure(int widthMeasureSpec, int heightMeasureSpec):match_parent時不知道parentSize的大小,測不出;具體數值時,直接makeMeasureSpec固定值,而後調用view..measure就能夠了;wrap_content時,在最大化模式下,用View理論上能支持的最大值去構造MeasureSpec是合理的。

View的繪製流程之Layout

首先,會經過setFrame方法來設定View的四個頂點的位置,即View在父容器中的位置。而後,會執行到onLayout空方法,子類若是是ViewGroup類型,則重寫這個方法,實現ViewGroup中全部View控件佈局流程。

LinearLayout的onLayout方法實現解析(layoutVertical核心源碼)

其中會遍歷調用每一個子View的setChildFrame方法爲子元素肯定對應的位置。其中的childTop會逐漸增大,意味着後面的子元素會被放置在靠下的位置。

注意:在View的默認實現中,View的測量寬/高和最終寬/高是相等的,只不過測量寬/高造成於View的measure過程,而最終寬/高造成於View的layout過程,即二者的賦值時機不一樣,測量寬/高的賦值時機稍微早一些。在一些特殊的狀況下則二者不相等:

  • 重寫View的layout方法,使最終寬度老是比測量寬/高大100px。
  • View須要屢次measure才能肯定本身的測量寬/高,在前幾回測量的過程當中,其得出的測量寬/高有可能和最終寬/高不一致,但最終來講,測量寬/高仍是和最終寬/高相同。

View的繪製流程之Draw

Draw的基本流程

繪製基本上能夠分爲六個步驟:

  • 首先繪製View的背景;
  • 若是須要的話,保持canvas的圖層,爲fading作準備;
  • 而後,繪製View的內容;
  • 接着,繪製View的子View;
  • 若是須要的話,繪製View的fading邊緣並恢復圖層;
  • 最後,繪製View的裝飾(例如滾動條等等)。
setWillNotDraw的做用

若是一個View不須要繪製任何內容,那麼設置這個標記位爲true之後,系統會進行相應的優化。

  • 默認狀況下,View沒有啓用這個優化標記位,可是ViewGroup會默認啓用這個優化標記位。
  • 當咱們的自定義控件繼承於ViewGroup而且自己不具有繪製功能時,就能夠開啓這個標記位從而便於系統進行後續的優化。
  • 當明確知道一個ViewGroup須要經過onDraw來繪製內容時,咱們須要顯示地關閉WILL_NOT_DRAW這個標記位。

Requestlayout,onlayout,onDraw,DrawChild區別與聯繫?

requestLayout()方法 :會致使調用 measure()過程 和 layout()過程,將會根據標誌位判斷是否須要ondraw。

onLayout()方法:若是該View是ViewGroup對象,須要實現該方法,對每一個子視圖進行佈局。

onDraw()方法:繪製視圖自己 (每一個View都須要重載該方法,ViewGroup不須要實現該方法)。

drawChild():去從新回調每一個子視圖的draw()方法。

invalidate() 和 postInvalidate()的區別 ?

invalidate()與postInvalidate()都用於刷新View,主要區別是invalidate()在主線程中調用,若在子線程中使用須要配合handler;而postInvalidate()可在子線程中直接調用。

更詳細的內容請點擊這裏

四、跨進程通訊。

Android中進程和線程的關係?區別?

  • 線程是CPU調度的最小單元,同時線程是一種有限的系統資源;而進程通常指一個執行單元,在PC和移動設備上指一個程序或者一個應用。
  • 通常來講,一個App程序至少有一個進程,一個進程至少有一個線程(包含與被包含的關係),通俗來說就是,在App這個工廠裏面有一個進程,線程就是裏面的生產線,但主線程(即主生產線)只有一條,而子線程(即副生產線)能夠有多個。
  • 進程有本身獨立的地址空間,而進程中的線程共享此地址空間,均可以併發執行。

如何開啓多進程?應用是否能夠開啓N個進程?

在AndroidManifest中給四大組件指定屬性android:process開啓多進程模式,在內存容許的條件下能夠開啓N個進程。

爲什麼須要IPC?多進程通訊可能會出現的問題?

全部運行在不一樣進程的四大組件(Activity、Service、Receiver、ContentProvider)共享數據都會失敗,這是因爲Android爲每一個應用分配了獨立的虛擬機,不一樣的虛擬機在內存分配上有不一樣的地址空間,這會致使在不一樣的虛擬機中訪問同一個類的對象會產生多份副本。好比經常使用例子(經過開啓多進程獲取更大內存空間、兩個或者多個應用之間共享數據、微信全家桶)。

通常來講,使用多進程通訊會形成以下幾方面的問題:

  • 靜態成員和單例模式徹底失效:獨立的虛擬機形成。
  • 線程同步機制徹底失效:獨立的虛擬機形成。
  • SharedPreferences的可靠性降低:這是由於Sp不支持兩個進程併發進行讀寫,有必定概率致使數據丟失。
  • Application會屢次建立:Android系統在建立新的進程時會分配獨立的虛擬機,因此這個過程其實就是啓動一個應用的過程,天然也會建立新的Application。

Android中IPC方式、各類方式優缺點?

image

講講AIDL?如何優化多模塊都使用AIDL的狀況?

AIDL(Android Interface Definition Language,Android接口定義語言):若是在一個進程中要調用另外一個進程中對象的方法,可以使用AIDL生成可序列化的參數,AIDL會生成一個服務端對象的代理類,經過它客戶端能夠實現間接調用服務端對象的方法。

AIDL的本質是系統提供了一套可快速實現Binder的工具。關鍵類和方法:

  • AIDL接口:繼承IInterface。
  • Stub類:Binder的實現類,服務端經過這個類來提供服務。
  • Proxy類:服務端的本地代理,客戶端經過這個類調用服務端的方法。
  • asInterface():客戶端調用,將服務端返回的Binder對象,轉換成客戶端所須要的AIDL接口類型的對象。若是客戶端和服務端位於同一進程,則直接返回Stub對象自己,不然返回系統封裝後的Stub.proxy對象。
  • asBinder():根據當前調用狀況返回代理Proxy的Binder對象。
  • onTransact():運行在服務端的Binder線程池中,當客戶端發起跨進程請求時,遠程請求會經過系統底層封裝後交由此方法來處理。
  • transact():運行在客戶端,當客戶端發起遠程請求的同時將當前線程掛起。以後調用服務端的onTransact()直到遠程請求返回,當前線程才繼續執行。

當有多個業務模塊都須要AIDL來進行IPC,此時須要爲每一個模塊建立特定的aidl文件,那麼相應的Service就會不少。必然會出現系統資源耗費嚴重、應用過分重量級的問題。解決辦法是創建Binder鏈接池,即將每一個業務模塊的Binder請求統一轉發到一個遠程Service中去執行,從而避免重複建立Service。

工做原理:每一個業務模塊建立本身的AIDL接口並實現此接口,而後向服務端提供本身的惟一標識和其對應的Binder對象。服務端只須要一個Service並提供一個queryBinder接口,它會根據業務模塊的特徵來返回相應的Binder對象,不一樣的業務模塊拿到所需的Binder對象後就能夠進行遠程方法的調用了。

爲何選擇Binder?

爲何選用Binder,在討論這個問題以前,咱們知道Android也是基於Linux內核,Linux現有的進程通訊手段有如下幾種:

  • 管道:在建立時分配一個page大小的內存,緩存區大小比較有限;
  • 消息隊列:信息複製兩次,額外的CPU消耗;不合適頻繁或信息量大的通訊;
  • 共享內存:無須複製,共享緩衝區直接附加到進程虛擬地址空間,速度快;但進程間的同步問題操做系統沒法實現,必須各進程利用同步工具解決;
  • 套接字:做爲更通用的接口,傳輸效率低,主要用於不一樣機器或跨網絡的通訊;
  • 信號量:常做爲一種鎖機制,防止某進程正在訪問共享資源時,其餘進程也訪問該資源。所以,主要做爲進程間以及同一進程內不一樣線程之間的同步手段。 不適用於信息交換,更適用於進程中斷控制,好比非法內存訪問,殺死某個進程等;

既然有現有的IPC方式,爲何從新設計一套Binder機制呢。主要是出於以上三個方面的考量:

  • 一、效率:傳輸效率主要影響因素是內存拷貝的次數,拷貝次數越少,傳輸速率越高。從Android進程架構角度分析:對於消息隊列、Socket和管道來講,數據先從發送方的緩存區拷貝到內核開闢的緩存區中,再從內核緩存區拷貝到接收方的緩存區,一共兩次拷貝,如圖:

image

而對於Binder來講,數據從發送方的緩存區拷貝到內核的緩存區,而接收方的緩存區與內核的緩存區是映射到同一塊物理地址的,節省了一次數據拷貝的過程,如圖:

image

共享內存不須要拷貝,Binder的性能僅次於共享內存。

  • 二、穩定性:上面說到共享內存的性能優於Binder,那爲何不採用共享內存呢,由於共享內存須要處理併發同步問題,容易出現死鎖和資源競爭,穩定性較差。Socket雖然是基於C/S架構的,可是它主要是用於網絡間的通訊且傳輸效率較低。Binder基於C/S架構 ,Server端與Client端相對獨立,穩定性較好。
  • 三、安全性:傳統Linux IPC的接收方沒法得到對方進程可靠的UID/PID,從而沒法鑑別對方身份;而Binder機制爲每一個進程分配了UID/PID,且在Binder通訊時會根據UID/PID進行有效性檢測。

Binder機制的做用和原理?

Linux系統將一個進程分爲用戶空間和內核空間。對於進程之間來講,用戶空間的數據不可共享,內核空間的數據可共享,爲了保證安全性和獨立性,一個進程不能直接操做或者訪問另外一個進程,即Android的進程是相互獨立、隔離的,這就須要跨進程之間的數據通訊方式。普通的跨進程通訊方式通常須要2次內存拷貝,以下圖所示:

image

一次完整的 Binder IPC 通訊過程一般是這樣:

  • 首先 Binder 驅動在內核空間建立一個數據接收緩存區。
  • 接着在內核空間開闢一塊內核緩存區,創建內核緩存區和內核中數據接收緩存區之間的映射關係,以及內核中數據接收緩存區和接收進程用戶空間地址的映射關係。
  • 發送方進程經過系統調用 copyfromuser() 將數據 copy 到內核中的內核緩存區,因爲內核緩存區和接收進程的用戶空間存在內存映射,所以也就至關於把數據發送到了接收進程的用戶空間,這樣便完成了一次進程間的通訊。

image

Binder框架中ServiceManager的做用?

Binder框架 是基於 C/S 架構的。由一系列的組件組成,包括 Client、Server、ServiceManager、Binder驅動,其中 Client、Server、Service Manager 運行在用戶空間,Binder 驅動運行在內核空間。以下圖所示:

image

  • Server&Client:服務器&客戶端。在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通訊。
  • ServiceManager(如同DNS域名服務器)服務的管理者,將Binder名字轉換爲Client中對該Binder的引用,使得Client能夠經過Binder名字得到Server中Binder實體的引用。
  • Binder驅動(如同路由器):負責進程之間binder通訊的創建,計數管理以及數據的傳遞交互等底層支持。

最後,結合Android跨進程通訊:圖文詳解 Binder機制 的總結圖來綜合理解一下:

image

Binder 的完整定義

  • 從進程間通訊的角度看,Binder 是一種進程間通訊的機制;
  • 從 Server 進程的角度看,Binder 指的是 Server 中的 Binder 實體對象;
  • 從 Client 進程的角度看,Binder 指的是 Binder 代理對象,是 Binder 實體對象的一個遠程代理;
  • 從傳輸過程的角度看,Binder 是一個能夠跨進程傳輸的對象;Binder 驅動會對這個跨越進程邊界的對象對一點點特殊處理,自動完成代理對象和本地對象之間的轉換。

手寫實現簡化版AMS(AIDL實現)

與Binder相關的幾個類的職責:

  • IBinder:跨進程通訊的Base接口,它聲明瞭跨進程通訊須要實現的一系列抽象方法,實現了這個接口就說明能夠進行跨進程通訊,Client和Server都要實現此接口。
  • IInterface:這也是一個Base接口,用來表示Server提供了哪些能力,是Client和Server通訊的協議。
  • Binder:提供Binder服務的本地對象的基類,它實現了IBinder接口,全部本地對象都要繼承這個類。
  • BinderProxy:在Binder.java這個文件中還定義了一個BinderProxy類,這個類表示Binder代理對象它一樣實現了IBinder接口,不過它的不少實現都交由native層處理。Client中拿到的其實是這個代理對象。
  • Stub:這個類在編譯aidl文件後自動生成,它繼承自Binder,表示它是一個Binder本地對象;它是一個抽象類,實現了IInterface接口,代表它的子類須要實現Server將要提供的具體能力(即aidl文件中聲明的方法)。
  • Proxy:它實現了IInterface接口,說明它是Binder通訊過程的一部分;它實現了aidl中聲明的方法,但最終仍是交由其中的mRemote成員來處理,說明它是一個代理對象,mRemote成員實際上就是BinderProxy。

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就能夠通訊了。

簡單講講 binder 驅動吧?

從 Java 層來看就像訪問本地接口同樣,客戶端基於 BinderProxy 服務端基於 IBinder 對象,從 native 層來看來看客戶端基於 BpBinder 到 ICPThreadState 到 binder 驅動,服務端由 binder 驅動喚醒 IPCThreadSate 到 BbBinder 。跨進程通訊的原理最終是要基於內核的,因此最會會涉及到 binder_open 、binder_mmap 和 binder_ioctl這三種系統調用。

跨進程傳遞大內存數據如何作?

binder 確定是不行的,由於映射的最大內存只有 1M-8K,能夠採用 binder + 匿名共享內存的形式,像跨進程傳遞大的 bitmap 須要打開系統底層的 ashmem 機制。

請按順序仔細閱讀下列文章提高對Binder機制的理解程度:

寫給 Android 應用工程師的 Binder 原理剖析

Binder學習指南

Binder設計與實現

老羅Binder機制分析系列或Android系統源代碼情景分析Binder章節

五、Android系統啓動流程是什麼?(提示:init進程 -> Zygote進程 –> SystemServer進程 –> 各類系統服務 –> 應用進程)

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啓動後會將已安裝應用的快捷圖標顯示到系統桌面上。

須要更詳細的分析請查看如下系列文章:

Android系統啓動流程之init進程啓動

Android系統啓動流程之Zygote進程啓動

Android系統啓動流程之SystemServer進程啓動

Android系統啓動流程之Launcher進程啓動

系統是怎麼幫咱們啓動找到桌面應用的?

經過意圖,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是相對單一的,從程序中跳轉或者啓動可能樣式更多一些。本質是相同的。

七、AMS家族重要術語解釋。

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,那麼對這概念應該不陌生。

八、App啓動流程(Activity的冷啓動流程)。

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

Activity的啓動流程圖(放大可查看)以下所示:

image

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

  • 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新進程。
  • 新啓動的應用進程,該進程就是用來承載應用運行的進程了,它也是應用的主線程(新建立的進程就是主線程),處理組件生命週期、界面繪製等相關事情。

有了以上的理解,整個流程能夠歸納以下:

  • 一、點擊桌面應用圖標,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的啓動。

最後,再看看另外一幅啓動流程圖來加深理解:

image

九、ActivityThread工做原理。

十、說下四大組件的啓動過程,四大組件的啓動與銷燬的方式。

廣播發送和接收的原理了解嗎?

  • 繼承BroadcastReceiver,重寫onReceive()方法。
  • 經過Binder機制向ActivityManagerService註冊廣播。
  • 經過Binder機制向ActivityMangerService發送廣播。
  • ActivityManagerService查找符合相應條件的廣播(IntentFilter/Permission)的BroadcastReceiver,將廣播發送到BroadcastReceiver所在的消息隊列中。
  • BroadcastReceiver所在消息隊列拿到此廣播後,回調它的onReceive()方法。

十一、AMS是如何管理Activity的?

十二、理解Window和WindowManager。

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。

1三、WMS是如何管理Window的?

1四、大致說清一個應用程序安裝到手機上時發生了什麼?

APK的安裝流程以下所示:

image

複製APK到/data/app目錄下,解壓並掃描安裝包。

資源管理器解析APK裏的資源文件。

解析AndroidManifest文件,並在/data/data/目錄下建立對應的應用數據目錄。

而後對dex文件進行優化,並保存在dalvik-cache目錄下。

將AndroidManifest文件解析出的四大組件信息註冊到PackageManagerService中。

安裝完成後,發送廣播。

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

apk打包流程

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

APK總體的的打包流程以下圖所示:

image

具體說來:

  • 經過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:程序的全局清單配置文件。
  • 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-22四、SHA-25六、SHA-38四、SHA-5十二、SHA-512/22四、SHA-512/256。

特徵:

  • 惟一性
  • 固定長度:比較經常使用的Hash算法有MD5和SHA1,MD5的長度是128拉,SHA1的長度是160位。
  • 不可逆性
簽名和校驗的主要過程

簽名就是在摘要的基礎上再進行一次加密,對摘要加密後的數據就能夠看成數字簽名。

簽名過程:
  • 一、計算摘要:經過Hash算法提取出原始數據的摘要。
  • 二、計算簽名:再經過基於密鑰(私鑰)的非對稱加密算法對提取出的摘要進行加密,加密後的數據就是簽名信息。
  • 三、寫入簽名:將簽名信息寫入原始數據的簽名區塊內。
校驗過程:
  • 一、首先用一樣的Hash算法從接收到的數據中提取出摘要。
  • 二、解密簽名:使用發送方的公鑰對數字簽名進行解密,解密出原始摘要。
  • 三、比較摘要:若是解密後的數據和提取的摘要一致,則校驗經過;若是數據被第三方篡改過,解密後的數據和摘要將會不一致,則校驗不經過。
數字證書

如何保證公鑰的可靠性呢?答案是數字證書,數字證書是身份認證機構(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 簽名原理
  • 一、解析出 CERT.RSA 文件中的證書、公鑰,解密 CERT.RSA 中的加密數據。
  • 二、解密結果和 CERT.SF 的指紋進行對比,保證 CERT.SF 沒有被篡改。
  • 三、而 CERT.SF 中的內容再和 MANIFEST.MF 指紋對比,保證 MANIFEST.MF 文件沒有被篡改。
  • 四、MANIFEST.MF 中的內容和 APK 全部文件指紋逐一對比,保證 APK 沒有被篡改。

1六、說下安卓虛擬機和java虛擬機的原理和不一樣點?(JVM、Davilk、ART三者的原理和區別)

JVM 和Dalvik虛擬機的區別

JVM:.java -> javac -> .class -> jar -> .jar

架構: 堆和棧的架構.

DVM:.java -> javac -> .class -> dx.bat -> .dex

架構: 寄存器(cpu上的一塊高速緩存)

Android2個虛擬機的區別(一個5.0以前,一個5.0以後)

什麼是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缺點:

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

ART和Davlik中垃圾回收的區別?

1七、安卓採用自動垃圾回收機制,請說下安卓內存管理的原理?

開放性問題:如何設計垃圾回收算法?

1八、Android中App是如何沙箱化的,爲什麼要這麼作?

1九、一個圖片在app中調用R.id後是如何找到的

20、JNI

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);  
    }  
    複製代碼

如何在jni中註冊native函數,有幾種註冊方式?

so 的加載流程是怎樣的,生命週期是怎樣的?

這個要從 java 層去看源碼分析,是從 ClassLoader 的 PathList 中去找到目標路徑加載的,同時 so 是經過 mmap 加載映射到虛擬空間的。生命週期加載庫和卸載庫時分別調用 JNI_OnLoad 和 JNI_OnUnload() 方法。

2一、請介紹一下NDK?

讚揚

若是這個庫對您有很大幫助,您願意支持這個項目的進一步開發和這個項目的持續維護。你能夠掃描下面的二維碼,讓我喝一杯咖啡或啤酒。很是感謝您的捐贈。謝謝!


Contanct Me

● 微信 && 微信羣:

歡迎關注個人微信:bcce5360。因爲微信羣人數太多沒法生成羣邀二維碼,因此麻煩你們想進微信羣的朋友們,加我微信拉你進羣(PS:微信羣的學習氛圍與各項福利將會超乎你的想象)

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~

About me

很感謝您閱讀這篇文章,但願您能將它分享給您的朋友或技術羣,這對我意義重大。

但願咱們能成爲朋友,在 Github掘金上一塊兒分享知識。

相關文章
相關標籤/搜索