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

前言

成爲一名優秀的Android開發,需要一份完備的知識體系,在這裏,讓我們一起成長爲自己所想的那樣~。

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

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

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

三、Android優秀三方庫源碼

1、你項目中用到哪些開源庫?說說其實現原理?

一、網絡底層框架:OkHttp實現原理

這個庫是做什麼用的?

網絡底層庫,它是基於http協議封裝的一套請求客戶端,雖然它也可以開線程,但根本上它更偏向真正的請求,跟HttpClient, HttpUrlConnection的職責是一樣的。其中封裝了網絡請求get、post等底層操作的實現。

爲什麼要在項目中使用這個庫?
  • OkHttp 提供了對最新的 HTTP 協議版本 HTTP/2 和 SPDY 的支持,這使得對同一個主機發出的所有請求都可以共享相同的套接字連接。
  • 如果 HTTP/2 和 SPDY 不可用,OkHttp 會使用連接池來複用連接以提高效率。
  • OkHttp 提供了對 GZIP 的默認支持來降低傳輸內容的大小。
  • OkHttp 也提供了對 HTTP 響應的緩存機制,可以避免不必要的網絡請求。
  • 當網絡出現問題時,OkHttp 會自動重試一個主機的多個 IP 地址。
這個庫都有哪些用法?對應什麼樣的使用場景?

get、post請求、上傳文件、上傳表單等等。

這個庫的優缺點是什麼,跟同類型庫的比較?
  • 優點:在上面
  • 缺點:使用的時候仍然需要自己再做一層封裝。
這個庫的核心實現原理是什麼?如果讓你實現這個庫的某些核心功能,你會考慮怎麼去實現?

OkHttp內部的請求流程:使用OkHttp會在請求的時候初始化一個Call的實例,然後執行它的execute()方法或enqueue()方法,內部最後都會執行到getResponseWithInterceptorChain()方法,這個方法裏面通過攔截器組成的責任鏈,依次經過用戶自定義普通攔截器、重試攔截器、橋接攔截器、緩存攔截器、連接攔截器和用戶自定義網絡攔截器以及訪問服務器攔截器等攔截處理過程,來獲取到一個響應並交給用戶。其中,除了OKHttp的內部請求流程這點之外,緩存和連接這兩部分內容也是兩個很重要的點,掌握了這3點就說明你理解了OkHttp。

各個攔截器的作用:
  • interceptors:用戶自定義攔截器
  • retryAndFollowUpInterceptor:負責失敗重試以及重定向
  • BridgeInterceptor:請求時,對必要的Header進行一些添加,接收響應時,移除必要的Header
  • CacheInterceptor:負責讀取緩存直接返回(根據請求的信息和緩存的響應的信息來判斷是否存在緩存可用)、更新緩存
  • ConnectInterceptor:負責和服務器建立連接

ConnectionPool:

1、判斷連接是否可用,不可用則從ConnectionPool獲取連接,ConnectionPool無連接,創建新連接,握手,放入ConnectionPool。

2、它是一個Deque,add添加Connection,使用線程池負責定時清理緩存。

3、使用連接複用省去了進行 TCP 和 TLS 握手的一個過程。

  • networkInterceptors:用戶定義網絡攔截器
  • CallServerInterceptor:負責向服務器發送請求數據、從服務器讀取響應數據
你從這個庫中學到什麼有價值的或者說可借鑑的設計思想?

使用責任鏈模式實現攔截器的分層設計,每一個攔截器對應一個功能,充分實現了功能解耦,易維護。

手寫攔截器?
OKhttp針對網絡層有哪些優化?
網絡請求緩存處理,okhttp如何處理網絡緩存的?
HttpUrlConnection 和 okhttp關係?
Volley與OkHttp的對比:

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黑名單。

自己去設計網絡請求框架,怎麼做?
從網絡加載一個10M的圖片,說下注意事項?
http怎麼知道文件過大是否傳輸完畢的響應?
談談你對WebSocket的理解?
WebSocket與socket的區別?

二、網絡封裝框架:Retrofit實現原理

這個庫是做什麼用的?

Retrofit 是一個 RESTful 的 HTTP 網絡請求框架的封裝。Retrofit 2.0 開始內置 OkHttp,前者專注於接口的封裝,後者專注於網絡請求的高效。

爲什麼要在項目中使用這個庫?

1、功能強大:

  • 支持同步、異步
  • 支持多種數據的解析 & 序列化格式
  • 支持RxJava

2、簡潔易用:

  • 通過註解配置網絡請求參數
  • 採用大量設計模式簡化使用

3、可擴展性好:

  • 功能模塊高度封裝
  • 解耦徹底,如自定義Converters
這個庫都有哪些用法?對應什麼樣的使用場景?

任何網絡場景都應該優先選擇,特別是後臺API遵循Restful API設計風格 & 項目中使用到RxJava。

這個庫的優缺點是什麼,跟同類型庫的比較?
  • 優點:在上面
  • 缺點:擴展性差,高度封裝所帶來的必然後果,如果服務器不能給出統一的API形式,會很難處理。
這個庫的核心實現原理是什麼?如果讓你實現這個庫的某些核心功能,你會考慮怎麼去實現?

Retrofit主要是在create方法中採用動態代理模式(通過訪問代理對象的方式來間接訪問目標對象)實現接口方法,這個過程構建了一個ServiceMethod對象,根據方法註解獲取請求方式,參數類型和參數註解拼接請求的鏈接,當一切都準備好之後會把數據添加到Retrofit的RequestBuilder中。然後當我們主動發起網絡請求的時候會調用okhttp發起網絡請求,okhttp的配置包括請求方式,URL等在Retrofit的RequestBuilder的build()方法中實現,併發起真正的網絡請求。

你從這個庫中學到什麼有價值的或者說可借鑑的設計思想?

內部使用了優秀的架構設計和大量的設計模式,在我分析過Retrofit最新版的源碼和大量優秀的Retrofit源碼分析文章後,我發現,要想真正理解Retrofit內部的核心源碼流程和設計思想,首先,需要對它使用到的九大設計模式有一定的瞭解,下面我簡單說一說:

1、創建Retrofit實例:

  • 使用建造者模式通過內部Builder類建立了一個Retroift實例。
  • 網絡請求工廠使用了工廠方法模式。

2、創建網絡請求接口的實例:

  • 首先,使用外觀模式統一調用創建網絡請求接口實例和網絡請求參數配置的方法。
  • 然後,使用動態代理動態地去創建網絡請求接口實例。
  • 接着,使用了建造者模式 & 單例模式創建了serviceMethod對象。
  • 再者,使用了策略模式對serviceMethod對象進行網絡請求參數配置,即通過解析網絡請求接口方法的參數、返回值和註解類型,從Retrofit對象中獲取對應的網絡的url地址、網絡請求執行器、網絡請求適配器和數據轉換器。
  • 最後,使用了裝飾者模式ExecuteCallBack爲serviceMethod對象加入線程切換的操作,便於接受數據後通過Handler從子線程切換到主線程從而對返回數據結果進行處理。

3、發送網絡請求:

  • 在異步請求時,通過靜態delegate代理對網絡請求接口的方法中的每個參數使用對應的ParameterHanlder進行解析。

4、解析數據

5、切換線程:

  • 使用了適配器模式通過檢測不同的Platform使用不同的回調執行器,然後使用回調執行器切換線程,這裏同樣是使用了裝飾模式。

6、處理結果

Android:主流網絡請求開源庫的對比(Android-Async-Http、Volley、OkHttp、Retrofit)

www.jianshu.com/p/050c6db5a…

三、響應式編程框架:RxJava實現原理

RxJava 變換操作符 map flatMap concatMap buffer?
  • map:【數據類型轉換】將被觀察者發送的事件轉換爲另一種類型的事件。
  • flatMap:【化解循環嵌套和接口嵌套】將被觀察者發送的事件序列進行拆分 & 轉換 後合併成一個新的事件序列,最後再進行發送。
  • concatMap:【有序】與 flatMap 的 區別在於,拆分 & 重新合併生成的事件序列 的順序與被觀察者舊序列生產的順序一致。
  • buffer:定期從被觀察者發送的事件中獲取一定數量的事件並放到緩存區中,然後把這些數據集合打包發射。
RxJava中map和flatmap操作符的區別及底層實現
手寫rxjava遍歷數組。
你認爲Rxjava的線程池與你們自己實現任務管理框架有什麼區別?

四、圖片加載框架:Glide實現原理

這個庫是做什麼用的?

Glide是Android中的一個圖片加載庫,用於實現圖片加載。

爲什麼要在項目中使用這個庫?

1、多樣化媒體加載:不僅可以進行圖片緩存,還支持Gif、WebP、縮略圖,甚至是Video。

2、通過設置綁定生命週期:可以使加載圖片的生命週期動態管理起來。

3、高效的緩存策略:支持內存、Disk緩存,並且Picasso只會緩存原始尺寸的圖片,內Glide緩存的是多種規格,也就是Glide會根據你ImageView的大小來緩存相應大小的圖片尺寸。

4、內存開銷小:默認的Bitmap格式是RGB_565格式,而Picasso默認的是ARGB_8888格式,內存開銷小一半。

這個庫都有哪些用法?對應什麼樣的使用場景?

1、圖片加載:Glide.with(this).load(imageUrl).override(800, 800).placeholder().error().animate().into()。

2、多樣式媒體加載:asBitamp、asGif。

3、生命週期集成。

4、可以配置磁盤緩存策略ALL、NONE、SOURCE、RESULT。

這個庫的優缺點是什麼,跟同類型庫的比較?

庫比較大,源碼實現複雜。

這個庫的核心實現原理是什麼?如果讓你實現這個庫的某些核心功能,你會考慮怎麼去實現?
  • Glide&with:

1、初始化各式各樣的配置信息(包括緩存,請求線程池,大小,圖片格式等等)以及glide對象。

2、將glide請求和application/SupportFragment/Fragment的生命週期綁定在一塊。

  • Glide&load:

設置請求url,並記錄url已設置的狀態。

3、Glide&into:

1、首先根據轉碼類transcodeClass類型返回不同的ImageViewTarget:BitmapImageViewTarget、DrawableImageViewTarget。

