來源:http://www.lxweimin.com/p/cf5092fa2694
上次寫這篇文章的時候也差不多是一年前了,這一年我兜兜轉轉從android到java又回到android,校招面了很多大廠,阿里、京東、小米、頭條、知乎、騰訊、有贊,也收獲了幾個offer。感謝大家的關注,讓我在簡書上面也混到了一個簡書程序員優秀作者的稱號,所以為了回饋大家,一篇最完全的android面經誕生了。這是我集合了牛客網、百度、簡書等網站的幾十篇面經和我自己面試的經歷的合集,希望大家喜歡。(ps:里面當然會有紕漏,如果有問題歡迎大家留言或者加我QQ討論)
1.android事件分發機制,請詳細說下整個流程
image
2.android view繪制機制和加載過程,請詳細說下整個流程
- 1.ViewRootImpl會調用performTraversals(),其內部會調用performMeasure()、performLayout、performDraw()。
- 2.performMeasure()會調用最外層的ViewGroup的measure()-->onMeasure(),ViewGroup的onMeasure()是抽象方法,但其提供了measureChildren(),這之中會遍歷子View然后循環調用measureChild()這之中會用getChildMeasureSpec()+父View的MeasureSpec+子View的LayoutParam一起獲取本View的MeasureSpec,然后調用子View的measure()到View的onMeasure()-->setMeasureDimension(getDefaultSize(),getDefaultSize()),getDefaultSize()默認返回measureSpec的測量數值,所以繼承View進行自定義的wrap_content需要重寫。
- 3.performLayout()會調用最外層的ViewGroup的layout(l,t,r,b),本View在其中使用setFrame()設置本View的四個頂點位置。在onLayout(抽象方法)中確定子View的位置,如LinearLayout會遍歷子View,循環調用setChildFrame()-->子View.layout()。
- 4.performDraw()會調用最外層ViewGroup的draw():其中會先后調用background.draw()(繪制背景)、onDraw()(繪制自己)、dispatchDraw()(繪制子View)、onDrawScrollBars()(繪制裝飾)。
- 5.MeasureSpec由2位SpecMode(UNSPECIFIED、EXACTLY(對應精確值和match_parent)、AT_MOST(對應warp_content))和30位SpecSize組成一個int,DecorView的MeasureSpec由窗口大小和其LayoutParams決定,其他View由父View的MeasureSpec和本View的LayoutParams決定。ViewGroup中有getChildMeasureSpec()來獲取子View的MeasureSpec。
- 6.三種方式獲取measure()后的寬高:
- 1.Activity#onWindowFocusChange()中調用獲取
- 2.view.post(Runnable)將獲取的代碼投遞到消息隊列的尾部。
- 3.ViewTreeObservable.
3.android四大組件的加載過程,請詳細介紹下
- 1.android四大組件的加載過程:這是我總結的一篇博客
4.Activity的啟動模式
- 1.standard:默認標準模式,每啟動一個都會創建一個實例,
- 2.singleTop:棧頂復用,如果在棧頂就調用onNewIntent復用,從onResume()開始
- 3.singleTask:棧內復用,本棧內只要用該類型Activity就會將其頂部的activity出棧
- 4.singleInstance:單例模式,除了3中特性,系統會單獨給該Activity創建一個棧,
5.A、B、C、D分別是四種Activity的啟動模式,那么A->B->C->D->A->B->C->D分別啟動,最后的activity棧是怎么樣的
- 1.這個題目需要深入了解activity的啟動模式
- 2.最后的答案是:兩個棧,前臺棧是只有D,后臺棧從底至上是A、B、C
6.Activity緩存方法
- 1.配置改變導致Activity被殺死,橫屏變豎屏:在onStop之前會調用onSaveInstanceState()保存數據在重建Activity之后,會在onStart()之后調用onRestoreInstanceState(),并把保存下來的Bundle傳給onCreate()和它會默認重建Activity當前的視圖,我們可以在onCreate()中,回復自己的數據。
- 2.內存不足殺掉Activity,優先級分別是:前臺可見,可見非前臺,后臺。
7.Service的生命周期,兩種啟動方法,有什么區別
- 1.context.startService() ->onCreate()- >onStart()->Service running-->(如果調用context.stopService() )->onDestroy() ->Service shut down
- 1.如果Service還沒有運行,則調用onCreate()然后調用onStart();
- 2.如果Service已經運行,則只調用onStart(),所以一個Service的onStart方法可能會重復調用多次。
- 3.調用stopService的時候直接onDestroy,
- 4.如果是調用者自己直接退出而沒有調用stopService的話,Service會一直在后臺運行。該Service的調用者再啟動起來后可以通過stopService關閉Service。
- 2.context.bindService()->onCreate()->onBind()->Service running-->onUnbind() -> onDestroy() ->Service stop
- 1.onBind將返回給客戶端一個IBind接口實例,IBind允許客戶端回調服務的方法,比如得到Service運行的狀態或其他操作。
- 2.這個時候會把調用者和Service綁定在一起,Context退出了,Service就會調用onUnbind->onDestroy相應退出。
- 3.所以調用bindService的生命周期為:onCreate --> onBind(只一次,不可多次綁定) --> onUnbind --> onDestory。
8.怎么保證service不被殺死
- 1.提升service優先級
- 2.提升service進程優先級
- 3.onDestroy方法里重啟service
9.靜態的Broadcast 和動態的有什么區別
- 1.動態的比靜態的安全
- 2.靜態在app啟動的時候就初始化了 動態使用代碼初始化
- 3.靜態需要配置 動態不需要
- 4.生存期,靜態廣播的生存期可以比動態廣播的長很多
- 5.優先級動態廣播的優先級比靜態廣播高
10.Intent可以傳遞哪些數據類型
- 1.Serializable
- 2.charsequence: 主要用來傳遞String,char等
- 3.parcelable
- 4.Bundle
11.Json有什么優劣勢、解析的原理
- 1.JSON的速度要遠遠快于XML
- 2.JSON相對于XML來講,數據的體積小
- 3.JSON對數據的描述性比XML較差
- 4.解析的基本原理是:詞法分析
12.一個語言的編譯過程
- 1.詞法分析:將一串文本按規則分割成最小的結構,關鍵字、標識符、運算符、界符和常量等。一般實現方法是自動機和正則表達式
- 2.語法分析:將一系列單詞組合成語法樹。一般實現方法有自頂向下和自底向上
- 3.語義分析:對結構上正確的源程序進行上下文有關性質的審查
- 4.目標代碼生成
- 5.代碼優化:優化生成的目標代碼,
13.動畫有哪幾類,各有什么特點
- 1.動畫的基本原理:其實就是利用插值器和估值器,來計算出各個時刻View的屬性,然后通過改變View的屬性來,實現View的動畫效果。
- 2.View動畫:只是影像變化,view的實際位置還在原來的地方。
- 3.幀動畫是在xml中定義好一系列圖片之后,使用AnimationDrawable來播放的動畫。
- 4.View的屬性動畫:
- 1.插值器:作用是根據時間的流逝的百分比來計算屬性改變的百分比
- 2.估值器:在1的基礎上由這個東西來計算出屬性到底變化了多少數值的類
14.Handler、Looper消息隊列模型,各部分的作用
- 1.MessageQueue:讀取會自動刪除消息,單鏈表維護,在插入和刪除上有優勢。在其next()中會無限循環,不斷判斷是否有消息,有就返回這條消息并移除。
- 2.Looper:Looper創建的時候會創建一個MessageQueue,調用loop()方法的時候消息循環開始,loop()也是一個死循環,會不斷調用messageQueue的next(),當有消息就處理,否則阻塞在messageQueue的next()中。當Looper的quit()被調用的時候會調用messageQueue的quit(),此時next()會返回null,然后loop()方法也跟著退出。
- 3.Handler:在主線程構造一個Handler,然后在其他線程調用sendMessage(),此時主線程的MessageQueue中會插入一條message,然后被Looper使用。
- 4.系統的主線程在ActivityThread的main()為入口開啟主線程,其中定義了內部類Activity.H定義了一系列消息類型,包含四大組件的啟動停止。
- 5.MessageQueue和Looper是一對一關系,Handler和Looper是多對一
15.怎樣退出終止App
- 1.自己設置一個Activity的棧,然后一個個finish()
16.Android IPC:Binder原理
- 1.在Activity和Service進行通訊的時候,用到了Binder。
- 1.當屬于同個進程我們可以繼承Binder然后在Activity中對Service進行操作
- 2.當不屬于同個進程,那么要用到AIDL讓系統給我們創建一個Binder,然后在Activity中對遠端的Service進行操作。
- 2.系統給我們生成的Binder:
- 1.Stub類中有:接口方法的id,有該Binder的標識,有asInterface(IBinder)(讓我們在Activity中獲取實現了Binder的接口,接口的實現在Service里,同進程時候返回Stub否則返回Proxy),有onTransact()這個方法是在不同進程的時候讓Proxy在Activity進行遠端調用實現Activity操作Service
- 2.Proxy類是代理,在Activity端,其中有:IBinder mRemote(這就是遠端的Binder),兩個接口的實現方法不過是代理最終還是要在遠端的onTransact()中進行實際操作。
- 3.哪一端的Binder是副本,該端就可以被另一端進行操作,因為Binder本體在定義的時候可以操作本端的東西。所以可以在Activity端傳入本端的Binder,讓Service端對其進行操作稱為Listener,可以用RemoteCallbackList這個容器來裝Listener,防止Listener因為經歷過序列化而產生的問題。
- 4.當Activity端向遠端進行調用的時候,當前線程會掛起,當方法處理完畢才會喚醒。
- 5.如果一個AIDL就用一個Service太奢侈,所以可以使用Binder池的方式,建立一個AIDL其中的方法是返回IBinder,然后根據方法中傳入的參數返回具體的AIDL。
- 6.IPC的方式有:Bundle(在Intent啟動的時候傳入,不過是一次性的),文件共享(對于SharedPreference是特例,因為其在內存中會有緩存),使用Messenger(其底層用的也是AIDL,同理要操作哪端,就在哪端定義Messenger),AIDL,ContentProvider(在本進程中繼承實現一個ContentProvider,在增刪改查方法中調用本進程的SQLite,在其他進程中查詢),Socket
17.描述一次跨進程通訊
- 1.client、proxy、serviceManager、BinderDriver、impl、service
- 2.client發起一個請求service信息的Binder請求到BinderDriver中,serviceManager發現BinderDiriver中有自己的請求 然后將clinet請求的service的數據返回給client這樣完成了一次Binder通訊
- 3.clinet獲取的service信息就是該service的proxy,此時調用proxy的方法,proxy將請求發送到BinderDriver中,此時service的 Binder線程池循環發現有自己的請求,然后用impl就處理這個請求最后返回,這樣完成了第二次Binder通訊
4.中間client可掛起,也可以不掛起,有一個關鍵字oneway可以解決這個
18.android重要術語解釋
- 1.ActivityManagerServices,簡稱AMS,服務端對象,負責系統中所有Activity的生命周期
- 2.ActivityThread,App的真正入口。當開啟App之后,會調用main()開始運行,開啟消息循環隊列,這就是傳說中的UI線程或者叫主線程。與ActivityManagerServices配合,一起完成Activity的管理工作
- 3.ApplicationThread,用來實現ActivityManagerService與ActivityThread之間的交互。在ActivityManagerService需要管理相關Application中的Activity的生命周期時,通過ApplicationThread的代理對象與ActivityThread通訊。
- 4.ApplicationThreadProxy,是ApplicationThread在服務器端的代理,負責和客戶端的ApplicationThread通訊。AMS就是通過該代理與ActivityThread進行通信的。
- 5.Instrumentation,每一個應用程序只有一個Instrumentation對象,每個Activity內都有一個對該對象的引用。Instrumentation可以理解為應用進程的管家,ActivityThread要創建或暫停某個Activity時,都需要通過Instrumentation來進行具體的操作。
- 6.ActivityStack,Activity在AMS的棧管理,用來記錄已經啟動的Activity的先后關系,狀態信息等。通過ActivityStack決定是否需要啟動新的進程。
- 7.ActivityRecord,ActivityStack的管理對象,每個Activity在AMS對應一個ActivityRecord,來記錄Activity的狀態以及其他的管理信息。其實就是服務器端的Activity對象的映像。
- 8.TaskRecord,AMS抽象出來的一個“任務”的概念,是記錄ActivityRecord的棧,一個“Task”包含若干個ActivityRecord。AMS用TaskRecord確保Activity啟動和退出的順序。如果你清楚Activity的4種launchMode,那么對這個概念應該不陌生。
19.理解Window和WindowManager
- 1.Window用于顯示View和接收各種事件,Window有三種類型:應用Window(每個Activity對應一個Window)、子Window(不能單獨存在,附屬于特定Window)、系統window(Toast和狀態欄)
- 2.Window分層級,應用Window在1-99、子Window在1000-1999、系統Window在2000-2999.WindowManager提供了增刪改View三個功能。
- 3.Window是個抽象概念:每一個Window對應著一個View和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。
- 6.Activity創建Window:Activity會在attach()中創建Window并設置其回調(onAttachedToWindow()、dispatchTouchEvent()),Activity的Window是由Policy類創建PhoneWindow實現的。然后通過Activity#setContentView()調用PhoneWindow的setContentView。
20.Bitmap的處理
- 1.當使用ImageView的時候,可能圖片的像素大于ImageView,此時就可以通過BitmapFactory.Option來對圖片進行壓縮,inSampleSize表示縮小2^(inSampleSize-1)倍。
- 2.BitMap的緩存:
- 1.使用LruCache進行內存緩存。
- 2.使用DiskLruCache進行硬盤緩存。
- 3.實現一個ImageLoader的流程:同步異步加載、圖片壓縮、內存硬盤緩存、網絡拉取
- 1.同步加載只創建一個線程然后按照順序進行圖片加載
- 2.異步加載使用線程池,讓存在的加載任務都處于不同線程
- 3.為了不開啟過多的異步任務,只在列表靜止的時候開啟圖片加載
21.如何實現一個網絡框架(參考Volley)
- 1.緩存隊列,以url為key緩存內容可以參考Bitmap的處理方式,這里單獨開啟一個線程。
- 2.網絡請求隊列,使用線程池進行請求。
- 3.提供各種不同類型的返回值的解析如String,Json,圖片等等。
22.ClassLoader的基礎知識
- 1.雙親委托:一個ClassLoader類負責加載這個類所涉及的所有類,在加載的時候會判斷該類是否已經被加載過,然后會遞歸去他父ClassLoader中找。
- 2.可以動態加載Jar通過URLClassLoader
- 3.ClassLoader 隔離問題 JVM識別一個類是由:ClassLoader id+PackageName+ClassName。
- 4.加載不同Jar包中的公共類:
- 1.讓父ClassLoader加載公共的Jar,子ClassLoader加載包含公共Jar的Jar,此時子ClassLoader在加載公共Jar的時候會先去父ClassLoader中找。(只適用Java)
- 2.重寫加載包含公共Jar的Jar的ClassLoader,在loadClass中找到已經加載過公共Jar的ClassLoader,也就是把父ClassLoader替換掉。(只適用Java)
- 3.在生成包含公共Jar的Jar時候把公共Jar去掉。
23.插件化框架描述:dynamicLoadApk為例子
- 1.可以通過DexClassLoader來對apk中的dex包進行加載訪問
- 2.如何加載資源是個很大的問題,因為宿主程序中并沒有apk中的資源,所以調用R資源會報錯,所以這里使用了Activity中的實現ContextImpl的getAssets()和getResources()再加上反射來實現。
- 3.由于系統啟動Activity有很多初始化動作要做,而我們手動反射很難完成,所以可以采用接口機制,將Activity的大部分生命周期提取成接口,然后通過代理Activity去調用插件Activity的生命周期。同時如果像增加一個新生命周期方法的時候,只需要在接口中和代理中聲明一下就行。
- 4.缺點:
- 1.慎用this,因為在apk中使用this并不代表宿主中的activity,當然如果this只是表示自己的接口還是可以的。除此之外可以使用that代替this。
- 2.不支持Service和靜態注冊的Broadcast
- 3.不支持LaunchMode和Apk中Activity的隱式調用。
24.熱修復:Andfix為例子
- 1.大致原理:apkpatch將兩個apk做一次對比,然后找出不同的部分。可以看到生成的apatch了文件,后綴改成zip再解壓開,里面有一個dex文件。通過jadx查看一下源碼,里面就是被修復的代碼所在的類文件,這些更改過的類都加上了一個_CF的后綴,并且變動的方法都被加上了一個叫@MethodReplace的annotation,通過clazz和method指定了需要替換的方法。然后客戶端sdk得到補丁文件后就會根據annotation來尋找需要替換的方法。最后由JNI層完成方法的替換。
- 2.無法添加新類和新的字段、補丁文件很容易被反編譯、加固平臺可能會使熱補丁功能失效
25.線程同步的問題,常用的線程同步
- 1.sycn:保證了原子性、可見性、有序性
- 2.鎖:保證了原子性、可見性、有序性
- 1.自旋鎖:可以使線程在沒有取得鎖的時候,不被掛起,而轉去執行一個空循環。
- 1.優點:線程被掛起的幾率減少,線程執行的連貫性加強。用于對于鎖競爭不是很激烈,鎖占用時間很短的并發線程。
- 2.缺點:過多浪費CPU時間,有一個線程連續兩次試圖獲得自旋鎖引起死鎖
- 2.阻塞鎖:沒得到鎖的線程等待或者掛起,Sycn、Lock
- 3.可重入鎖:一個線程可多次獲取該鎖,Sycn、Lock
- 4.悲觀鎖:每次去拿數據的時候都認為別人會修改,所以會阻塞全部其他線程 Sycn、Lock
- 5.樂觀鎖:每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。cas
- 6.顯示鎖和內置鎖:顯示鎖用Lock來定義、內置鎖用synchronized。
- 7.讀-寫鎖:為了提高性能,Java提供了讀
- 1.自旋鎖:可以使線程在沒有取得鎖的時候,不被掛起,而轉去執行一個空循環。
- 3.volatile
- 1.只能保證可見性,不能保證原子性
- 2.自增操作有三步,此時多線程寫會出現問題
- 4.cas
- 1.操作:內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,將內存值修改為B并返回true,否則什么都不做并返回false。
- 2.解釋:本地副本為A,共享內存為V,線程A要把V修改成B。某個時刻線程A要把V修改成B,如果A和V不同那么就表示有其他線程在修改V,此時就表示修改失敗,否則表示沒有其他線程修改,那么把V改成B。
- 3.局限:如果V被修改成V1然后又被改成V,此時cas識別不出變化,還是認為沒有其他線程在修改V,此時就會有問題
- 4.局限解決:將V帶上版本。
- 5.線程不安全到底是怎么回事:
- 1.一個線程寫,多個線程讀的時候,會造成寫了一半就去讀
- 2.多線程寫,會造成臟數據
26.Asynctask和線程池,GC相關(怎么判斷哪些內存該GC,GC算法)
- 1.Asynctask:異步任務類,單線程線程池+Handler
- 2.線程池:
- 1.ThreadPoolExecutor:通過Executors可以構造單線程池、固定數目線程池、不固定數目線程池。
- 2.ScheduledThreadPoolExecutor:可以延時調用線程或者延時重復調度線程。
- 3.GC相關:重要
- 1.搜索算法:
- 1.引用計數
- 2.圖搜索,可達性分析
- 2.回收算法:
- 1.標記清除復制:用于青年代
- 2.標記整理:用于老年代
- 3.堆分區:
- 1.青年區eden 80%、survivor1 10%、survivor2 10%
- 2.老年區
- 4.虛擬機棧分區:
- 1.局部變量表
- 2.操作數棧
- 3.動態鏈接
- 4.方法返回地址
- 5.GC Roots:
- 1.虛擬機棧(棧楨中的本地變量表)中的引用的對象
- 2.方法區中的類靜態屬性引用的對象
- 3.方法區中的常量引用的對象
- 4.本地方法棧中JNI的引用的對象
- 1.搜索算法:
27.網絡
- 1.ARP協議:在IP以太網中,當一個上層協議要發包時,有了該節點的IP地址,ARP就能提供該節點的MAC地址。
- 2.HTTP HTTPS的區別:
- 1.HTTPS使用TLS(SSL)進行加密
- 2.HTTPS缺省工作在TCP協議443端口
- 3.它的工作流程一般如以下方式:
- 1.完成TCP三次同步握手
- 2.客戶端驗證服務器數字證書,通過,進入步驟3
- 3.DH算法協商對稱加密算法的密鑰、hash算法的密鑰
- 4.SSL安全加密隧道協商完成
- 5.網頁以加密的方式傳輸,用協商的對稱加密算法和密鑰加密,保證數據機密性;用協商的hash算法進行數據完整性保護,保證數據不被篡改
- 3.http請求包結構,http返回碼的分類,400和500的區別
- 1.包結構:
- 1.請求:請求行、頭部、數據
- 2.返回:狀態行、頭部、數據
- 2.http返回碼分類:1到5分別是,消息、成功、重定向、客戶端錯誤、服務端錯誤
- 1.包結構:
- 4.Tcp
- 1.可靠連接,三次握手,四次揮手
- 1.三次握手:防止了服務器端的一直等待而浪費資源,例如只是兩次握手,如果s確認之后c就掉線了,那么s就會浪費資源
- 1.syn-c = x,表示這消息是x序號
- 2.ack-s = x + 1,表示syn-c這個消息接收成功。syn-s = y,表示這消息是y序號。
- 3.ack-c = y + 1,表示syn-s這條消息接收成功
- 1.三次握手:防止了服務器端的一直等待而浪費資源,例如只是兩次握手,如果s確認之后c就掉線了,那么s就會浪費資源
- 2.四次揮手:TCP是全雙工模式
- 1.fin-c = x , 表示現在需要關閉c到s了。ack-c = y,表示上一條s的消息已經接收完畢
- 2.ack-s = x + 1,表示需要關閉的fin-c消息已經接收到了,同意關閉
- 3.fin-s = y + 1,表示s已經準備好關閉了,就等c的最后一條命令
- 4.ack-c = y + 1,表示c已經關閉,讓s也關閉
- 3.滑動窗口,停止等待、后退N、選擇重傳
- 4.擁塞控制,慢啟動、擁塞避免、加速遞減、快重傳快恢復
- 1.可靠連接,三次握手,四次揮手
28.數據庫性能優化:索引和事務,需要找本專門的書大概了解一下
29.13.APK打包流程和其內容
- 1.流程
- 1.aapt生成R文件
- 2.aidl生成java文件
- 3.將全部java文件編譯成class文件
- 4.將全部class文件和第三方包合并成dex文件
- 5.將資源、so文件、dex文件整合成apk
- 6.apk簽名
- 7.apk字節對齊
- 1.aapt生成R文件
- 2.內容:so、dex、asset、資源文件
30.網絡劫持的類型原理:可以百度一下了解一下具體概念
- 1.DNS劫持、欺騙、污染
- 2.http劫持:重定向、注入js,http注入、報文擴展
31.java類加載過程:
- 1.加載時機:創建實例、訪問靜態變量或方法、反射、加載子類之前
- 2.驗證:驗證文件格式、元數據、字節碼、符號引用的正確性
- 3.加載:根據全類名獲取文件字節流、將字節流轉化為靜態儲存結構放入方法區、生成class對象
- 4.準備:在堆上為靜態變量劃分內存
- 5.解析:將常量池中的符號引用轉換為直接引用
- 6.初始化:初始化靜態變量
- 7.書籍推薦:深入理解java虛擬機,博客推薦:Java/Android阿里面試JVM部分理解
32.retrofit的了解
- 1.動態代理創建一個接口的代理類
- 2.通過反射解析每個接口的注解、入參構造http請求
- 3.獲取到返回的http請求,使用Adapter解析成需要的返回值。
33.bundle的數據結構,如何存儲
- 1.鍵值對儲存
- 2.傳遞的數據可以是boolean、byte、int、long、float、double、string等基本類型或它們對應的數組,也可以是對象或對象數組。
- 3.當Bundle傳遞的是對象或對象數組時,必須實現Serializable 或Parcelable接口
34.listview內點擊buttom并移動的事件流完整攔截過程:
- 1.點下按鈕的時候:
- 1.產生了一個down事件,activity-->phoneWindow-->ViewGroup-->ListView-->botton,中間如果有重寫了攔截方法,則事件被該view攔截可能消耗。
- 2.沒攔截,事件到達了button,這個過程中建立了一條事件傳遞的view鏈表
- 3.到button的dispatch方法-->onTouch-->view是否可用-->Touch代理
- 2.移動點擊按鈕的時候:
- 1.產生move事件,listView中會對move事件做攔截
- 2.此時listView會將該滑動事件消費掉
- 3.后續的滑動事件都會被listView消費掉
- 3.手指抬起來時候:前面建立了一個view鏈表,listView的父view在獲取事件的時候,會直接取鏈表中的listView讓其進行事件消耗。
35.service的意義:不需要界面,在后臺執行的程序
36.android的IPC通信方式,線程(進程間)通信機制有哪些
- 1.ipc通信方式:binder、contentprovider、socket
- 2.操作系統進程通訊方式:共享內存、socket、管道
37.操作系統進程和線程的區別
- 1.簡而言之,一個程序至少有一個進程,一個進程至少有一個線程.
- 2.線程的劃分尺度小于進程,使得多線程程序的并發性高。
- 3.另外,進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。
- 4.多線程的意義在于一個應用程序中,有多個執行部分可以同時執行。有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配
38.HashMap的實現過程:Capacity就是buckets的數目,Load factor就是buckets填滿程度的最大比例。如果對迭代性能要求很高的話不要把capacity設置過大,也不要把load factor設置過小。
- 1.簡單來說HashMap就是一個會自動擴容的數組鏈表
- 2.put過程
- 1.對key的hashCode()做hash,然后再計算index;
- 2.如果沒碰撞直接放到bucket里;
- 3.如果碰撞了,以鏈表的形式存在buckets后;
- 4.如果碰撞導致鏈表過長(大于等于TREEIFY_THRESHOLD),就把鏈表轉換成紅黑樹;
- 5.如果節點已經存在就替換old value(保證key的唯一性)
- 6.如果bucket滿了(超過load factor*current capacity),就要resize。
- 3.resize:當put時,如果發現目前的bucket占用程度已經超過了Load Factor所希望的比例,那么就會發生resize。在resize的過程,簡單的說就是把bucket擴充為2倍,之后重新計算index,把節點再放到新的bucket中
- 4.get過程
- 1.根據key的hash算出數組下表
- 2.使用equals遍歷鏈表進行比較
39.mvc、mvp、mvvm:
- 1.mvc:數據、View、Activity,View將操作反饋給Activity,Activitiy去獲取數據,數據通過觀察者模式刷新給View。循環依賴
- 1.Activity重,很難單元測試
- 2.View和Model耦合嚴重
- 2.mvp:數據、View、Presenter,View將操作給Presenter,Presenter去獲取數據,數據獲取好了返回給Presenter,Presenter去刷新View。PV,PM雙向依賴
- 1.接口爆炸
- 2.Presenter很重
- 3.mvvm:數據、View、ViewModel,View將操作給ViewModel,ViewModel去獲取數據,數據和界面綁定了,數據更新界面更新。
- 1.viewModel的業務邏輯可以單獨拿來測試
- 2.一個view 對應一個 viewModel 業務邏輯可以分離,不會出現全能類
- 3.數據和界面綁定了,不用寫垃圾代碼,但是復用起來不舒服
40.java的線程如何實現
- 1.Thread繼承
- 2.Runnale
- 3.Future
- 4.線程池
41.ArrayList 如何刪除重復的元素或者指定的元素;
- 1.刪除重復:Set
- 2.刪除指定:迭代器
42.如何設計在 UDP 上層保證 UDP 的可靠性傳輸;
- 1.簡單來講,要使用UDP來構建可靠的面向連接的數據傳輸,就要實現類似于TCP協議的超時重傳,有序接受,應答確認,滑動窗口流量控制等機制,等于說要在傳輸層的上一層(或者直接在應用層)實現TCP協議的可靠數據傳輸機制。
- 2.比如使用UDP數據包+序列號,UDP數據包+時間戳等方法,在服務器端進行應答確認機制,這樣就會保證不可靠的UDP協議進行可靠的數據傳輸。
- 3.基于udp的可靠傳輸協議有:RUDP、RTP、UDT
43.Java 中內部類為什么可以訪問外部類
- 1.因為內部類創建的時候,需要外部類的對象,在內部類對象創建的時候會把外部類的引用傳遞進去
44.設計移動端的聯系人存儲與查詢的功能,要求快速搜索聯系人,可以用到哪些數據結構?數據庫索引,平衡二叉樹(B樹、紅黑樹)
45.紅黑樹特點
- 1.root節點和葉子節點是黑色
- 2.紅色節點后必須為黑色節點
- 3.從root到葉子每條路徑的黑節點數量相同
46.linux異步和同步I/o:
- 1.同步:對于client,client一直等待,但是client不掛起:主線程調用
- 2.異步:對于client,client發起請求,service好了再回調client:其他線程調用,調用完成之后進行回調
- 3.阻塞:對于service,在準備io的時候會將service端掛起,直至準備完成然后喚醒service:bio
- 3.非阻塞:對于service,在準備io的時候不會將service端掛起,而是service一直去輪詢判斷io是否準備完成,準備完成了就進行操作:nio、linux的select、poll、epoll
- 4.多路復用io:非阻塞io的一種優化,java nio,用一個線程去輪詢多個 io端口是否可用,如果一個可用就通知對應的io請求,這使用一個線程輪詢可以大大增強性能。
- 1.我可以采用 多線程+ 阻塞IO 達到類似的效果,但是由于在多線程 + 阻塞IO 中,每個socket對應一個線程,這樣會造成很大的資源占用。
- 2.而在多路復用IO中,輪詢每個socket狀態是內核在進行的,這個效率要比用戶線程要高的多。
- 5.異步io:aio,用戶線程完全不感知io的進行,所有操作都交給內核,io完成之后內核通知用戶線程。
- 1.這種io才是異步的,2、3、4都是同步io,因為內核進行數據拷貝的過程都會讓用戶線程阻塞。
- 2.異步IO是需要操作系統的底層支持,也就是內核支持,Java 7中,提供了Asynchronous IO
47.ConcurrentHashMap內部實現,HashTable的實現被廢棄的原因:
- 1.HashTable容器在競爭激烈的并發環境下表現出效率低下的原因,是因為所有訪問HashTable的線程都必須競爭同一把鎖,那假如容器里有多把鎖,每一把鎖用于鎖容器其中一部分數據,那么當多線程訪問容器里不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高并發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術,首先將數據分成一段一段的存儲,然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。
- 2.ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap里扮演鎖的角色,HashEntry則用于存儲鍵值對數據。一個ConcurrentHashMap里包含一個Segment數組,Segment的結構和HashMap類似,是一種數組和鏈表結構, 一個Segment里包含一個HashEntry數組,每個HashEntry是一個鏈表結構的元素,每個Segment守護者一個HashEntry數組里的元素,當對HashEntry數組的數據進行修改時,必須首先獲得它對應的Segment鎖。
48.HandlerThread是什么
- 1.MessageQueue + Looper + Handler
49.IntentService是什么
- 1.含有HandlerThread的Service,可以多次startService()來多次在子線程中進行 onHandlerIntent()的調用。
50.class和dex
- 1.dvm執行的是dex格式文件,jvm執行的是class文件,android程序編譯完之后生產class文件。然后dex工具會把class文件處理成dex文件,然后把資源文件和.dex文件等打包成apk文件。
- 2.dvm是基于寄存器的虛擬機,而jvm執行是基于虛擬棧的虛擬機。寄存器存取速度比棧快的多,dvm可以根據硬件實現最大的優化,比較適合移動設備。
- 3.class文件存在很多的冗余信息,dex工具會去除冗余信息,并把所有的class文件整合到dex文件中。減少了I/O操作,提高了類的查找速度
51.內存泄漏
- 1.其他線程持有一個Listener,Listener操作activity。那么在線程么有完畢的時候,activity關閉了,原本是要被回收的但是,不能被回收。
- 2.例如Handler導致的內存泄漏,Handler就相當于Listener。
- 3.在activity關閉的時候注意停止線程,或者將Listener的注冊取消
- 3.使用弱引用,這樣即使Listener持有了activity,在GC的時候還是會被回收
- 4.工具:LeakCanary
52.過度繪制、卡頓優化:
- 1.過度繪制:
- 1.移除Window默認的Background:getWidow.setBackgroundDrawable(null);
- 2.移除XML布局文件中非必需的Background
- 3.減少布局嵌套(扁平化的一個體現,減少View數的深度,也就減少了View樹的遍歷時間,渲染的時候,前后期的工作,總是按View樹結點來)
- 4.在引入布局文件里面,最外層可以用merge替代LinearLayout,RelativeLayout,這樣把子UI元素直接銜接在include位置
- 5.工具:HierarchyViewer 查看視圖層級
- 2.卡頓優化:16ms數據更新
53.apk瘦身:
- 1.classes.dex:通過代碼混淆,刪掉不必要的jar包和代碼實現該文件的優化
- 2.資源文件:通過Lint工具掃描代碼中沒有使用到的靜態資源
- 3.圖片資源:使用tinypng和webP,下面詳細介紹圖片資源優化的方案,矢量圖
- 4.SO文件將不用的去掉,目前主流app一般只放一個arm的so包
54.ANR的形成,各個組件上出現ARN的時間限制是多少
- 1.只要是主線程耗時的操作就會ARN 如io
- 2.broadcast超時時間為10秒 按鍵無響應的超時時間為5秒 前臺service無響應的超時時間為20秒,后臺service為200秒
55.Serializable和Parcelable 的區別
- 1.P 消耗內存小
- 2.網絡傳輸用S 程序內使用P
- 3.S將數據持久化方便
- 4.S使用了反射 容易觸發垃圾回收 比較慢
56.Sharedpreferences源碼簡述
- 1.儲存于硬盤上的xml鍵值對,數據多了會有性能問題
- 2.ContextImpl記錄著SharedPreferences的重要數據,文件路徑和實例的鍵值對
- 3.在xml文件全部內加載到內存中之前,讀取操作是阻塞的,在xml文件全部內加載到內存中之后,是直接讀取內存中的數據
- 4.apply因為是異步的沒有返回值, commit是同步的有返回值能知道修改是否提交成功
- 5.多并發的提交commit時,需等待正在處理的commit數據更新到磁盤文件后才會繼續往下執行,從而降低效率; 而apply只是原子更新到內存,后調用apply函數會直接覆蓋前面內存數據,從一定程度上提高很多效率。 3.edit()每次都是創建新的EditorImpl對象.
- 6.博客推薦:全面剖析SharedPreferences
57.操作系統如何管理內存的:
- 1.使用寄存器進行將進程地址和物理內存進行映射
- 2.虛擬內存進行內存映射到硬盤上增大內存
- 3.虛擬內存是進行內存分頁管理
- 4.頁表實現分頁,就是 頁+地址偏移。
- 5.如果程序的內存在硬盤上,那么就需要用頁置換算法來將其調入內存中:先進先出、最近未使用最少等等
- 6.博客推薦:現代操作系統部分章節筆記
58.瀏覽器輸入地址到返回結果發生了什么
- 1.DNS解析
- 2.TCP連接
- 3.發送HTTP請求
- 4.服務器處理請求并返回HTTP報文
- 5.瀏覽器解析渲染頁面
- 6.連接結束
59.java泛型類型擦除發生在什么時候,通配符有什么需要注意的。
- 1.發生在編譯的時候
- 2.PECS,extends善于提供精確的對象 A是B的子集,Super善于插入精確的對象 A是B的超集
- 3.博客推薦:Effective Java筆記(不含反序列化、并發、注解和枚舉)、android阿里面試java基礎錦集
60.activity的生命周期
- 1.a啟動b,后退鍵再到a的生命周期流程:a.create-->a.start-->a.resume-->a.pause-->b.create-->b.start-->b.resume-->b界面繪制-->a.stop-->b.pause-->b.stop-->b.destroy-->a.restart-->a.start-->a.resume
- 2.意外銷毀會調用saveInstance,重新恢復的時候回調用restoreInstance。儲存數據的時候使用了委托機制,從activity-->window-->viewGroup-->view 會遞歸調用save來保持本view的數據,restore則是遞歸恢復本view數據。我們可以在里面做一些自己需要的數據操作。
61.面試常考的算法
- 1.快排、堆排序為首的各種排序算法
- 2.鏈表的各種操作:判斷成環、判斷相交、合并鏈表、倒數K個節點、尋找成環節點
- 3.二叉樹、紅黑樹、B樹定義以及時間復雜度計算方式
- 4.動態規劃、貪心算法、簡單的圖論
- 5.推薦書籍:算法導論,將圖論之前的例子寫一遍
62.Launcher進程啟動另外一個進程的過程:啟動一個app
63.開源框架源碼
- 1.Fresco
- 1.mvc框架:
- 1.Controller控制數據顯示在Hierarchy中的Drawable的顯隱
- 2.ImagePipeline在Controller中負責進行數據獲取,返回的數據是CloseableImage
- 3.Drawee把除了初始化之外的操作全部交給Holder去做,Holder持有Controller和Hierarchy
- 2.Drawable層次以及繪制:
- 1.如果要繪制一次Drawable就調用invalidateSelf()來觸發onDraw()
- 2.Drawable分為:容器類(保存一些Drawable)、自我繪制類(進度條)、圖形變換類(scale、rotate、矩陣變換)、動畫類(內部不斷刷新,進行webp和gif的幀繪制)
- 3.ImagePipeline返回的CloseableImage是由一個個DrawableFactory解析成Drawable的
- 4.webp和gif動畫是由jni代碼解析的,然后其他靜態圖片是根據不同的android平臺使用BitmapFactory來解析的
- 3.職責鏈模式:producer不做操作標n,表示只是提供一個consumer。獲取圖片--》解碼圖片緩存Producer--》后臺線程Producer--》client圖片處理producer(n)--》解碼producer(n)--》旋轉或剪裁producer(n)--》編碼圖片內存緩存producer--》讀硬盤緩存producer--》寫硬盤緩存producer(n)--》網絡producer提供CloseableImage《--解碼圖片緩存consumer《--client圖片處理consumer《--解碼consumer《--旋轉或剪裁consumer《--編碼圖片內存緩存consumer《--寫硬盤緩存consumer《--圖片數據
- 4.內存緩存:
- 1.一個CountingLruMap保存已經沒有被引用的緩存條目,一個CountingLruMap保存所有的條目包括沒有引用的條目。每當緩存策略改變和一定時間緩存配置的更新的時候,就會將 待銷毀條目Map中的條目一個個移除,直到緩存大小符合配置。
- 2.這里的引用計數是用Fresco組件實現的引用計數器。
- 3.緩存有一個代理類,用來追蹤緩存的存取。
- 4.CountingLruMap是使用LinkedHashMap來儲存數據的。
- 5.硬盤緩存:
- 1.DefaultDiskStorage使用Lru策略。
- 2.為了不讓所有的文件集中在一個文件中,創建很多命名不同的文件夾,然后使用hash算法把緩存文件分散
- 3.DiskStorageCache封裝了DefaultDiskStorage,不僅進行緩存存取追蹤,并且其在內存里面維持著一個 <key,value> 的鍵值對,因為文件修改頻繁,所有只是定時刷新,因此如果在內存中找不到,還要去硬盤中找一次。
- 4.刪除硬盤的緩存只出現在硬盤數據大小超限的時候,此時同時也會刪除緩存中的key,所以不會出現內存中有key,但是硬盤上沒有的情況。
- 5.在插入硬盤數據的時候,采用的是插入器的形式。返回一個Inserter,在Inserter.writeData()中傳入一個CallBack(里面封裝了客戶端插入數據的邏輯和文件引用),讓內部實現調用CallBack的邏輯來插入文件數據,前面寫的文件后綴是.temp,只有調用commit()之后才會修改后綴,讓文件對客戶端可見。
- 6.使用了java提供的FileTreeVisitor來遍歷文件
- 6.對象池:
- 1.使用數組來存儲一個桶,桶內部是一個Queue。數組下標是數據申請內存的byte大小,桶內部的Queue存的是內存塊的。所以數組使用的是稀疏數組
- 2.申請內存的方式有兩種 1.java堆上開辟的內存 2.ashme 的本地內存中開辟的內存
- 7.設計模式:Builder、職責鏈、觀察者、代理、組合、享元、適配器、裝飾者、策略、生產者消費者、提供者
- 8.自定義計數引用:類似c++智能指針
- 1.使用一個靜態IdentityHashMap <儲存需要被計數引用的對象,其被引用的次數>
- 2.用SharedReference分裝需要被計數引用的對象,提供一個銷毀資源的銷毀器,提供一個靜態工廠方法來復制自己,復制一個引用計數加一。提供一個方法銷毀自己,表示自己需要變成無人引用的對象了,此時引用計數減一。
- 3.引用計數歸零,銷毀器將銷毀資源,如bitmap的recycle或者是jni內存調用jni方法歸還內存。
- 9.博客推薦:Android Fresco源碼文檔翻譯、從零開始擼一個Fresco之硬盤緩存、從零開始擼一個Fresco之gif和Webp動畫、從零開始擼一個Fresco之內存緩存、從零開始擼一個Fresco之總結
- 1.mvc框架:
- 2.oKhttp:
- 1.同步和異步:
- 1.異步使用了Dispatcher來將存儲在 Deque 中的請求分派給線程池中各個線程執行。
- 2.當任務執行完成后,無論是否有異常,finally代碼段總會被執行,也就是會調用Dispatcher的finished函數,它將正在運行的任務Call從隊列runningAsyncCalls中移除后,主動的把緩存隊列向前走了一步。
- 2.連接池:
- 1.一個Connection封裝了一個socket,ConnectionPool中儲存s著所有的Connection,StreamAllocation是引用計數的一個單位
- 2.當一個請求獲取一個Connection的時候要傳入一個StreamAllocation,Connection中存著一個弱引用的StreamAllocation列表,每當上層應用引用一次Connection,StreamAllocation就會加一個。反之如果上層應用不使用了,就會刪除一個。
- 3.ConnectionPool中會有一個后臺任務定時清理StreamAllocation列表為空的Connection。5分鐘時間,維持5個socket
- 3.選擇路線與建立連接
- 1.選擇路線有兩種方式:
- 1.無代理,那么在本地使用DNS查找到ip,注意結果是數組,即一個域名有多個IP,這就是自動重連的來源
- 2.有代理HTTP:設置socket的ip為代理地址的ip,設置socket的端口為代理地址的端口
- 3.代理好處:HTTP代理會幫你在遠程服務器進行DNS查詢,可以減少DNS劫持。
- 2.建立連接
- 1.連接池中已經存在連接,就從中取出(get)RealConnection,如果沒有命中就進入下一步
- 2.根據選擇的路線(Route),調用Platform.get().connectSocket選擇當前平臺Runtime下最好的socket庫進行握手
- 3.將建立成功的RealConnection放入(put)連接池緩存
- 4.如果存在TLS,就根據SSL版本與證書進行安全握手
- 5.構造HttpStream并維護剛剛的socket連接,管道建立完成
- 1.選擇路線有兩種方式:
- 4.職責鏈模式:緩存、重試、建立連接等功能存在于攔截器中網絡請求相關,主要是網絡請求優化。網絡請求的時候遇到的問題
- 5.博客推薦:Android數據層架構的實現 上篇、Android數據層架構的實現 下篇
- 1.同步和異步:
- 3.okio
- 1.簡介;
- 1.sink:自己--》別人
- 2.source:別人--》自己
- 3.BufferSink:有緩存區域的sink
- 4.BufferSource:有緩存區域的source
- 5.Buffer:實現了3、4的緩存區域,內部有Segment的雙向鏈表,在在轉移數據的時候,只需要將指針轉移指向就行
- 2.比java io的好處:
- 1.減少內存申請和數據拷貝
- 2.類少,功能齊全,開發效率高
- 3.內部實現:
- 1.Buffer的Segment雙向鏈表,減少數據拷貝
- 2.Segment的內部byte數組的共享,減少數據拷貝
- 3.SegmentPool的共享和回收Segment
- 4.sink和source中被實際操作的其實是Buffer,Buffer可以充當sink和source
- 5.最終okio只是對java io的封裝,所有操作都是基于java io 的
- 1.簡介;
寫在最后:能看到這里的人,我挺佩服你的.這篇文章是我在頭條面試之前整理的,最后80%的題目都命中了,所以祝你好運.
不販賣焦慮,也不標題黨。分享一些這個世界上有意思的事情。題材包括且不限于:科幻、科學、科技、互聯網、程序員、計算機編程。下面是我的微信公眾號:世界上有意思的事,干貨多多等你來看。