面經(jīng)專題系列:
Android面經(jīng)| 問題歸納
Android面經(jīng)| 回顧展望
Android面經(jīng)| 算法題解
@[toc]
Android相關(guān)
AMS相關(guān)
ActivityManagerService是Android中最核心的服務(wù) 交洗, 主要負(fù)責(zé)系統(tǒng)中四大組件的啟動疆液、切換玛歌、調(diào)度及應(yīng)用進(jìn)程的管理和調(diào)度等工作袖瞻,其職責(zé)與操作系統(tǒng)中的進(jìn)程管理和調(diào)度模塊類似。
看源碼談?wù)凙MS啟動過程:ActivityManagerService分析——AMS啟動流程
Activity相關(guān)
SingleTask優(yōu)化
//如果標(biāo)簽為singleTask的activity不在棧頂
onNewIntent()
//如果標(biāo)簽為singleTask的activity在棧頂
onNewIntent() -> onStart() -> onResume() -> ...
回退棧中的activity不會調(diào)用onCreate()偏瓤,但是這里雖然onNewIntent被調(diào)用了丧失,但是Intent中所保存的數(shù)據(jù)卻仍然還是舊數(shù)據(jù)母截,因此需要進(jìn)一步重寫。
/***
* 將activity 的創(chuàng)建模式設(shè)置為singletask,
* 使用這個方法可以再其他activity想這個activity發(fā)送Intent時吴菠,這個Intent能夠及時更新
*/
@Override
protected void onNewIntent(Intent intent)
{
super.onNewIntent(intent);
setIntent(intent); //這一句必須的者填,否則Intent無法獲得最新的數(shù)據(jù)
}
SingleTask優(yōu)化搭配:
<activity
android:launchMode="singleTask"
android:taskAffinity="com.renly.MainActivity"
android:name="com.renly.MainActivity"
android:screenOrientation="portrait" />
taskAffinity,可以翻譯為任務(wù)相關(guān)性做葵。這個參數(shù)標(biāo)識了一個 Activity 所需要的任務(wù)棧的名字占哟,默認(rèn)情況下,所有 Activity 所需的任務(wù)棧的名字為應(yīng)用的包名酿矢,當(dāng) Activity 設(shè)置了 taskAffinity 屬性榨乎,那么這個 Activity 在被創(chuàng)建時就會運行在和 taskAffinity 名字相同的任務(wù)棧中,如果沒有瘫筐,則新建 taskAffinity 指定的任務(wù)棧蜜暑,并將 Activity 放入該棧中。另外策肝,taskAffinity 屬性主要和 singleTask 或者 allowTaskReparenting 屬性配對使用肛捍,在其他情況下沒有意義隐绵。
allowTaskReparenting = "true"
在這種情況下,Activity 可以從其啟動的任務(wù)移動到與其具有關(guān)聯(lián)的任務(wù)(如果該任務(wù)出現(xiàn)在前臺)拙毫。
啟動模式應(yīng)用場景
launchMode | 使用場景 |
---|---|
singleTop | 適合啟動同類型的 Activity依许,例如接收通知啟動的內(nèi)容顯示頁面 |
singleTask | 適合作為程序入口 |
singleInstance | 適合需要與程序分離開的頁面,例如鬧鈴的響鈴界面 |
Intent
顯式啟動:
一般用于啟動應(yīng)用內(nèi)的Activity
隱式啟動:
a.例子:Intent intent = new Intent(String action缀蹄,Uri uri);
b.一般用于啟動應(yīng)用外的Activity,操作系統(tǒng)會自動把匹配隱式Intent的Acttivity顯
示出來供用戶選擇峭跳,匹 配的規(guī)則跟Activity聲明的 Intent-filter 有關(guān)
c.主要組成部分:
(1)action 要執(zhí)行的操作。也可以通過 setAction() 設(shè)置
(2)uri待訪問數(shù)據(jù)的位置袍患。也可以通過 setData() 和 setDataAndType() 設(shè)
置坦康。可以是網(wǎng)頁的URL诡延,某個文件的滞欠,或指向 ContentProvider 的某個內(nèi)容 URI
(3)操作涉及的的數(shù)據(jù)類型。setType() 和 setDataAndType()設(shè)置肆良。如intent.setType("text/html")
(4)可選類別筛璧。描述何時,何地或者如何啟動某個 Activity惹恃。
intent.addCategory(Intent.CATEGORY_LAUNCHER)
Service相關(guān)
兩種啟動方式
1.startService()啟動方式:主要用于執(zhí)行后臺計算
2.bindService()啟動方式:主要用于和其它組件的交互
說明:這兩種狀態(tài)是可以共存的夭谤,即一個Service既可以處于啟動狀態(tài),也可以同時處于綁定狀態(tài)巫糙。
生命周期
startService() -> onCreate() -> onStartCommand() -> Service運行 -> onDestroy()
bindService() -> onCreate() -> onBind() -> Service運行 -> onUnBind() -> onDestroy()
service 運行于主線程
int onStartCommand(Intent intent, int flags, int startId)會在每次調(diào)用startService時回調(diào)
FLAG使用START_FLAG_REDELIVERY朗儒,意味著當(dāng)Service因內(nèi)存不足而被系統(tǒng)kill后,則會重建服務(wù)参淹,并通過傳遞給服務(wù)的最后一個 Intent 調(diào)用 onStartCommand()
onStartCommand()返回值:
START_STICKY醉锄,START_NOT_STICKY,START_REDELIVER_INTENT
Service 如何和 Activity 進(jìn)行通信浙值?
① 通過綁定服務(wù)的方式恳不。在綁定的服務(wù)中聲明一個Binder類,并創(chuàng)建一個Binder對象开呐,在onBind()函數(shù)中返回這個對象烟勋,并讓Activity實現(xiàn)ServiceConnection接口,在OnServiceConnected方法中獲取到Service提供的這個Binder對象筐付,通過這個對象的各種自定義的方法就能完成Service與Activity的通信卵惦。
② 通過Intent的方式,在StartService()中需要傳入一個Intent對象作為參數(shù)瓦戚,通過這個Intent實例對象進(jìn)行實現(xiàn)通信沮尿。
③ 通過Callback和Handler的方式,在綁定的服務(wù)中聲明一個Binder類伤极,并創(chuàng)建一個Binder對象蛹找,在onBind()函數(shù)中返回這個對象姨伤,讓Activity實現(xiàn)ServiceConnection接口,并且在OnserviceConnected方法中實例化Service中的CallBack接口庸疾,并且實現(xiàn)OnDataChange()方法乍楚,其中的實質(zhì)是一段Handler代碼,可以在其中完成耗時操作届慈,以這種方式完成通信徒溪。
BroadcastReciever相關(guān)
寫一個類繼承BroadcastReceiver重寫onReceive方法,注意onReceive是主線程不要做耗時操作否則阻塞10s會ANR金顿,onReceive()中耗時操作不能開線程做臊泌,可以使用goAsync()或使用service來做(由于onReceive在結(jié)束后會釋放資源,依賴線程也很有可能會被釋放)
通過LocalBroadcastManager動態(tài)注冊的Receiver只會在App應(yīng)用內(nèi)廣播揍拆。
兩種注冊方式
1.靜態(tài)注冊渠概,在注冊清單文件進(jìn)行聲明
2.動態(tài)注冊,在代碼中動態(tài)進(jìn)行注冊
//靜態(tài)注冊
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="kt.com.MyReceiver"/>
</intent-filter>
</receiver>
//動態(tài)注冊
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("kt.com.MyReceiver");
registerReceiver(new MyReceiver(),intentFilter);
兩種注冊方式的區(qū)別
1.動態(tài)注冊的廣播是非常駐型廣播嫂拴,此時廣播是跟隨宿主的生命周期的播揪,宿主不在了廣播也就不在了。
2.靜態(tài)注冊的廣播是常駐型廣播筒狠,即應(yīng)用程序關(guān)閉后猪狈,依然能夠收到廣播。
ContentProvider相關(guān)
ContentProvider管理android以結(jié)構(gòu)化方式存放的數(shù)據(jù)辩恼。他以相對安全的方式封裝數(shù)據(jù)并且提供簡易的處理機(jī)制雇庙。Content provider提供不同進(jìn)程間數(shù)據(jù)交互的標(biāo)準(zhǔn)化接口。
參考博客
Broadcast的分類灶伊?有序疆前,無序?粘性谁帕,非粘性峡继?本地廣播冯袍?
廣播可以分為有序廣播匈挖、無序廣播、本地廣播康愤、粘性廣播儡循。其中無序廣播通過sendBroadcast(intent)發(fā)送,有序廣播通過sendOrderedBroadcast(intent)發(fā)送征冷。
有序廣播择膝。
(1) 有序廣播可以用priority來調(diào)整優(yōu)先級 取值范圍-1000~+1000,默認(rèn)為0检激,數(shù)值越大優(yōu)先級越高肴捉,優(yōu)先級越高越優(yōu)先獲得廣播響應(yīng)腹侣。
(2) abortBroadcast()可來終止該廣播的傳播,對更低優(yōu)先級的屏蔽齿穗,注意只對有序廣播生效傲隶。
(3) 有序廣播在傳播數(shù)據(jù)中會發(fā)生比如setResultData(),getResultData()窃页,在傳播過程中跺株,可以從新設(shè)置數(shù)據(jù)關(guān)于本地廣播,可以查看這篇文章脖卖∑故。總的來說,本地廣播是通過LocalBroadcastManager內(nèi)置的Handler來實現(xiàn)的畦木,只是利用了IntentFilter的match功能袖扛,至于BroadcastReceiver 換成其他接口也無所謂,順便利用了現(xiàn)成的類和概念而已十籍。在register()的時候保存BroadcastReceiver以及對應(yīng)的IntentFilter攻锰,在sendBroadcast()的時候找到和Intent對應(yīng)的BroadcastReceiver,然后通過Handler發(fā)送消息妓雾,觸發(fā)executePendingBroadcasts()函數(shù)娶吞,再在后者中調(diào)用對應(yīng)BroadcastReceiver的onReceive()方法。
粘性消息:粘性消息在發(fā)送后就一直存在于系統(tǒng)的消息容器里面械姻,等待對應(yīng)的處理器去處理妒蛇,如果暫時沒有處理器處理這個消息則一直在消息容器里面處于等待狀態(tài),粘性廣播的Receiver如果被銷毀楷拳,那么下次重建時會自動接收到消息數(shù)據(jù)绣夺。(在 android 5.0/api 21中deprecated,不再推薦使用,相應(yīng)的還有粘性有序廣播欢揖,同樣已經(jīng)deprecated)
自定義View的步驟
Android中的事件傳遞機(jī)制陶耍?
當(dāng)我們的手指觸碰到屏幕,事件是按照Activity->ViewGroup->View這樣的流程到達(dá)最終響應(yīng)觸摸事件的View的她混。而在事件分發(fā)過程中烈钞,涉及到三個最重要的方法:dispatchTouchEvent()、onInterceptTouchEvent()坤按、onTouchEvent毯欣。我們的手指觸摸到屏幕的時候,會觸發(fā)一個Action_Down類型的事件臭脓,當(dāng)前頁面的Activity會首先做出相應(yīng)酗钞,也就是說會走到Activity的dispatchTouchEvent()方法內(nèi)。在這個方法內(nèi)部有下面兩個邏輯:
調(diào)用getWindow.superDispatchTouchEvent()。
如果上一步返回true砚作,則直接返回true窘奏;否則return自己的onTouchEvent()。顯然葫录,當(dāng)getWindow.superDispatchTouchEvent()返回true蔼夜,表示當(dāng)前事件已經(jīng)被消費掉,無需調(diào)用onTouchEvent压昼;否則代表事件并沒有被處理求冷,因此需要調(diào)用Activity的onTouchEvent進(jìn)行處理。
我們都知道窍霞,getWindow()返回的是PhoneWindow匠题,因此這句代碼本質(zhì)上調(diào)用了PhoneWindow中的superDispatchTouchEvent()但金。而后者實際上調(diào)用了mDecor.superDispatchTouchEvent(event)韭山。這個mDecor也就是DecorView,它是FrameLayout的一個子類冷溃。在DecorView中的superDispatchTouchEvent(event)中調(diào)用的是super.dispatchTouchEvent()钱磅。因此,本質(zhì)上調(diào)用的是ViewGroup的dispatchTouchEvent()似枕。
到這里盖淡,事件已經(jīng)從Activity傳遞到ViewGroup了。接下來我們分析ViewGroup凿歼。
在ViewGroup的dispatchTouchEvent()中邏輯大致如下:通過onInterceptTouchEvent()判斷當(dāng)前ViewGroup是否攔截褪迟,默認(rèn)的ViewGroup都是不攔截的;
如果攔截答憔,則return自己的onTouchEvent()味赃;
如果不攔截,則根據(jù)child.dispatchTouchEvent()的返回值判斷虐拓。如果返回true心俗,則return true;否則return自身的onTouchEvent()蓉驹,在這里實現(xiàn)了未處理事件的向上傳遞城榛。
通常情況下,ViewGroup 的 onInterceptTouchEvent() 都返回 false戒幔,表示不攔截吠谢。這里需要注意的是事件序列土童,比如Down事件诗茎、Move事件…Up事件,從 Down到 Up 是一個完整的事件序列,對應(yīng)著手指從按下到抬起這一系列事件敢订,如果ViewGroup 攔截了 Down 事件王污,那么后續(xù)事件都會交給這個 ViewGroup 的onTouchEvent。如果 ViewGroup 攔截的不是 Down 事件楚午,那么會給之前處理這個Down 事件的 View發(fā)送一個Action_Cancel 類型的事件昭齐,通知子View這個后續(xù)的事件序列已經(jīng)被 ViewGroup 接管了,子 View 恢復(fù)之前的狀態(tài)即可矾柜。
這里舉一個常見的例子:在一個 Recyclerview 中有很多的 Button阱驾,我們首先按下了一個 button,然后滑動一段距離再松開怪蔑,這時候 Recyclerview 會跟著滑動里覆,并不會觸發(fā)這個 button 的點擊事件。這個例子中缆瓣,當(dāng)我們按下 button 時喧枷,這個 button 接收到了 Action_Down 事件,正常情況下后續(xù)的事件序列應(yīng)該由這個 button處理弓坞。但我們滑動了一段距離隧甚,這時 Recyclerview 察覺到這是一個滑動操作,攔截了這個事件序列渡冻,走了自身的 onTouchEvent()方法戚扳,反映在屏幕上就是列表的滑動。而這時 button 仍然處于按下的狀態(tài)族吻,所以在攔截的時候需要發(fā)送一個 Action_Cancel 來通知 button 恢復(fù)之前狀態(tài)咖城。
事件分發(fā)最終會走到View的dispatchTouchEvent()中。在View的dispatchTouchEvent()中沒有onInterceptTouchEvent()呼奢,這里很容易理解宜雀,View沒有child,也就不存在攔截握础。View的dispatchTouchEvent()直接return了自己的onTouchEvent()辐董。如果onTouchEvent()返回true代表事件被消費,否則未消費的事件會向上傳遞禀综,直到有View處理了事件或一直沒有消費简烘,最終回到Activity的onTouchEvent()終止。
有時候會有人混淆onTouchEvent和onTouch定枷。首先孤澎,這兩個方法都在View的dispatchTouchEvent()中:
如果touchListener不為null,并且這個View是enable的欠窒,而且onTouch返回true覆旭,都滿足時直接return true,走不到onTouchEvent()方法。
否則型将,就會觸發(fā)onTouchEvent()寂祥。因此onTouch優(yōu)先于onTouchEvent獲得事件處理權(quán)。
最后附上流程圖總結(jié):
touch事件傳遞流程
Handler的原理
Handler七兜,Message丸凭,looper 和 MessageQueue 構(gòu)成了安卓的消息機(jī)制,handler創(chuàng)建后可以通過 sendMessage 將消息加入消息隊列腕铸,然后 looper不斷的將消息從 MessageQueue 中取出來惜犀,回調(diào)到 Hander 的 handleMessage方法,從而實現(xiàn)線程的通信狠裹。
從兩種情況來說向拆,第一在UI線程創(chuàng)建Handler,此時我們不需要手動開啟looper,因為在應(yīng)用啟動時酪耳,在ActivityThread的main方法中就創(chuàng)建了一個當(dāng)前主線程的looper浓恳,并開啟了消息隊列,消息隊列是一個無限循環(huán)碗暗,為什么無限循環(huán)不會ANR?因為可以說颈将,應(yīng)用的整個生命周期就是運行在這個消息循環(huán)中的,安卓是由事件驅(qū)動的言疗,Looper.loop不斷的接收處理事件晴圾,每一個點擊觸摸或者Activity每一個生命周期都是在Looper.loop的控制之下的,looper.loop一旦結(jié)束噪奄,應(yīng)用程序的生命周期也就結(jié)束了死姚。我們可以想想什么情況下會發(fā)生ANR,第一勤篮,事件沒有得到處理都毒,第二,事件正在處理碰缔,但是沒有及時完成账劲,而對事件進(jìn)行處理的就是looper,所以只能說事件的處理如果阻塞會導(dǎo)致ANR金抡,而不能說looper的無限循環(huán)會ANR
另一種情況就是在子線程創(chuàng)建Handler,此時由于這個線程中沒有默認(rèn)開啟的消息隊列瀑焦,所以我們需要手動調(diào)用looper.prepare(),并通過looper.loop開啟消息
主線程Looper從消息隊列讀取消息,當(dāng)讀完所有消息時梗肝,主線程阻塞榛瓮。子線程往消息隊列發(fā)送消息,并且往管道文件寫數(shù)據(jù)巫击,主線程即被喚醒禀晓,從管道文件讀取數(shù)據(jù)精续,主線程被喚醒只是為了讀取消息,當(dāng)消息讀取完畢匆绣,再次睡眠驻右。因此loop的循環(huán)并不會對CPU性能有過多的消耗什黑。
ANR出現(xiàn)的情況有幾種崎淳? 怎么分析解決ANR問題?
ANR(Application Not responding)愕把。Android中拣凹,主線程(UI線程)如果在規(guī)定時內(nèi)沒有處理完相應(yīng)工作,就會出現(xiàn)ANR恨豁。具體來說嚣镜,ANR會在以下幾種情況中出現(xiàn):
(1) 輸入事件(按鍵和觸摸事件)5s內(nèi)沒被處理
(2) BroadcastReceiver的事件(onRecieve方法)在規(guī)定時間內(nèi)沒處理完(前臺廣播為10s,后臺廣播為60s)
(3) service 前臺20s后臺200s未完成啟動
(4) ContentProvider的publish在10s內(nèi)沒進(jìn)行完
內(nèi)存泄露的場景有哪些橘蜜?內(nèi)存泄漏分析工具使用方法菊匿?
常見的內(nèi)存泄露有:
1.非靜態(tài)內(nèi)部類的靜態(tài)實例
非靜態(tài)內(nèi)部類會持有外部類的引用,如果非靜態(tài)內(nèi)部類的實例是靜態(tài)的计福,就會長期的維持著外部類的引用跌捆,組織被系統(tǒng)回收,解決辦法是使用靜態(tài)內(nèi)部類
2.多線程相關(guān)的匿名內(nèi)部類和非靜態(tài)內(nèi)部類
匿名內(nèi)部類同樣會持有外部類的引用象颖,如果在線程中執(zhí)行耗時操作就有可能發(fā)生內(nèi)存泄漏佩厚,導(dǎo)致外部類無法被回收,直到耗時任務(wù)結(jié)束说订,解決辦法是在頁面退出時結(jié)束線程中的任務(wù)
3.Handler內(nèi)存泄漏
Handler導(dǎo)致的內(nèi)存泄漏也可以被歸納為非靜態(tài)內(nèi)部類導(dǎo)致的抄瓦,Handler內(nèi)部message是被存儲在MessageQueue中的,有些message不能馬上被處理陶冷,存在的時間會很長钙姊,導(dǎo)致handler無法被回收,如果handler是非靜態(tài)的埂伦,就會導(dǎo)致它的外部類無法被回收摸恍,解決辦法是1.使用靜態(tài)handler,外部類引用使用弱引用處理2.在退出頁面時移除消息隊列中的消息
4.Context導(dǎo)致內(nèi)存泄漏
根據(jù)場景確定使用Activity的Context還是Application的Context,因為二者生命周期不同赤屋,對于不必須使用Activity的Context的場景(Dialog),一律采用Application的Context,單例模式是最常見的發(fā)生此泄漏的場景立镶,比如傳入一個Activity的Context被靜態(tài)類引用,導(dǎo)致無法回收
5.靜態(tài)View導(dǎo)致泄漏
使用靜態(tài)View可以避免每次啟動Activity都去讀取并渲染View类早,但是靜態(tài)View會持有Activity的引用媚媒,導(dǎo)致無法回收,解決辦法是在Activity銷毀的時候?qū)㈧o態(tài)View設(shè)置為null(View一旦被加載到界面中將會持有一個Context對象的引用涩僻,在這個例子中缭召,這個context對象是我們的Activity栈顷,聲明一個靜態(tài)變量引用這個View,也就引用了activity)
6.WebView導(dǎo)致的內(nèi)存泄漏
WebView只要使用一次嵌巷,內(nèi)存就不會被釋放萄凤,所以WebView都存在內(nèi)存泄漏的問題,通常的解決辦法是為WebView單開一個進(jìn)程搪哪,使用AIDL進(jìn)行通信靡努,根據(jù)業(yè)務(wù)需求在合適的時機(jī)釋放掉
7.資源對象未關(guān)閉導(dǎo)致
如Cursor,F(xiàn)ile等晓折,內(nèi)部往往都使用了緩沖惑朦,會造成內(nèi)存泄漏,一定要確保關(guān)閉它并將引用置為null
8.集合中的對象未清理
集合用于保存對象漓概,如果集合越來越大漾月,不進(jìn)行合理的清理,尤其是入股集合是靜態(tài)的
9.Bitmap導(dǎo)致內(nèi)存泄漏
bitmap是比較占內(nèi)存的胃珍,所以一定要在不使用的時候及時進(jìn)行清理梁肿,避免靜態(tài)變量持有大的bitmap對象
10.監(jiān)聽器未關(guān)閉
很多需要register和unregister的系統(tǒng)服務(wù)要在合適的時候進(jìn)行unregister,手動添加的listener也需要及時移除
而對于內(nèi)存泄露的檢測,常用的工具有LeakCanary觅彰、MAT(Memory Analyer Tools)吩蔑、Android Studio自帶的Profiler。
如何避免OOM缔莲?
1.使用更加輕量的數(shù)據(jù)結(jié)構(gòu):如使用ArrayMap/SparseArray替代HashMap,HashMap更耗內(nèi)存哥纫,因為它需要額外的實例對象來記錄Mapping操作,SparseArray更加高效痴奏,因為它避免了Key Value的自動裝箱蛀骇,和裝箱后的解箱操作
2.避免枚舉的使用,可以用靜態(tài)常量或者注解@IntDef替代
3.Bitmap優(yōu)化:
a.尺寸壓縮:通過InSampleSize設(shè)置合適的縮放
b.顏色質(zhì)量:設(shè)置合適的format读拆,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6擅憔,存在很大差異
c.inBitmap:使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經(jīng)存在的內(nèi)存區(qū)域,新解碼的Bitmap會嘗試去使用之前那張Bitmap在Heap中所占據(jù)的pixel data內(nèi)存區(qū)域檐晕,而不是去問內(nèi)存重新申請一塊區(qū)域來存放Bitmap暑诸。利用這種特性,即使是上千張的圖片辟灰,也只會僅僅只需要占用屏幕所能夠顯示的圖片數(shù)量的內(nèi)存大小个榕,但復(fù)用存在一些限制,具體體現(xiàn)在:在Android 4.4之前只能重用相同大小的Bitmap的內(nèi)存芥喇,而Android 4.4及以后版本則只要后來的Bitmap比之前的小即可西采。使用inBitmap參數(shù)前,每創(chuàng)建一個Bitmap對象都會分配一塊內(nèi)存供其使用继控,而使用了inBitmap參數(shù)后械馆,多個Bitmap可以復(fù)用一塊內(nèi)存胖眷,這樣可以提高性能
4.StringBuilder替代String: 在有些時候,代碼中會需要使用到大量的字符串拼接的操作霹崎,這種時候有必要考慮使用StringBuilder來替代頻繁的“+”
5.避免在類似onDraw這樣的方法中創(chuàng)建對象珊搀,因為它會迅速占用大量內(nèi)存,引起頻繁的GC甚至內(nèi)存抖動
6.減少內(nèi)存泄漏也是一種避免OOM的方法
如何實現(xiàn)進(jìn)程蔽补剑活
a: Service 設(shè)置成 START_STICKY kill 后會被重啟(等待5秒左右)境析,重傳Intent,保持與重啟前一樣
b: 通過 startForeground將進(jìn)程設(shè)置為前臺進(jìn)程错沽, 做前臺服務(wù)簿晓,優(yōu)先級和前臺應(yīng)用一個級別眶拉,除非在系統(tǒng)內(nèi)存非常缺,否則此進(jìn)程不會被 kill
c: 雙進(jìn)程Service: 讓2個進(jìn)程互相保護(hù)對方,其中一個Service被清理后医清,另外沒被清理的進(jìn)程可以立即重啟進(jìn)程
d: 用C編寫守護(hù)進(jìn)程(即子進(jìn)程) : Android系統(tǒng)中當(dāng)前進(jìn)程(Process)fork出來的子進(jìn)程法褥,被系統(tǒng)認(rèn)為是兩個不同的進(jìn)程。當(dāng)父進(jìn)程被殺死的時候朝刊,子進(jìn)程仍然可以存活耀里,并不受影響(Android5.0以上的版本不可行)聯(lián)系廠商,加入白名單
e.鎖屏狀態(tài)下拾氓,開啟一個一像素Activity
數(shù)據(jù)庫如何進(jìn)行升級冯挎?SQLite增刪改查的基礎(chǔ)sql語句?
/**
* Create a helper object to create, open, and/or manage a database.
* This method always returns very quickly. The database is not actually
* created or opened until one of {@link #getWritableDatabase} or
* {@link #getReadableDatabase} is called.
*
* @param context to use to open or create the database
* @param name of the database file, or null for an in-memory database
* @param factory to use for creating cursor objects, or null for the default
* @param version number of the database (starting at 1); if the database is older,
* {@link #onUpgrade} will be used to upgrade the database; if the database is
* newer, {@link #onDowngrade} will be used to downgrade the database
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
this(context, name, factory, version, null);
}
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);
}
}
private SQLiteDatabase getDatabaseLocked(boolean writable) {
.......
db.beginTransaction();
try {
if (version == 0) {
onCreate(db);
} else {
if (version > mNewVersion) {
onDowngrade(db, version, mNewVersion);
} else {
onUpgrade(db, version, mNewVersion);
}
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
在 SQLiteOpenHelper 的構(gòu)造函數(shù)中,包含了一個 version 的參數(shù)咙鞍。這個參數(shù)即是數(shù)據(jù)庫的版本房官。 所以,我們可以通過修改 version 來實現(xiàn)數(shù)據(jù)庫的升級续滋。 當(dāng)version 大于原數(shù)據(jù)庫版本時翰守,onUpgrade()會被觸發(fā),可以在該方法中編寫數(shù)據(jù)庫升級邏輯疲酌。具體的數(shù)據(jù)庫升級邏輯示例可參考這里蜡峰。
常用的SQL增刪改查:
增:INSERT INTO table_name (列1, 列2,…) VALUES (值1, 值2,….)
刪: DELETE FROM 表名稱 WHERE 列名稱 = 值
改:UPDATE 表名稱 SET 列名稱 = 新值 WHERE 列名稱 = 某值
查:SELECT 列名稱(通配是*符號) FROM 表名稱
ps:操作數(shù)據(jù)表是:ALTER TABLE。該語句用于在已有的表中添加朗恳、修改或刪除列湿颅。
ALTER TABLE table_name ADD column_name datatype
ALTER TABLE table_name DROP COLUMN column_name
Android屏幕適配
一種極低成本的Android屏幕適配方式 - 字節(jié)跳動技術(shù)團(tuán)隊
AsyncTask、HandlerThread粥诫、IntentService區(qū)別和使用
AsyncTask,HandlerThread,IntentService
AsyncTask原理:內(nèi)部是Handler和兩個線程池實現(xiàn)的油航,Handler用于將線程切換到主線程,兩個線程池一個用于任務(wù)的排隊臀脏,一個用于執(zhí)行任務(wù)劝堪,當(dāng)AsyncTask執(zhí)行execute方法時會封裝出一個FutureTask對象冀自,將這個對象加入隊列中,如果此時沒有正在執(zhí)行的任務(wù)秒啦,就執(zhí)行它熬粗,執(zhí)行完成之后繼續(xù)執(zhí)行隊列中下一個任務(wù),執(zhí)行完成通過Handler將事件發(fā)送到主線程余境。AsyncTask必須在主線程初始化驻呐,因為內(nèi)部的Handler是一個靜態(tài)對象,在AsyncTask類加載的時候他就已經(jīng)被初始化了芳来。在Android3.0開始含末,execute方法串行執(zhí)行任務(wù)的,一個一個來即舌,3.0之前是并行執(zhí)行的佣盒。如果要在3.0上執(zhí)行并行任務(wù),可以調(diào)用executeOnExecutor方法顽聂。
HandlerThread原理:繼承自 Thread肥惭,start開啟線程后,會在其run方法中會通過Looper 創(chuàng)建消息隊列并開啟消息循環(huán)紊搪,這個消息隊列運行在子線程中蜜葱,所以可以將HandlerThread 中的 Looper 實例傳遞給一個 Handler,從而保證這個 Handler 的 handleMessage 方法運行在子線程中耀石,Android 中使用 HandlerThread的一個場景就是 IntentService
IntentService原理:繼承自Service牵囤,它的內(nèi)部封裝了 HandlerThread 和Handler,可以執(zhí)行耗時任務(wù)滞伟,同時因為它是一個服務(wù)揭鳞,優(yōu)先級比普通線程高很多,所以更適合執(zhí)行一些高優(yōu)先級的后臺任務(wù)诗良,HandlerThread底層通過Looper消息隊列實現(xiàn)的汹桦,所以它是順序的執(zhí)行每一個任務(wù)〖可以通過Intent的方式開啟IntentService舞骆,IntentService通過handler將每一個intent加入HandlerThread子線程中的消息隊列,通過looper按順序一個個的取出并執(zhí)行径荔,執(zhí)行完成后自動結(jié)束自己督禽,不需要開發(fā)者手動關(guān)閉
IntentService分析
Bitmap優(yōu)化
主動釋放Bitmap資源
bitmap.recycle();
bitmap = null;
在不加載圖片的前提下獲得圖片的寬高
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
圖片大小壓縮,采樣率一般是2的指數(shù),即1总处、2狈惫、4、8鹦马、16……
bitmapFactoryOptions.inSampleSize = 2;
圖片像素壓縮
Android中圖片有四種屬性胧谈,分別是:
ALPHA_8:每個像素占用1byte內(nèi)存
ARGB_4444:每個像素占用2byte內(nèi)存
ARGB_8888:每個像素占用4byte內(nèi)存 (默認(rèn))
RGB_565:每個像素占用2byte內(nèi)存
Android默認(rèn)的顏色模式為ARGB_8888忆肾,這個顏色模式色彩最細(xì)膩,顯示質(zhì)量最高菱肖。但同樣的客冈,占用的內(nèi)存也最大。 所以在對圖片效果不是特別高的情況下使用RGB_565(565沒有透明度屬性)稳强,如下:
public static Bitmap readBitMap(Context context, intresId) {
BitmapFactory.Options opt = newBitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
//獲取資源圖片
InputStreamis = context.getResources().openRawResource(resId);
returnBitmapFactory.decodeStream(is, null, opt);
}
Android框架相關(guān)
LeakCanary相關(guān)
Retrofit相關(guān)
RxJava相關(guān)
okHttp相關(guān)
rxjava相關(guān)
mvp相關(guān)
以上移步筆者博客OkHttp+Retrofit+Dagger2+RxJava+MVP架構(gòu) 學(xué)習(xí)筆記
Java相關(guān)
該部分內(nèi)容筆者按照該博客集合順序+閱讀源碼進(jìn)行復(fù)習(xí)场仲。
Java相關(guān)博客集合
collection里面有什么子類?
(其實面試的時候聽到這個問題的時候退疫,你要知道渠缕,面試官是想考察List,Set)
list和set是實現(xiàn)了collection接口的褒繁。
List:
1.可以允許重復(fù)的對象亦鳞。
2.可以插入多個null元素。
3.是一個有序容器澜汤,保持了每個元素的插入順序蚜迅,輸出的順序就是插入的順序舵匾。
4.常用的實現(xiàn)類有 ArrayList俊抵、LinkedList 和 Vector。ArrayList 最為流行坐梯,它提供了使用索引的隨意訪問徽诲,而 LinkedList 則對于經(jīng)常需要從 List 中添加或刪除元素的場合更為合適。Set:
1.不允許重復(fù)對象
2.無序容器吵血,你無法保證每個元素的存儲順序谎替,TreeSet通過 Comparator 或者 Comparable 維護(hù)了一個排序順序。
3.只允許一個 null 元素
4.Set 接口最流行的幾個實現(xiàn)類是 HashSet蹋辅、LinkedHashSet 以及 TreeSet钱贯。最流行的是基于HashMap 實現(xiàn)的 HashSet;TreeSet 還實現(xiàn)了 SortedSet 接口侦另,因此 TreeSet 是一個根據(jù)其compare() 和 compareTo() 的定義進(jìn)行排序的有序容器秩命。
什么場景下使用list,set褒傅,map弃锐?
- 如果你經(jīng)常會使用索引來對容器中的元素進(jìn)行訪問,那么 List 是你的正確的選擇殿托。如果你已經(jīng)知道索引了的話霹菊,那么 List 的實現(xiàn)類比如 ArrayList 可以提供更快速的訪問,如果經(jīng)常添加刪除元素的,那么肯定要選擇LinkedList支竹。
- 如果你想容器中的元素能夠按照它們插入的次序進(jìn)行有序存儲旋廷,那么還是 List鸠按,因為 List 是一個有序容器,它按照插入順序進(jìn)行存儲饶碘。
- 如果你想保證插入元素的唯一性待诅,也就是你不想有重復(fù)值的出現(xiàn),那么可以選擇一個 Set 的實現(xiàn)類熊镣,比如 HashSet卑雁、LinkedHashSet 或者 TreeSet。所有 Set 的實現(xiàn)類都遵循了統(tǒng)一約束比如唯一性绪囱,而且還提供了額外的特性比如 TreeSet 還是一個 SortedSet测蹲,所有存儲于 TreeSet 中的元素可以使用 Java 里的 Comparator 或者 Comparable 進(jìn)行排序。LinkedHashSet 也按照元素的插入順序?qū)λ鼈冞M(jìn)行存儲鬼吵。
- 如果你以鍵和值的形式進(jìn)行數(shù)據(jù)存儲那么 Map 是你正確的選擇扣甲。你可以根據(jù)你的后續(xù)需要從 Hashtable、HashMap齿椅、TreeMap 中進(jìn)行選擇琉挖。
fail-fast機(jī)制
“快速失敗”也就是fail-fast,它是Java集合的一種錯誤檢測機(jī)制涣脚。當(dāng)多個線程對集合進(jìn)行結(jié)構(gòu)上的改變的操作時示辈,有可能會產(chǎn)生fail-fast機(jī)制。記住是有可能遣蚀,而不是一定矾麻。例如:假設(shè)存在兩個線程(線程1、線程2)芭梯,線程1通過Iterator在遍歷集合A中的元素险耀,在某個時候線程2修改了集合A的結(jié)構(gòu)(是結(jié)構(gòu)上面的修改,而不是簡單的修改集合元素的內(nèi)容)玖喘,那么這個時候程序就會拋出ConcurrentModificationException 異常甩牺,從而產(chǎn)生fail-fast機(jī)制。
相關(guān)博客
ArrayList和LinkedList的區(qū)別
Arraylist
底層是基于動態(tài)數(shù)組累奈,根據(jù)下表隨機(jī)訪問數(shù)組元素的效率高贬派,向數(shù)組尾部添加元素的效率高;但是费尽,刪除數(shù)組中的數(shù)據(jù)以及向數(shù)組中間添加數(shù)據(jù)效率低赠群,因為需要移動數(shù)組。例如最壞的情況是刪除第一個數(shù)組元素旱幼,則需要將第2至第n個數(shù)組元素各向前移動一位查描。而之所以稱為動態(tài)數(shù)組,是因為Arraylist在數(shù)組元素超過其容量大,Arraylist可以進(jìn)行擴(kuò)容(針對JDK1.8? 數(shù)組擴(kuò)容后的容量是擴(kuò)容前的1.5倍)Linkedlist
基于鏈表的動態(tài)數(shù)組冬三,數(shù)據(jù)添加刪除效率高匀油,只需要改變指針指向即可,但是訪問數(shù)據(jù)的平均效率低勾笆,需要對鏈表進(jìn)行遍歷敌蚜。
HashMap、Hashtable窝爪、ConcurrentHashMap的原理與區(qū)別
HashTable
- 底層數(shù)組+鏈表實現(xiàn)弛车,無論key還是value都不能為null,線程安全蒲每,實現(xiàn)線程安全的方式是在修改數(shù)據(jù)時鎖住整個HashTable纷跛,效率低,ConcurrentHashMap做了相關(guān)優(yōu)化
- 初始size為11邀杏,擴(kuò)容:newsize = olesize*2+1
- 計算index的方法:index = (hash & 0x7FFFFFFF) % tab.length
HashMap
- 底層數(shù)組+鏈表實現(xiàn)贫奠,可以存儲null鍵和null值,線程不安全
- 初始size為16望蜡,擴(kuò)容:newsize = oldsize*2唤崭,size一定為2的n次冪
- 擴(kuò)容針對整個Map,每次擴(kuò)容時脖律,原來數(shù)組中的元素依次重新計算存放位置谢肾,并重新插入
- 插入元素后才判斷該不該擴(kuò)容,有可能無效擴(kuò)容(插入后如果擴(kuò)容状您,如果沒有再次插入勒叠,就會產(chǎn)生無效擴(kuò)容)
- 當(dāng)Map中元素總數(shù)超過Entry數(shù)組的75%,觸發(fā)擴(kuò)容操作膏孟,為了減少鏈表長度,元素分配更均勻
- 計算index方法:index = hash & (tab.length – 1)
HashMap面試題
ConcurrentHashMap
- 底層采用分段的數(shù)組+鏈表實現(xiàn)拌汇,線程安全
- 通過把整個Map分為N個Segment柒桑,可以提供相同的線程安全,但是效率提升N倍噪舀,默認(rèn)提升16倍魁淳。(讀操作不加鎖,由于HashEntry的value變量是 volatile的与倡,也能保證讀取到最新的值界逛。)
- Hashtable的synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨占纺座,ConcurrentHashMap允許多個修改操作并發(fā)進(jìn)行息拜,其關(guān)鍵在于使用了鎖分離技術(shù)
- 有些方法需要跨段,比如size()和containsValue(),它們可能需要鎖定整個表而而不僅僅是某個段少欺,這需要按順序鎖定所有段喳瓣,操作完畢后,又按順序釋放所有段的鎖
- 擴(kuò)容:段內(nèi)擴(kuò)容(段內(nèi)元素超過該段對應(yīng)Entry數(shù)組長度的75%觸發(fā)擴(kuò)容赞别,不會對整個Map進(jìn)行擴(kuò)容)畏陕,插入前檢測需不需要擴(kuò)容,有效避免無效擴(kuò)容
Hash沖突如何解決仿滔?
開放定址法
這種方法也稱再散列法惠毁,其基本思想是:當(dāng)關(guān)鍵字key的哈希地址p=H(key)出現(xiàn)沖突時,以p為基礎(chǔ)崎页,產(chǎn)生另一個哈希地址p1仁讨,如果p1仍然沖突,再以p為基礎(chǔ)实昨,產(chǎn)生另一個哈希地址p2洞豁,…,直到找出一個不沖突的哈希地址pi 荒给,將相應(yīng)元素存入其中丈挟。這種方法有一個通用的再散列函數(shù)形式:
Hi=(H(key)+di)% m i=1,2志电,…曙咽,n
其中H(key)為哈希函數(shù),m 為表長挑辆,di稱為增量序列例朱。增量序列的取值方式不同,相應(yīng)的再散列方式也不同鱼蝉。主要有以下三種:
- 線性探測再散列
dii=1洒嗤,2,3魁亦,…渔隶,m-1
這種方法的特點是:沖突發(fā)生時,順序查看表中下一單元洁奈,直到找出一個空單元或查遍全表间唉。 - 二次探測再散列
di=12,-12利术,22呈野,-22,…印叁,k2被冒,-k2 ( k<=m/2 )
這種方法的特點是:沖突發(fā)生時军掂,在表的左右進(jìn)行跳躍式探測,比較靈活姆打。 - 偽隨機(jī)探測再散列
再哈希法
這種方法是同時構(gòu)造多個不同的哈希函數(shù):
Hi=RH1(key) i=1良姆,2,…幔戏,k
當(dāng)哈希地址Hi=RH1(key)發(fā)生沖突時玛追,再計算Hi=RH2(key)……,直到?jīng)_突不再產(chǎn)生闲延。這種方法不易產(chǎn)生聚集痊剖,但增加了計算時間。
鏈地址法
這種方法的基本思想是將所有哈希地址為i的元素構(gòu)成一個稱為同義詞鏈的單鏈表垒玲,并將單鏈表的頭指針存在哈希表的第i個單元中陆馁,因而查找、插入和刪除主要在同義詞鏈中進(jìn)行合愈。鏈地址法適用于經(jīng)常進(jìn)行插入和刪除的情況叮贩。
如何理解Java的多態(tài)?其中佛析,重載和重寫有什么區(qū)別益老?
多態(tài)是同一個行為具有多個不同表現(xiàn)形式或形態(tài)的能力,多態(tài)是同一個接口寸莫,使用不同的實例而執(zhí)行不同操作捺萌,多態(tài)就是程序運行期間才確定,一個引用變量倒底會指向哪個類的實例對象膘茎,該引用變量發(fā)出的方法調(diào)用到底是哪個類中實現(xiàn)的方法桃纯。
多態(tài)存在的三個必要條件是:繼承,重寫披坏,父類引用指向子類引用态坦。
多態(tài)的三個實現(xiàn)方式是:重寫,接口刮萌,抽象類和抽象方法驮配。
final關(guān)鍵字的用法?
final 可以修飾類着茸、變量和方法。修飾類代表這個類不可被繼承琐旁。修飾變量代表此變量不可被改變涮阔。修飾方法表示此方法不可被重寫 (override)。
死鎖是怎么導(dǎo)致的灰殴?如何定位死鎖敬特?
某個任務(wù)在等待另一個任務(wù)掰邢,而后者又等待別的任務(wù),這樣一直下去伟阔,直到這個鏈條上的任務(wù)又在等待第一個任務(wù)釋放鎖辣之。這得到了一個任務(wù)之間互相等待的連續(xù)循環(huán),沒有哪個線程能繼續(xù)皱炉。這被稱之為死鎖怀估。當(dāng)以下四個條件同時滿足時,就會產(chǎn)生死鎖:
(1) 互斥條件合搅。任務(wù)所使用的資源中至少有一個是不能共享的多搀。
(2) 任務(wù)必須持有一個資源,同時等待獲取另一個被別的任務(wù)占有的資源灾部。
(3) 資源不能被強(qiáng)占康铭。
(4) 必須有循環(huán)等待。一個任務(wù)正在等待另一個任務(wù)所持有的資源赌髓,后者又在等待別的任務(wù)所持有的資源从藤,這樣一直下去,直到有一個任務(wù)在等待第一個任務(wù)所持有的資源锁蠕,使得大家都被鎖住夷野。
首先模擬問題定位,使用jstack匿沛,使用 jps或者系統(tǒng)的ps命令扫责、任務(wù)管理器等工具,確定進(jìn)程ID逃呼。
然后獲取線程棧:
${JAVA_HOME}\bin\jstack your_pid
避免死鎖的方法思路:
盡量避免使用多個鎖鳖孤,并且只有需要的時候才持有鎖。
如果使用多個鎖抡笼,盡量設(shè)計好鎖的獲取順序苏揣。例如銀行家算法。
使用帶超時的方法推姻,為程序帶來更多的可控性平匈。
反射【待補(bǔ)全】
說下java中的線程創(chuàng)建方式,線程池的工作原理藏古。
java中有三種創(chuàng)建線程的方式增炭,或者說四種
- 繼承Thread類實現(xiàn)多線程
- 實現(xiàn)Runnable接口
- 實現(xiàn)Callable接口
- 通過線程池
線程池的工作原理:線程池可以減少創(chuàng)建和銷毀線程的次數(shù),從而減少系統(tǒng)資源的消耗拧晕,當(dāng)一個任務(wù)提交到線程池時
a. 首先判斷核心線程池中的線程是否已經(jīng)滿了隙姿,如果沒滿,則創(chuàng)建一個核心線程執(zhí)行任務(wù)厂捞,否則進(jìn)入下一步
b. 判斷工作隊列是否已滿输玷,沒有滿則加入工作隊列队丝,否則執(zhí)行下一步
c. 判斷線程數(shù)是否達(dá)到了最大值,如果不是欲鹏,則創(chuàng)建非核心線程執(zhí)行任務(wù)机久,否則執(zhí)行飽和策略,默認(rèn)拋出異常
進(jìn)程與線程的區(qū)別
進(jìn)程是資源(CPU赔嚎、內(nèi)存等)分配的基本單位膘盖,它是程序執(zhí)行時的一個實例。
線程是程序執(zhí)行時的最小單位尽狠,它是進(jìn)程的一個執(zhí)行流衔憨,是CPU調(diào)度和分派的基本單位,一個進(jìn)程可以由很多個線程組成袄膏,線程間共享進(jìn)程的所有資源践图,每個線程有自己的堆棧和局部變量。線程由CPU獨立調(diào)度執(zhí)行沉馆,在多CPU環(huán)境下就允許多個線程同時運行码党。同樣多線程也可以實現(xiàn)并發(fā)操作,每個請求分配一個線程來處理斥黑。
設(shè)計模式相關(guān)
說下你所知道的設(shè)計模式與使用場景
建造者模式
將一個復(fù)雜對象的構(gòu)建與它的表示分離揖盘,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
使用場景比如最常見的AlertDialog,拿我們開發(fā)過程中舉例锌奴,比如Camera開發(fā)過程中兽狭,可能需要設(shè)置一個初始化的相機(jī)配置,設(shè)置攝像頭方向鹿蜀,閃光燈開閉箕慧,成像質(zhì)量等等,這種場景下就可以使用建造者模式
裝飾者模式
動態(tài)的給一個對象添加一些額外的職責(zé)茴恰,就增加功能來說颠焦,裝飾模式比生成子類更為靈活。裝飾者模式可以在不改變原有類結(jié)構(gòu)的情況下曾強(qiáng)類的功能往枣,比如Java中的BufferedInputStream 包裝FileInputStream伐庭,舉個開發(fā)中的例子,比如在我們現(xiàn)有網(wǎng)絡(luò)框架上需要增加新的功能分冈,那么再包裝一層即可圾另,裝飾者模式解決了繼承存在的一些問題,比如多層繼承代碼的臃腫雕沉,使代碼邏輯更清晰
生產(chǎn)者消費者模式
生產(chǎn)者消費者模式并不是GOF提出的23種設(shè)計模式之一盯捌,23種設(shè)計模式都是建立在面向?qū)ο蟮幕A(chǔ)之上的,但其實面向過程的編程中也有很多高效的編程模式蘑秽,生產(chǎn)者消費者模式便是其中之一饺著,它是我們編程過程中最常用的一種設(shè)計模式。
在實際的軟件開發(fā)過程中肠牲,經(jīng)常會碰到如下場景:某個模塊負(fù)責(zé)產(chǎn)生數(shù)據(jù)幼衰,這些數(shù)據(jù)由另一個模塊來負(fù)責(zé)處理(此處的模塊是廣義的,可以是類缀雳、函數(shù)渡嚣、線程、進(jìn)程等)肥印。產(chǎn)生數(shù)據(jù)的模塊识椰,就形象地稱為生產(chǎn)者;而處理數(shù)據(jù)的模塊深碱,就稱為消費者腹鹉。
單單抽象出生產(chǎn)者和消費者,還夠不上是生產(chǎn)者/消費者模式敷硅。該模式還需要有一個緩沖區(qū)處于生產(chǎn)者和消費者之間功咒,作為一個中介。生產(chǎn)者把數(shù)據(jù)放入緩沖區(qū)绞蹦,而消費者從緩沖區(qū)取出數(shù)據(jù)力奋。
JVM相關(guān)
談一下JVM內(nèi)存區(qū)域劃分?哪部分是線程公有的幽七,哪部分是私有的景殷?
JVM 的內(nèi)存區(qū)域可以分為兩類:線程私有和區(qū)域和線程共有的區(qū)域。
線程私有的區(qū)域:程序計數(shù)器澡屡、JVM 虛擬機(jī)棧猿挚、本地方法棧;
線程共有的區(qū)域:堆挪蹭、方法區(qū)亭饵、運行時常量池。
程序計數(shù)器梁厉,也有稱作PC寄存器辜羊。每個線程都有一個私有的程序計數(shù)器,任何時間一個線程都只會有一個方法正在執(zhí)行词顾,也就是所謂的當(dāng)前方法八秃。程序計數(shù)器存放的就是這個當(dāng)前方法的JVM指令地址。當(dāng)CPU需要執(zhí)行指令時肉盹,需要從程序計數(shù)器中得到當(dāng)前需要執(zhí)行的指令所在存儲單元的地址昔驱,然后根據(jù)得到的地址獲取到指令,在得到指令之后上忍,程序計數(shù)器便自動加1或者根據(jù)轉(zhuǎn)移指針得到下一條指令的地址骤肛,如此循環(huán)纳本,直至執(zhí)行完所有的指令。
JVM虛擬機(jī)棧腋颠。創(chuàng)建線程的時候會創(chuàng)建線程內(nèi)的虛擬機(jī)棧繁成,棧中存放著一個個的棧幀,對應(yīng)著一個個方法的調(diào)用淑玫。JVM 虛擬機(jī)棧有兩種操作巾腕,分別是壓棧和出站。棧幀中存放著局部變量表(Local Variables)絮蒿、操作數(shù)棧(Operand Stack)尊搬、指向當(dāng)前方法所屬的類的運行時常量池的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些額外的附加信息土涝。
本地方法棧佛寿。本地方法棧與Java棧的作用和原理非常相似。區(qū)別只不過是Java棧是為執(zhí)行Java方法服務(wù)的回铛,而本地方法棧則是為執(zhí)行本地方法(Native Method)服務(wù)的狗准。在JVM規(guī)范中,并沒有對本地方發(fā)展的具體實現(xiàn)方法以及數(shù)據(jù)結(jié)構(gòu)作強(qiáng)制規(guī)定茵肃,虛擬機(jī)可以自由實現(xiàn)它腔长。在HotSopt虛擬機(jī)中直接就把本地方法棧和Java棧合二為一。
堆验残。堆是內(nèi)存管理的核心區(qū)域捞附,用來存放對象實例。幾乎所有創(chuàng)建的對象實例都會直接分配到堆上您没。所以堆也是垃圾回收的主要區(qū)域鸟召,垃圾收集器會對堆有著更細(xì)的劃分,最常見的就是把堆劃分為新生代和老年代氨鹏。java堆允許處于不連續(xù)的物理內(nèi)存空間中欧募,只要邏輯連續(xù)即可。堆中如果沒有空間完成實例分配無法擴(kuò)展時將會拋出OutOfMemoryError異常仆抵。
方法區(qū)跟继。方法區(qū)與堆一樣所有線程所共享的內(nèi)存區(qū)域,它用于存儲已被虛擬機(jī)加載的類信息镣丑、常量舔糖、靜態(tài)變量、及時編譯器編譯后的代碼等數(shù)據(jù)莺匠。在Class文件中除了類的字段金吗、方法、接口等描述信息外,還有一項信息是常量池摇庙,用來存儲編譯期間生成的字面量和符號引用旱物。
其實除了程序計數(shù)器,其他的部分都會發(fā)生 OOM跟匆。
堆异袄。 通常發(fā)生的 OOM 都會發(fā)生在堆中,最常見的可能導(dǎo)致 OOM 的原因就是內(nèi)存泄漏玛臂。
JVM虛擬機(jī)棧和本地方法棧。 當(dāng)我們寫一個遞歸方法封孙,這個遞歸方法沒有循環(huán)終止條件迹冤,最終會導(dǎo)致 StackOverflow 的錯誤。當(dāng)然虎忌,如果椗葆悖空間擴(kuò)展失敗,也是會發(fā)生 OOM 的膜蠢。
方法區(qū)堪藐。方法區(qū)現(xiàn)在基本上不太會發(fā)生 OOM,但在早期內(nèi)存中加載的類信息過多的情況下也是會發(fā)生 OOM 的挑围。
GC的兩種判定方法(對象存活判定):
引用計數(shù)和可達(dá)性分析礁竞。
引用計數(shù)方式
最基本的形態(tài)就是讓每個被管理的對象與一個引用計數(shù)器關(guān)聯(lián)在一起,該計數(shù)器記錄著該對象當(dāng)前被引用的次數(shù)杉辙,每當(dāng)創(chuàng)建一個新的引用指向該對象時其計數(shù)器就加1模捂,每當(dāng)指向該對象的引用失效時計數(shù)器就減1。當(dāng)該計數(shù)器的值降到0就認(rèn)為對象死亡蜘矢。
可達(dá)性分析算法
將GC Roots對象作為起始節(jié)點狂男,向下搜索,搜索走過的路徑為引用鏈品腹;當(dāng)一個對象到GC Roots沒有引用鏈時岖食,則該對象是不可用的;
可作為GC Roots的對象:
方法區(qū)中靜態(tài)屬性引用的對象
方法區(qū)中常量引用的對象
虛擬機(jī)棧引用的對象 (棧幀中本地變量表)
本地方法棧中JNI引用的對象 (Native方法)
GC的三種收集方法:
標(biāo)記清除舞吭、標(biāo)記整理泡垃、復(fù)制算法的原理與特點,分別用在什么地方镣典,如果讓你優(yōu)化收集方法兔毙,有什么思路?
標(biāo)記清除算法
最基礎(chǔ)的收集算法兄春,其他收集算法都是基于這種思想澎剥。標(biāo)記清除算法分為“標(biāo)記”和“清除”兩個階段:首先標(biāo)記出需要回收的對象,標(biāo)記完成之后統(tǒng)一清除對象。
它的主要缺點:
①.標(biāo)記和清除過程效率不高 哑姚。
②.標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片祭饭。
標(biāo)記整理
標(biāo)記操作和“標(biāo)記-清除”算法一致,后續(xù)操作不只是直接清理對象,而是在清理無用對象完成后讓所有存活的對象都向一端移動泽西,并更新引用其對象的指針旅敷。主要缺點:在標(biāo)記-清除的基礎(chǔ)上還需進(jìn)行對象的移動,成本相對較高寺鸥,好處則是不會產(chǎn)生內(nèi)存碎片。
復(fù)制算法
它將可用內(nèi)存容量劃分為大小相等的兩塊品山,每次只使用其中的一塊胆建。當(dāng)這一塊用完之后,就將還存活的對象復(fù)制到另外一塊上面肘交,然后在把已使用過的內(nèi)存空間一次理掉笆载。這樣使得每次都是對其中的一塊進(jìn)行內(nèi)存回收,不會產(chǎn)生碎片等情況涯呻,只要移動堆訂的指針凉驻,按順序分配內(nèi)存即可,實現(xiàn)簡單复罐,運行高效涝登。主要缺點:內(nèi)存縮小為原來的一半。
雙親委派模型:
Bootstrap ClassLoader市栗、Extension ClassLoader缀拭、ApplicationClassLoader。
啟動類加載器填帽,負(fù)責(zé)將存放在<JAVA_HOME>\lib目錄中的蛛淋,或者被-Xbootclasspath參數(shù)所指定的路徑中,并且是虛擬機(jī)識別的(僅按照文件名識別篡腌,如rt.jar褐荷,名字不符合的類庫即時放在lib目錄中也不會被加載)類庫加載到虛擬機(jī)內(nèi)存中。啟動類加載器無法被java程序直接引用嘹悼。
擴(kuò)展類加載器:負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的叛甫,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫,開發(fā)者可以直接使用該類加載器杨伙。
應(yīng)用程序類加載器:負(fù)責(zé)加載用戶路徑上所指定的類庫其监,開發(fā)者可以直接使用這個類加載器,也是默認(rèn)的類加載器限匣。 三種加載器的關(guān)系:啟動類加載器->擴(kuò)展類加載器->應(yīng)用程序類加載器->自定義類加載器抖苦。
這種關(guān)系即為類加載器的雙親委派模型。其要求除啟動類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器锌历。這里類加載器之間的父子關(guān)系一般不以繼承關(guān)系實現(xiàn)贮庞,而是用組合的方式來復(fù)用父類的代碼。
雙親委派模型的工作過程:如果一個類加載器接收到了類加載的請求究西,它首先把這個請求委托給他的父類加載器去完成窗慎,每個層次的類加載器都是如此,因此所有的加載請求都應(yīng)該傳送到頂層的啟動類加載器中卤材,只有當(dāng)父加載器反饋自己無法完成這個加載請求(它在搜索范圍中沒有找到所需的類)時遮斥,子加載器才會嘗試自己去加載。
好處:java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系商膊。例如類java.lang.Object伏伐,它存放在rt.jar中,無論哪個類加載器要加載這個類晕拆,最終都會委派給啟動類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個類材蹬。相反实幕,如果用戶自己寫了一個名為java.lang.Object的類,并放在程序的Classpath中堤器,那系統(tǒng)中將會出現(xiàn)多個不同的Object類昆庇,java類型體系中最基礎(chǔ)的行為也無法保證,應(yīng)用程序也會變得一片混亂闸溃。
實現(xiàn):在java.lang.ClassLoader的loadClass()方法中整吆,先檢查是否已經(jīng)被加載過,若沒有加載則調(diào)用父類加載器的loadClass()方法辉川,若父加載器為空則默認(rèn)使用啟動類加載器作為父加載器表蝙。如果父加載失敗,則拋出ClassNotFoundException異常后乓旗,再調(diào)用自己的findClass()方法進(jìn)行加載府蛇。
高并發(fā)相關(guān)
ThreadLocal的設(shè)計理念與作用
ThreadPool用法與優(yōu)勢(這里在Android SDK原生的AsyncTask底層也有使用)
線程池的底層實現(xiàn)和工作原理(建議寫一個雛形簡版源碼實現(xiàn))
計算機(jī)網(wǎng)絡(luò)相關(guān)
TCP/IP五層模型
在TCP/IP四層協(xié)議中,網(wǎng)絡(luò)層又被稱為網(wǎng)際層(用網(wǎng)際層這個名字是強(qiáng)調(diào)這一層是為了解決不同網(wǎng)絡(luò)的互連問題)屿愚,而數(shù)據(jù)鏈路層與物理層合并為網(wǎng)絡(luò)接口層汇跨。
TCP協(xié)議
- Transmission Control Protocol,傳輸控制協(xié)議
- 面向連接的協(xié)議
- 需要三次握手建立連接
- 需要四次揮手?jǐn)嚅_連接
- TCP報頭最小長度:20字節(jié)
三次握手過程
- 客戶端發(fā)送:SYN = 1, SEQ = X, 端口號
- 服務(wù)器回復(fù):SYN = 1, ACK = X + 1, SEQ = Y
- 客戶端發(fā)送:ACK = Y + 1, SEQ = X + 1
確認(rèn)應(yīng)答信號ACK = 收到的SEQ + 1妆距。 連接建立中穷遂,同步信號SYN始終為1。連接建立后娱据,同步信號SYN=0蚪黑。
四次揮手過程
- A向B提出停止連接請求,F(xiàn)IN = 1
- B收到,ACK = 1
- B向A提出停止連接請求祠锣,F(xiàn)IN = 1
- A收到酷窥,ACK = 1
SYN:同步位
seq:編號位
ACK:確認(rèn)位
ack:確認(rèn)編號位