2、遞歸建立縮略圖請求,沒有縮略圖請求,則直接進行正常請求。

3、如果沒指定寬高,會根據ImageView的寬高計算出圖片寬高,最終執行到onSizeReay()方法中的engine.load()方法。

4、engine是一個負責加載和管理緩存資源的類

  • 常規三級緩存的流程:強引用->軟引用->硬盤緩存

當我們的APP中想要加載某張圖片時,先去LruCache中尋找圖片,如果LruCache中有,則直接取出來使用,如果LruCache中沒有,則去SoftReference中尋找(軟引用適合當cache,當內存喫緊的時候纔會被回收。而weakReference在每次system.gc()就會被回收)(當LruCache存儲緊張時,會把最近最少使用的數據放到SoftReference中),如果SoftReference中有,則從SoftReference中取出圖片使用,同時將圖片重新放回到LruCache中,如果SoftReference中也沒有圖片,則去硬盤緩存中中尋找,如果有則取出來使用,同時將圖片添加到LruCache中,如果沒有,則連接網絡從網上下載圖片。圖片下載完成後,將圖片保存到硬盤緩存中,然後放到LruCache中。

  • Glide的三層緩存機制:

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中。

Glide源碼機制的核心思想:

使用一個弱引用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的高效的三層緩存機制,如上。

Glide如何確定圖片加載完畢?
Glide使用什麼緩存?
Glide內存緩存如何控制大小?
計算一張圖片的大小

圖片佔用內存的計算公式:圖片高度 * 圖片寬度 * 一個像素佔用的內存大小。所以,計算圖片佔用內存大小的時候,要考慮圖片所在的目錄跟設備密度,這兩個因素其實影響的是圖片的寬高,android會對圖片進行拉昇跟壓縮。

加載bitmap過程(怎樣保證不產生內存溢出)

由於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等其他方法。

Android中軟引用與弱引用的應用場景。

Java 引用類型分類:

在 Android 應用的開發中,爲了防止內存溢出,在處理一些佔用內存大而且生命週期較長的對象時候,可以儘量應用軟引用和弱引用技術。

  • 1、軟/弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java 虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。利用這個隊列可以得知被回收的軟/弱引用的對象列表,從而爲緩衝器清除已失效的軟 / 弱引用。
  • 2、如果只是想避免 OOM 異常的發生,則可以使用軟引用。如果對於應用的性能更在意,想盡快回收一些佔用內存比較大的對象,則可以使用弱引用。
  • 3、可以根據對象是否經常使用來判斷選擇軟引用還是弱引用。如果該對象可能會經常使用的,就儘量用軟引用。如果該對象不被使用的可能性更大些,就可以用弱引用。
Android裏的內存緩存和磁盤緩存是怎麼實現的。

內存緩存基於LruCache實現,磁盤緩存基於DiskLruCache實現。這兩個類都基於Lru算法和LinkedHashMap來實現。

LRU算法可以用一句話來描述,如下所示:

LRU是Least Recently Used的縮寫,最近最少使用算法,從它的名字就可以看出,它的核心原則是如果一個數據在最近一段時間沒有使用到,那麼它在將來被訪問到的可能性也很小,則這類數據項會被優先淘汰掉。

LruCache原理

之前,我們會使用內存緩存技術實現,也就是軟引用或弱引用,在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()方法獲得對應集合元素,同時會更新該元素到隊尾。

LruCache put方法核心邏輯

在添加過緩存對象後,調用trimToSize()方法,來判斷緩存是否已滿,如果滿了就要刪除近期最少使用的對象。trimToSize()方法不斷地刪除LinkedHashMap中隊頭的元素,即近期最少訪問的,直到緩存大小小於最大值(maxSize)。

LruCache get方法核心邏輯

當調用LruCache的get()方法獲取集合中的緩存對象時,就代表訪問了一次該元素,將會更新隊列,保持整個隊列是按照訪問順序排序的。

爲什麼會選擇LinkedHashMap呢?

這跟LinkedHashMap的特性有關,LinkedHashMap的構造函數裏有個布爾參數accessOrder,當它爲true時,LinkedHashMap會以訪問順序爲序排列元素,否則以插入順序爲序排序元素。

LinkedHashMap原理

LinkedHashMap 幾乎和 HashMap 一樣:從技術上來說,不同的是它定義了一個 Entry<K,V> header,這個 header 不是放在 Table 裏,它是額外獨立出來的。LinkedHashMap 通過繼承 hashMap 中的 Entry<K,V>,並添加兩個屬性 Entry<K,V> before,after,和 header 結合起來組成一個雙向鏈表,來實現按插入順序或訪問順序排序。

DisLruCache原理

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 壓縮策略

加載 Bitmap 的方式:

BitmapFactory 四類方法:

  • decodeFile( 文件系統 )
  • decodeResourece( 資源 )
  • decodeStream( 輸入流 )
  • decodeByteArray( 字節數 )

BitmapFactory.options 參數:

  • inSampleSize 採樣率,對圖片高和寬進行縮放,以最小比進行縮放(一般取值爲 2 的指數)。通常是根據圖片寬高實際的大小/需要的寬高大小,分別計算出寬和高的縮放比。但應該取其中最小的縮放比,避免縮放圖片太小,到達指定控件中不能鋪滿,需要拉伸從而導致模糊。
  • inJustDecodeBounds 獲取圖片的寬高信息,交給 inSampleSize 參數選擇縮放比。通過 inJustDecodeBounds = true,然後加載圖片就可以實現只解析圖片的寬高信息,並不會真正的加載圖片,所以這個操作是輕量級的。當獲取了寬高信息,計算出縮放比後,然後在將 inJustDecodeBounds = false,再重新加載圖片,就可以加載縮放後的圖片。

高效加載 Bitmap 的流程:

  • 1、將 BitmapFactory.Options 的 inJustDecodeBounds 參數設爲 true 並加載圖片
  • 2、從 BitmapFactory.Options 中取出圖片原始的寬高信息,對應於 outWidth 和 outHeight 參數
  • 3、根據採樣率規則並結合目標 view 的大小計算出採樣率 inSampleSize
  • 4、將 BitmapFactory.Options 的 inJustDecodeBounds 設置爲 false 重新加載圖片
Bitmap的處理:

當使用ImageView的時候,可能圖片的像素大於ImageView,此時就可以通過BitmapFactory.Option來對圖片進行壓縮,inSampleSize表示縮小2^(inSampleSize-1)倍。

BitMap的緩存:

1.使用LruCache進行內存緩存。

2.使用DiskLruCache進行硬盤緩存。

實現一個ImageLoader的流程

同步異步加載、圖片壓縮、內存硬盤緩存、網絡拉取

  • 1.同步加載只創建一個線程然後按照順序進行圖片加載
  • 2.異步加載使用線程池,讓存在的加載任務都處於不同線程
  • 3.爲了不開啓過多的異步任務,只在列表靜止的時候開啓圖片加載

具體爲:

  • 1、ImageLoader作爲一個單例,提供了加載圖片到指定控件的方法:直接從內存緩存中獲取對象,如果沒有則用一個ThreadPoolExecutor去執行Runnable任務來加載圖片。ThreadPoolExecutor的創建需要指定核心線程數CPU數+1,最大線程數CPU數*2+1,線程閒置超市時長10s,這幾個關鍵數據,還可以加入ThreadFactory參數來創建定製化的線程。
  • 2、ImageLoader的具體實現loadBitmap:先從內存緩存LruCache中加載,如果爲空再從磁盤緩存中加載,加載成功後記得存入內存緩存,如果爲空則從網絡中直接下載輸出流到磁盤緩存,然後再從磁盤中加載,如果爲空並且磁盤緩存沒有被創建的話,直接通過BitmapFactory的decodeStream獲取網絡請求的輸入流獲取Bitmap對象。
  • 3、v4包的LruCache可以兼容到2.2版本,LruCache採用LinkedHashMap存儲緩存對象。創建對象只需要提供緩存容量並重寫sizeOf方法:作用是計算緩存對象的大小。有時需要重寫entryRemoved方法,用於回收一些資源。
  • 4、DiskLruCache通過open方法創建,設置緩存路徑,緩存容量。緩存添加通過Editor對象創建輸出流,下載資源到輸出流完成後,commit,如果失敗則abort撤回。然後刷新磁盤緩存。緩存查找通過Snapshot對象獲取輸入流,獲取FileDescriptor,通過FileDescriptor解析出Bitmap對象。
  • 5、列表中需要加載圖片的時候,當列表在滑動中不進行圖片加載,當滑動停止後再去加載圖片。
Bitmap在decode的時候申請的內存如何複用,釋放時機
圖片庫對比

stackoverflow.com/questions/2…

www.trinea.cn/android/and…

Fresco與Glide的對比:

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結尾的當然就是以構造者模式創建對象的類。

Bitmap如何處理大圖,如一張30M的大圖,如何預防OOM?

blog.csdn.net/guolin_blog…

blog.csdn.net/lmj62356579…

使用BitmapRegionDecoder動態加載圖片的顯示區域。

Bitmap對象的理解。
對inBitmap的理解。
自己去實現圖片庫,怎麼做?(對擴展開發,對修改封閉,同時又保持獨立性,參考Android源碼設計模式解析實戰的圖片加載庫案例即可)
寫個圖片瀏覽器,說出你的思路?

五、事件總線框架:EventBus實現原理

六、內存泄漏檢測框架:LeakCanary實現原理

這個庫是做什麼用?

內存泄露檢測框架。

爲什麼要在項目中使用這個庫?
  • 針對Android Activity組件完全自動化的內存泄漏檢查,在最新的版本中,還加入了android.app.fragment的組件自動化的內存泄漏檢測。
  • 易用集成,使用成本低。
  • 友好的界面展示和通知。
這個庫都有哪些用法?對應什麼樣的使用場景?

直接從application中拿到全局的 refWatcher 對象,在Fragment或其他組件的銷燬回調中使用refWatcher.watch(this)檢測是否發生內存泄漏。

這個庫的優缺點是什麼,跟同類型庫的比較?

檢測結果並不是特別的準確,因爲內存的釋放和對象的生命週期有關也和GC的調度有關。

