🔥 A awesome android expert interview questions and answers(continuous updating ...)javascript
從幾十份頂級面試倉庫和300多篇高質量面經中總結出一份全面成體系化的Android高級面試題集。html
歡迎來到2020年中高級Android大廠面試祕籍,爲你保駕護航金三銀四,直通大廠的Android高級篇下。前端
網絡底層庫,它是基於http協議封裝的一套請求客戶端,雖然它也能夠開線程,但根本上它更偏向真正的請求,跟HttpClient, HttpUrlConnection的職責是同樣的。其中封裝了網絡請求get、post等底層操做的實現。java
get、post請求、上傳文件、上傳表單等等。react
OkHttp內部的請求流程:使用OkHttp會在請求的時候初始化一個Call的實例,而後執行它的execute()方法或enqueue()方法,內部最後都會執行到getResponseWithInterceptorChain()方法,這個方法裏面經過攔截器組成的責任鏈,依次通過用戶自定義普通攔截器、重試攔截器、橋接攔截器、緩存攔截器、鏈接攔截器和用戶自定義網絡攔截器以及訪問服務器攔截器等攔截處理過程,來獲取到一個響應並交給用戶。其中,除了OKHttp的內部請求流程這點以外,緩存和鏈接這兩部份內容也是兩個很重要的點,掌握了這3點就說明你理解了OkHttp。android
ConnectionPool:git
一、判斷鏈接是否可用,不可用則從ConnectionPool獲取鏈接,ConnectionPool無鏈接,建立新鏈接,握手,放入ConnectionPool。github
二、它是一個Deque,add添加Connection,使用線程池負責定時清理緩存。web
三、使用鏈接複用省去了進行 TCP 和 TLS 握手的一個過程。面試
使用責任鏈模式實現攔截器的分層設計,每個攔截器對應一個功能,充分實現了功能解耦,易維護。
Volley:支持HTTPS。緩存、異步請求,不支持同步請求。協議類型是Http/1.0, Http/1.1,網絡傳輸使用的是 HttpUrlConnection/HttpClient,數據讀寫使用的IO。 OkHttp:支持HTTPS。緩存、異步請求、同步請求。協議類型是Http/1.0, Http/1.1, SPDY, Http/2.0, WebSocket,網絡傳輸使用的是封裝的Socket,數據讀寫使用的NIO(Okio)。 SPDY協議相似於HTTP,但旨在縮短網頁的加載時間和提升安全性。SPDY協議經過壓縮、多路複用和優先級來縮短加載時間。
Okhttp的子系統層級結構圖以下所示:
網絡配置層:利用Builder模式配置各類參數,例如:超時時間、攔截器等,這些參數都會由Okhttp分發給各個須要的子系統。 重定向層:負責重定向。 Header拼接層:負責把用戶構造的請求轉換爲發送給服務器的請求,把服務器返回的響應轉換爲對用戶友好的響應。 HTTP緩存層:負責讀取緩存以及更新緩存。 鏈接層:鏈接層是一個比較複雜的層級,它實現了網絡協議、內部的攔截器、安全性認證,鏈接與鏈接池等功能,但這一層尚未發起真正的鏈接,它只是作了鏈接器一些參數的處理。 數據響應層:負責從服務器讀取響應的數據。 在整個Okhttp的系統中,咱們還要理解如下幾個關鍵角色:
OkHttpClient:通訊的客戶端,用來統一管理髮起請求與解析響應。 Call:Call是一個接口,它是HTTP請求的抽象描述,具體實現類是RealCall,它由CallFactory建立。 Request:請求,封裝請求的具體信息,例如:url、header等。 RequestBody:請求體,用來提交流、表單等請求信息。 Response:HTTP請求的響應,獲取響應信息,例如:響應header等。 ResponseBody:HTTP請求的響應體,被讀取一次之後就會關閉,因此咱們重複調用responseBody.string()獲取請求結果是會報錯的。 Interceptor:Interceptor是請求攔截器,負責攔截並處理請求,它將網絡請求、緩存、透明壓縮等功能都統一塊兒來,每一個功能都是一個Interceptor,全部的Interceptor最 終鏈接成一個Interceptor.Chain。典型的責任鏈模式實現。 StreamAllocation:用來控制Connections與Streas的資源分配與釋放。 RouteSelector:選擇路線與自動重連。 RouteDatabase:記錄鏈接失敗的Route黑名單。
Retrofit 是一個 RESTful 的 HTTP 網絡請求框架的封裝。Retrofit 2.0 開始內置 OkHttp,前者專一於接口的封裝,後者專一於網絡請求的高效。
一、功能強大:
二、簡潔易用:
三、可擴展性好:
任何網絡場景都應該優先選擇,特別是後臺API遵循Restful API設計風格 & 項目中使用到RxJava。
Retrofit主要是在create方法中採用動態代理模式(經過訪問代理對象的方式來間接訪問目標對象)實現接口方法,這個過程構建了一個ServiceMethod對象,根據方法註解獲取請求方式,參數類型和參數註解拼接請求的連接,當一切都準備好以後會把數據添加到Retrofit的RequestBuilder中。而後當咱們主動發起網絡請求的時候會調用okhttp發起網絡請求,okhttp的配置包括請求方式,URL等在Retrofit的RequestBuilder的build()方法中實現,併發起真正的網絡請求。
內部使用了優秀的架構設計和大量的設計模式,在我分析過Retrofit最新版的源碼和大量優秀的Retrofit源碼分析文章後,我發現,要想真正理解Retrofit內部的核心源碼流程和設計思想,首先,須要對它使用到的九大設計模式有必定的瞭解,下面我簡單說一說:
一、建立Retrofit實例:
二、建立網絡請求接口的實例:
三、發送網絡請求:
四、解析數據
五、切換線程:
六、處理結果
Glide是Android中的一個圖片加載庫,用於實現圖片加載。
一、多樣化媒體加載:不只能夠進行圖片緩存,還支持Gif、WebP、縮略圖,甚至是Video。
二、經過設置綁定生命週期:能夠使加載圖片的生命週期動態管理起來。
三、高效的緩存策略:支持內存、Disk緩存,而且Picasso只會緩存原始尺寸的圖片,內Glide緩存的是多種規格,也就是Glide會根據你ImageView的大小來緩存相應大小的圖片尺寸。
四、內存開銷小:默認的Bitmap格式是RGB_565格式,而Picasso默認的是ARGB_8888格式,內存開銷小一半。
一、圖片加載:Glide.with(this).load(imageUrl).override(800, 800).placeholder().error().animate().into()。
二、多樣式媒體加載:asBitamp、asGif。
三、生命週期集成。
四、能夠配置磁盤緩存策略ALL、NONE、SOURCE、RESULT。
庫比較大,源碼實現複雜。
一、初始化各式各樣的配置信息(包括緩存,請求線程池,大小,圖片格式等等)以及glide對象。
二、將glide請求和application/SupportFragment/Fragment的生命週期綁定在一塊。
設置請求url,並記錄url已設置的狀態。
三、Glide&into:
一、首先根據轉碼類transcodeClass類型返回不一樣的ImageViewTarget:BitmapImageViewTarget、DrawableImageViewTarget。
二、遞歸創建縮略圖請求,沒有縮略圖請求,則直接進行正常請求。
三、若是沒指定寬高,會根據ImageView的寬高計算出圖片寬高,最終執行到onSizeReay()方法中的engine.load()方法。
四、engine是一個負責加載和管理緩存資源的類
當咱們的APP中想要加載某張圖片時,先去LruCache中尋找圖片,若是LruCache中有,則直接取出來使用,若是LruCache中沒有,則去SoftReference中尋找(軟引用適合當cache,當內存吃緊的時候纔會被回收。而weakReference在每次system.gc()就會被回收)(當LruCache存儲緊張時,會把最近最少使用的數據放到SoftReference中),若是SoftReference中有,則從SoftReference中取出圖片使用,同時將圖片從新放回到LruCache中,若是SoftReference中也沒有圖片,則去硬盤緩存中中尋找,若是有則取出來使用,同時將圖片添加到LruCache中,若是沒有,則鏈接網絡從網上下載圖片。圖片下載完成後,將圖片保存到硬盤緩存中,而後放到LruCache中。
Glide緩存機制大體分爲三層:內存緩存、弱引用緩存、磁盤緩存。
取的順序是:內存、弱引用、磁盤。
存的順序是:弱引用、內存、磁盤。
三層存儲的機制在Engine中實現的。先說下Engine是什麼?Engine這一層負責加載時作管理內存緩存的邏輯。持有MemoryCache、Map<Key, WeakReference<EngineResource<?>>>。經過load()來加載圖片,加載先後會作內存存儲的邏輯。若是內存緩存中沒有,那麼纔會使用EngineJob這一層來進行異步獲取硬盤資源或網絡資源。EngineJob相似一個異步線程或observable。Engine是一個全局惟一的,經過Glide.getEngine()來獲取。
須要一個圖片資源,若是Lrucache中有相應的資源圖片,那麼就返回,同時從Lrucache中清除,放到activeResources中。activeResources map是盛放正在使用的資源,以弱引用的形式存在。同時資源內部有被引用的記錄。若是資源沒有引用記錄了,那麼再放回Lrucache中,同時從activeResources中清除。若是Lrucache中沒有,就從activeResources中找,找到後相應資源引用加1。若是Lrucache和activeResources中沒有,那麼進行資源異步請求(網絡/diskLrucache),請求成功後,資源放到diskLrucache和activeResources中。
使用一個弱引用map activeResources來盛放項目中正在使用的資源。Lrucache中不含有正在使用的資源。資源內部有個計數器來顯示本身是否是還有被引用的狀況,把正在使用的資源和沒有被使用的資源分開有什麼好處呢??由於當Lrucache須要移除一個緩存時,會調用resource.recycle()方法。注意到該方法上面註釋寫着只有沒有任何consumer引用該資源的時候才能夠調用這個方法。那麼爲何調用resource.recycle()方法須要保證該資源沒有任何consumer引用呢?glide中resource定義的recycle()要作的事情是把這個不用的資源(假設是bitmap或drawable)放到bitmapPool中。bitmapPool是一個bitmap回收再利用的庫,在作transform的時候會從這個bitmapPool中拿一個bitmap進行再利用。這樣就避免了從新建立bitmap,減小了內存的開支。而既然bitmapPool中的bitmap會被重複利用,那麼確定要保證回收該資源的時候(即調用資源的recycle()時),要保證該資源真的沒有外界引用了。這也是爲何glide花費那麼多邏輯來保證Lrucache中的資源沒有外界引用的緣由。
Glide的高效的三層緩存機制,如上。
圖片佔用內存的計算公式:圖片高度 * 圖片寬度 * 一個像素佔用的內存大小。因此,計算圖片佔用內存大小的時候,要考慮圖片所在的目錄跟設備密度,這兩個因素其實影響的是圖片的寬高,android會對圖片進行拉昇跟壓縮。
因爲Android對圖片使用內存有限制,如果加載幾兆的大圖片便內存溢出。Bitmap會將圖片的全部像素(即長x寬)加載到內存中,若是圖片分辨率過大,會直接致使內存OOM,只有在BitmapFactory加載圖片時使用BitmapFactory.Options對相關參數進行配置來減小加載的像素。
BitmapFactory.Options相關參數詳解:
(1).Options.inPreferredConfig值來下降內存消耗。
好比:默認值ARGB_8888改成RGB_565,節約一半內存。
(2).設置Options.inSampleSize 縮放比例,對大圖片進行壓縮 。
(3).設置Options.inPurgeable和inInputShareable:讓系統能及時回收內存。
A:inPurgeable:設置爲True時,表示系統內存不足時能夠被回收,設置爲False時,表示不能被回收。
B:inInputShareable:設置是否深拷貝,與inPurgeable結合使用,inPurgeable爲false時,該參數無心義。
複製代碼
(4).使用decodeStream代替decodeResource等其餘方法。
Java 引用類型分類:
在 Android 應用的開發中,爲了防止內存溢出,在處理一些佔用內存大並且生命週期較長的對象時候,能夠儘可能應用軟引用和弱引用技術。
內存緩存基於LruCache實現,磁盤緩存基於DiskLruCache實現。這兩個類都基於Lru算法和LinkedHashMap來實現。
LRU算法能夠用一句話來描述,以下所示:
LRU是Least Recently Used的縮寫,最近最少使用算法,從它的名字就能夠看出,它的核心原則是若是一個數據在最近一段時間沒有使用到,那麼它在未來被訪問到的可能性也很小,則這類數據項會被優先淘汰掉。
以前,咱們會使用內存緩存技術實現,也就是軟引用或弱引用,在Android 2.3(APILevel 9)開始,垃圾回收器會更傾向於回收持有軟引用或弱引用的對象,這讓軟引用和弱引用變得再也不可靠。
其實LRU緩存的實現相似於一個特殊的棧,把訪問過的元素放置到棧頂(若棧中存在,則更新至棧頂;若棧中不存在則直接入棧),而後若是棧中元素數量超過限定值,則刪除棧底元素(即最近最少使用的元素)。
它的內部存在一個 LinkedHashMap 和 maxSize,把最近使用的對象用強引用存儲在 LinkedHashMap 中,給出來 put 和 get 方法,每次 put 圖片時計算緩存中全部圖片的總大小,跟 maxSize 進行比較,大於 maxSize,就將最久添加的圖片移除,反之小於 maxSize 就添加進來。
LruCache的原理就是利用LinkedHashMap持有對象的強引用,按照Lru算法進行對象淘汰。具體說來假設咱們從表尾訪問數據,在表頭刪除數據,當訪問的數據項在鏈表中存在時,則將該數據項移動到表尾,不然在表尾新建一個數據項。當鏈表容量超過必定閾值,則移除表頭的數據。
詳細來講就是LruCache中維護了一個集合LinkedHashMap,該LinkedHashMap是以訪問順序排序的。當調用put()方法時,就會在結合中添加元素,並調用trimToSize()判斷緩存是否已滿,若是滿了就用LinkedHashMap的迭代器刪除隊頭元素,即近期最少訪問的元素。當調用get()方法訪問緩存對象時,就會調用LinkedHashMap的get()方法得到對應集合元素,同時會更新該元素到隊尾。
在添加過緩存對象後,調用trimToSize()方法,來判斷緩存是否已滿,若是滿了就要刪除近期最少使用的對象。trimToSize()方法不斷地刪除LinkedHashMap中隊頭的元素,即近期最少訪問的,直到緩存大小小於最大值(maxSize)。
當調用LruCache的get()方法獲取集合中的緩存對象時,就表明訪問了一次該元素,將會更新隊列,保持整個隊列是按照訪問順序排序的。
爲何會選擇LinkedHashMap呢?
這跟LinkedHashMap的特性有關,LinkedHashMap的構造函數裏有個布爾參數accessOrder,當它爲true時,LinkedHashMap會以訪問順序爲序排列元素,不然以插入順序爲序排序元素。
LinkedHashMap 幾乎和 HashMap 同樣:從技術上來講,不一樣的是它定義了一個 Entry<K,V> header,這個 header 不是放在 Table 裏,它是額外獨立出來的。LinkedHashMap 經過繼承 hashMap 中的 Entry<K,V>,並添加兩個屬性 Entry<K,V> before,after,和 header 結合起來組成一個雙向鏈表,來實現按插入順序或訪問順序排序。
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對應多個文件,那麼就會有多個數字。
加載 Bitmap 的方式:
BitmapFactory 四類方法:
BitmapFactory.options 參數:
高效加載 Bitmap 的流程:
當使用ImageView的時候,可能圖片的像素大於ImageView,此時就能夠經過BitmapFactory.Option來對圖片進行壓縮,inSampleSize表示縮小2^(inSampleSize-1)倍。
BitMap的緩存:
1.使用LruCache進行內存緩存。
2.使用DiskLruCache進行硬盤緩存。
同步異步加載、圖片壓縮、內存硬盤緩存、網絡拉取
具體爲:
stackoverflow.com/questions/2…
Glide:相對輕量級,用法簡單優雅,支持Gif動態圖,適合用在那些對圖片依賴不大的App中。 Fresco:採用匿名共享內存來保存圖片,也就是Native堆,有效的的避免了OOM,功能強大,可是庫體積過大,適合用在對圖片依賴比較大的App中。
Fresco的總體架構以下圖所示:
DraweeView:繼承於ImageView,只是簡單的讀取xml文件的一些屬性值和作一些初始化的工做,圖層管理交由Hierarchy負責,圖層數據獲取交由負責。 DraweeHierarchy:由多層Drawable組成,每層Drawable提供某種功能(例如:縮放、圓角)。 DraweeController:控制數據的獲取與圖片加載,向pipeline發出請求,並接收相應事件,並根據不一樣事件控制Hierarchy,從DraweeView接收用戶的事件,而後執行取消網絡請求、回收資源等操做。 DraweeHolder:統籌管理Hierarchy與DraweeHolder。 ImagePipeline:Fresco的核心模塊,用來以各類方式(內存、磁盤、網絡等)獲取圖像。 Producer/Consumer:Producer也有不少種,它用來完成網絡數據獲取,緩存數據獲取、圖片解碼等多種工做,它產生的結果由Consumer進行消費。 IO/Data:這一層即是數據層了,負責實現內存緩存、磁盤緩存、網絡緩存和其餘IO相關的功能。 縱觀整個Fresco的架構,DraweeView是門面,和用戶進行交互,DraweeHierarchy是視圖層級,管理圖層,DraweeController是控制器,管理數據。它們構成了整個Fresco框架的三駕馬車。固然還有咱們 幕後英雄Producer,全部的髒活累活都是它乾的,最佳勞模👍
理解了Fresco總體的架構,咱們還有了解在這套礦建裏發揮重要做用的幾個關鍵角色,以下所示:
Supplier:提供一種特定類型的對象,Fresco裏有不少以Supplier結尾的類都實現了這個接口。 SimpleDraweeView:這個咱們就很熟悉了,它接收一個URL,而後調用Controller去加載圖片。該類繼承於GenericDraweeView,GenericDraweeView又繼承於DraweeView,DraweeView是Fresco的頂層View類。 PipelineDraweeController:負責圖片數據的獲取與加載,它繼承於AbstractDraweeController,由PipelineDraweeControllerBuilder構建而來。AbstractDraweeController實現了DraweeController接口,DraweeController 是Fresco的數據大管家,因此的圖片數據的處理都是由它來完成的。 GenericDraweeHierarchy:負責SimpleDraweeView上的圖層管理,由多層Drawable組成,每層Drawable提供某種功能(例如:縮放、圓角),該類由GenericDraweeHierarchyBuilder進行構建,該構建器 將placeholderImage、retryImage、failureImage、progressBarImage、background、overlays與pressedStateOverlay等 xml文件或者Java代碼裏設置的屬性信息都傳入GenericDraweeHierarchy中,由GenericDraweeHierarchy進行處理。 DraweeHolder:該類是一個Holder類,和SimpleDraweeView關聯在一塊兒,DraweeView是經過DraweeHolder來統一管理的。而DraweeHolder又是用來統一管理相關的Hierarchy與Controller DataSource:相似於Java裏的Futures,表明數據的來源,和Futures不一樣,它能夠有多個result。 DataSubscriber:接收DataSource返回的結果。 ImagePipeline:用來調取獲取圖片的接口。 Producer:加載與處理圖片,它有多種實現,例如:NetworkFetcherProducer,LocalAssetFetcherProducer,LocalFileFetchProducer。從這些類的名字咱們就能夠知道它們是幹什麼的。 Producer由ProducerFactory這個工廠類構建的,並且全部的Producer都是像Java的IO流那樣,能夠一層嵌套一層,最終只獲得一個結果,這是一個很精巧的設計👍 Consumer:用來接收Producer產生的結果,它與Producer組成了生產者與消費者模式。 注:Fresco源碼裏的類的名字都比較長,可是都是按照必定的命令規律來的,例如:以Supplier結尾的類都實現了Supplier接口,它能夠提供某一個類型的對象(factory, generator, builder, closure等)。 以Builder結尾的固然就是以構造者模式建立對象的類。
使用BitmapRegionDecoder動態加載圖片的顯示區域。
內存泄露檢測框架。
直接從application中拿到全局的 refWatcher 對象,在Fragment或其餘組件的銷燬回調中使用refWatcher.watch(this)檢測是否發生內存泄漏。
檢測結果並非特別的準確,由於內存的釋放和對象的生命週期有關也和GC的調度有關。
主要分爲以下7個步驟:
簡單來講就是:
在一個Activity執行完onDestroy()以後,將它放入WeakReference中,而後將這個WeakReference類型的Activity對象與ReferenceQueque關聯。這時再從ReferenceQueque中查看是否有該對象,若是沒有,執行gc,再次查看,仍是沒有的話則判斷髮生內存泄露了。最後用HAHA這個開源庫去分析dump以後的heap內存(主要就是建立一個HprofParser解析器去解析出對應的引用內存快照文件snapshot)。
流程圖:
源碼分析中一些核心分析點:
AndroidExcludedRefs:它是一個enum類,它聲明瞭Android SDK和廠商定製的SDK中存在的內存泄露的case,根據AndroidExcludedRefs這個類的類名就可看出這些case都會被Leakcanary的監測過濾掉。
buildAndInstall()(即install方法)這個方法應該僅僅只調用一次。
debuggerControl : 判斷是否處於調試模式,調試模式中不會進行內存泄漏檢測。爲何呢?由於在調試過程當中可能會保留上一個引用從而致使錯誤信息上報。
watchExecutor : 線程控制器,在 onDestroy() 以後而且主線程空閒時執行內存泄漏檢測。
gcTrigger : 用於 GC,watchExecutor 首次檢測到可能的內存泄漏,會主動進行 GC,GC 以後會再檢測一次,仍然泄漏的斷定爲內存泄漏,最後根據heapDump信息生成相應的泄漏引用鏈。
gcTrigger的runGc()方法:這裏並無使用System.gc()方法進行回收,由於system.gc()並不會每次都執行。而是從AOSP中拷貝一段GC回收的代碼,從而相比System.gc()更可以保證進行垃圾回收的工做。
Runtime.getRuntime().gc();
複製代碼
子線程延時1000ms;
System.runFinalization();
install方法內部最終仍是調用了application的registerActivityLifecycleCallbacks()方法,這樣就可以監聽activity對應的生命週期事件了。
在RefWatcher#watch()中使用隨機的UUID保證了每一個檢測對象對應的key 的惟一性。
在KeyedWeakReference內部,使用了key和name標識了一個被檢測的WeakReference對象。在其構造方法中將弱引用和引用隊列 ReferenceQueue 關聯起來,若是弱引用reference持有的對象被GC回收,JVM就會把這個弱引用加入到與之關聯的引用隊列referenceQueue中。即 KeyedWeakReference 持有的 Activity 對象若是被GC回收,該對象就會加入到引用隊列 referenceQueue 中。
使用Android SDK的API Debug.dumpHprofData() 來生成 hprof 文件。
在HeapAnalyzerService(類型爲IntentService的ForegroundService)的runAnalysis()方法中,爲了不減慢app進程或佔用內存,這裏將HeapAnalyzerService設置在了一個獨立的進程中。
該組件利用了主線程的消息隊列處理機制,應用發生卡頓,必定是在dispatchMessage中執行了耗時操做。咱們經過給主線程的Looper設置一個Printer,打點統計dispatchMessage方法執行的時間,若是超出閥值,表示發生卡頓,則dump出各類信息,提供開發者分析性能瓶頸。
ButterKnife對性能的影響很小,由於沒有使用使用反射,而是使用的Annotation Processing Tool(APT),註解處理器,javac中用於編譯時掃描和解析Java註解的工具。在編譯階段執行的,它的原理就是讀入Java源代碼,解析註解,而後生成新的Java代碼。新生成的Java代碼最後被編譯成Java字節碼,註解解析器不能改變讀入的Java類,好比不能加入或刪除Java方法。
使用平衡二叉樹能保證數據的左右兩邊的節點層級相差不會大於1.,經過這樣避免樹形結構因爲刪除增長變成線性鏈表影響查詢效率,保證數據平衡的狀況下查找數據的速度近於二分法查找。
目前大部分數據庫系統及文件系統都採用B-Tree或其變種B+Tree做爲索引結構。
B樹和平衡二叉樹稍有不一樣的是B樹屬於多叉樹又名平衡多路查找樹(查找路徑不僅兩個)。
B樹相對於平衡二叉樹的不一樣是,每一個節點包含的關鍵字增多了,把樹的節點關鍵字增多後樹的層級比原來的二叉樹少了,減小數據查找的次數和複雜度。
一、B+樹的層級更少:相較於B樹B+每一個非葉子節點存儲的關鍵字數更多,樹的層級更少因此查詢數據更快。
二、B+樹查詢速度更穩定:B+全部關鍵字數據地址都存在葉子節點上,因此每次查找的次數都相同因此查詢速度要比B樹更穩定。
三、B+樹自然具有排序功能:B+樹全部的葉子節點數據構成了一個有序鏈表,在查詢大小區間的數據時候更方便,數據緊密性很高,緩存的命中率也會比B樹高。
四、B+樹全節點遍歷更快:B+樹遍歷整棵樹只須要遍歷全部的葉子節點便可,而不須要像B樹同樣須要對每一層進行遍歷,這有利於數據庫作全表掃描。
B樹相對於B+樹的優勢是,若是常常訪問的數據離根節點很近,而B樹的非葉子節點自己存有關鍵字其數據的地址,因此這種數據檢索的時候會要比B+樹快。
B*樹是B+樹的變種,相對於B+樹他們的不一樣之處以下:
一、首先是關鍵字個數限制問題,B+樹初始化的關鍵字初始化個數是cei(m/2),b樹的初始化個數爲(cei(2/3m))。
二、B+樹節點滿時就會分裂,而B*樹節點滿時會檢查兄弟節點是否滿(由於每一個節點都有指向兄弟的指針),若是兄弟節點未滿則向兄弟節點轉移關鍵字,若是兄弟節點已滿,則從當前節點和兄弟節點各拿出1/3的數據建立一個新的節點出來。
在B+樹的基礎上因其初始化的容量變大,使得節點空間使用率更高,而又存有兄弟節點的指針,能夠向兄弟節點轉移關鍵字的特性使得B*樹分解次數變得更少。
還不理解請查看:平衡二叉樹、B樹、B+樹、B*樹 理解其中一種你就都明白了。
插件化:動態加載主要解決3個技術問題:
插件化是體如今功能拆分方面的,它將某個功能獨立提取出來,獨立開發,獨立測試,再插入到主應用中。以此來減小主應用的規模。
熱修復:
緣由:由於一個dvm中存儲方法id用的是short類型,致使dex中方法不能超過65536個。
相同點:
都使用ClassLoader來實現加載新的功能類,均可以使用PathClassLoader與DexClassLoader。
不一樣點:
熱修復由於是爲了修復Bug的,因此要將新的類替代同名的Bug類,要搶先加載新的類而不是Bug類,因此多作兩件事:在原先的app打包的時候,阻止相關類去打上CLASS_ISPREVERIFIED標誌,還有在熱修復時動態改變BaseDexClassLoader對象間接引用的dexElements,這樣才能搶先代替Bug類,完成系統不加載舊的Bug類.。 而插件化只是增長新的功能類或者是資源文件,因此不涉及搶先加載新的類這樣的使命,就避過了阻止相關類去打上CLASS_ISPREVERIFIED標誌和還有在熱修復時動態改變BaseDexClassLoader對象間接引用的dexElements.
因此插件化比熱修復簡單,熱修復是在插件化的基礎上在進行替換舊的Bug類。
不少熱修復框架的資源修復參考了Instant Run的資源修復的原理。
傳統編譯部署流程以下:
Instant Run編譯部署流程以下:
Instant Run中的資源熱修復流程:
一、類加載方案:
65536限制:
65536的主要緣由是DVM Bytecode的限制,DVM指令集的方法調用指令invoke-kind索引爲16bits,最多能引用65535個方法。
LinearAlloc限制:
Dex分包方案主要作的是在打包時將應用代碼分紅多個Dex,將應用啓動時必須用到的類和這些類的直接引用類放到Dex中,其餘代碼放到次Dex中。當應用啓動時先加載主Dex,等到應用啓動後再動態地加載次Dex,從而緩解了主Dex的65536限制和LinearAlloc限制。
加載流程:
類加載方案須要重啓App後讓ClassLoader從新加載新的類,爲何須要重啓呢?
各個熱修復框架的實現細節差別:
二、底層替換方案:
當咱們要反射Key的show方法,會調用Key.class.getDeclaredMethod("show").invoke(Key.class.newInstance());,最終會在native層將傳入的javaMethod在ART虛擬機中對應一個ArtMethod指針,ArtMethod結構體中包含了Java方法的全部信息,包括執行入口、訪問權限、所屬類和代碼執行地址等。
替換ArtMethod結構體中的字段或者替換整個ArtMethod結構體,這就是底層替換方案。
AndFix採用的是替換ArtMethod結構體中的字段,這樣會有兼容性問題,由於廠商可能會修改ArtMethod結構體,致使方法替換失敗。
Sophix採用的是替換整個ArtMethod結構體,這樣不會存在兼容問題。
底層替換方案直接替換了方法,能夠當即生效不須要重啓。採用底層替換方案主要是阿里係爲主,包括AndFix、Dexposed、阿里百川、Sophix。
三、Instant Run方案:
什麼是ASM?
ASM是一個java字節碼操控框架,它可以動態生成類或者加強現有類的功能。ASM能夠直接產生class文件,也能夠在類被加載到虛擬機以前動態改變類的行爲。
Instant Run在第一次構建APK時,使用ASM在每個方法中注入了相似的代碼邏輯:當dispatch方法,參數爲具體的方法名和方法參數。當MainActivity的onCreate方法作了修改,就會生成替換類MainActivity
change設置爲MainActivity
change就不會爲null,則會執行MainActivity
dispatch方法,最終會執行onCreate方法,從而實現了onCreate方法的修改。
借鑑Instant Run原理的熱修復框架有Robust和Aceso。
從新加載so。
加載so主要用到了System類的load和loadLibrary方法,最終都會調用到nativeLoad方法。其會調用JavaVMExt的LoadNativeLibrary函數來加載so。
so修復主要有兩個方案:
在Android傳統開發中,一旦應用的代碼被打包成APK並被上傳到各個應用市場,咱們就不能修改應用的源碼了,只能經過服務器來控制應用中預留的分支代碼。可是不少時候咱們沒法預知需求和忽然發生的狀況,也就不能提早在應用代碼中預留分支代碼,這時就須要採用動態加載技術,即在程序運行時,動態加載一些程序中本來不存在的可執行文件並運行這些文件裏的代碼邏輯。其中可執行文件包括動態連接庫so和dex相關文件(dex以及包含dex的jar/apk文件)。隨着應用開發技術和業務的逐步發展,動態加載技術派生出兩個技術:熱修復和插件化。其中熱修復技術主要用來修復Bug,而插件化技術則主要用於解決應用愈來愈龐大以及功能模塊的解耦。詳細點說,就是爲了解決如下幾種狀況:
安裝的應用能夠理解爲插件,這些插件能夠自由地進行插拔。
插件通常是指通過處理的APK,so和dex等文件,插件能夠被宿主進行加載,有的插件也能夠做爲APK獨立運行。
將一個應用按照插件的方式進行改造的過程就叫做插件化。
主要實現方式有三種:
Hook實現方式有兩種:Hook IActivityManager和Hook Instrumentation。主要方案就是先用一個在AndroidManifest.xml中註冊的Activity來進行佔坑,用來經過AMS的校驗,接着在合適的時機用插件Activity替換佔坑的Activity。
Hook IActivityManager:
一、佔坑、經過校驗:
在Android 7.0和8.0的源碼中IActivityManager藉助了Singleton類實現單例,並且該單例是靜態的,所以IActivityManager是一個比較好的Hook點。
接着,定義替換IActivityManager的代理類IActivityManagerProxy,因爲Hook點IActivityManager是一個接口,建議這裏採用動態代理。
而後,用代理類IActivityManagerProxy來替換IActivityManager。
二、還原插件Activity:
自定義的Callback實現了Handler.Callback,並重寫了handleMessage方法,當收到消息的類型爲LAUNCH_ACTIVITY時,將啓動SubActivity的Intent替換爲啓動TargetActivity的Intent。而後使用反射將Handler的mCallback替換爲自定義的CallBack便可。使用時則在application的attachBaseContext方法中進行hook便可。
三、插件Activity的生命週期:
Hook Instrumentation:
Hook Instrumentation實現一樣也須要用到佔坑Activity,與Hook IActivity實現不一樣的是,用佔坑Activity替換插件Activity以及還原插件Activity的地方不一樣。
分析:在Activity經過AMS校驗前,會調用Activity的startActivityForResult方法,其中調用了Instrumentation的execStartActivity方法來激活Activity的生命週期。而且在ActivityThread的performLaunchActivity中使用了mInstrumentation的newActivity方法,其內部會用類加載器來建立Activity的實例。
方案:在Instrumentation的execStartActivity方法中用佔坑SubActivity來經過AMS的驗證,在Instrumentation的newActivity方法中還原TargetActivity,這兩部操做都和Instrumentation有關,所以咱們能夠用自定義的Instumentation來替換掉mInstrumentation。具體爲:
資源的插件化和熱修復的資源修復都藉助了AssetManager。
資源的插件化方案主要有兩種:
so的插件化方案和so熱修復的第一種方案相似,就是將so插件插入到NativelibraryElement數組中,而且將存儲so插件的文件添加到nativeLibraryDirectories集合中就能夠了。
不少大廠的組件化方案是以 多工程 + 多 Module 的結構(微信, 美團等超級 App 更是以 多工程 + 多 Module + 多 P 工程(以頁面爲單元的代碼隔離方式) 的三級工程結構), 使用 Git Submodule 建立多個子倉庫管理各個模塊的代碼, 並將各個模塊的代碼打包成 AAR 上傳至私有 Maven 倉庫使用遠程版本號依賴的方式進行模塊間代碼的隔離。
跨組件通訊場景:
在公共服務(CommonService) 中聲明 Service 接口 (含有須要被調用的自定義方法), 而後在本身的模塊中實現這個 Service 接口, 再經過 ARouter API 暴露實現類。
經過 ARouter 的 API 拿到這個 Service 接口(多態持有, 實際持有實現類), 便可調用 Service 接口中聲明的自定義方法, 這樣就能夠達到模塊之間的交互。 此外,能夠使用 AndroidEventBus 其獨有的 Tag, 能夠在開發時更容易定位發送事件和接受事件的代碼, 若是以組件名來做爲 Tag 的前綴進行分組, 也能夠更好的統一管理和查看每一個組件的事件, 固然也不建議你們過多使用 EventBus。
RouterHub 存在於基礎庫, 能夠被看做是全部組件都須要遵照的通信協議, 裏面不只能夠放路由地址常量, 還能夠放跨組件傳遞數據時命名的各類 Key 值, 再配以適當註釋, 任何組件開發人員不須要事先溝通只要依賴了這個協議, 就知道了各自該怎樣協同工做, 既提升了效率又下降了出錯風險, 約定的東西天然要比口頭上說的強。
Tips: 若是您以爲把每一個路由地址都寫在基礎庫的 RouterHub 中, 太麻煩了, 也能夠在每一個組件內部創建一個私有 RouterHub, 將不須要跨組件的路由地址放入私有 RouterHub 中管理, 只將須要跨組件的路由地址放入基礎庫的公有 RouterHub 中管理, 若是您不須要集中管理全部路由地址的話, 這也是比較推薦的一種方式。
ARouter維護了一個路由表Warehouse,其中保存着所有的模塊跳轉關係,ARouter路由跳轉實際上仍是調用了startActivity的跳轉,使用了原生的Framework機制,只是經過apt註解的形式製造出跳轉規則,並人爲地攔截跳轉和設置跳轉條件。
經過設計是模塊程序化,從而作到高內聚低耦合,讓開發者能更專一於功能實現自己,提供程序開發效率、更容易進行測試、維護和定位問題等等。並且,不一樣的規模的項目應該選用不一樣的架構設計。
MVC是模型(model)-視圖(view)-控制器(controller)的縮寫,其中M層處理數據,業務邏輯等;V層處理界面的顯示結果;C層起到橋樑的做用,來控制V層和M層通訊以此來達到分離視圖顯示和業務邏輯層。在Android中的MVC劃分是這樣的:
在Android開發中,Activity並非一個標準的MVC模式中的Controller,它的首要職責是加載應用的佈局和初始化用戶界面,並接受和處理來自用戶的操做請求,進而做出響應。隨着界面及其邏輯的複雜度不斷提高,Activity類的職責不斷增長,以至變得龐大臃腫。
MVP框架由3部分組成:View負責顯示,Presenter負責邏輯處理,Model提供數據。
MVP的Presenter是框架的控制者,承擔了大量的邏輯操做,而MVC的Controller更多時候承擔一種轉發的做用。所以在App中引入MVP的緣由,是爲了將此前在Activty中包含的大量邏輯操做放到控制層中,避免Activity的臃腫。
UI層通常包括Activity,Fragment,Adapter等直接和UI相關的類,UI層的Activity在啓動以後實例化相應的Presenter,App的控制權後移,由UI轉移到Presenter,二者之間的通訊經過BroadCast、Handler、事件總線機制或者接口完成,只傳遞事件和結果。
MVP的執行流程:首先V層通知P層用戶發起了一個網絡請求,P層會決定使用負責網絡相關的M層去發起請求網絡,最後,P層將完成的結果更新到V層。
View直接依賴Presenter,可是Presenter間接依賴View,它直接依賴的是View實現的接口。相對於View的被動,那Presenter就是主動的一方。對於Presenter的主動,有以下的理解:
將邏輯操做從V層轉移到P層後,可能有一些Activity仍是比較膨脹,此時,能夠經過繼承BaseActivity的方式加入模板方法。注意,最好不要超過3層繼承。
模型層(Model)中的總體代碼量是最大的,此時能夠進行模塊的劃分和接口隔離。
在UI層和Presenter之間設置中介者Mediator,將例如數據校驗、組裝在內的輕量級邏輯操做放在Mediator中;在Presenter和Model之間使用代理Proxy;經過上述二者分擔一部分Presenter的邏輯操做,但總體框架的控制權仍是在Presenter手中。
MVVM能夠算是MVP的升級版,其中的VM是ViewModel的縮寫,ViewModel能夠理解成是View的數據模型和Presenter的合體,ViewModel和View之間的交互經過Data Binding完成,而Data Binding能夠實現雙向的交互,這就使得視圖和控制層之間的耦合程度進一步下降,關注點分離更爲完全,同時減輕了Activity的壓力。
MVC -> MVP -> MVVM 這幾個軟件設計模式是一步步演化發展的,MVVM 是從 MVP 的進一步發展與規範,MVP 隔離了MVC中的 M 與 V 的直接聯繫後,靠 Presenter 來中轉,因此使用 MVP 時 P 是直接調用 View 的接口來實現對視圖的操做的,這個 View 接口的東西通常來講是 showData、showLoading等等。M 與 V已經隔離了,方便測試了,但代碼還不夠優雅簡潔,因此 MVVM 就彌補了這些缺陷。在 MVVM 中就出現的 Data Binding 這個概念,意思就是 View 接口的 showData 這些實現方法能夠不寫了,經過 Binding 來實現。
M層和V層的實現是同樣的。
三者的差別在於如何粘合View和Model,實現用戶的交互操做以及變動通知。
AOP(Aspect-Oriented Programming, 面向切面編程),誕生於上個世紀90年代,是對OOP(Object-Oriented Programming, 面向對象編程)的補充和完善。OOP引入封裝、繼承和多態性等概念來創建一種從上道下的對象層次結構,用以模擬公共行爲的一個集合。當咱們須要爲分散的對象引入公共行爲的時候,即定義從左到右的關係時,OOP則顯得無能爲力。例如日誌功能。日誌代碼每每水平地散佈在全部對象層次中,而與它所散佈到的對象的核心功能毫無關係。對於其餘類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種散佈在各處的無關的代碼被稱爲橫切(Cross-Cutting)代碼,在OOP設計中,它致使了大量代碼的重複,而不利於各個模塊的重用。而AOP技術則偏偏相反,它利用一種稱爲「橫切」的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲「Aspect」,即方面。所謂「方面」,簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊間的耦合度,並有利於將來的可操做性和可維護性。
在Android App中的橫切關注點有Http, SharedPreferences, Log, Json, Xml, File, Device, System, 格式轉換等。Android App的需求差異很大,不一樣的需求橫切關注點必然是不同的。通常的App工程中應該有一個Util Package來存放相關的切面操做,在項目多了以後能夠將其中使用較多的Util封裝爲一個Jar包/aar文件/遠程依賴的方式供工程調用。
在使用MVP和AOP對App進行縱向和橫向的切割以後,可以使得App總體的結構更清晰合理,避免局部的代碼臃腫,方便開發、測試以及後續的維護。這樣縱,橫兩次對於App代碼的分割已經能使得程序不會過多堆積在一個Java文件裏,但靠一次開發過程就寫出高質量的代碼是很困難的,趁着項目的間歇期,對代碼進行重構頗有必要。
若是「從零開始」,用什麼設計架構的問題屬於想得太多作得太少的問題。 從零開始意味着一個項目的主要技術難點是基本功能實現。當每個功能都須要考慮如何作到的時候,我以爲通常人都沒辦法考慮如何作好。 由於,全部的優化都是站在最上層進行統籌規劃。在這以前,你必須對下層的每個模塊都很是熟悉,進而提煉可複用的代碼、規劃邏輯流程。
一、AIDL方式單進程、雙進程方式保活Service。(基於onStartCommand() return START_STICKY)
START_STICKY 在運行onStartCommand後service進程被kill後,那將保留在開始狀態,可是不保留那些傳入的intent。不久後service就會再次嘗試從新建立,由於保留在開始狀態,在建立 service後將保證調用onstartCommand。若是沒有傳遞任何開始命令給service,那將獲取到null的intent。
除了華爲此方案無效以及未更改底層的廠商不起做用外(START_STICKY字段就能夠保持Service不被殺)。此方案能夠與其餘方案混合使用
二、下降oom_adj的值(提高service進程優先級):
Android中的進程是託管的,當系統進程空間緊張的時候,會依照優先級自動進行進程的回收。Android將進程分爲6個等級,它們按優先級順序由高到低依次是:
當service運行在低內存的環境時,將會kill掉一些存在的進程。所以進程的優先級將會很重要,能夠使用startForeground 將service放到前臺狀態。這樣在低內存時被kill的概率會低一些。
常駐通知欄(可經過啓動另一個服務關閉Notification,不對oom_adj值有影響)。
使用」1像素「的Activity覆蓋在getWindow()的view上。
此方案無效果
成功對華爲手機保活。小米8下也成功突破20分鐘
一、onDestroy方法裏重啓service
service + broadcast 方式,就是當service走onDestory的時候,發送一個自定義的廣播,當收到廣播的時候,從新啓動service。
二、JobScheduler:原理相似定時器,5.0,5.1,6.0做用很大,7.0時候有必定影響(能夠在電源管理中給APP受權)。
只對5.0,5.一、6.0起做用。
三、推送互相喚醒復活:極光、友盟、以及各大廠商的推送。
四、同派系APP廣播互相喚醒:好比今日頭條系、阿里系。
此外還能夠監聽系統廣播判斷Service狀態,經過系統的一些廣播,好比:手機重啓、界面喚醒、應用狀態改變等等監聽並捕獲到,而後判斷咱們的Service是否還存活。
Animation 框架定義了透明度,旋轉,縮放和位移幾種常見的動畫,並且控制的是整個View。實現原理:
每次繪製視圖時,View 所在的 ViewGroup 中的 drawChild 函數獲取該View 的 Animation 的 Transformation 值,而後調用canvas.concat(transformToApply.getMatrix()),經過矩陣運算完成動畫幀,若是動畫沒有完成,繼續調用 invalidate() 函數,啓動下次繪製來驅動動畫,動畫過程當中的幀之間間隙時間是繪製函數所消耗的時間,可能會致使動畫消耗比較多的CPU資源,最重要的是,動畫改變的只是顯示,並不能響應事件。
Activity像一個工匠(控制單元),Window像窗戶(承載模型),View像窗花(顯示視圖) LayoutInflater像剪刀,Xml配置像窗花圖紙。
在Activity中調用attach,建立了一個Window, 建立的window是其子類PhoneWindow,在attach中建立PhoneWindow。 在Activity中調用setContentView(R.layout.xxx), 其中其實是調用的getWindow().setContentView(), 內部調用了PhoneWindow中的setContentView方法。
建立ParentView:
做爲ViewGroup的子類,實際是建立的DecorView(做爲FramLayout的子類), 將指定的R.layout.xxx進行填充, 經過佈局填充器進行填充【其中的parent指的就是DecorView】, 調用ViewGroup的removeAllView(),先將全部的view移除掉,添加新的view:addView()。
1、使用方面:
ListView的基礎使用:
RecyclerView 基礎使用關鍵點一樣有兩點:
RecyclerView 相比 ListView 在基礎使用上的區別主要有以下幾點:
2、佈局方面:
RecyclerView 支持 線性佈局、網格佈局、瀑布流佈局 三種,並且同時還可以控制橫向仍是縱向滾動。
3、API提供方面:
ListView 提供了 setEmptyView ,addFooterView 、 addHeaderView.
RecyclerView 供了 notifyItemChanged 用於更新單個 Item View 的刷新,咱們能夠省去本身寫局部更新的工做。
4、動畫效果:
RecyclerView 在作局部刷新的時候有一個漸變的動畫效果。繼承 RecyclerView.ItemAnimator 類,並實現相應的方法,再調用 RecyclerView的 setItemAnimator(RecyclerView.ItemAnimator animator) 方法設置完便可實現自定義的動畫效果。
5、監聽 Item 的事件:
ListView 提供了單擊、長按、選中某個 Item 的監聽設置。
一:客戶端不斷的查詢服務器,檢索新內容,也就是所謂的pull 或者輪詢方式。
二:客戶端和服務器之間維持一個TCP/IP長鏈接,服務器向客戶端push。
Scroller執行流程裏面的三個核心方法
mScroller.startScroll();
mScroller.computeScrollOffset();
view.computeScroll();
複製代碼
一、在mScroller.startScroll()中爲滑動作了一些初始化準備,好比:起始座標,滑動的距離和方向以及持續時間(有默認值),動畫開始時間等。
二、mScroller.computeScrollOffset()方法主要是根據當前已經消逝的時間來計算當前的座標點。由於在mScroller.startScroll()中設置了動畫時間,那麼在computeScrollOffset()方法中依據已經消逝的時間就很容易獲得當前時刻應該所處的位置並將其保存在變量mCurrX和mCurrY中。除此以外該方法還可判斷動畫是否已經結束。
webView.addJavaScriptInterface(new Object(){xxx}, "xxx");
1
答案:能夠使用WebView控件執行JavaScript腳本,而且能夠在JavaScript中執行Java代碼。要想讓WebView控件執行JavaScript,須要調用WebSettings.setJavaScriptEnabled方法,代碼以下:
WebView webView = (WebView)findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
//設置WebView支持JavaScript
webSettings.setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient());
JavaScript調用Java方法須要使用WebView.addJavascriptInterface方法設置JavaScript調用的Java方法,代碼以下:
webView.addJavascriptInterface(new Object()
{
//JavaScript調用的方法
public String process(String value)
{
//處理代碼
return result;
}
}, "demo"); //demo是Java對象映射到JavaScript中的對象名
能夠使用下面的JavaScript代碼調用process方法,代碼以下:
<script language="javascript">
function search()
{
//調用searchWord方法
result.innerHTML = "<font color='red'>" + window.demo.process('data') + "</font>";
}
複製代碼
答:照常執行
擴展:sleep時間<=5 對兩個消息無影響,5< sleep時間 <=10 對第一個消息有影響,第一個消息會延遲到sleep後執行,sleep時間>10 對兩個時間都有影響,都會延遲到sleep後執行。
長鏈接:長鏈接是創建鏈接以後, 不主動斷開. 雙方互相發送數據, 發完了也不主動斷開鏈接, 以後有須要發送的數據就繼續經過這個鏈接發送.
心跳包:其實主要是爲了防止NAT超時,客戶端隔一段時間就主動發一個數據,探測鏈接是否斷開。
服務器處理心跳包:假如客戶端心跳間隔是固定的, 那麼服務器在鏈接閒置超過這個時間還沒收到心跳時, 能夠認爲對方掉線, 關閉鏈接. 若是客戶端心跳會動態改變, 應當設置一個最大值, 超過這個最大值才認爲對方掉線. 還有一種狀況就是服務器經過TCP鏈接主動給客戶端發消息出現寫超時, 能夠直接認爲對方掉線.
獲取app crash的信息保存在本地而後在下一次打開app的時候發送到服務器。
複製代碼
SurfaceView是在一個新起的單獨線程中能夠從新繪製畫面,而view必須在UI的主線程中更新畫面。
在UI的主線程中更新畫面可能會引起問題,好比你更新的時間過長,那麼你的主UI線程就會被你正在畫的函數阻塞。那麼將沒法響應按鍵、觸屏等消息。當使用SurfaceView因爲是在新的線程中更新畫面因此不會阻塞你的UI主線程。但這也帶來了另一個問題,就是事件同步。好比你觸屏了一下,你須要在SurfaceView中的thread處理,通常就須要有一個event queue的設計來保存touchevent,這會稍稍複雜一點,由於涉及到線程安全。
一、Linux 文件系統權限。不一樣的用戶對文件有不一樣的讀寫執行權限。在android系統中,system和應用程序是分開的,system裏的數據是不可更改的。
二、Android中有3種權限,進程權限UserID,簽名,應用申明權限。每次安裝時,系統根據包名爲應用分配惟一的userID,不一樣的userID運行在不一樣的進程裏,進程間的內存是獨立的,不能夠相互訪問,除非經過特定的Binder機制。
Android提供了以下的一種機制,能夠使兩個apk打破前面講的這種壁壘。
在AndroidManifest.xml中利用sharedUserId屬性給不一樣的package分配相同的userID,經過這樣作,兩個package能夠被當作同一個程序,系統會分配給兩個程序相同的UserID。固然,基於安全考慮,兩個package須要有相同的簽名,不然沒有驗證也就沒有意義了。
能夠,當訪問UI時,ViewRootImpl會調用checkThread方法去檢查當前訪問UI的線程是哪一個,若是不是UI線程則會拋出異常。執行onCreate方法的那個時候ViewRootImpl還沒建立,沒法去檢查當前線程.ViewRootImpl的建立在onResume方法回調以後。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
複製代碼
非UI線程是能夠刷新UI的,前提是它要擁有本身的ViewRoot,即更新UI的線程和建立ViewRoot的線程是同一個,或者在執行checkThread()前更新UI。
首先,Android測試主要分爲三個方面:
WanAndroid項目和XXX項目中使用用到了單元測試和部分自動化UI測試,其中單元測試使用的是Junit4+Mockito+PowerMockito+Robolectric。下面我分別簡單介紹下這些測試框架:
一、Junit4:
使用@Test註解指定一個方法爲一個測試方法,除此以外,還有以下經常使用註解@BeforeClass->@Before->@Test->@After->@AfterClass以及@Ignore。
Junit4的主要測試方法就是斷言,即assertEquals()方法。而後,你能夠經過實現TestRule接口的方式重寫apply()方法去自定義Junit Rule,這樣就能夠在執行測試方法的先後作一些通用的初始化或釋放資源等工做,接着在想要的測試類中使用@Rule註解聲明使用JsonChaoRule便可。(注意被@Rule註解的變量必須是final的。最後,咱們直接運行對應的單元測試方法或類,若是你想要一鍵運行項目中全部的單元測試類,直接點擊運行Gradle Projects下的app/Tasks/verification/test便可,它會在module下的build/reports/tests/下生成對應的index.html報告。
Junit4它的優勢是速度快,支持代碼覆蓋率如jacoco等代碼質量的檢測工具。缺點就是沒法單獨對Android UI,一些類進行操做,與原生Java有一些差別。
二、Mockito:
能夠使用mock()方法模擬各類各樣的對象,以替代真正的對象作出但願的響應。除此以外,它還有不少驗證方法調用的方式如Mockit.when(調用方法).thenReturn(驗證的返回值)、verfiy(模擬對象).驗證方法等等。
這裏有一點要補充下:簡單的測試會使總體的代碼更簡潔,更可讀、更可維護。若是你不能把測試寫的很簡單,那麼請在測試時重構你的代碼。
最後,對於Mockito來講,它的優勢是有各類各樣的方式去驗證"模仿對象"的互動或驗證發生的某些行爲。而它的缺點就是不支持mock匿名類、final類、static方法private方法。
三、PowerMockito:
所以,爲了解決Mockito的缺陷,PoweMockito出現了,它擴展了Mockito,支持mock匿名類、final類、static方法、private方法。只要使用它提供的api如PowerMockito.mockStatic()去mock含靜態方法或字段的類,PowerMockito.suppress(PowerMockito.method(類.class, 方法名)便可。
四、Robolectric
前面3種咱們說的都是Java相關的單元測試方法,若是想在Java單元測試裏面進行Android單元測試,還得使用Robolectric,它提供了一套能運行在JVM的Android代碼。它提供了一系列相似ShadowToast.getLatestToast()、ShadowApplication.getInstance()這種方式來獲取Android平臺對應的對象。能夠看到它的優勢就是支持大部分Android平臺依賴類的底層引用與模擬。缺點就是在異步測試的狀況下有些問題,這是能夠結合Mockito來將異步轉爲同步便可解決。
最後,自動化UI測試項目中我使用的是Expresso,它提供了一系列相似onView().check().perform()的方式來實現點擊、滑動、檢測頁面顯示等自動化的UI測試效果,這裏在個人WanAndroid項目下的BasePageTest基類裏面封裝了一系列通用的方法,有興趣能夠去看看。
區分出是系統調用仍是開發者調用:
根據堆棧,回溯Class,查看ClassLoader是不是BootStrapClassLoader。
區分後,再區分是不是hidden api:
Method,Field都有access_flag,有一些備用字段,hidden信息存儲其中。
一、不用反射:
利用一個fakelib,例如寫一個android.app.ActivityThread#currentActivityThread空實現,直接調用;
二、假裝系統調用:
jni修改一個class的classloder爲BootStrapClassLoader,麻煩。
利用系統方法去反射:
利用原反射,即:getDeclaredMethod這個方法是系統的方法,經過getDeclaredmethod反射去執行hidden api。
三、修改Method,Field中存儲hidden信息的字段:
利用jni去修改。
一、經過WebView的loadUrl():
設置與Js交互的權限:
webSettings.setJavaScriptEnabled(true)
設置容許JS彈窗:
webSettings.setJavaScriptCanOpenWindowsAutomatically(true)
載入JS代碼:
mWebView.loadUrl("file:///android_asset/javascript.html")
webview只是載體,內容的渲染須要使用webviewChromClient類去實現,經過設置WebChromeClient對象處理JavaScript的對話框。
特別注意:
JS代碼調用必定要在 onPageFinished() 回調以後才能調用,不然不會調用。
二、經過WebView的evaluateJavascript():
只須要將第一種方法的loadUrl()換成evaluateJavascript()便可,經過onReceiveValue()回調接收返回值。
建議:兩種方法混合使用,即Android 4.4如下使用方法1,Android 4.4以上方法2。
一、經過 WebView的addJavascriptInterface()進行對象映射:
-定義一個與JS對象映射關係的Android類:AndroidtoJs:
優勢:使用簡單,僅將Android對象和JS對象映射便可。
缺點:addJavascriptInterface 接口引發遠程代碼執行漏洞,漏洞產生緣由是:
當JS拿到Android這個對象後,就能夠調用這個Android對象中全部的方法,包括系統類(java.lang.Runtime 類),從而進行任意代碼執行。
二、經過 WebViewClient 的方法shouldOverrideUrlLoading ()回調攔截 url:
Android經過 WebViewClient 的回調方法shouldOverrideUrlLoading ()攔截 url。
解析該 url 的協議。
若是檢測到是預先約定好的協議,就調用相應方法。
根據協議的參數,判斷是不是所須要的url。 通常根據scheme(協議格式) & authority(協議名)判斷(前兩個參數)。
優勢:不存在方式1的漏洞;
缺點:JS獲取Android方法的返回值複雜,若是JS想要獲得Android方法的返回值,只能經過 WebView 的 loadUrl ()去執行 JS 方法把返回值傳遞回去。
三、經過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調攔截JS對話框alert()、confirm()、prompt() 消息:
原理:
Android經過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調分別攔截JS對話框 (警告框、確認框、輸入框),獲得他們的消息內容,而後解析便可。
經常使用的攔截是:攔截 JS的輸入框(即prompt()方法),由於只有prompt()能夠返回任意類型的值,操做最全面方便、更加靈活;而alert()對話框沒有返回值;confirm()對話框只能返回兩種狀態(肯定 / 取消)兩個值。
Android:你要的WebView與 JS 交互方式 都在這裏了
技術是沒有止境的,因此確定會不斷有演進和難點。
一. 底層和框架如何更好地設計及優化以適應業務的高速增加。提及來很簡單,低耦合高擴展,作起來是須要長期經驗積累。
二. 我拋幾個細節難點:
三. 若是你以爲沒有難點或者難點在兼容、UI 之類問題上,那麼可能兩個緣由:
你在公司的一天是如何度過的?
可否給我簡單介紹下貴公司業務與戰略的將來發展?
貴公司最讓你自豪的企業文化是什麼?(適合大公司)
團隊、公司如今面臨的最大挑戰是什麼?
對於將來加入這個團隊,你對個人指望是什麼?
您以爲我哪方面知識須要深刻學習或者個人不足在那些方面,從此我該注意什麼*?
你還能夠問下項目團隊多少人,主要以什麼方向爲主,一年內的目標怎樣,團隊氣氛怎樣,等內容着手。
其實,在面試中,不少題目並無真正的答案,你能回答到什麼樣的深度,仍是得靠本身真正的去使用和研究。知識的深度和廣度都很重要,因此都須要花相應的時間去學習。這樣,就算被問到本身不熟悉的領域也能夠同面試官嘮嗑兩句,而後能夠在適當的時機引向本身有深刻研究的領域,讓面試官以爲你是這個領域的專家。
若是這個庫對您有很大幫助,您願意支持這個項目的進一步開發和這個項目的持續維護。你能夠掃描下面的二維碼,讓我喝一杯咖啡或啤酒。很是感謝您的捐贈。謝謝!
歡迎關注個人微信:
bcce5360
。因爲微信羣人數太多沒法生成羣邀二維碼,因此麻煩你們想進微信羣的朋友們,加我微信拉你進羣(PS:微信羣的學習氛圍與各項福利將會超乎你的想象)。
2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~