Android面經(jīng)| 問題歸納

面經(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)中四大組件的啟動(dòng)、切換、調(diào)度及應(yīng)用進(jìn)程的管理和調(diào)度等工作,其職責(zé)與操作系統(tǒng)中的進(jìn)程管理和調(diào)度模塊類似。
看源碼談?wù)凙MS啟動(dòng)過程:ActivityManagerService分析——AMS啟動(dòng)流程

Activity相關(guān)

SingleTask優(yōu)化

//如果標(biāo)簽為singleTask的activity不在棧頂
onNewIntent()
//如果標(biāo)簽為singleTask的activity在棧頂
onNewIntent() -> onStart() -> onResume() -> ...

回退棧中的activity不會(huì)調(diào)用onCreate(),但是這里雖然onNewIntent被調(diào)用了,但是Intent中所保存的數(shù)據(jù)卻仍然還是舊數(shù)據(jù),因此需要進(jìn)一步重寫。

/***
* 將activity 的創(chuàng)建模式設(shè)置為singletask, 
* 使用這個(gè)方法可以再其他activity想這個(gè)activity發(fā)送Intent時(shí),這個(gè)Intent能夠及時(shí)更新
*/
@Override
protected void onNewIntent(Intent intent)
{
     super.onNewIntent(intent);
     setIntent(intent); //這一句必須的,否則Intent無(wú)法獲得最新的數(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)性。這個(gè)參數(shù)標(biāo)識(shí)了一個(gè) Activity 所需要的任務(wù)棧的名字,默認(rèn)情況下,所有 Activity 所需的任務(wù)棧的名字為應(yīng)用的包名,當(dāng) Activity 設(shè)置了 taskAffinity 屬性,那么這個(gè) Activity 在被創(chuàng)建時(shí)就會(huì)運(yùn)行在和 taskAffinity 名字相同的任務(wù)棧中,如果沒有,則新建 taskAffinity 指定的任務(wù)棧,并將 Activity 放入該棧中。另外,taskAffinity 屬性主要和 singleTask 或者 allowTaskReparenting 屬性配對(duì)使用,在其他情況下沒有意義。

allowTaskReparenting = "true"

在這種情況下,Activity 可以從其啟動(dòng)的任務(wù)移動(dòng)到與其具有關(guān)聯(lián)的任務(wù)(如果該任務(wù)出現(xiàn)在前臺(tái))。

啟動(dòng)模式應(yīng)用場(chǎng)景

launchMode 使用場(chǎng)景
singleTop 適合啟動(dòng)同類型的 Activity,例如接收通知啟動(dòng)的內(nèi)容顯示頁(yè)面
singleTask 適合作為程序入口
singleInstance 適合需要與程序分離開的頁(yè)面,例如鬧鈴的響鈴界面

Intent

顯式啟動(dòng):
一般用于啟動(dòng)應(yīng)用內(nèi)的Activity

隱式啟動(dòng):
a.例子:Intent intent = new Intent(String action,Uri uri);

b.一般用于啟動(dòng)應(yīng)用外的Activity,操作系統(tǒng)會(huì)自動(dòng)把匹配隱式Intent的Acttivity顯
示出來(lái)供用戶選擇,匹 配的規(guī)則跟Activity聲明的 Intent-filter 有關(guān)

c.主要組成部分:

(1)action 要執(zhí)行的操作。也可以通過 setAction() 設(shè)置

(2)uri待訪問數(shù)據(jù)的位置。也可以通過 setData() 和 setDataAndType() 設(shè)
置。可以是網(wǎng)頁(yè)的URL,某個(gè)文件的,或指向 ContentProvider 的某個(gè)內(nèi)容 URI

(3)操作涉及的的數(shù)據(jù)類型。setType() 和 setDataAndType()設(shè)置。如intent.setType("text/html")

(4)可選類別。描述何時(shí),何地或者如何啟動(dòng)某個(gè) Activity。
intent.addCategory(Intent.CATEGORY_LAUNCHER)

參考博客

Service相關(guān)

兩種啟動(dòng)方式

1.startService()啟動(dòng)方式:主要用于執(zhí)行后臺(tái)計(jì)算
2.bindService()啟動(dòng)方式:主要用于和其它組件的交互
說(shuō)明:這兩種狀態(tài)是可以共存的,即一個(gè)Service既可以處于啟動(dòng)狀態(tài),也可以同時(shí)處于綁定狀態(tài)。

生命周期

startService() -> onCreate() -> onStartCommand() -> Service運(yùn)行 -> onDestroy()
bindService() -> onCreate() -> onBind() -> Service運(yùn)行 -> onUnBind() -> onDestroy()

service 運(yùn)行于主線程

int onStartCommand(Intent intent, int flags, int startId)會(huì)在每次調(diào)用startService時(shí)回調(diào)

FLAG使用START_FLAG_REDELIVERY,意味著當(dāng)Service因內(nèi)存不足而被系統(tǒng)kill后,則會(huì)重建服務(wù),并通過傳遞給服務(wù)的最后一個(gè) Intent 調(diào)用 onStartCommand()

onStartCommand()返回值:
START_STICKY,START_NOT_STICKY,START_REDELIVER_INTENT

service詳細(xì)介紹

Service 如何和 Activity 進(jìn)行通信?

① 通過綁定服務(wù)的方式。在綁定的服務(wù)中聲明一個(gè)Binder類,并創(chuàng)建一個(gè)Binder對(duì)象,在onBind()函數(shù)中返回這個(gè)對(duì)象,并讓Activity實(shí)現(xiàn)ServiceConnection接口,在OnServiceConnected方法中獲取到Service提供的這個(gè)Binder對(duì)象,通過這個(gè)對(duì)象的各種自定義的方法就能完成Service與Activity的通信。
② 通過Intent的方式,在StartService()中需要傳入一個(gè)Intent對(duì)象作為參數(shù),通過這個(gè)Intent實(shí)例對(duì)象進(jìn)行實(shí)現(xiàn)通信。
③ 通過Callback和Handler的方式,在綁定的服務(wù)中聲明一個(gè)Binder類,并創(chuàng)建一個(gè)Binder對(duì)象,在onBind()函數(shù)中返回這個(gè)對(duì)象,讓Activity實(shí)現(xiàn)ServiceConnection接口,并且在OnserviceConnected方法中實(shí)例化Service中的CallBack接口,并且實(shí)現(xiàn)OnDataChange()方法,其中的實(shí)質(zhì)是一段Handler代碼,可以在其中完成耗時(shí)操作,以這種方式完成通信。

BroadcastReciever相關(guān)

寫一個(gè)類繼承BroadcastReceiver重寫onReceive方法,注意onReceive是主線程不要做耗時(shí)操作否則阻塞10s會(huì)ANR,onReceive()中耗時(shí)操作不能開線程做,可以使用goAsync()或使用service來(lái)做(由于onReceive在結(jié)束后會(huì)釋放資源,依賴線程也很有可能會(huì)被釋放)

通過LocalBroadcastManager動(dòng)態(tài)注冊(cè)的Receiver只會(huì)在App應(yīng)用內(nèi)廣播。

兩種注冊(cè)方式

1.靜態(tài)注冊(cè),在注冊(cè)清單文件進(jìn)行聲明
2.動(dòng)態(tài)注冊(cè),在代碼中動(dòng)態(tài)進(jìn)行注冊(cè)

//靜態(tài)注冊(cè)
<receiver android:name=".MyReceiver">
            <intent-filter>
                <action android:name="kt.com.MyReceiver"/>
            </intent-filter>
</receiver>
//動(dòng)態(tài)注冊(cè)
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("kt.com.MyReceiver");
registerReceiver(new MyReceiver(),intentFilter);

兩種注冊(cè)方式的區(qū)別

1.動(dòng)態(tài)注冊(cè)的廣播是非常駐型廣播,此時(shí)廣播是跟隨宿主的生命周期的,宿主不在了廣播也就不在了。
2.靜態(tài)注冊(cè)的廣播是常駐型廣播,即應(yīng)用程序關(guān)閉后,依然能夠收到廣播。

參考博客

ContentProvider相關(guān)

ContentProvider管理android以結(jié)構(gòu)化方式存放的數(shù)據(jù)。他以相對(duì)安全的方式封裝數(shù)據(jù)并且提供簡(jiǎn)易的處理機(jī)制。Content provider提供不同進(jìn)程間數(shù)據(jù)交互的標(biāo)準(zhǔn)化接口。
參考博客

Broadcast的分類?有序,無(wú)序?粘性,非粘性?本地廣播?

  • 廣播可以分為有序廣播、無(wú)序廣播、本地廣播、粘性廣播。其中無(wú)序廣播通過sendBroadcast(intent)發(fā)送,有序廣播通過sendOrderedBroadcast(intent)發(fā)送。

  • 有序廣播。
    (1) 有序廣播可以用priority來(lái)調(diào)整優(yōu)先級(jí) 取值范圍-1000~+1000,默認(rèn)為0,數(shù)值越大優(yōu)先級(jí)越高,優(yōu)先級(jí)越高越優(yōu)先獲得廣播響應(yīng)。
    (2) abortBroadcast()可來(lái)終止該廣播的傳播,對(duì)更低優(yōu)先級(jí)的屏蔽,注意只對(duì)有序廣播生效。
    (3) 有序廣播在傳播數(shù)據(jù)中會(huì)發(fā)生比如setResultData(),getResultData(),在傳播過程中,可以從新設(shè)置數(shù)據(jù)

  • 關(guān)于本地廣播,可以查看這篇文章??偟膩?lái)說(shuō),本地廣播是通過LocalBroadcastManager內(nèi)置的Handler來(lái)實(shí)現(xiàn)的,只是利用了IntentFilter的match功能,至于BroadcastReceiver 換成其他接口也無(wú)所謂,順便利用了現(xiàn)成的類和概念而已。在register()的時(shí)候保存BroadcastReceiver以及對(duì)應(yīng)的IntentFilter,在sendBroadcast()的時(shí)候找到和Intent對(duì)應(yīng)的BroadcastReceiver,然后通過Handler發(fā)送消息,觸發(fā)executePendingBroadcasts()函數(shù),再在后者中調(diào)用對(duì)應(yīng)BroadcastReceiver的onReceive()方法。

  • 粘性消息:粘性消息在發(fā)送后就一直存在于系統(tǒng)的消息容器里面,等待對(duì)應(yīng)的處理器去處理,如果暫時(shí)沒有處理器處理這個(gè)消息則一直在消息容器里面處于等待狀態(tài),粘性廣播的Receiver如果被銷毀,那么下次重建時(shí)會(huì)自動(dòng)接收到消息數(shù)據(jù)。(在 android 5.0/api 21中deprecated,不再推薦使用,相應(yīng)的還有粘性有序廣播,同樣已經(jīng)deprecated)