這個庫的核心實現原理是什麼?如果讓你實現這個庫的某些核心功能,你會考慮怎麼去實現?

主要分爲如下7個步驟:

  • 1、RefWatcher.watch()創建了一個KeyedWeakReference用於去觀察對象。
  • 2、然後,在後臺線程中,它會檢測引用是否被清除了,並且是否沒有觸發GC。
  • 3、如果引用仍然沒有被清除,那麼它將會把堆棧信息保存在文件系統中的.hprof文件裏。
  • 4、HeapAnalyzerService被開啓在一個獨立的進程中,並且HeapAnalyzer使用了HAHA開源庫解析了指定時刻的堆棧快照文件heap dump。
  • 5、從heap dump中,HeapAnalyzer根據一個獨特的引用key找到了KeyedWeakReference,並且定位了泄露的引用。
  • 6、HeapAnalyzer爲了確定是否有泄露,計算了到GC Roots的最短強引用路徑,然後建立了導致泄露的鏈式引用。
  • 7、這個結果被傳回到app進程中的DisplayLeakService,然後一個泄露通知便展現出來了。

簡單來說就是:

在一個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設置在了一個獨立的進程中。

你從這個庫中學到什麼有價值的或者說可借鑑的設計思想?
leakCannary中如何判斷一個對象是否被回收?如何觸發手動gc?c層實現?
BlockCanary原理:

該組件利用了主線程的消息隊列處理機制,應用發生卡頓,一定是在dispatchMessage中執行了耗時操作。我們通過給主線程的Looper設置一個Printer,打點統計dispatchMessage方法執行的時間,如果超出閥值,表示發生卡頓,則dump出各種信息,提供開發者分析性能瓶頸。

七、依賴注入框架:ButterKnife實現原理

ButterKnife對性能的影響很小,因爲沒有使用使用反射,而是使用的Annotation Processing Tool(APT),註解處理器,javac中用於編譯時掃描和解析Java註解的工具。在編譯階段執行的,它的原理就是讀入Java源代碼,解析註解,然後生成新的Java代碼。新生成的Java代碼最後被編譯成Java字節碼,註解解析器不能改變讀入的Java類,比如不能加入或刪除Java方法。

AOP IOC 的好處以及在 Android 開發中的應用

八、依賴全局管理框架:Dagger2實現原理

九、數據庫框架:GreenDao實現原理

數據庫框架對比?
數據庫的優化
數據庫數據遷移問題
數據庫索引的數據結構
平衡二叉樹
  • 1、非葉子節點只能允許最多兩個子節點存在。
  • 2、每一個非葉子節點數據分佈規則爲左邊的子節點小當前節點的值,右邊的子節點大於當前節點的值(這裏值是基於自己的算法規則而定的,比如hash值)。
  • 3、樹的左右兩邊的層級數相差不會大於1。

使用平衡二叉樹能保證數據的左右兩邊的節點層級相差不會大於1.,通過這樣避免樹形結構由於刪除增加變成線性鏈表影響查詢效率,保證數據平衡的情況下查找數據的速度近於二分法查找。

目前大部分數據庫系統及文件系統都採用B-Tree或其變種B+Tree作爲索引結構。

B-Tree

B樹和平衡二叉樹稍有不同的是B樹屬於多叉樹又名平衡多路查找樹(查找路徑不只兩個)。

  • 1、排序方式:所有節點關鍵字是按遞增次序排列,並遵循左小右大原則。
  • 2、子節點數:非葉節點的子節點數>1,且<=M ,且M>=2,空樹除外(注:M階代表一個樹節點最多有多少個查找路徑,M=M路,當M=2則是2叉樹,M=3則是3叉)。
  • 3、關鍵字數:枝節點的關鍵字數量大於等於ceil(m/2)-1個且小於等於M-1個(注:ceil()是個朝正無窮方向取整的函數 如ceil(1.1)結果爲2)。
  • 4、所有葉子節點均在同一層、葉子節點除了包含了關鍵字和關鍵字記錄的指針外也有指向其子節點的指針只不過其指針地址都爲null對應下圖最後一層節點的空格子。

B樹相對於平衡二叉樹的不同是,每個節點包含的關鍵字增多了,把樹的節點關鍵字增多後樹的層級比原來的二叉樹少了,減少數據查找的次數和複雜度。

B+Tree
規則:
  • 1、B+跟B樹不同B+樹的非葉子節點不保存關鍵字記錄的指針,只進行數據索引。
  • 2、B+樹葉子節點保存了父節點的所有關鍵字記錄的指針,所有數據地址必須要到葉子節點才能獲取到。所以每次數據查詢的次數都一樣。
  • 3、B+樹葉子節點的關鍵字從小到大有序排列,左邊結尾數據都會保存右邊節點開始數據的指針。
  • 4、非葉子節點的子節點數=關鍵字數(來源百度百科)(根據各種資料 這裏有兩種算法的實現方式,另一種爲非葉節點的關鍵字數=子節點數-1(來源維基百科),雖然他們數據排列結構不一樣,但其原理還是一樣的Mysql 的B+樹是用第一種方式實現)。

特點:

1、B+樹的層級更少:相較於B樹B+每個非葉子節點存儲的關鍵字數更多,樹的層級更少所以查詢數據更快。

2、B+樹查詢速度更穩定:B+所有關鍵字數據地址都存在葉子節點上,所以每次查找的次數都相同所以查詢速度要比B樹更穩定。

3、B+樹天然具備排序功能:B+樹所有的葉子節點數據構成了一個有序鏈表,在查詢大小區間的數據時候更方便,數據緊密性很高,緩存的命中率也會比B樹高。

4、B+樹全節點遍歷更快:B+樹遍歷整棵樹只需要遍歷所有的葉子節點即可,而不需要像B樹一樣需要對每一層進行遍歷,這有利於數據庫做全表掃描。

B樹相對於B+樹的優點是,如果經常訪問的數據離根節點很近,而B樹的非葉子節點本身存有關鍵字其數據的地址,所以這種數據檢索的時候會要比B+樹快。

B*Tree

B*樹是B+樹的變種,相對於B+樹他們的不同之處如下:

  • 1、首先是關鍵字個數限制問題,B+樹初始化的關鍵字初始化個數是cei(m/2),b樹的初始化個數爲(cei(2/3m))。

  • 2、B+樹節點滿時就會分裂,而B*樹節點滿時會檢查兄弟節點是否滿(因爲每個節點都有指向兄弟的指針),如果兄弟節點未滿則向兄弟節點轉移關鍵字,如果兄弟節點已滿,則從當前節點和兄弟節點各拿出1/3的數據創建一個新的節點出來。

在B+樹的基礎上因其初始化的容量變大,使得節點空間使用率更高,而又存有兄弟節點的指針,可以向兄弟節點轉移關鍵字的特性使得B*樹分解次數變得更少。

結論:
  • 1、相同思想和策略:從平衡二叉樹、B樹、B+樹、B*樹總體來看它們貫徹的思想是相同的,都是採用二分法和數據平衡策略來提升查找數據的速度。
  • 2、不同的方式的磁盤空間利用:不同點是他們一個一個在演變的過程中通過IO從磁盤讀取數據的原理進行一步步的演變,每一次演變都是爲了讓節點的空間更合理的運用起來,從而使樹的層級減少達到快速查找數據的目的;

還不理解請查看:平衡二叉樹、B樹、B+樹、B*樹 理解其中一種你就都明白了

四、熱修復、插件化、模塊化、組件化、Gradle、編譯插樁技術

1、熱修復和插件化

Android中ClassLoader的種類&特點

  • BootClassLoader(Java的BootStrap ClassLoader): 用於加載Android Framework層class文件。
  • PathClassLoader(Java的App ClassLoader): 用於加載已經安裝到系統中的apk中的class文件。
  • DexClassLoader(Java的Custom ClassLoader): 用於加載指定目錄中的class文件。
  • BaseDexClassLoader: 是PathClassLoader和DexClassLoader的父類。

熱修補技術是怎樣實現的,和插件化有什麼區別?

插件化:動態加載主要解決3個技術問題:

  • 1、使用ClassLoader加載類。
  • 2、資源訪問。
  • 3、生命週期管理。

插件化是體現在功能拆分方面的,它將某個功能獨立提取出來,獨立開發,獨立測試,再插入到主應用中。以此來減少主應用的規模。

熱修復:

原因:因爲一個dvm中存儲方法id用的是short類型,導致dex中方法不能超過65536個。

代碼熱修復原理:
  • 將編譯好的class文件拆分打包成兩個dex,繞過dex方法數量的限制以及安裝時的檢查,在運行時再動態加載第二個dex文件中。
  • 熱修復是體現在bug修復方面的,它實現的是不需要重新發版和重新安裝,就可以去修復已知的bug。
  • 利用PathClassLoader和DexClassLoader去加載與bug類同名的類,替換掉bug類,進而達到修復bug的目的,原理是在app打包的時候阻止類打上CLASS_ISPREVERIFIED標誌,然後在熱修復的時候動態改變BaseDexClassLoader對象間接引用的dexElements,替換掉舊的類。

相同點:

都使用ClassLoader來實現加載新的功能類,都可以使用PathClassLoader與DexClassLoader。

不同點:

熱修復因爲是爲了修復Bug的,所以要將新的類替代同名的Bug類,要搶先加載新的類而不是Bug類,所以多做兩件事:在原先的app打包的時候,阻止相關類去打上CLASS_ISPREVERIFIED標誌,還有在熱修復時動態改變BaseDexClassLoader對象間接引用的dexElements,這樣才能搶先代替Bug類,完成系統不加載舊的Bug類.。 而插件化只是增加新的功能類或者是資源文件,所以不涉及搶先加載新的類這樣的使命,就避過了阻止相關類去打上CLASS_ISPREVERIFIED標誌和還有在熱修復時動態改變BaseDexClassLoader對象間接引用的dexElements.

所以插件化比熱修復簡單,熱修復是在插件化的基礎上在進行替換舊的Bug類。

熱修復原理:

資源修復:

很多熱修復框架的資源修復參考了Instant Run的資源修復的原理。

傳統編譯部署流程如下:

Instant Run編譯部署流程如下:

  • Hot Swap:修改一個現有方法中的代碼時會採用Hot Swap。
  • Warm Swap:修改或刪除一個現有的資源文件時會採用Warm Swap。
  • Cold Swap:有很多情況,如添加、刪除或修改一個字段和方法、添加一個類等。

Instant Run中的資源熱修復流程:

  • 1、創建新的AssetManager,通過反射調用addAssetPath方法加載外部的資源,這樣新創建的AssetManager就含有了外部資源。
  • 2、將AssetManager類型的mAssets字段的引用全部替換爲新創建的AssetManager。
代碼修復:

1、類加載方案:

65536限制:

65536的主要原因是DVM Bytecode的限制,DVM指令集的方法調用指令invoke-kind索引爲16bits,最多能引用65535個方法。

LinearAlloc限制:

  • DVM中的LinearAlloc是一個固定的緩存區,當方法數超過了緩存區的大小時會報錯。

Dex分包方案主要做的是在打包時將應用代碼分成多個Dex,將應用啓動時必須用到的類和這些類的直接引用類放到Dex中,其他代碼放到次Dex中。當應用啓動時先加載主Dex,等到應用啓動後再動態地加載次Dex,從而緩解了主Dex的65536限制和LinearAlloc限制。

加載流程:

  • 根據dex文件的查找流程,我們將有Bug的類Key.class進行修改,再將Key.class打包成包含dex的補丁包Patch.jar,放在Element數組dexElements的第一個元素,這樣會首先找到Patch.dex中的Key.class去替換之前存在Bug的Key.class,排在數組後面的dex文件中存在Bug的Key.class根據ClassLoader的雙親委託模式就不會被加載。

類加載方案需要重啓App後讓ClassLoader重新加載新的類,爲什麼需要重啓呢?

  • 這是因爲類是無法被卸載的,要想重新加載新的類就需要重啓App,因此採用類加載方案的熱修復框架是不能即時生效的。

各個熱修復框架的實現細節差異:

  • QQ空間的超級補丁和Nuwa是按照上面說的將補丁包放在Element數組的第一個元素得到優先加載。
  • 微信的Tinker將新舊APK做了diff,得到path.dex,再將patch.dex與手機中APK的classes.dex做合併,生成新的classes.dex,然後在運行時通過反射將classes.dex放在Elements數組的第一個元素。
  • 餓了麼的Amigo則是將補丁包中每個dex對應的Elements取出來,之後組成新的Element數組,在運行時通過反射用新的Elements數組替換掉現有的Elements數組。

2、底層替換方案:

當我們要反射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。

3、Instant Run方案:

什麼是ASM?

ASM是一個java字節碼操控框架,它能夠動態生成類或者增強現有類的功能。ASM可以直接產生class文件,也可以在類被加載到虛擬機之前動態改變類的行爲。

Instant Run在第一次構建APK時,使用ASM在每一個方法中注入了類似的代碼邏輯:當change不爲null時,則調用它的accessdispatch方法,參數爲具體的方法名和方法參數。當MainActivity的onCreate方法做了修改,就會生成替換類MainActivityoverride,這個類實現了IncrementalChange接口,同時也會生成一個AppPatchesLoaderImpl類,這個類的getPatchedClasses方法會返回被修改的類的列表(裏面包含了MainActivity),根據列表會將MainActivity的change設置爲MainActivityoverride。最後這個change就不會爲null,則會執行MainActivityoverride的accessdispatch方法,最終會執行onCreate方法,從而實現了onCreate方法的修改。

借鑑Instant Run原理的熱修復框架有Robust和Aceso。

動態鏈接庫修復:

重新加載so。

加載so主要用到了System類的load和loadLibrary方法,最終都會調用到nativeLoad方法。其會調用JavaVMExt的LoadNativeLibrary函數來加載so。

so修復主要有兩個方案:

  • 1、將so補丁插入到NativeLibraryElement數組的前部,讓so補丁的路徑先被返回和加載。
  • 2、調用System的load方法來接管so的加載入口。

爲什麼選用插件化?

在Android傳統開發中,一旦應用的代碼被打包成APK並被上傳到各個應用市場,我們就不能修改應用的源碼了,只能通過服務器來控制應用中預留的分支代碼。但是很多時候我們無法預知需求和突然發生的情況,也就不能提前在應用代碼中預留分支代碼,這時就需要採用動態加載技術,即在程序運行時,動態加載一些程序中原本不存在的可執行文件並運行這些文件裏的代碼邏輯。其中可執行文件包括動態鏈接庫so和dex相關文件(dex以及包含dex的jar/apk文件)。隨着應用開發技術和業務的逐步發展,動態加載技術派生出兩個技術:熱修復和插件化。其中熱修復技術主要用來修復Bug,而插件化技術則主要用於解決應用越來越龐大以及功能模塊的解耦。詳細點說,就是爲了解決以下幾種情況:

  • 1、業務複雜、模塊耦合:隨着業務越來越複雜,應用程序的工程和功能模塊數量會越來越多,一個應用可能由幾十甚至幾百人來協同開發,其中的一個工程可能就由一個小組來進行開發維護,如果功能模塊間的耦合度較高,修改一個模塊會影響其它功能模塊,勢必會極大地增加溝通成本。
  • 2、應用間的接入:當一個應用需要接入其它應用時,如淘寶,爲了將流量引流到其它的淘寶應用如:飛豬旅遊、口碑外賣、聚划算等等應用,如使用常規技術有兩個問題:可能要維護多個版本的問題或單個應用體積將會非常龐大的問題。
  • 3、65536限制,內存佔用大。

插件化的思想:

安裝的應用可以理解爲插件,這些插件可以自由地進行插拔。

插件化的定義:

插件一般是指經過處理的APK,so和dex等文件,插件可以被宿主進行加載,有的插件也可以作爲APK獨立運行。

將一個應用按照插件的方式進行改造的過程就叫作插件化。

插件化的優勢:

  • 低耦合
  • 應用間的接入和維護更便捷,每個應用團隊只需要負責自己的那一部分。
  • 應用及主dex的體積也會相應變小,間接地避免了65536限制。
  • 第一次加載到內存的只有淘寶客戶端,當使用到其它插件時纔會加載相應插件到內存,以減少內存佔用。

插件化框架對比:

  • 最早的插件化框架:2012年大衆點評的屠毅敏就推出了AndroidDynamicLoader框架。
  • 目前主流的插件化方案有滴滴任玉剛的VirtualApk、360的DroidPlugin、RePlugin、Wequick的Small框架。
  • 如果加載的插件不需要和宿主有任何耦合,也無須和宿主進行通信,比如加載第三方App,那麼推薦使用RePlugin,其他情況推薦使用VirtualApk。由於VirtualApk在加載耦合插件方面是插件化框架的首選,具有普遍的適用性,因此有必要對它的源碼進行了解。

插件化原理:

Activity插件化:

主要實現方式有三種:

  • 反射:對性能有影響,主流的插件化框架沒有采用此方式。
  • 接口:dynamic-load-apk採用。
  • Hook:主流。

Hook實現方式有兩種:Hook IActivityManager和Hook Instrumentation。主要方案就是先用一個在AndroidManifest.xml中註冊的Activity來進行佔坑,用來通過AMS的校驗,接着在合適的時機用插件Activity替換佔坑的Activity。

Hook IActivityManager:

1、佔坑、通過校驗:

在Android 7.0和8.0的源碼中IActivityManager藉助了Singleton類實現單例,而且該單例是靜態的,因此IActivityManager是一個比較好的Hook點。

接着,定義替換IActivityManager的代理類IActivityManagerProxy,由於Hook點IActivityManager是一個接口,建議這裏採用動態代理。

  • 攔截startActivity方法,獲取參數args中保存的Intent對象,它是原本要啓動插件TargetActivity的Intent。
  • 新建一個subIntent用來啓動StubActivity,並將前面得到的TargetActivity的Intent保存到subIntent中,便於以後還原TargetActivity。
  • 最後,將subIntent賦值給參數args,這樣啓動的目標就變爲了StubActivity,用來通過AMS的校驗。

然後,用代理類IActivityManagerProxy來替換IActivityManager。

  • 當版本大於等於26時,使用反射獲取ActivityManager的IActivityManagerSingleton字段,小於時則獲取ActivityManagerNative中的gDefault字段。
  • 然後,通過反射獲取對應的Singleton實例,從上面得到的2個字段中拿到對應的IActivityManager。
  • 最後,使用Proxy.newProxyInstance()方法動態創建代理類IActivityManagerProxy,用IActivityManagerProxy來替換IActivityManager。

2、還原插件Activity:

  • 前面用佔坑Activity通過了AMS的校驗,但是我們要啓動的是插件TargetActivity,還需要用插件TargetActivity來替換佔坑的SubActivity,替換時機爲圖中步驟2之後。
  • 在ActivityThread的H類中重寫的handleMessage方法會對LAUNCH_ACTIVITY類型的消息進行處理,最終會調用Activity的onCreate方法。在Handler的dispatchMessage處理消息的這個方法中,看到如果Handelr的Callback類型的mCallBack不爲null,就會執行mCallback的handleMessage方法,因此mCallback可以作爲Hook點。我們可以用自定義的Callback來替換mCallback。

自定義的Callback實現了Handler.Callback,並重寫了handleMessage方法,當收到消息的類型爲LAUNCH_ACTIVITY時,將啓動SubActivity的Intent替換爲啓動TargetActivity的Intent。然後使用反射將Handler的mCallback替換爲自定義的CallBack即可。使用時則在application的attachBaseContext方法中進行hook即可。

3、插件Activity的生命週期:

  • AMS和ActivityThread之間的通信採用了token來對Activity進行標識,並且此後的Activity的生命週期處理也是根據token來對Activity進行標識的,因爲我們在Activity啓動時用插件TargetActivity替換佔坑SubActivity,這一過程在performLaunchActivity之前,因此performLaunchActivity的r.token就是TargetActivity。所以TargetActivity具有生命週期。

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。具體爲:

  • 首先檢查TargetActivity是否已經註冊,如果沒有則將TargetActivity的ClassName保存起來用於後面還原。接着把要啓動的TargetActivity替換爲StubActivity,最後通過反射調用execStartActivity方法,這樣就可以用StubActivity通過AMS的驗證。
  • 在newActivity方法中創建了此前保存的TargetActivity,完成了還原TargetActivity。最後使用反射用InstrumentationProxy替換mInstumentation。
資源插件化:

資源的插件化和熱修復的資源修復都藉助了AssetManager。

資源的插件化方案主要有兩種:

  • 1、合併資源方案,將插件的資源全部添加到宿主的Resources中,這種方案插件可以訪問宿主的資源。
  • 2、構建插件資源方案,每個插件都構造出獨立的Resources,這種方案插件不可以訪問宿主資源。
so的插件化:

so的插件化方案和so熱修復的第一種方案類似,就是將so插件插入到NativelibraryElement數組中,並且將存儲so插件的文件添加到nativeLibraryDirectories集合中就可以了。

插件的加載機制方案:
  • 1、Hook ClassLoader。
  • 2、委託給系統的ClassLoader幫忙加載。

2、模塊化和組件化

模塊化的好處

www.jianshu.com/p/376ea8a19…

分析現有的組件化方案:

很多大廠的組件化方案是以 多工程 + 多 Module 的結構(微信, 美團等超級 App 更是以 多工程 + 多 Module + 多 P 工程(以頁面爲單元的代碼隔離方式) 的三級工程結構), 使用 Git Submodule 創建多個子倉庫管理各個模塊的代碼, 並將各個模塊的代碼打包成 AAR 上傳至私有 Maven 倉庫使用遠程版本號依賴的方式進行模塊間代碼的隔離。

組件化開發的好處:

  • 避免重複造輪子,可以節省開發和維護的成本。
  • 可以通過組件和模塊爲業務基準合理地安排人力,提高開發效率。
  • 不同的項目可以共用一個組件或模塊,確保整體技術方案的統一性。
  • 爲未來插件化共用同一套底層模型做準備。

跨組件通信:

跨組件通信場景:

  • 第一種是組件之間的頁面跳轉 (Activity 到 Activity, Fragment 到 Fragment, Activity 到 Fragment, Fragment 到 Activity) 以及跳轉時的數據傳遞 (基礎數據類型和可序列化的自定義類類型)。
  • 第二種是組件之間的自定義類和自定義方法的調用(組件向外提供服務)。

跨組件通信方案分析:

  • 第一種組件之間的頁面跳轉不需要過多描述了, 算是 ARouter 中最基礎的功能, API 也比較簡單, 跳轉時想傳遞不同類型的數據也提供有相應的 API。
  • 第二種組件之間的自定義類和自定義方法的調用要稍微複雜點, 需要 ARouter 配合架構中的 公共服務(CommonService) 實現:
提供服務的業務模塊:

在公共服務(CommonService) 中聲明 Service 接口 (含有需要被調用的自定義方法), 然後在自己的模塊中實現這個 Service 接口, 再通過 ARouter API 暴露實現類。

使用服務的業務模塊:

通過 ARouter 的 API 拿到這個 Service 接口(多態持有, 實際持有實現類), 即可調用 Service 接口中聲明的自定義方法, 這樣就可以達到模塊之間的交互。 此外,可以使用 AndroidEventBus 其獨有的 Tag, 可以在開發時更容易定位發送事件和接受事件的代碼, 如果以組件名來作爲 Tag 的前綴進行分組, 也可以更好的統一管理和查看每個組件的事件, 當然也不建議大家過多使用 EventBus。

如何管理過多的路由表?

RouterHub 存在於基礎庫, 可以被看作是所有組件都需要遵守的通訊協議, 裏面不僅可以放路由地址常量, 還可以放跨組件傳遞數據時命名的各種 Key 值, 再配以適當註釋, 任何組件開發人員不需要事先溝通只要依賴了這個協議, 就知道了各自該怎樣協同工作, 既提高了效率又降低了出錯風險, 約定的東西自然要比口頭上說的強。

Tips: 如果您覺得把每個路由地址都寫在基礎庫的 RouterHub 中, 太麻煩了, 也可以在每個組件內部建立一個私有 RouterHub, 將不需要跨組件的路由地址放入私有 RouterHub 中管理, 只將需要跨組件的路由地址放入基礎庫的公有 RouterHub 中管理, 如果您不需要集中管理所有路由地址的話, 這也是比較推薦的一種方式。

ARouter路由原理:

ARouter維護了一個路由表Warehouse,其中保存着全部的模塊跳轉關係,ARouter路由跳轉實際上還是調用了startActivity的跳轉,使用了原生的Framework機制,只是通過apt註解的形式製造出跳轉規則,並人爲地攔截跳轉和設置跳轉條件。

多模塊開發的時候不同的負責人可能會引入重複資源,相同的字符串,相同的icon等但是文件名並不一樣,怎樣去重?

3、gradle

gradle熟悉麼,自動打包知道麼?

如何加快 Gradle 的編譯速度?

Gradle的Flavor能否配置sourceset?

Gradle生命週期

4、編譯插樁

談談你對AOP技術的理解?

說說你瞭解的編譯插樁技術?

五、架構設計

MVC MVP MVVM原理和區別?

架構設計的目的

通過設計是模塊程序化,從而做到高內聚低耦合,讓開發者能更專注於功能實現本身,提供程序開發效率、更容易進行測試、維護和定位問題等等。而且,不同的規模的項目應該選用不同的架構設計。

MVC

MVC是模型(model)-視圖(view)-控制器(controller)的縮寫,其中M層處理數據,業務邏輯等;V層處理界面的顯示結果;C層起到橋樑的作用,來控制V層和M層通信以此來達到分離視圖顯示和業務邏輯層。在Android中的MVC劃分是這樣的:

  • 視圖層(View):一般採用XML文件進行界面的描述,也可以在界面中使用動態佈局的方式。
  • 控制層(Controller):由Activity承擔。
  • 模型層(Model):數據庫的操作、對網絡等的操作,複雜業務計算等等。
MVC缺點

在Android開發中,Activity並不是一個標準的MVC模式中的Controller,它的首要職責是加載應用的佈局和初始化用戶界面,並接受和處理來自用戶的操作請求,進而作出響應。隨着界面及其邏輯的複雜度不斷提升,Activity類的職責不斷增加,以致變得龐大臃腫。

MVP

MVP框架由3部分組成:View負責顯示,Presenter負責邏輯處理,Model提供數據。

  • View:負責繪製UI元素、與用戶進行交互(在Android中體現爲Activity)。
  • Model:負責存儲、檢索、操縱數據(有時也實現一個Model interface用來降低耦合)。
  • Presenter:作爲View與Model交互的中間紐帶,處理與用戶交互的邏輯。
  • View interface:需要View實現的接口,View通過View interface與Presenter進行交互,降低耦合,方便使用MOCK對Presenter進行單元測試。

MVP的Presenter是框架的控制者,承擔了大量的邏輯操作,而MVC的Controller更多時候承擔一種轉發的作用。因此在App中引入MVP的原因,是爲了將此前在Activty中包含的大量邏輯操作放到控制層中,避免Activity的臃腫。

MVP與MVC的主要區別:
  • 1、(最主要區別)View與Model並不直接交互,而是通過與Presenter交互來與Model間接交互。而在MVC中View可以與Model直接交互。
  • 2、Presenter與View的交互是通過接口來進行的,更有利於添加單元測試。
MVP的優點
  • 1、模型與視圖完全分離,我們可以修改視圖而不影響模型。
  • 2、可以更高效地使用模型,因爲所有的交互都發生在一個地方——Presenter內部。
  • 3、我們可以將一個Presenter用於多個視圖,而不需要改變Presenter的邏輯。這個特性非常的有用,因爲視圖的變化總是比模型的變化頻繁。
  • 4、如果我們把邏輯放在Presenter中,那麼我們就可以脫離用戶接口來測試這些邏輯(單元測試)。

UI層一般包括Activity,Fragment,Adapter等直接和UI相關的類,UI層的Activity在啓動之後實例化相應的Presenter,App的控制權後移,由UI轉移到Presenter,兩者之間的通信通過BroadCast、Handler、事件總線機制或者接口完成,只傳遞事件和結果。

MVP的執行流程:首先V層通知P層用戶發起了一個網絡請求,P層會決定使用負責網絡相關的M層去發起請求網絡,最後,P層將完成的結果更新到V層。

MVP的變種:Passive View

View直接依賴Presenter,但是Presenter間接依賴View,它直接依賴的是View實現的接口。相對於View的被動,那Presenter就是主動的一方。對於Presenter的主動,有如下的理解:

  • Presenter是整個MVP體系的控制中心,而不是單純的處理View請求的人。
  • View僅僅是用戶交互請求的彙報者,對於響應用戶交互相關的邏輯和流程,View不參與決策,真正的決策者是Presenter。
  • View向Presenter發送用戶交互請求應該採用這樣的口吻:“我現在將用戶交互請求發送給你,你看着辦,需要我的時候我會協助你”。
  • 對於綁定到View上的數據,不應該是View從Presenter上“拉”回來的,應該是Presenter主動“推”給View的。(這裏借鑑了IOC做法)
  • View儘可能不維護數據狀態,因爲其本身僅僅實現單純的、獨立的UI操作;Presenter纔是整個體系的協調者,它根據處理用於交互的邏輯給View和Model安排工作。
MVP架構存在的問題與解決辦法
  • 1、加入模板方法

將邏輯操作從V層轉移到P層後,可能有一些Activity還是比較膨脹,此時,可以通過繼承BaseActivity的方式加入模板方法。注意,最好不要超過3層繼承。

  • 2、Model內部分層

模型層(Model)中的整體代碼量是最大的,此時可以進行模塊的劃分和接口隔離。

  • 3、使用中介者和代理

在UI層和Presenter之間設置中介者Mediator,將例如數據校驗、組裝在內的輕量級邏輯操作放在Mediator中;在Presenter和Model之間使用代理Proxy;通過上述兩者分擔一部分Presenter的邏輯操作,但整體框架的控制權還是在Presenter手中。

MVVM