自定義View的步驟

自定義View繪制流程圖

參考博客

Android中的事件傳遞機(jī)制?

當(dāng)我們的手指觸碰到屏幕,事件是按照Activity->ViewGroup->View這樣的流程到達(dá)最終響應(yīng)觸摸事件的View的。而在事件分發(fā)過程中,涉及到三個(gè)最重要的方法:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent。我們的手指觸摸到屏幕的時(shí)候,會(huì)觸發(fā)一個(gè)Action_Down類型的事件,當(dāng)前頁(yè)面的Activity會(huì)首先做出相應(yīng),也就是說(shuō)會(huì)走到Activity的dispatchTouchEvent()方法內(nèi)。在這個(gè)方法內(nèi)部有下面兩個(gè)邏輯:

  • 調(diào)用getWindow.superDispatchTouchEvent()。

  • 如果上一步返回true,則直接返回true;否則return自己的onTouchEvent()。顯然,當(dāng)getWindow.superDispatchTouchEvent()返回true,表示當(dāng)前事件已經(jīng)被消費(fèi)掉,無(wú)需調(diào)用onTouchEvent;否則代表事件并沒有被處理,因此需要調(diào)用Activity的onTouchEvent進(jìn)行處理。
    我們都知道,getWindow()返回的是PhoneWindow,因此這句代碼本質(zhì)上調(diào)用了PhoneWindow中的superDispatchTouchEvent()。而后者實(shí)際上調(diào)用了mDecor.superDispatchTouchEvent(event)。這個(gè)mDecor也就是DecorView,它是FrameLayout的一個(gè)子類。在DecorView中的superDispatchTouchEvent(event)中調(diào)用的是super.dispatchTouchEvent()。因此,本質(zhì)上調(diào)用的是ViewGroup的dispatchTouchEvent()。
    到這里,事件已經(jīng)從Activity傳遞到ViewGroup了。接下來(lái)我們分析ViewGroup。
    在ViewGroup的dispatchTouchEvent()中邏輯大致如下:

  • 通過onInterceptTouchEvent()判斷當(dāng)前ViewGroup是否攔截,默認(rèn)的ViewGroup都是不攔截的;

  • 如果攔截,則return自己的onTouchEvent();

  • 如果不攔截,則根據(jù)child.dispatchTouchEvent()的返回值判斷。如果返回true,則return true;否則return自身的onTouchEvent(),在這里實(shí)現(xiàn)了未處理事件的向上傳遞。

通常情況下,ViewGroup 的 onInterceptTouchEvent() 都返回 false,表示不攔截。這里需要注意的是事件序列,比如Down事件、Move事件…Up事件,從 Down到 Up 是一個(gè)完整的事件序列,對(duì)應(yīng)著手指從按下到抬起這一系列事件,如果ViewGroup 攔截了 Down 事件,那么后續(xù)事件都會(huì)交給這個(gè) ViewGroup 的onTouchEvent。如果 ViewGroup 攔截的不是 Down 事件,那么會(huì)給之前處理這個(gè)Down 事件的 View發(fā)送一個(gè)Action_Cancel 類型的事件,通知子View這個(gè)后續(xù)的事件序列已經(jīng)被 ViewGroup 接管了,子 View 恢復(fù)之前的狀態(tài)即可。

這里舉一個(gè)常見的例子:在一個(gè) Recyclerview 中有很多的 Button,我們首先按下了一個(gè) button,然后滑動(dòng)一段距離再松開,這時(shí)候 Recyclerview 會(huì)跟著滑動(dòng),并不會(huì)觸發(fā)這個(gè) button 的點(diǎn)擊事件。這個(gè)例子中,當(dāng)我們按下 button 時(shí),這個(gè) button 接收到了 Action_Down 事件,正常情況下后續(xù)的事件序列應(yīng)該由這個(gè) button處理。但我們滑動(dòng)了一段距離,這時(shí) Recyclerview 察覺到這是一個(gè)滑動(dòng)操作,攔截了這個(gè)事件序列,走了自身的 onTouchEvent()方法,反映在屏幕上就是列表的滑動(dòng)。而這時(shí) button 仍然處于按下的狀態(tài),所以在攔截的時(shí)候需要發(fā)送一個(gè) Action_Cancel 來(lái)通知 button 恢復(fù)之前狀態(tài)。