MVVM可以算是MVP的升級版,其中的VM是ViewModel的縮寫,ViewModel可以理解成是View的數據模型和Presenter的合體,ViewModel和View之間的交互通過Data Binding完成,而Data Binding可以實現雙向的交互,這就使得視圖和控制層之間的耦合程度進一步降低,關注點分離更爲徹底,同時減輕了Activity的壓力。

MVC->MVP->MVVM演進過程

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,實現用戶的交互操作以及變更通知。

  • Controller:接收View的命令,對Model進行操作,一個Controller可以對應多個View。
  • Presenter:Presenter與Controller一樣,接收View的命令,對Model進行操作;與Controller不同的是Presenter會反作用於View,Model的變更通知首先被Presenter獲得,然後Presenter再去更新View。通常一個Presenter只對應於一個View。據Presenter和View對邏輯代碼分擔的程度不同,這種模式又有兩種情況:普通的MVP模式和Passive View模式。
  • ViewModel:注意這裏的“Model”指的是View的Model,跟MVVM中的一個Model不是一回事。所謂View的Model就是包含View的一些數據屬性和操作的這麼一個東東,這種模式的關鍵技術就是數據綁定(data binding),View的變化會直接影響ViewModel,ViewModel的變化或者內容也會直接體現在View上。這種模式實際上是框架替應用開發者做了一些工作,開發者只需要較少的代碼就能實現比較複雜的交互。
補充:基於AOP的架構設計

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文件裏,但靠一次開發過程就寫出高質量的代碼是很困難的,趁着項目的間歇期,對代碼進行重構很有必要。

最後的建議

如果“從零開始”,用什麼設計架構的問題屬於想得太多做得太少的問題。 從零開始意味着一個項目的主要技術難點是基本功能實現。當每一個功能都需要考慮如何做到的時候,我覺得一般人都沒辦法考慮如何做好。 因爲,所有的優化都是站在最上層進行統籌規劃。在這之前,你必須對下層的每一個模塊都非常熟悉,進而提煉可複用的代碼、規劃邏輯流程。

MVC的情況下怎麼把Activity的C和V抽離?

MVP 架構中 Presenter 定義爲接口有什麼好處;

MVP如何管理Presenter的生命週期,何時取消網絡請求?

aop思想

Fragment如果在Adapter中使用應該如何解耦?

項目框架裏有沒有Base類,BaseActivity和BaseFragment這種封裝導致的問題,以及解決方法?

設計一個音樂播放界面,你會如何實現,用到那些類,如何設計,如何定義接口,如何與後臺交互,如何緩存與下載,如何優化(15分鐘時間)

從0設計一款App整體架構,如何去做?

說一款你認爲當前比較火的應用並設計(比如:直播APP,P2P金融,小視頻等)

實現一個庫,完成日誌的實時上報和延遲上報兩種功能,該從哪些方面考慮?

你最優秀的工程設計項目,是怎麼設計和實現的;擴展,如何做成一個平臺級產品?

六、其它高頻面試題

1、如何保證一個後臺服務不被殺死?(相同問題:如何保證service在後臺不被kill?)比較省電的方式是什麼?

保活方案

1、AIDL方式單進程、雙進程方式保活Service。(基於onStartCommand() return START_STICKY)

START_STICKY 在運行onStartCommand後service進程被kill後,那將保留在開始狀態,但是不保留那些傳入的intent。不久後service就會再次嘗試重新創建,因爲保留在開始狀態,在創建 service後將保證調用onstartCommand。如果沒有傳遞任何開始命令給service,那將獲取到null的intent。

除了華爲此方案無效以及未更改底層的廠商不起作用外(START_STICKY字段就可以保持Service不被殺)。此方案可以與其他方案混合使用

2、降低oom_adj的值(提升service進程優先級):

Android中的進程是託管的,當系統進程空間緊張的時候,會依照優先級自動進行進程的回收。Android將進程分爲6個等級,它們按優先級順序由高到低依次是:

  • 1.前臺進程 (Foreground process)
  • 2.可見進程 (Visible process)
  • 3.服務進程 (Service process)
  • 4.後臺進程 (Background process)
  • 5.空進程 (Empty process)

當service運行在低內存的環境時,將會kill掉一些存在的進程。因此進程的優先級將會很重要,可以使用startForeground 將service放到前臺狀態。這樣在低內存時被kill的機率會低一些。

  • 常駐通知欄(可通過啓動另外一個服務關閉Notification,不對oom_adj值有影響)。

  • 使用”1像素“的Activity覆蓋在getWindow()的view上。

此方案無效果

  • 循環播放無聲音頻(黑科技,7.0下殺不掉)。

成功對華爲手機保活。小米8下也成功突破20分鐘

  • 3、監聽鎖屏廣播:使Activity始終保持前臺。
  • 4、使用自定義鎖屏界面:覆蓋了系統鎖屏界面。
  • 5、通過android:process屬性來爲Service創建一個進程。
  • 6、跳轉到系統白名單界面讓用戶自己添加app進入白名單。

復活方案

1、onDestroy方法裏重啓service

service + broadcast 方式,就是當service走onDestory的時候,發送一個自定義的廣播,當收到廣播的時候,重新啓動service。

2、JobScheduler:原理類似定時器,5.0,5.1,6.0作用很大,7.0時候有一定影響(可以在電源管理中給APP授權)。

只對5.0,5.1、6.0起作用。

3、推送互相喚醒復活:極光、友盟、以及各大廠商的推送。

4、同派系APP廣播互相喚醒:比如今日頭條系、阿里系。

此外還可以監聽系統廣播判斷Service狀態,通過系統的一些廣播,比如:手機重啓、界面喚醒、應用狀態改變等等監聽並捕獲到,然後判斷我們的Service是否還存活。

結論:高版本情況下可以使用彈出通知欄、雙進程、無聲音樂提高後臺服務的保活概率。

2、Android動畫框架實現原理。

Animation 框架定義了透明度,旋轉,縮放和位移幾種常見的動畫,而且控制的是整個View。實現原理:

每次繪製視圖時,View 所在的 ViewGroup 中的 drawChild 函數獲取該View 的 Animation 的 Transformation 值,然後調用canvas.concat(transformToApply.getMatrix()),通過矩陣運算完成動畫幀,如果動畫沒有完成,繼續調用 invalidate() 函數,啓動下次繪製來驅動動畫,動畫過程中的幀之間間隙時間是繪製函數所消耗的時間,可能會導致動畫消耗比較多的CPU資源,最重要的是,動畫改變的只是顯示,並不能響應事件。

3、Activity-Window-View三者的差別?

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()。

參考文章

4、低版本SDK如何實現高版本api?

  • 1、在使用了高版本API的方法前面加一個 @TargetApi(API號)。
  • 2、在代碼上用版本判斷來控制不同版本使用不同的代碼。

5、說說你對Context的理解?

6、Android的生命週期和啓動模式

由A啓動B Activity,A爲棧內複用模式,B爲標準模式,然後再次啓動A或者殺死B,說說A,B的生命週期變化,爲什麼?

Activity的啓動模式有哪些?棧裏是A-B-C,先想直接到A,BC都清理掉,有幾種方法可以做到?這幾種方法產生的結果是有幾個A的實例?

7、ListView和RecyclerView系列

RecyclerView和ListView有什麼區別?局部刷新?前者使用時多重type場景下怎麼避免滑動卡頓。懶加載怎麼實現,怎麼優化滑動體驗。

ListView、RecyclerView區別?

一、使用方面:

ListView的基礎使用:

  • 繼承重寫 BaseAdapter 類
  • 自定義 ViewHolder 和 convertView 一起完成複用優化工作

RecyclerView 基礎使用關鍵點同樣有兩點:

  • 繼承重寫 RecyclerView.Adapter 和 RecyclerView.ViewHolder
  • 設置佈局管理器,控制佈局效果

RecyclerView 相比 ListView 在基礎使用上的區別主要有如下幾點:

  • ViewHolder 的編寫規範化了
  • RecyclerView 複用 Item 的工作 Google 全幫你搞定,不再需要像 ListView 那樣自己調用 setTag
  • RecyclerView 需要多出一步 LayoutManager 的設置工作

二、佈局方面:

RecyclerView 支持 線性佈局、網格佈局、瀑布流佈局 三種,而且同時還能夠控制橫向還是縱向滾動。

三、API提供方面:

ListView 提供了 setEmptyView ,addFooterView 、 addHeaderView.

RecyclerView 供了 notifyItemChanged 用於更新單個 Item View 的刷新,我們可以省去自己寫局部更新的工作。

四、動畫效果:

RecyclerView 在做局部刷新的時候有一個漸變的動畫效果。繼承 RecyclerView.ItemAnimator 類,並實現相應的方法,再調用 RecyclerView的 setItemAnimator(RecyclerView.ItemAnimator animator) 方法設置完即可實現自定義的動畫效果。

五、監聽 Item 的事件:

ListView 提供了單擊、長按、選中某個 Item 的監聽設置。

RecyclerView與ListView緩存機制的不同

想改變listview的高度,怎麼做?

listview跟recyclerview上拉加載的時候分別應該如何處理?

如何自己實現RecyclerView的側滑刪除?

RecyclerView的ItemTouchHelper的實現原理

8、如何實現一個推送,消息推送原理?推送到達率的問題?

一:客戶端不斷的查詢服務器,檢索新內容,也就是所謂的pull 或者輪詢方式。

二:客戶端和服務器之間維持一個TCP/IP長連接,服務器向客戶端push。

blog.csdn.net/clh604/arti…

www.jianshu.com/p/45202dcd5…

9、動態權限系列。

動態權限適配方案,權限組的概念

Runtime permission,如何把一個預置的app默認給它權限?不要授權。

10、自定義View系列。

Canvas的底層機制,繪製框架,硬件加速是什麼原理,canvas lock的緩衝區是怎麼回事?

雙指縮放拖動大圖

TabLayout中如何讓當前標籤永遠位於屏幕中間

TabLayout如何設置指示器的寬度包裹內容?

自定義View如何考慮機型適配?

  • 合理使用warp_content,match_parent。
  • 儘可能地使用RelativeLayout。
  • 針對不同的機型,使用不同的佈局文件放在對應的目錄下,android會自動匹配。
  • 儘量使用點9圖片。
  • 使用與密度無關的像素單位dp,sp。
  • 引入android的百分比佈局。
  • 切圖的時候切大分辨率的圖,應用到佈局當中,在小分辨率的手機上也會有很好的顯示效果。

11、對谷歌新推出的Room架構。

12、沒有給權限如何定位,特定機型定位失敗,如何解決?

13、Debug跟Release的APK的區別?

14、android文件存儲,各版本存儲位置的權限控制的演進,外部存儲,內部存儲

15、有什麼提高編譯速度的方法?

16、Scroller原理。

Scroller執行流程裏面的三個核心方法

mScroller.startScroll();
mScroller.computeScrollOffset();
view.computeScroll();
複製代碼

1、在mScroller.startScroll()中爲滑動做了一些初始化準備,比如:起始座標,滑動的距離和方向以及持續時間(有默認值),動畫開始時間等。

2、mScroller.computeScrollOffset()方法主要是根據當前已經消逝的時間來計算當前的座標點。因爲在mScroller.startScroll()中設置了動畫時間,那麼在computeScrollOffset()方法中依據已經消逝的時間就很容易得到當前時刻應該所處的位置並將其保存在變量mCurrX和mCurrY中。除此之外該方法還可判斷動畫是否已經結束。

17、Hybrid系列。

webwiew瞭解?怎麼實現和javascript的通信?相互雙方的通信。@JavascriptInterface在?版本有bug,除了這個還有其他調用android方法的方案嗎?

Android中Java和JavaScript交互
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>";
    }
複製代碼

18、如果在當前線程內使用Handler postdelayed 兩個消息,一個延遲5s,一個延遲10s,然後使當前線程sleep 5秒,以上消息的執行時間會如何變化?

答:照常執行

擴展:sleep時間<=5 對兩個消息無影響,5< sleep時間 <=10 對第一個消息有影響,第一個消息會延遲到sleep後執行,sleep時間>10 對兩個時間都有影響,都會延遲到sleep後執行。

19、Android中進程內存的分配,能不能自己分配定額內存?

20、下拉狀態欄是不是影響activity的生命週期,如果在onStop的時候做了網絡請求,onResume的時候怎麼恢復

21、Android長連接,怎麼處理心跳機制。

長連接:長連接是建立連接之後, 不主動斷開. 雙方互相發送數據, 發完了也不主動斷開連接, 之後有需要發送的數據就繼續通過這個連接發送.

心跳包:其實主要是爲了防止NAT超時,客戶端隔一段時間就主動發一個數據,探測連接是否斷開。

服務器處理心跳包:假如客戶端心跳間隔是固定的, 那麼服務器在連接閒置超過這個時間還沒收到心跳時, 可以認爲對方掉線, 關閉連接. 如果客戶端心跳會動態改變, 應當設置一個最大值, 超過這個最大值才認爲對方掉線. 還有一種情況就是服務器通過TCP連接主動給客戶端發消息出現寫超時, 可以直接認爲對方掉線.

22、CrashHandler實現原理?

獲取app crash的信息保存在本地然後在下一次打開app的時候發送到服務器。
複製代碼

23、SurfaceView和View的最本質的區別?

SurfaceView是在一個新起的單獨線程中可以重新繪製畫面,而view必須在UI的主線程中更新畫面。

在UI的主線程中更新畫面可能會引發問題,比如你更新的時間過長,那麼你的主UI線程就會被你正在畫的函數阻塞。那麼將無法響應按鍵、觸屏等消息。當使用SurfaceView由於是在新的線程中更新畫面所以不會阻塞你的UI主線程。但這也帶來了另外一個問題,就是事件同步。比如你觸屏了一下,你需要在SurfaceView中的thread處理,一般就需要有一個event queue的設計來保存touchevent,這會稍稍複雜一點,因爲涉及到線程安全。

24、Android程序運行時權限與文件系統權限

1、Linux 文件系統權限。不同的用戶對文件有不同的讀寫執行權限。在android系統中,system和應用程序是分開的,system裏的數據是不可更改的。

2、Android中有3種權限,進程權限UserID,簽名,應用申明權限。每次安裝時,系統根據包名爲應用分配唯一的userID,不同的userID運行在不同的進程裏,進程間的內存是獨立的,不可以相互訪問,除非通過特定的Binder機制。

Android提供瞭如下的一種機制,可以使兩個apk打破前面講的這種壁壘。

在AndroidManifest.xml中利用sharedUserId屬性給不同的package分配相同的userID,通過這樣做,兩個package可以被當做同一個程序,系統會分配給兩個程序相同的UserID。當然,基於安全考慮,兩個package需要有相同的簽名,否則沒有驗證也就沒有意義了。

25、曲面屏的適配。

26、TextView調用setText方法的內部執行流程。

27、怎麼控制另外一個進程的View顯示(RemoteView)?

28、如何實現右滑finish activity?

29、如何在整個系統層面實現界面的圓角效果。(即所有的APP打開界面都會是圓角)

30、非UI線程可以更新UI嗎?

可以,當訪問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。

31、如何解決git衝突?

32、單元測試有沒有做過,說說熟悉的單元測試框架?

首先,Android測試主要分爲三個方面:

  • 單元測試(Junit4、Mockito、PowerMockito、Robolectric)
  • UI測試(Espresso、UI Automator)
  • 壓力測試(Monkey)

WanAndroid項目和XXX項目中使用用到了單元測試和部分自動化UI測試,其中單元測試使用的是Junit4+Mockito+PowerMockito+Robolectric。下面我分別簡單介紹下這些測試框架:

1、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有一些差異。

2、Mockito:

可以使用mock()方法模擬各種各樣的對象,以替代真正的對象做出希望的響應。除此之外,它還有很多驗證方法調用的方式如Mockit.when(調用方法).thenReturn(驗證的返回值)、verfiy(模擬對象).驗證方法等等。

這裏有一點要補充下:簡單的測試會使整體的代碼更簡潔,更可讀、更可維護。如果你不能把測試寫的很簡單,那麼請在測試時重構你的代碼。

最後,對於Mockito來說,它的優點是有各種各樣的方式去驗證"模仿對象"的互動或驗證發生的某些行爲。而它的缺點就是不支持mock匿名類、final類、static方法private方法。

3、PowerMockito:

因此,爲了解決Mockito的缺陷,PoweMockito出現了,它擴展了Mockito,支持mock匿名類、final類、static方法、private方法。只要使用它提供的api如PowerMockito.mockStatic()去mock含靜態方法或字段的類,PowerMockito.suppress(PowerMockito.method(類.class, 方法名)即可。

4、Robolectric

前面3種我們說的都是Java相關的單元測試方法,如果想在Java單元測試裏面進行Android單元測試,還得使用Robolectric,它提供了一套能運行在JVM的Android代碼。它提供了一系列類似ShadowToast.getLatestToast()、ShadowApplication.getInstance()這種方式來獲取Android平臺對應的對象。可以看到它的優點就是支持大部分Android平臺依賴類的底層引用與模擬。缺點就是在異步測試的情況下有些問題,這是可以結合Mockito來將異步轉爲同步即可解決。

最後,自動化UI測試項目中我使用的是Expresso,它提供了一系列類似onView().check().perform()的方式來實現點擊、滑動、檢測頁面顯示等自動化的UI測試效果,這裏在我的WanAndroid項目下的BasePageTest基類裏面封裝了一系列通用的方法,有興趣可以去看看。

33、Jenkins持續集成。

34、工作中有沒有用過或者寫過什麼工具?腳本,插件等等;比如:多人協同開發可能對一些相同資源都各自放了一份,有沒有方法自動檢測這種重複之類的。

35、如何繞過9.0限制?

如何限制?

  • 1、阻止java反射和JNI。
  • 2、當獲取方法或Field時進行檢測。
  • 3、怎麼檢測?

區分出是系統調用還是開發者調用:

根據堆棧,回溯Class,查看ClassLoader是否是BootStrapClassLoader。

區分後,再區分是否是hidden api:

Method,Field都有access_flag,有一些備用字段,hidden信息存儲其中。

如何繞過?

1、不用反射:

利用一個fakelib,例如寫一個android.app.ActivityThread#currentActivityThread空實現,直接調用;

2、僞裝系統調用:

jni修改一個class的classloder爲BootStrapClassLoader,麻煩。

利用系統方法去反射:

利用原反射,即:getDeclaredMethod這個方法是系統的方法,通過getDeclaredmethod反射去執行hidden api。

3、修改Method,Field中存儲hidden信息的字段:

利用jni去修改。

36、對文件描述符怎麼理解?

37、如何實現進程安全寫文件?

其它面試題


其他擴展面試題

一、Kotlin (⭐⭐)

1、Kotlin 特性,和 Java 相比有什麼不同的地方?

  • 能直接與Java相互調用,能與Java工程共存
  • 大大減少樣板代碼
  • 可以將Kotlin代碼編譯爲無需虛擬機就可運行的原生二進制文件
  • 支持協程
  • 支持高階函數
  • 語言層面解決空指針問題
  • 對字符串格式化的處理($變量名)
  • 更像Python的語法
  • 對λ表達式支持更好

mp.weixin.qq.com/s/FqXLNz5p9…

2、Kotlin爲什麼能和Java混編?

3、什麼是協程?

二、大前端 (⭐⭐)

1、Hybrid通信原理是什麼,有做研究嗎?

2、JS的交互理解嗎?平時工作用的多嗎,項目中是怎麼與Web交互的?

Android通過WebView調用JS代碼:

1、通過WebView的loadUrl():

  • 設置與Js交互的權限:

    webSettings.setJavaScriptEnabled(true)

  • 設置允許JS彈窗:

    webSettings.setJavaScriptCanOpenWindowsAutomatically(true)

  • 載入JS代碼:

    mWebView.loadUrl("file:///android_asset/javascript.html")

  • webview只是載體,內容的渲染需要使用webviewChromClient類去實現,通過設置WebChromeClient對象處理JavaScript的對話框。

特別注意:

JS代碼調用一定要在 onPageFinished() 回調之後才能調用,否則不會調用。

2、通過WebView的evaluateJavascript():

  • 該方法比第一種方法效率更高、使用更簡潔,因爲該方法的執行不會使頁面刷新,而第一種方法(loadUrl )的執行則會。
  • Android 4.4 後纔可使用。

只需要將第一種方法的loadUrl()換成evaluateJavascript()即可,通過onReceiveValue()回調接收返回值。

建議:兩種方法混合使用,即Android 4.4以下使用方法1,Android 4.4以上方法2。

JS通過WebView調用 Android 代碼:

1、通過 WebView的addJavascriptInterface()進行對象映射:

-定義一個與JS對象映射關係的Android類:AndroidtoJs:

  • 定義JS需要調用的方法,被JS調用的方法必須加入@JavascriptInterface註解。
  • 通過addJavascriptInterface()將Java對象映射到JS對象。

優點:使用簡單,僅將Android對象和JS對象映射即可。

缺點:addJavascriptInterface 接口引起遠程代碼執行漏洞,漏洞產生原因是:

當JS拿到Android這個對象後,就可以調用這個Android對象中所有的方法,包括系統類(java.lang.Runtime 類),從而進行任意代碼執行。

2、通過 WebViewClient 的方法shouldOverrideUrlLoading ()回調攔截 url:

  • Android通過 WebViewClient 的回調方法shouldOverrideUrlLoading ()攔截 url。

  • 解析該 url 的協議。

  • 如果檢測到是預先約定好的協議,就調用相應方法。

    根據協議的參數,判斷是否是所需要的url。 一般根據scheme(協議格式) & authority(協議名)判斷(前兩個參數)。

優點:不存在方式1的漏洞;

缺點:JS獲取Android方法的返回值複雜,如果JS想要得到Android方法的返回值,只能通過 WebView 的 loadUrl ()去執行 JS 方法把返回值傳遞回去。

3、通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調攔截JS對話框alert()、confirm()、prompt() 消息:

原理:

Android通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調分別攔截JS對話框 (警告框、確認框、輸入框),得到他們的消息內容,然後解析即可。

常用的攔截是:攔截 JS的輸入框(即prompt()方法),因爲只有prompt()可以返回任意類型的值,操作最全面方便、更加靈活;而alert()對話框沒有返回值;confirm()對話框只能返回兩種狀態(確定 / 取消)兩個值。

Android:你要的WebView與 JS 交互方式 都在這裏了

3、react native有多少了解?講一下原理。

4、weex瞭解嗎?如何自己實現類似技術?

5、flutter瞭解嗎?內部是如何實現跨平臺的?如何實現多Native頁面接入?如何實現對現有工程的flutter遷移?

6、Dart語言有研究過嗎?

7、快應用瞭解嗎?跟其她方式相比有什麼優缺點?

8、說說你用過的混合開發技術有哪些?各有什麼優缺點?

三、腳本語言 (⭐⭐)

1、腳本語言會嗎?

2、Python會嗎?

Python基礎

人工智能瞭解

3、Gradle瞭解多少?groovy語法會嗎?

非技術面試題

一、高頻題集 (⭐⭐⭐)

1、你覺得安卓開發最關鍵的技術在哪裏?

技術是沒有止境的,所以肯定會不斷有演進和難點。

一. 底層和框架如何更好地設計及優化以適應業務的高速增長。說起來很簡單,低耦合高擴展,做起來是需要長期經驗積累。

二. 我拋幾個細節難點:

  • 插件化如何使插件的 Manifest 生效
  • H5 容器如何更好地優化和兼容
  • App 端優化,這是個沒止境的話題,網絡、圖片、動畫、內存、電量等等隨着優化的加深,你會發現不能侷限在客戶端,服務端也需要深入。
  • SPDY 的優點併入 HTTP 2.0 你們有在測試或用嗎?
  • Fresco 出來前你是不是覺得圖片緩存已經到頭了?
  • Android App 爲什麼整體流暢性總是被詬病?……

三. 如果你覺得沒有難點或者難點在兼容、UI 之類問題上,那麼可能兩個原因:

  • 公司業務發展過慢,對技術的需求不夠迫切
  • 個人長時間在業務開發上,這個對於走技術路線的人來說挺麻煩的,不主動去接觸學習的話,n 年以後也還是這個樣子爲了更好的個人成長,這兩點都是需要注意和解決的問題。

2、你還要什麼瞭解和要問的嗎?

你在公司的一天是如何度過的?

能否給我簡單介紹下貴公司業務與戰略的未來發展?

貴公司最讓你自豪的企業文化是什麼?(適合大公司)

團隊、公司現在面臨的最大挑戰是什麼?

對於未來加入這個團隊,你對我的期望是什麼?

您覺得我哪方面知識需要深入學習或者我的不足在那些方面,今後我該注意什麼*?

你還可以問下項目團隊多少人,主要以什麼方向爲主,一年內的目標怎樣,團隊氣氛怎樣,等內容着手。

3、研究比較深入的領域有哪些?

4、自己最擅長的技術點,最感興趣的技術領域和技術?

5、項目中用了哪些開源庫,如何避免因爲引入開源庫而導致的安全性和穩定性問題?

6、說下你都看過那些技術書籍,你是如何自學的。你覺得自己的優勢與弱點是什麼。

7、說下項目中遇到的棘手問題,包括技術,交際和溝通。

8、說下你近幾年的規劃?

9、對加班怎麼看(不要太浮誇,現實一點哦)?

10、介紹你做過的哪些項目。

11、你並非畢業於名牌院校?

12、爲什麼要離職?

13、當你的開發任務很緊張,你怎麼去做代碼優化的?

二、次高頻題集 (⭐⭐)

1、對業內信息的關注渠道有哪些?

2、最近都讀哪些書?

3、給你一個項目,你怎麼看待他的市場和技術的關係?

4、你以往的項目中,以你現在的眼光去評價項目的利弊?

5、對於非立項(KPI)項目,怎麼推進?

6、都使用過哪些自定義控件?

7、除了簡歷上的工作經歷,您還會去關注哪些領域?

8、評價下自己,評價下自己的技術水平,個人代碼量如何?

9、你朋友對你的評價?

10、自己的優點和缺點是什麼?並舉例說明?

11、你覺得你個性上最大的優點是什麼?

12、說說你最大的缺點?

13、最能概括你自己的三個詞是什麼?

14、說說你的家庭?

15、除了本公司外,還應聘了哪些公司?(類似問題:當前的offer狀況)

16、通過哪些渠道瞭解的招聘信息?

17、你的業餘愛好是什麼?

18、你做過的哪件事最令自己感到驕傲?

19、談談你對跳槽的看法?

20、怎樣看待學歷和能力?

21、您跟您的主管或直接上司有沒有針對以上離職原因的這些問題溝通過?如果沒有請說明原因。如果有請說一下過程和結果?

22、您覺得你關注的這些領域跟您目前從事的職業有哪些利弊關係?如果有請說明利弊關係?

23、您在選擇工作中更看重的是什麼?(可能是成長空間、培訓機會、發揮平臺、薪酬等答案)

24、您可不可以說說您在薪酬方面的心裏預期?

25、有人說掙未來比掙錢更爲重要,您怎麼理解?

26、假設,某一天,在工作辦公室走廊,您和一位同事正在抱怨上級陳某平時做事缺乏公平性,恰巧被陳某聽到,您會怎麼辦?

27、怎麼樣處理工作和生活的關係?怎麼處理在工作中遇到困難?請舉例說明

28、在您的現實生活中,您最不喜歡和什麼樣的人共事?爲什麼?舉例說明。

29、在您認識的人中,有沒有人不喜歡您?爲什麼不喜歡您?請舉例說明。

30、當老闆/上司/同事/客戶誤會你,你會怎麼辦?

31、當你發現其他部門的工作疏漏已經影響到您的工作績效時,您怎麼辦?

32、您希望在什麼樣的領導下工作?

33、我們工作與生活歷程並不是一帆風順的,談談您的工作或生活中出現的挫折或低潮期,您如何克服?

34、假如您的上司是一個非常嚴厲、領導手腕強硬,時常給您巨大壓力的人,您覺得這種領導方式對您有何利、弊?

35、您的領導給您佈置了一項您以前從未觸及過的任務,您打算如何去完成它?(如果有類似的經歷說說完成的經歷。)

36、談談您以往職業生涯中最有壓力的一、兩件事,並說說是如何克服的。

37、談談您以往職業生涯中令您有成就感的一、兩件事,並說說它給您的啓示。

38、請您舉一個例子,說明在完成一項重要任務時,您是怎樣和他人進行有效合作的。

39、當你要犧牲自己的某些方面與他人共事時,你會怎麼辦?

40、有時團隊成員不能有效共事,當遇到這種問題時你是怎麼處理的?你又是如何改善這類情況的?

41、我們有時不得不與自己不喜歡的人在一個團隊工作,如果遇到這樣的情況你會怎麼辦?

42、您對委任的任務完成不了時如何處理?

43、說說您對下屬佈置的任務在時間方面是如何要求的?

44、說說您在完成上司佈置的任務時,在時間方面是如何要求自己的?

45、您以往在領導崗位中,一個月內分別有哪些主要的工作任務?

46、當您發現您的部屬目前士氣較低沉,您一般從哪些方面去調動?

47、說說您在以往領導崗位中出現管理失控的事例及事後的原因分析。您的部屬在一個專業的問題上跟您發生爭議,您如何對待這種事件?

48、你對某某某互聯網發生事情的看法?(直播答題等等)

49、怎麼看待前端和後端?

結語

其實,在面試中,很多題目並沒有真正的答案,你能回答到什麼樣的深度,還是得靠自己真正的去使用和研究。知識的深度和廣度都很重要,所以都需要花相應的時間去學習。這樣,就算被問到自己不熟悉的領域也可以同面試官嘮嗑兩句,然後可以在適當的時機引向自己有深入研究的領域,讓面試官覺得你是這個領域的專家。

公衆號

我的公衆號 JsonChao 開通啦,如果您想第一時間獲取最新文章和最新動態,歡迎掃描關注~

讚賞

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


Contanct Me

● 微信 && 微信羣:

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

● QQ羣:

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

About me

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

希望我們能成爲朋友,在 Github掘金上一起分享知識。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章