事件分發(fā)最終會(huì)走到View的dispatchTouchEvent()中。在View的dispatchTouchEvent()中沒有onInterceptTouchEvent(),這里很容易理解,View沒有child,也就不存在攔截。View的dispatchTouchEvent()直接return了自己的onTouchEvent()。如果onTouchEvent()返回true代表事件被消費(fèi),否則未消費(fèi)的事件會(huì)向上傳遞,直到有View處理了事件或一直沒有消費(fèi),最終回到Activity的onTouchEvent()終止。

有時(shí)候會(huì)有人混淆onTouchEvent和onTouch。首先,這兩個(gè)方法都在View的dispatchTouchEvent()中:

  • 如果touchListener不為null,并且這個(gè)View是enable的,而且onTouch返回true,都滿足時(shí)直接return true,走不到onTouchEvent()方法。

  • 否則,就會(huì)觸發(fā)onTouchEvent()。因此onTouch優(yōu)先于onTouchEvent獲得事件處理權(quán)。

最后附上流程圖總結(jié):
touch事件傳遞流程

Handler的原理

Handler,Message,looper 和 MessageQueue 構(gòu)成了安卓的消息機(jī)制,handler創(chuàng)建后可以通過 sendMessage 將消息加入消息隊(duì)列,然后 looper不斷的將消息從 MessageQueue 中取出來(lái),回調(diào)到 Hander 的 handleMessage方法,從而實(shí)現(xiàn)線程的通信。

從兩種情況來(lái)說(shuō),第一在UI線程創(chuàng)建Handler,此時(shí)我們不需要手動(dòng)開啟looper,因?yàn)樵趹?yīng)用啟動(dòng)時(shí),在ActivityThread的main方法中就創(chuàng)建了一個(gè)當(dāng)前主線程的looper,并開啟了消息隊(duì)列,消息隊(duì)列是一個(gè)無(wú)限循環(huán),為什么無(wú)限循環(huán)不會(huì)ANR?因?yàn)榭梢哉f(shuō),應(yīng)用的整個(gè)生命周期就是運(yùn)行在這個(gè)消息循環(huán)中的,安卓是由事件驅(qū)動(dòng)的,Looper.loop不斷的接收處理事件,每一個(gè)點(diǎn)擊觸摸或者Activity每一個(gè)生命周期都是在Looper.loop的控制之下的,looper.loop一旦結(jié)束,應(yīng)用程序的生命周期也就結(jié)束了。我們可以想想什么情況下會(huì)發(fā)生ANR,第一,事件沒有得到處理,第二,事件正在處理,但是沒有及時(shí)完成,而對(duì)事件進(jìn)行處理的就是looper,所以只能說(shuō)事件的處理如果阻塞會(huì)導(dǎo)致ANR,而不能說(shuō)looper的無(wú)限循環(huán)會(huì)ANR

另一種情況就是在子線程創(chuàng)建Handler,此時(shí)由于這個(gè)線程中沒有默認(rèn)開啟的消息隊(duì)列,所以我們需要手動(dòng)調(diào)用looper.prepare(),并通過looper.loop開啟消息

主線程Looper從消息隊(duì)列讀取消息,當(dāng)讀完所有消息時(shí),主線程阻塞。子線程往消息隊(duì)列發(fā)送消息,并且往管道文件寫數(shù)據(jù),主線程即被喚醒,從管道文件讀取數(shù)據(jù),主線程被喚醒只是為了讀取消息,當(dāng)消息讀取完畢,再次睡眠。因此loop的循環(huán)并不會(huì)對(duì)CPU性能有過多的消耗。

ANR出現(xiàn)的情況有幾種? 怎么分析解決ANR問題?

ANR(Application Not responding)。Android中,主線程(UI線程)如果在規(guī)定時(shí)內(nèi)沒有處理完相應(yīng)工作,就會(huì)出現(xiàn)ANR。具體來(lái)說(shuō),ANR會(huì)在以下幾種情況中出現(xiàn):
(1) 輸入事件(按鍵和觸摸事件)5s內(nèi)沒被處理
(2) BroadcastReceiver的事件(onRecieve方法)在規(guī)定時(shí)間內(nèi)沒處理完(前臺(tái)廣播為10s,后臺(tái)廣播為60s)
(3) service 前臺(tái)20s后臺(tái)200s未完成啟動(dòng)
(4) ContentProvider的publish在10s內(nèi)沒進(jìn)行完

內(nèi)存泄露的場(chǎng)景有哪些??jī)?nèi)存泄漏分析工具使用方法?

常見的內(nèi)存泄露有:
1.非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例
非靜態(tài)內(nèi)部類會(huì)持有外部類的引用,如果非靜態(tài)內(nèi)部類的實(shí)例是靜態(tài)的,就會(huì)長(zhǎng)期的維持著外部類的引用,組織被系統(tǒng)回收,解決辦法是使用靜態(tài)內(nèi)部類

2.多線程相關(guān)的匿名內(nèi)部類和非靜態(tài)內(nèi)部類
匿名內(nèi)部類同樣會(huì)持有外部類的引用,如果在線程中執(zhí)行耗時(shí)操作就有可能發(fā)生內(nèi)存泄漏,導(dǎo)致外部類無(wú)法被回收,直到耗時(shí)任務(wù)結(jié)束,解決辦法是在頁(yè)面退出時(shí)結(jié)束線程中的任務(wù)

3.Handler內(nèi)存泄漏
Handler導(dǎo)致的內(nèi)存泄漏也可以被歸納為非靜態(tài)內(nèi)部類導(dǎo)致的,Handler內(nèi)部message是被存儲(chǔ)在MessageQueue中的,有些message不能馬上被處理,存在的時(shí)間會(huì)很長(zhǎng),導(dǎo)致handler無(wú)法被回收,如果handler是非靜態(tài)的,就會(huì)導(dǎo)致它的外部類無(wú)法被回收,解決辦法是1.使用靜態(tài)handler,外部類引用使用弱引用處理2.在退出頁(yè)面時(shí)移除消息隊(duì)列中的消息

4.Context導(dǎo)致內(nèi)存泄漏
根據(jù)場(chǎng)景確定使用Activity的Context還是Application的Context,因?yàn)槎呱芷诓煌瑢?duì)于不必須使用Activity的Context的場(chǎng)景(Dialog),一律采用Application的Context,單例模式是最常見的發(fā)生此泄漏的場(chǎng)景,比如傳入一個(gè)Activity的Context被靜態(tài)類引用,導(dǎo)致無(wú)法回收

5.靜態(tài)View導(dǎo)致泄漏
使用靜態(tài)View可以避免每次啟動(dòng)Activity都去讀取并渲染View,但是靜態(tài)View會(huì)持有Activity的引用,導(dǎo)致無(wú)法回收,解決辦法是在Activity銷毀的時(shí)候?qū)㈧o態(tài)View設(shè)置為null(View一旦被加載到界面中將會(huì)持有一個(gè)Context對(duì)象的引用,在這個(gè)例子中,這個(gè)context對(duì)象是我們的Activity,聲明一個(gè)靜態(tài)變量引用這個(gè)View,也就引用了activity)

6.WebView導(dǎo)致的內(nèi)存泄漏
WebView只要使用一次,內(nèi)存就不會(huì)被釋放,所以WebView都存在內(nèi)存泄漏的問題,通常的解決辦法是為WebView單開一個(gè)進(jìn)程,使用AIDL進(jìn)行通信,根據(jù)業(yè)務(wù)需求在合適的時(shí)機(jī)釋放掉

7.資源對(duì)象未關(guān)閉導(dǎo)致
如Cursor,F(xiàn)ile等,內(nèi)部往往都使用了緩沖,會(huì)造成內(nèi)存泄漏,一定要確保關(guān)閉它并將引用置為null

8.集合中的對(duì)象未清理
集合用于保存對(duì)象,如果集合越來(lái)越大,不進(jìn)行合理的清理,尤其是入股集合是靜態(tài)的

9.Bitmap導(dǎo)致內(nèi)存泄漏
bitmap是比較占內(nèi)存的,所以一定要在不使用的時(shí)候及時(shí)進(jìn)行清理,避免靜態(tài)變量持有大的bitmap對(duì)象

10.監(jiān)聽器未關(guān)閉
很多需要register和unregister的系統(tǒng)服務(wù)要在合適的時(shí)候進(jìn)行unregister,手動(dòng)添加的listener也需要及時(shí)移除

而對(duì)于內(nèi)存泄露的檢測(cè),常用的工具有LeakCanary、MAT(Memory Analyer Tools)、Android Studio自帶的Profiler。

如何避免OOM?

1.使用更加輕量的數(shù)據(jù)結(jié)構(gòu):如使用ArrayMap/SparseArray替代HashMap,HashMap更耗內(nèi)存,因?yàn)樗枰~外的實(shí)例對(duì)象來(lái)記錄Mapping操作,SparseArray更加高效,因?yàn)樗苊饬薑ey Value的自動(dòng)裝箱,和裝箱后的解箱操作

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會(huì)嘗試去使用之前那張Bitmap在Heap中所占據(jù)的pixel data內(nèi)存區(qū)域,而不是去問內(nèi)存重新申請(qǐng)一塊區(qū)域來(lái)存放Bitmap。利用這種特性,即使是上千張的圖片,也只會(huì)僅僅只需要占用屏幕所能夠顯示的圖片數(shù)量的內(nèi)存大小,但復(fù)用存在一些限制,具體體現(xiàn)在:在Android 4.4之前只能重用相同大小的Bitmap的內(nèi)存,而Android 4.4及以后版本則只要后來(lái)的Bitmap比之前的小即可。使用inBitmap參數(shù)前,每創(chuàng)建一個(gè)Bitmap對(duì)象都會(huì)分配一塊內(nèi)存供其使用,而使用了inBitmap參數(shù)后,多個(gè)Bitmap可以復(fù)用一塊內(nèi)存,這樣可以提高性能

4.StringBuilder替代String: 在有些時(shí)候,代碼中會(huì)需要使用到大量的字符串拼接的操作,這種時(shí)候有必要考慮使用StringBuilder來(lái)替代頻繁的“+”

5.避免在類似onDraw這樣的方法中創(chuàng)建對(duì)象,因?yàn)樗鼤?huì)迅速占用大量?jī)?nèi)存,引起頻繁的GC甚至內(nèi)存抖動(dòng)

6.減少內(nèi)存泄漏也是一種避免OOM的方法

如何實(shí)現(xiàn)進(jìn)程?;?/h2>

a: Service 設(shè)置成 START_STICKY kill 后會(huì)被重啟(等待5秒左右),重傳Intent,保持與重啟前一樣
b: 通過 startForeground將進(jìn)程設(shè)置為前臺(tái)進(jìn)程, 做前臺(tái)服務(wù),優(yōu)先級(jí)和前臺(tái)應(yīng)用一個(gè)級(jí)別,除非在系統(tǒng)內(nèi)存非常缺,否則此進(jìn)程不會(huì)被 kill
c: 雙進(jìn)程Service: 讓2個(gè)進(jìn)程互相保護(hù)對(duì)方,其中一個(gè)Service被清理后,另外沒被清理的進(jìn)程可以立即重啟進(jìn)程
d: 用C編寫守護(hù)進(jìn)程(即子進(jìn)程) : Android系統(tǒng)中當(dāng)前進(jìn)程(Process)fork出來(lái)的子進(jìn)程,被系統(tǒng)認(rèn)為是兩個(gè)不同的進(jìn)程。當(dāng)父進(jìn)程被殺死的時(shí)候,子進(jìn)程仍然可以存活,并不受影響(Android5.0以上的版本不可行)聯(lián)系廠商,加入白名單
e.鎖屏狀態(tài)下,開啟一個(gè)一像素Activity

數(shù)據(jù)庫(kù)如何進(jìn)行升級(jí)?SQLite增刪改查的基礎(chǔ)sql語(yǔ)句?

/**
     * 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ù)中,包含了一個(gè) version 的參數(shù)。這個(gè)參數(shù)即是數(shù)據(jù)庫(kù)的版本。 所以,我們可以通過修改 version 來(lái)實(shí)現(xiàn)數(shù)據(jù)庫(kù)的升級(jí)。 當(dāng)version 大于原數(shù)據(jù)庫(kù)版本時(shí),onUpgrade()會(huì)被觸發(fā),可以在該方法中編寫數(shù)據(jù)庫(kù)升級(jí)邏輯。具體的數(shù)據(jù)庫(kù)升級(jí)邏輯示例可參考這里。

常用的SQL增刪改查:

  • 增:INSERT INTO table_name (列1, 列2,…) VALUES (值1, 值2,….)

  • 刪: DELETE FROM 表名稱 WHERE 列名稱 = 值

  • 改:UPDATE 表名稱 SET 列名稱 = 新值 WHERE 列名稱 = 某值

  • 查:SELECT 列名稱(通配是*符號(hào)) FROM 表名稱

ps:操作數(shù)據(jù)表是:ALTER TABLE。該語(yǔ)句用于在已有的表中添加、修改或刪除列。
ALTER TABLE table_name ADD column_name datatype
ALTER TABLE table_name DROP COLUMN column_name

Android屏幕適配

一種極低成本的Android屏幕適配方式 - 字節(jié)跳動(dòng)技術(shù)團(tuán)隊(duì)

AsyncTask、HandlerThread、IntentService區(qū)別和使用

AsyncTask,HandlerThread,IntentService

  • AsyncTask原理:內(nèi)部是Handler和兩個(gè)線程池實(shí)現(xiàn)的,Handler用于將線程切換到主線程,兩個(gè)線程池一個(gè)用于任務(wù)的排隊(duì),一個(gè)用于執(zhí)行任務(wù),當(dāng)AsyncTask執(zhí)行execute方法時(shí)會(huì)封裝出一個(gè)FutureTask對(duì)象,將這個(gè)對(duì)象加入隊(duì)列中,如果此時(shí)沒有正在執(zhí)行的任務(wù),就執(zhí)行它,執(zhí)行完成之后繼續(xù)執(zhí)行隊(duì)列中下一個(gè)任務(wù),執(zhí)行完成通過Handler將事件發(fā)送到主線程。AsyncTask必須在主線程初始化,因?yàn)閮?nèi)部的Handler是一個(gè)靜態(tài)對(duì)象,在AsyncTask類加載的時(shí)候他就已經(jīng)被初始化了。在Android3.0開始,execute方法串行執(zhí)行任務(wù)的,一個(gè)一個(gè)來(lái),3.0之前是并行執(zhí)行的。如果要在3.0上執(zhí)行并行任務(wù),可以調(diào)用executeOnExecutor方法。

  • HandlerThread原理:繼承自 Thread,start開啟線程后,會(huì)在其run方法中會(huì)通過Looper 創(chuàng)建消息隊(duì)列并開啟消息循環(huán),這個(gè)消息隊(duì)列運(yùn)行在子線程中,所以可以將HandlerThread 中的 Looper 實(shí)例傳遞給一個(gè) Handler,從而保證這個(gè) Handler 的 handleMessage 方法運(yùn)行在子線程中,Android 中使用 HandlerThread的一個(gè)場(chǎng)景就是 IntentService

  • IntentService原理:繼承自Service,它的內(nèi)部封裝了 HandlerThread 和Handler,可以執(zhí)行耗時(shí)任務(wù),同時(shí)因?yàn)樗且粋€(gè)服務(wù),優(yōu)先級(jí)比普通線程高很多,所以更適合執(zhí)行一些高優(yōu)先級(jí)的后臺(tái)任務(wù),HandlerThread底層通過Looper消息隊(duì)列實(shí)現(xiàn)的,所以它是順序的執(zhí)行每一個(gè)任務(wù)??梢酝ㄟ^Intent的方式開啟IntentService,IntentService通過handler將每一個(gè)intent加入HandlerThread子線程中的消息隊(duì)列,通過looper按順序一個(gè)個(gè)的取出并執(zhí)行,執(zhí)行完成后自動(dòng)結(jié)束自己,不需要開發(fā)者手動(dòng)關(guān)閉
    IntentService分析

Bitmap優(yōu)化

主動(dòng)釋放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:每個(gè)像素占用1byte內(nèi)存
ARGB_4444:每個(gè)像素占用2byte內(nèi)存
ARGB_8888:每個(gè)像素占用4byte內(nèi)存 (默認(rèn))
RGB_565:每個(gè)像素占用2byte內(nèi)存
Android默認(rèn)的顏色模式為ARGB_8888,這個(gè)顏色模式色彩最細(xì)膩,顯示質(zhì)量最高。但同樣的,占用的內(nèi)存也最大。 所以在對(duì)圖片效果不是特別高的情況下使用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)

相關(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里面有什么子類?

(其實(shí)面試的時(shí)候聽到這個(gè)問題的時(shí)候,你要知道,面試官是想考察List,Set)
list和set是實(shí)現(xiàn)了collection接口的。

  • List:
    1.可以允許重復(fù)的對(duì)象。
    2.可以插入多個(gè)null元素。
    3.是一個(gè)有序容器,保持了每個(gè)元素的插入順序,輸出的順序就是插入的順序。
    4.常用的實(shí)現(xiàn)類有 ArrayList、LinkedList 和 Vector。ArrayList 最為流行,它提供了使用索引的隨意訪問,而 LinkedList 則對(duì)于經(jīng)常需要從 List 中添加或刪除元素的場(chǎng)合更為合適。

  • Set:
    1.不允許重復(fù)對(duì)象
    2.無(wú)序容器,你無(wú)法保證每個(gè)元素的存儲(chǔ)順序,TreeSet通過 Comparator 或者 Comparable 維護(hù)了一個(gè)排序順序。
    3.只允許一個(gè) null 元素
    4.Set 接口最流行的幾個(gè)實(shí)現(xiàn)類是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于HashMap 實(shí)現(xiàn)的 HashSet;TreeSet 還實(shí)現(xiàn)了 SortedSet 接口,因此 TreeSet 是一個(gè)根據(jù)其compare() 和 compareTo() 的定義進(jìn)行排序的有序容器。

什么場(chǎng)景下使用list,set,map?

  • 如果你經(jīng)常會(huì)使用索引來(lái)對(duì)容器中的元素進(jìn)行訪問,那么 List 是你的正確的選擇。如果你已經(jīng)知道索引了的話,那么 List 的實(shí)現(xiàn)類比如 ArrayList 可以提供更快速的訪問,如果經(jīng)常添加刪除元素的,那么肯定要選擇LinkedList。
  • 如果你想容器中的元素能夠按照它們插入的次序進(jìn)行有序存儲(chǔ),那么還是 List,因?yàn)?List 是一個(gè)有序容器,它按照插入順序進(jìn)行存儲(chǔ)。
  • 如果你想保證插入元素的唯一性,也就是你不想有重復(fù)值的出現(xiàn),那么可以選擇一個(gè) Set 的實(shí)現(xiàn)類,比如 HashSet、LinkedHashSet 或者 TreeSet。所有 Set 的實(shí)現(xiàn)類都遵循了統(tǒng)一約束比如唯一性,而且還提供了額外的特性比如 TreeSet 還是一個(gè) SortedSet,所有存儲(chǔ)于 TreeSet 中的元素可以使用 Java 里的 Comparator 或者 Comparable 進(jìn)行排序。LinkedHashSet 也按照元素的插入順序?qū)λ鼈冞M(jìn)行存儲(chǔ)。
  • 如果你以鍵和值的形式進(jìn)行數(shù)據(jù)存儲(chǔ)那么 Map 是你正確的選擇。你可以根據(jù)你的后續(xù)需要從 Hashtable、HashMap、TreeMap 中進(jìn)行選擇。

fail-fast機(jī)制

“快速失敗”也就是fail-fast,它是Java集合的一種錯(cuò)誤檢測(cè)機(jī)制。當(dāng)多個(gè)線程對(duì)集合進(jìn)行結(jié)構(gòu)上的改變的操作時(shí),有可能會(huì)產(chǎn)生fail-fast機(jī)制。記住是有可能,而不是一定。例如:假設(shè)存在兩個(gè)線程(線程1、線程2),線程1通過Iterator在遍歷集合A中的元素,在某個(gè)時(shí)候線程2修改了集合A的結(jié)構(gòu)(是結(jié)構(gòu)上面的修改,而不是簡(jiǎn)單的修改集合元素的內(nèi)容),那么這個(gè)時(shí)候程序就會(huì)拋出ConcurrentModificationException 異常,從而產(chǎn)生fail-fast機(jī)制。
相關(guān)博客

ArrayList和LinkedList的區(qū)別

  • Arraylist
    底層是基于動(dòng)態(tài)數(shù)組,根據(jù)下表隨機(jī)訪問數(shù)組元素的效率高,向數(shù)組尾部添加元素的效率高;但是,刪除數(shù)組中的數(shù)據(jù)以及向數(shù)組中間添加數(shù)據(jù)效率低,因?yàn)樾枰苿?dòng)數(shù)組。例如最壞的情況是刪除第一個(gè)數(shù)組元素,則需要將第2至第n個(gè)數(shù)組元素各向前移動(dòng)一位。而之所以稱為動(dòng)態(tài)數(shù)組,是因?yàn)锳rraylist在數(shù)組元素超過其容量大,Arraylist可以進(jìn)行擴(kuò)容(針對(duì)JDK1.8? 數(shù)組擴(kuò)容后的容量是擴(kuò)容前的1.5倍)

  • Linkedlist
    基于鏈表的動(dòng)態(tài)數(shù)組,數(shù)據(jù)添加刪除效率高,只需要改變指針指向即可,但是訪問數(shù)據(jù)的平均效率低,需要對(duì)鏈表進(jìn)行遍歷。

HashMap、Hashtable、ConcurrentHashMap的原理與區(qū)別

HashTable

  • 底層數(shù)組+鏈表實(shí)現(xiàn),無(wú)論key還是value都不能為null,線程安全,實(shí)現(xiàn)線程安全的方式是在修改數(shù)據(jù)時(shí)鎖住整個(gè)HashTable,效率低,ConcurrentHashMap做了相關(guān)優(yōu)化
  • 初始size為11,擴(kuò)容:newsize = olesize*2+1
  • 計(jì)算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

HashMap

  • 底層數(shù)組+鏈表實(shí)現(xiàn),可以存儲(chǔ)null鍵和null值,線程不安全
  • 初始size為16,擴(kuò)容:newsize = oldsize*2,size一定為2的n次冪
  • 擴(kuò)容針對(duì)整個(gè)Map,每次擴(kuò)容時(shí),原來(lái)數(shù)組中的元素依次重新計(jì)算存放位置,并重新插入
  • 插入元素后才判斷該不該擴(kuò)容,有可能無(wú)效擴(kuò)容(插入后如果擴(kuò)容,如果沒有再次插入,就會(huì)產(chǎn)生無(wú)效擴(kuò)容)
  • 當(dāng)Map中元素總數(shù)超過Entry數(shù)組的75%,觸發(fā)擴(kuò)容操作,為了減少鏈表長(zhǎng)度,元素分配更均勻
  • 計(jì)算index方法:index = hash & (tab.length – 1)
    HashMap面試題

ConcurrentHashMap

  • 底層采用分段的數(shù)組+鏈表實(shí)現(xiàn),線程安全
  • 通過把整個(gè)Map分為N個(gè)Segment,可以提供相同的線程安全,但是效率提升N倍,默認(rèn)提升16倍。(讀操作不加鎖,由于HashEntry的value變量是 volatile的,也能保證讀取到最新的值。)
  • Hashtable的synchronized是針對(duì)整張Hash表的,即每次鎖住整張表讓線程獨(dú)占,ConcurrentHashMap允許多個(gè)修改操作并發(fā)進(jìn)行,其關(guān)鍵在于使用了鎖分離技術(shù)
  • 有些方法需要跨段,比如size()和containsValue(),它們可能需要鎖定整個(gè)表而而不僅僅是某個(gè)段,這需要按順序鎖定所有段,操作完畢后,又按順序釋放所有段的鎖
  • 擴(kuò)容:段內(nèi)擴(kuò)容(段內(nèi)元素超過該段對(duì)應(yīng)Entry數(shù)組長(zhǎng)度的75%觸發(fā)擴(kuò)容,不會(huì)對(duì)整個(gè)Map進(jìn)行擴(kuò)容),插入前檢測(cè)需不需要擴(kuò)容,有效避免無(wú)效擴(kuò)容

詳細(xì)原理區(qū)別內(nèi)容

Hash沖突如何解決?

開放定址法

這種方法也稱再散列法,其基本思想是:當(dāng)關(guān)鍵字key的哈希地址p=H(key)出現(xiàn)沖突時(shí),以p為基礎(chǔ),產(chǎn)生另一個(gè)哈希地址p1,如果p1仍然沖突,再以p為基礎(chǔ),產(chǎn)生另一個(gè)哈希地址p2,…,直到找出一個(gè)不沖突的哈希地址pi ,將相應(yīng)元素存入其中。這種方法有一個(gè)通用的再散列函數(shù)形式:
Hi=(H(key)+di)% m i=1,2,…,n
其中H(key)為哈希函數(shù),m 為表長(zhǎng),di稱為增量序列。增量序列的取值方式不同,相應(yīng)的再散列方式也不同。主要有以下三種:

  • 線性探測(cè)再散列
    dii=1,2,3,…,m-1
    這種方法的特點(diǎn)是:沖突發(fā)生時(shí),順序查看表中下一單元,直到找出一個(gè)空單元或查遍全表。
  • 二次探測(cè)再散列
    di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )
    這種方法的特點(diǎn)是:沖突發(fā)生時(shí),在表的左右進(jìn)行跳躍式探測(cè),比較靈活。
  • 偽隨機(jī)探測(cè)再散列

再哈希法

這種方法是同時(shí)構(gòu)造多個(gè)不同的哈希函數(shù):

Hi=RH1(key) i=1,2,…,k

當(dāng)哈希地址Hi=RH1(key)發(fā)生沖突時(shí),再計(jì)算Hi=RH2(key)……,直到?jīng)_突不再產(chǎn)生。這種方法不易產(chǎn)生聚集,但增加了計(jì)算時(shí)間。

鏈地址法

這種方法的基本思想是將所有哈希地址為i的元素構(gòu)成一個(gè)稱為同義詞鏈的單鏈表,并將單鏈表的頭指針存在哈希表的第i個(gè)單元中,因而查找、插入和刪除主要在同義詞鏈中進(jìn)行。鏈地址法適用于經(jīng)常進(jìn)行插入和刪除的情況。

如何理解Java的多態(tài)?其中,重載和重寫有什么區(qū)別?

多態(tài)是同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力,多態(tài)是同一個(gè)接口,使用不同的實(shí)例而執(zhí)行不同操作,多態(tài)就是程序運(yùn)行期間才確定,一個(gè)引用變量倒底會(huì)指向哪個(gè)類的實(shí)例對(duì)象,該引用變量發(fā)出的方法調(diào)用到底是哪個(gè)類中實(shí)現(xiàn)的方法。
多態(tài)存在的三個(gè)必要條件是:繼承,重寫,父類引用指向子類引用。
多態(tài)的三個(gè)實(shí)現(xiàn)方式是:重寫,接口,抽象類和抽象方法。

final關(guān)鍵字的用法?

final 可以修飾類、變量和方法。修飾類代表這個(gè)類不可被繼承。修飾變量代表此變量不可被改變。修飾方法表示此方法不可被重寫 (override)。

死鎖是怎么導(dǎo)致的?如何定位死鎖?

某個(gè)任務(wù)在等待另一個(gè)任務(wù),而后者又等待別的任務(wù),這樣一直下去,直到這個(gè)鏈條上的任務(wù)又在等待第一個(gè)任務(wù)釋放鎖。這得到了一個(gè)任務(wù)之間互相等待的連續(xù)循環(huán),沒有哪個(gè)線程能繼續(xù)。這被稱之為死鎖。當(dāng)以下四個(gè)條件同時(shí)滿足時(shí),就會(huì)產(chǎn)生死鎖:
(1) 互斥條件。任務(wù)所使用的資源中至少有一個(gè)是不能共享的。
(2) 任務(wù)必須持有一個(gè)資源,同時(shí)等待獲取另一個(gè)被別的任務(wù)占有的資源。
(3) 資源不能被強(qiáng)占。
(4) 必須有循環(huán)等待。一個(gè)任務(wù)正在等待另一個(gè)任務(wù)所持有的資源,后者又在等待別的任務(wù)所持有的資源,這樣一直下去,直到有一個(gè)任務(wù)在等待第一個(gè)任務(wù)所持有的資源,使得大家都被鎖住。
首先模擬問題定位,使用jstack,使用 jps或者系統(tǒng)的ps命令、任務(wù)管理器等工具,確定進(jìn)程ID。
然后獲取線程棧:

${JAVA_HOME}\bin\jstack your_pid

避免死鎖的方法思路:

盡量避免使用多個(gè)鎖,并且只有需要的時(shí)候才持有鎖。
如果使用多個(gè)鎖,盡量設(shè)計(jì)好鎖的獲取順序。例如銀行家算法。
使用帶超時(shí)的方法,為程序帶來(lái)更多的可控性。

反射【待補(bǔ)全】

說(shuō)下java中的線程創(chuàng)建方式,線程池的工作原理。

java中有三種創(chuàng)建線程的方式,或者說(shuō)四種

  1. 繼承Thread類實(shí)現(xiàn)多線程
  2. 實(shí)現(xiàn)Runnable接口
  3. 實(shí)現(xiàn)Callable接口
  4. 通過線程池

線程池的工作原理:線程池可以減少創(chuàng)建和銷毀線程的次數(shù),從而減少系統(tǒng)資源的消耗,當(dāng)一個(gè)任務(wù)提交到線程池時(shí)
a. 首先判斷核心線程池中的線程是否已經(jīng)滿了,如果沒滿,則創(chuàng)建一個(gè)核心線程執(zhí)行任務(wù),否則進(jìn)入下一步
b. 判斷工作隊(duì)列是否已滿,沒有滿則加入工作隊(duì)列,否則執(zhí)行下一步
c. 判斷線程數(shù)是否達(dá)到了最大值,如果不是,則創(chuàng)建非核心線程執(zhí)行任務(wù),否則執(zhí)行飽和策略,默認(rèn)拋出異常

進(jìn)程與線程的區(qū)別

進(jìn)程是資源(CPU、內(nèi)存等)分配的基本單位,它是程序執(zhí)行時(shí)的一個(gè)實(shí)例。
線程是程序執(zhí)行時(shí)的最小單位,它是進(jìn)程的一個(gè)執(zhí)行流,是CPU調(diào)度和分派的基本單位,一個(gè)進(jìn)程可以由很多個(gè)線程組成,線程間共享進(jìn)程的所有資源,每個(gè)線程有自己的堆棧和局部變量。線程由CPU獨(dú)立調(diào)度執(zhí)行,在多CPU環(huán)境下就允許多個(gè)線程同時(shí)運(yùn)行。同樣多線程也可以實(shí)現(xiàn)并發(fā)操作,每個(gè)請(qǐng)求分配一個(gè)線程來(lái)處理。

設(shè)計(jì)模式相關(guān)

說(shuō)下你所知道的設(shè)計(jì)模式與使用場(chǎng)景

建造者模式

將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
使用場(chǎng)景比如最常見的AlertDialog,拿我們開發(fā)過程中舉例,比如Camera開發(fā)過程中,可能需要設(shè)置一個(gè)初始化的相機(jī)配置,設(shè)置攝像頭方向,閃光燈開閉,成像質(zhì)量等等,這種場(chǎng)景下就可以使用建造者模式

裝飾者模式

動(dòng)態(tài)的給一個(gè)對(duì)象添加一些額外的職責(zé),就增加功能來(lái)說(shuō),裝飾模式比生成子類更為靈活。裝飾者模式可以在不改變?cè)蓄惤Y(jié)構(gòu)的情況下曾強(qiáng)類的功能,比如Java中的BufferedInputStream 包裝FileInputStream,舉個(gè)開發(fā)中的例子,比如在我們現(xiàn)有網(wǎng)絡(luò)框架上需要增加新的功能,那么再包裝一層即可,裝飾者模式解決了繼承存在的一些問題,比如多層繼承代碼的臃腫,使代碼邏輯更清晰

生產(chǎn)者消費(fèi)者模式

生產(chǎn)者消費(fèi)者模式并不是GOF提出的23種設(shè)計(jì)模式之一,23種設(shè)計(jì)模式都是建立在面向?qū)ο蟮幕A(chǔ)之上的,但其實(shí)面向過程的編程中也有很多高效的編程模式,生產(chǎn)者消費(fèi)者模式便是其中之一,它是我們編程過程中最常用的一種設(shè)計(jì)模式。

在實(shí)際的軟件開發(fā)過程中,經(jīng)常會(huì)碰到如下場(chǎng)景:某個(gè)模塊負(fù)責(zé)產(chǎn)生數(shù)據(jù),這些數(shù)據(jù)由另一個(gè)模塊來(lái)負(fù)責(zé)處理(此處的模塊是廣義的,可以是類、函數(shù)、線程、進(jìn)程等)。產(chǎn)生數(shù)據(jù)的模塊,就形象地稱為生產(chǎn)者;而處理數(shù)據(jù)的模塊,就稱為消費(fèi)者。

單單抽象出生產(chǎn)者和消費(fèi)者,還夠不上是生產(chǎn)者/消費(fèi)者模式。該模式還需要有一個(gè)緩沖區(qū)處于生產(chǎn)者和消費(fèi)者之間,作為一個(gè)中介。生產(chǎn)者把數(shù)據(jù)放入緩沖區(qū),而消費(fèi)者從緩沖區(qū)取出數(shù)據(jù)。

JVM相關(guān)

談一下JVM內(nèi)存區(qū)域劃分?哪部分是線程公有的,哪部分是私有的?

JVM 的內(nèi)存區(qū)域可以分為兩類:線程私有和區(qū)域和線程共有的區(qū)域。
線程私有的區(qū)域:程序計(jì)數(shù)器、JVM 虛擬機(jī)棧、本地方法棧;
線程共有的區(qū)域:堆、方法區(qū)、運(yùn)行時(shí)常量池。

  • 程序計(jì)數(shù)器,也有稱作PC寄存器。每個(gè)線程都有一個(gè)私有的程序計(jì)數(shù)器,任何時(shí)間一個(gè)線程都只會(huì)有一個(gè)方法正在執(zhí)行,也就是所謂的當(dāng)前方法。程序計(jì)數(shù)器存放的就是這個(gè)當(dāng)前方法的JVM指令地址。當(dāng)CPU需要執(zhí)行指令時(shí),需要從程序計(jì)數(shù)器中得到當(dāng)前需要執(zhí)行的指令所在存儲(chǔ)單元的地址,然后根據(jù)得到的地址獲取到指令,在得到指令之后,程序計(jì)數(shù)器便自動(dòng)加1或者根據(jù)轉(zhuǎn)移指針得到下一條指令的地址,如此循環(huán),直至執(zhí)行完所有的指令。

  • JVM虛擬機(jī)棧。創(chuàng)建線程的時(shí)候會(huì)創(chuàng)建線程內(nèi)的虛擬機(jī)棧,棧中存放著一個(gè)個(gè)的棧幀,對(duì)應(yīng)著一個(gè)個(gè)方法的調(diào)用。JVM 虛擬機(jī)棧有兩種操作,分別是壓棧和出站。棧幀中存放著局部變量表(Local Variables)、操作數(shù)棧(Operand Stack)、指向當(dāng)前方法所屬的類的運(yùn)行時(shí)常量池的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些額外的附加信息。

  • 本地方法棧。本地方法棧與Java棧的作用和原理非常相似。區(qū)別只不過是Java棧是為執(zhí)行Java方法服務(wù)的,而本地方法棧則是為執(zhí)行本地方法(Native Method)服務(wù)的。在JVM規(guī)范中,并沒有對(duì)本地方發(fā)展的具體實(shí)現(xiàn)方法以及數(shù)據(jù)結(jié)構(gòu)作強(qiáng)制規(guī)定,虛擬機(jī)可以自由實(shí)現(xiàn)它。在HotSopt虛擬機(jī)中直接就把本地方法棧和Java棧合二為一。

  • 堆。堆是內(nèi)存管理的核心區(qū)域,用來(lái)存放對(duì)象實(shí)例。幾乎所有創(chuàng)建的對(duì)象實(shí)例都會(huì)直接分配到堆上。所以堆也是垃圾回收的主要區(qū)域,垃圾收集器會(huì)對(duì)堆有著更細(xì)的劃分,最常見的就是把堆劃分為新生代和老年代。java堆允許處于不連續(xù)的物理內(nèi)存空間中,只要邏輯連續(xù)即可。堆中如果沒有空間完成實(shí)例分配無(wú)法擴(kuò)展時(shí)將會(huì)拋出OutOfMemoryError異常。

  • 方法區(qū)。方法區(qū)與堆一樣所有線程所共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、及時(shí)編譯器編譯后的代碼等數(shù)據(jù)。在Class文件中除了類的字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用來(lái)存儲(chǔ)編譯期間生成的字面量和符號(hào)引用。

其實(shí)除了程序計(jì)數(shù)器,其他的部分都會(huì)發(fā)生 OOM。

  • 堆。 通常發(fā)生的 OOM 都會(huì)發(fā)生在堆中,最常見的可能導(dǎo)致 OOM 的原因就是內(nèi)存泄漏。

  • JVM虛擬機(jī)棧和本地方法棧。 當(dāng)我們寫一個(gè)遞歸方法,這個(gè)遞歸方法沒有循環(huán)終止條件,最終會(huì)導(dǎo)致 StackOverflow 的錯(cuò)誤。當(dāng)然,如果??臻g擴(kuò)展失敗,也是會(huì)發(fā)生 OOM 的。

  • 方法區(qū)。方法區(qū)現(xiàn)在基本上不太會(huì)發(fā)生 OOM,但在早期內(nèi)存中加載的類信息過多的情況下也是會(huì)發(fā)生 OOM 的。

GC的兩種判定方法(對(duì)象存活判定):

引用計(jì)數(shù)和可達(dá)性分析。

引用計(jì)數(shù)方式

最基本的形態(tài)就是讓每個(gè)被管理的對(duì)象與一個(gè)引用計(jì)數(shù)器關(guān)聯(lián)在一起,該計(jì)數(shù)器記錄著該對(duì)象當(dāng)前被引用的次數(shù),每當(dāng)創(chuàng)建一個(gè)新的引用指向該對(duì)象時(shí)其計(jì)數(shù)器就加1,每當(dāng)指向該對(duì)象的引用失效時(shí)計(jì)數(shù)器就減1。當(dāng)該計(jì)數(shù)器的值降到0就認(rèn)為對(duì)象死亡。

可達(dá)性分析算法

將GC Roots對(duì)象作為起始節(jié)點(diǎn),向下搜索,搜索走過的路徑為引用鏈;當(dāng)一個(gè)對(duì)象到GC Roots沒有引用鏈時(shí),則該對(duì)象是不可用的;

可作為GC Roots的對(duì)象:

  1. 方法區(qū)中靜態(tài)屬性引用的對(duì)象

  2. 方法區(qū)中常量引用的對(duì)象

  3. 虛擬機(jī)棧引用的對(duì)象 (棧幀中本地變量表)

  4. 本地方法棧中JNI引用的對(duì)象 (Native方法)

GC的三種收集方法:

標(biāo)記清除、標(biāo)記整理、復(fù)制算法的原理與特點(diǎn),分別用在什么地方,如果讓你優(yōu)化收集方法,有什么思路?

標(biāo)記清除算法

最基礎(chǔ)的收集算法,其他收集算法都是基于這種思想。標(biāo)記清除算法分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出需要回收的對(duì)象,標(biāo)記完成之后統(tǒng)一清除對(duì)象。
它的主要缺點(diǎn):
①.標(biāo)記和清除過程效率不高 。
②.標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片。

標(biāo)記整理

標(biāo)記操作和“標(biāo)記-清除”算法一致,后續(xù)操作不只是直接清理對(duì)象,而是在清理無(wú)用對(duì)象完成后讓所有存活的對(duì)象都向一端移動(dòng),并更新引用其對(duì)象的指針。主要缺點(diǎn):在標(biāo)記-清除的基礎(chǔ)上還需進(jìn)行對(duì)象的移動(dòng),成本相對(duì)較高,好處則是不會(huì)產(chǎn)生內(nèi)存碎片。

復(fù)制算法

它將可用內(nèi)存容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊用完之后,就將還存活的對(duì)象復(fù)制到另外一塊上面,然后在把已使用過的內(nèi)存空間一次理掉。這樣使得每次都是對(duì)其中的一塊進(jìn)行內(nèi)存回收,不會(huì)產(chǎn)生碎片等情況,只要移動(dòng)堆訂的指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效。主要缺點(diǎn):內(nèi)存縮小為原來(lái)的一半。

雙親委派模型:

Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。

啟動(dòng)類加載器,負(fù)責(zé)將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中,并且是虛擬機(jī)識(shí)別的(僅按照文件名識(shí)別,如rt.jar,名字不符合的類庫(kù)即時(shí)放在lib目錄中也不會(huì)被加載)類庫(kù)加載到虛擬機(jī)內(nèi)存中。啟動(dòng)類加載器無(wú)法被java程序直接引用。
擴(kuò)展類加載器:負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫(kù),開發(fā)者可以直接使用該類加載器。
應(yīng)用程序類加載器:負(fù)責(zé)加載用戶路徑上所指定的類庫(kù),開發(fā)者可以直接使用這個(gè)類加載器,也是默認(rèn)的類加載器。 三種加載器的關(guān)系:?jiǎn)?dòng)類加載器->擴(kuò)展類加載器->應(yīng)用程序類加載器->自定義類加載器。
這種關(guān)系即為類加載器的雙親委派模型。其要求除啟動(dòng)類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。這里類加載器之間的父子關(guān)系一般不以繼承關(guān)系實(shí)現(xiàn),而是用組合的方式來(lái)復(fù)用父類的代碼。

雙親委派模型的工作過程:如果一個(gè)類加載器接收到了類加載的請(qǐng)求,它首先把這個(gè)請(qǐng)求委托給他的父類加載器去完成,每個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(它在搜索范圍中沒有找到所需的類)時(shí),子加載器才會(huì)嘗試自己去加載。

好處:java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如類java.lang.Object,它存放在rt.jar中,無(wú)論哪個(gè)類加載器要加載這個(gè)類,最終都會(huì)委派給啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。相反,如果用戶自己寫了一個(gè)名為java.lang.Object的類,并放在程序的Classpath中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類,java類型體系中最基礎(chǔ)的行為也無(wú)法保證,應(yīng)用程序也會(huì)變得一片混亂。

實(shí)現(xiàn):在java.lang.ClassLoader的loadClass()方法中,先檢查是否已經(jīng)被加載過,若沒有加載則調(diào)用父類加載器的loadClass()方法,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器。如果父加載失敗,則拋出ClassNotFoundException異常后,再調(diào)用自己的findClass()方法進(jìn)行加載。

高并發(fā)相關(guān)

ThreadLocal的設(shè)計(jì)理念與作用

ThreadPool用法與優(yōu)勢(shì)(這里在Android SDK原生的AsyncTask底層也有使用)

線程池的底層實(shí)現(xiàn)和工作原理(建議寫一個(gè)雛形簡(jiǎn)版源碼實(shí)現(xiàn))

計(jì)算機(jī)網(wǎng)絡(luò)相關(guān)

TCP/IP五層模型

image

image

在TCP/IP四層協(xié)議中,網(wǎng)絡(luò)層又被稱為網(wǎng)際層(用網(wǎng)際層這個(gè)名字是強(qiáng)調(diào)這一層是為了解決不同網(wǎng)絡(luò)的互連問題),而數(shù)據(jù)鏈路層與物理層合并為網(wǎng)絡(luò)接口層。

TCP協(xié)議

  • Transmission Control Protocol,傳輸控制協(xié)議
  • 面向連接的協(xié)議
  • 需要三次握手建立連接
  • 需要四次揮手?jǐn)嚅_連接
  • TCP報(bào)頭最小長(zhǎng)度:20字節(jié)

三次握手過程

  1. 客戶端發(fā)送:SYN = 1, SEQ = X, 端口號(hào)
  2. 服務(wù)器回復(fù):SYN = 1, ACK = X + 1, SEQ = Y
  3. 客戶端發(fā)送:ACK = Y + 1, SEQ = X + 1

確認(rèn)應(yīng)答信號(hào)ACK = 收到的SEQ + 1。 連接建立中,同步信號(hào)SYN始終為1。連接建立后,同步信號(hào)SYN=0。

四次揮手過程

  1. A向B提出停止連接請(qǐng)求,F(xiàn)IN = 1
  2. B收到,ACK = 1
  3. B向A提出停止連接請(qǐng)求,F(xiàn)IN = 1
  4. A收到,ACK = 1
SYN:同步位
seq:編號(hào)位
ACK:確認(rèn)位
ack:確認(rèn)編號(hào)位

算法相關(guān)

面試中的 10 大排序算法總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容