2019年Android高級(jí)面試題與相關(guān)知識(shí)點(diǎn)總結(jié)

說下你所知道的設(shè)計(jì)模式與使用場(chǎng)景
a.建造者模式:
將一個(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é),就增加功能來說,裝飾模式比生成子類更為靈活。裝飾者模式可以在不改變?cè)蓄惤Y(jié)構(gòu)的情況下曾強(qiáng)類的功能,比如Java中的BufferedInputStream 包裝FileInputStream,舉個(gè)開發(fā)中的例子,比如在我們現(xiàn)有網(wǎng)絡(luò)框架上需要增加新的功能,那么再包裝一層即可,裝飾者模式解決了繼承存在的一些問題,比如多層繼承代碼的臃腫,使代碼邏輯更清晰
- 觀察者模式:
- 代理模式:
- 門面模式:
- 單例模式:
- 生產(chǎn)者消費(fèi)者模式:
java語(yǔ)言的特點(diǎn)與OOP思想
這個(gè)通過對(duì)比來描述,比如面向?qū)ο蠛兔嫦蜻^程的對(duì)比,針對(duì)這兩種思想的對(duì)比,還可以舉個(gè)開發(fā)中的例子,比如播放器的實(shí)現(xiàn),面向過程的實(shí)現(xiàn)方式就是將播放視頻的這個(gè)功能分解成多個(gè)過程,比如,加載視頻地址,獲取視頻信息,初始化解碼器,選擇合適的解碼器進(jìn)行解碼,讀取解碼后的幀進(jìn)行視頻格式轉(zhuǎn)換和音頻重采樣,然后讀取幀進(jìn)行播放,這是一個(gè)完整的過程,這個(gè)過程中不涉及類的概念,而面向?qū)ο笞畲蟮奶攸c(diǎn)就是類,封裝繼承和多態(tài)是核心,同樣的以播放器為例,一面向?qū)ο蟮姆绞絹韺?shí)現(xiàn),將會(huì)針對(duì)每一個(gè)功能封裝出一個(gè)對(duì)象,吧如說Muxer,獲取視頻信息,Decoder,解碼,格式轉(zhuǎn)換器,視頻播放器,音頻播放器等,每一個(gè)功能對(duì)應(yīng)一個(gè)對(duì)象,由這個(gè)對(duì)象來完成對(duì)應(yīng)的功能,并且遵循單一職責(zé)原則,一個(gè)對(duì)象只做它相關(guān)的事情
說下handler原理
Handler,Message,looper和MessageQueue構(gòu)成了安卓的消息機(jī)制,handler創(chuàng)建后可以通過sendMessage將消息加入消息隊(duì)列,然后looper不斷的將消息從MessageQueue中取出來,回調(diào)到Hander的handleMessage方法,從而實(shí)現(xiàn)線程的通信。
從兩種情況來說,第一在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è)無限循環(huán),為什么無限循環(huán)不會(huì)ANR?因?yàn)榭梢哉f,應(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,所以只能說事件的處理如果阻塞會(huì)導(dǎo)致ANR,而不能說looper的無限循環(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性能有過多的消耗。
WebView java js的通信
通過Android原生的方式進(jìn)行通信
1.Java層調(diào)用js方法:
通過WebView的loadUrl():沒返回值,得通過js改變iframe.src把結(jié)果返回,這樣執(zhí)行效率較低
通過WebView的evaluateJavascript():sdk19(4.4)以上,在回調(diào)方法里有返回值,效率優(yōu)于前一種,因?yàn)樵摲椒ǖ膱?zhí)行不會(huì)使頁(yè)面刷新,而方法(loadUrl )的執(zhí)行則會(huì)使頁(yè)面刷新。
建議兩種方法混合使用,即Android 4.4以下使用方法1,Android 4.4以上方法2
//假如js中定義了這個(gè)方法,要在java中調(diào)用它 <script> // Android需要調(diào)用的方法 function callJS(){ alert("Android調(diào)用了JS的callJS方法"); } </script>
//這樣調(diào)用,callJS就是方法名,javascript:是固定寫法 webView.loadUrl("javascript:callJS()"); // 只需要將第一種方法的loadUrl()換成下面該方法即可 webView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { //此處為 js 返回的結(jié)果 } }); }
2.Js調(diào)用Java代碼的方法有3種:
1.通過WebView的addJavascriptInterface()進(jìn)行對(duì)象映射
這種方法在安卓4.2以下存在遠(yuǎn)程代碼調(diào)用漏洞,漏洞產(chǎn)生原因是:當(dāng)JS拿到Android這個(gè)對(duì)象后,就可以調(diào)用這個(gè)Android對(duì)象中所有的方法,包括系統(tǒng)類(java.lang.Runtime 類),從而進(jìn)行任意代碼執(zhí)行。
具體獲取系統(tǒng)類的描述:(結(jié)合 Java 反射機(jī)制)
1.Android中的對(duì)象有一公共的方法:getClass() ;
2.該方法可以獲取到當(dāng)前類 類型Class
3.該類有一關(guān)鍵的方法: Class.forName;
4.該方法可以加載一個(gè)類(可加載 java.lang.Runtime 類)
5.而該類是可以執(zhí)行本地命令的
//以下是攻擊的Js核心代碼: function execute(cmdArgs) { // 步驟1:遍歷 window 對(duì)象 // 目的是為了找到包含 getClass ()的對(duì)象 // 因?yàn)锳ndroid映射的JS對(duì)象也在window中,所以肯定會(huì)遍歷到 for (var obj in window) { if ("getClass" in window[obj]) { // 步驟2:利用反射調(diào)用forName()得到Runtime類對(duì)象 return window[obj].getClass().forName("java.lang.Runtime") .getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs); // 步驟3:以后,就可以調(diào)用靜態(tài)方法來執(zhí)行一些命令,比如訪問文件的命令 // 從執(zhí)行命令后返回的輸入流中得到字符串,有很嚴(yán)重暴露隱私的危險(xiǎn)。 // 如執(zhí)行完訪問文件的命令之后,就可以得到文件名的信息了。 } } }
a.定義一個(gè)與JS對(duì)象映射關(guān)系的Android類:AndroidtoJs
// 繼承自O(shè)bject類 public class AndroidtoJs extends Object { // 定義JS需要調(diào)用的方法 // 被JS調(diào)用的方法必須加入@JavascriptInterface注解 @JavascriptInterface public void hello(String msg) { System.out.println("JS調(diào)用了Android的hello方法"); } }
b.在Android里通過WebView設(shè)置Android類與JS代碼的映射
WebSettings webSettings = mWebView.getSettings(); // 設(shè)置與Js交互的權(quán)限 webSettings.setJavaScriptEnabled(true); // 通過addJavascriptInterface()將Java對(duì)象映射到JS對(duì)象 //參數(shù)1:Javascript對(duì)象名 //參數(shù)2:Java對(duì)象名,在js中通過test調(diào)用hello方法 mWebView.addJavascriptInterface(new AndroidtoJs(), "test"); //AndroidtoJS類對(duì)象映射到j(luò)s的test對(duì)象
c.對(duì)應(yīng)的js代碼為
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Carson</title> <script> function callAndroid(){ // 由于對(duì)象映射,所以調(diào)用test對(duì)象等于調(diào)用Android映射的對(duì)象 test.hello("js調(diào)用了android中的hello方法"); } </script> </head> <body> //點(diǎn)擊按鈕則調(diào)用callAndroid函數(shù) <button type="button" id="button1" "callAndroid()"></button> </body> </html>
2.通過 WebViewClient 的shouldOverrideUrlLoading ()方法回調(diào)攔截 url,Android通過 WebViewClient 的回調(diào)方法shouldOverrideUrlLoading ()攔截 url解析該 url 的協(xié)議,如果檢測(cè)到是預(yù)先約定好的協(xié)議,就調(diào)用相應(yīng)方法
function callAndroid(){ /*約定的url協(xié)議為:js://webview?arg1=111&arg2=222*/ document.location = "js://webview?arg1=111&arg2=222"; } //點(diǎn)擊之后執(zhí)行了callAndroid()方法 <button type="button" id="button1" "callAndroid()">點(diǎn)擊調(diào)用Android代碼</button>
回調(diào)到shouldOverrideUrlLocading方法
public boolean shouldOverrideUrlLoading(WebView view, String url) { // 步驟2:根據(jù)協(xié)議的參數(shù),判斷是否是所需要的url // 一般根據(jù)scheme(協(xié)議格式) & authority(協(xié)議名)判斷(前兩個(gè)參數(shù)) //假定傳入進(jìn)來的 url = "js://webview?arg1=111&arg2=222"(同時(shí)也是約定好的需要攔截的) Uri uri = Uri.parse(url); // 如果url的協(xié)議 = 預(yù)先約定的 js 協(xié)議 // 就解析往下解析參數(shù) if ( uri.getScheme().equals("js")) { // 如果 authority = 預(yù)先約定協(xié)議里的 webview,即代表都符合約定的協(xié)議 // 所以攔截url,下面JS開始調(diào)用Android需要的方法 if (uri.getAuthority().equals("webview")) { // 步驟3: // 執(zhí)行JS所需要調(diào)用的邏輯 System.out.println("js調(diào)用了Android的方法"); } return true; } return super.shouldOverrideUrlLoading(view, url); }
如果JS想要得到Android方法的返回值,只能通過 WebView 的 loadUrl ()去執(zhí)行 JS 方法把返回值傳遞回去,相關(guān)的代碼如下
// Android: MainActivity.java mWebView.loadUrl("javascript:returnResult(" + result + ")"); // JS: javascript.html function returnResult(result){ alert("result is" + result); }
3.通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調(diào)攔截JS對(duì)話框alert()、confirm()、prompt()消息
1.常用的攔截是:攔截 JS的輸入框(即prompt()方法)
2.因?yàn)橹挥衟rompt()可以返回任意類型的值,操作最全面方便、更加靈活;而alert()對(duì)話框沒有返回值;confirm()對(duì)話框只能返回兩種狀態(tài)(確定 / 取消)兩個(gè)值
3.如果是攔截警告框(即alert()),則觸發(fā)回調(diào)onJsAlert();
4.如果是攔截確認(rèn)框(即confirm()),則觸發(fā)回調(diào)onJsConfirm();
//點(diǎn)擊按鈕調(diào)用clickprompt方法 <button type="button" id="button1" "clickprompt()">點(diǎn)擊調(diào)用Android代碼</button>
function clickprompt(){ // 調(diào)用prompt() var result=prompt("js://demo?arg1=111&arg2=222"); alert("demo " + result); } mWebView.setWebChromeClient(new WebChromeClient() { // 攔截輸入框(原理同方式2) // 參數(shù)message:代表promt()的內(nèi)容(不是url) // 參數(shù)result:代表輸入框的返回值 @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { // 根據(jù)協(xié)議的參數(shù),判斷是否是所需要的url(原理同方式2) // 一般根據(jù)scheme(協(xié)議格式) & authority(協(xié)議名)判斷(前兩個(gè)參數(shù)) //假定傳入進(jìn)來的 url = "js://webview?arg1=111&arg2=222"(同時(shí)也是約定好的需要攔截的) Uri uri = Uri.parse(message); // 如果url的協(xié)議 = 預(yù)先約定的 js 協(xié)議 // 就解析往下解析參數(shù) if ( uri.getScheme().equals("js")) { // 如果 authority = 預(yù)先約定協(xié)議里的 webview,即代表都符合約定的協(xié)議 // 所以攔截url,下面JS開始調(diào)用Android需要的方法 if (uri.getAuthority().equals("webview")) { // // 執(zhí)行JS所需要調(diào)用的邏輯 System.out.println("js調(diào)用了Android的方法"); // 可以在協(xié)議上帶有參數(shù)并傳遞到Android上 HashMap<String, String> params = new HashMap<>(); Set<String> collection = uri.getQueryParameterNames(); //參數(shù)result:代表消息框的返回值(輸入值) result.confirm("js調(diào)用了Android的方法成功啦"); } return true; } return super.onJsPrompt(view, url, message, defaultValue, result); }
說下Activity的啟動(dòng)模式,生命周期,兩個(gè)Activity跳轉(zhuǎn)的生命周期,如果一個(gè)Activity跳轉(zhuǎn)另一個(gè)Activity再按下Home鍵在回到Activity的生命周期是什么樣的
啟動(dòng)模式
Standard模式:Activity可以有多個(gè)實(shí)例,每次啟動(dòng)Activity,無論任務(wù)棧中是否已經(jīng)有這個(gè)Activity的實(shí)例,系統(tǒng)都會(huì)創(chuàng)建一個(gè)新的Activity實(shí)例
SingleTop模式:當(dāng)一個(gè)singleTop模式的Activity已經(jīng)位于任務(wù)棧的棧頂,再去啟動(dòng)它時(shí),不會(huì)再創(chuàng)建新的實(shí)例,如果不位于棧頂,就會(huì)創(chuàng)建新的實(shí)例
SingleTask模式:如果Activity已經(jīng)位于棧頂,系統(tǒng)不會(huì)創(chuàng)建新的Activity實(shí)例,和singleTop模式一樣。但Activity已經(jīng)存在但不位于棧頂時(shí),系統(tǒng)就會(huì)把該Activity移到棧頂,并把它上面的activity出棧
SingleInstance模式:singleInstance模式也是單例的,但和singleTask不同,singleTask只是任務(wù)棧內(nèi)單例,系統(tǒng)里是可以有多個(gè)singleTask Activity實(shí)例的,而singleInstance Activity在整個(gè)系統(tǒng)里只有一個(gè)實(shí)例,啟動(dòng)一singleInstanceActivity時(shí),系統(tǒng)會(huì)創(chuàng)建一個(gè)新的任務(wù)棧,并且這個(gè)任務(wù)棧只有他一個(gè)Activity
生命周期
onCreate onStart onResume onPause onStop onDestroy
兩個(gè)Activity跳轉(zhuǎn)的生命周期
1.啟動(dòng)A
onCreate - onStart - onResume
2.在A中啟動(dòng)B
ActivityA onPause
ActivityB onCreate
ActivityB onStart
ActivityB onResume
ActivityA onStop
3.從B中返回A(按物理硬件返回鍵)
ActivityB onPause
ActivityA onRestart
ActivityA onStart
ActivityA onResume
ActivityB onStop
ActivityB onDestroy
4.繼續(xù)返回
ActivityA onPause
ActivityA onStop
ActivityA onDestroy
onRestart的調(diào)用場(chǎng)景
(1)按下home鍵之后,然后切換回來,會(huì)調(diào)用onRestart()。
(2)從本Activity跳轉(zhuǎn)到另一個(gè)Activity之后,按back鍵返回原來Activity,會(huì)調(diào)用onRestart();
(3)從本Activity切換到其他的應(yīng)用,然后再?gòu)钠渌麘?yīng)用切換回來,會(huì)調(diào)用onRestart();
說下Activity的橫豎屏的切換的生命周期,用那個(gè)方法來保存數(shù)據(jù),兩者的區(qū)別。觸發(fā)在什么時(shí)候在那個(gè)方法里可以獲取數(shù)據(jù)等。
如何實(shí)現(xiàn)進(jìn)程?;?br />
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出來的子進(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
說下冷啟動(dòng)與熱啟動(dòng)是什么,區(qū)別,如何優(yōu)化,使用場(chǎng)景等。
app冷啟動(dòng): 當(dāng)應(yīng)用啟動(dòng)時(shí),后臺(tái)沒有該應(yīng)用的進(jìn)程,這時(shí)系統(tǒng)會(huì)重新創(chuàng)建一個(gè)新的進(jìn)程分配給該應(yīng)用, 這個(gè)啟動(dòng)方式就叫做冷啟動(dòng)(后臺(tái)不存在該應(yīng)用進(jìn)程)。冷啟動(dòng)因?yàn)橄到y(tǒng)會(huì)重新創(chuàng)建一個(gè)新的進(jìn)程分配給它,所以會(huì)先創(chuàng)建和初始化Application類,再創(chuàng)建和初始化MainActivity類(包括一系列的測(cè)量、布局、繪制),最后顯示在界面上。
app熱啟動(dòng): 當(dāng)應(yīng)用已經(jīng)被打開, 但是被按下返回鍵、Home鍵等按鍵時(shí)回到桌面或者是其他程序的時(shí)候,再重新打開該app時(shí), 這個(gè)方式叫做熱啟動(dòng)(后臺(tái)已經(jīng)存在該應(yīng)用進(jìn)程)。熱啟動(dòng)因?yàn)闀?huì)從已有的進(jìn)程中來啟動(dòng),所以熱啟動(dòng)就不會(huì)走Application這步了,而是直接走M(jìn)ainActivity(包括一系列的測(cè)量、布局、繪制),所以熱啟動(dòng)的過程只需要?jiǎng)?chuàng)建和初始化一個(gè)MainActivity就行了,而不必創(chuàng)建和初始化Application
冷啟動(dòng)的流程
當(dāng)點(diǎn)擊app的啟動(dòng)圖標(biāo)時(shí),安卓系統(tǒng)會(huì)從Zygote進(jìn)程中fork創(chuàng)建出一個(gè)新的進(jìn)程分配給該應(yīng)用,之后會(huì)依次創(chuàng)建和初始化Application類、創(chuàng)建MainActivity類、加載主題樣式Theme中的windowBackground等屬性設(shè)置給MainActivity以及配置Activity層級(jí)上的一些屬性、再inflate布局、當(dāng)onCreate/onStart/onResume方法都走完了后最后才進(jìn)行contentView的measure/layout/draw顯示在界面上
冷啟動(dòng)的生命周期簡(jiǎn)要流程:
Application構(gòu)造方法 –> attachBaseContext()–>onCreate –>Activity構(gòu)造方法 –> onCreate() –> 配置主體中的背景等操作 –>onStart() –> onResume() –> 測(cè)量、布局、繪制顯示
冷啟動(dòng)的優(yōu)化主要是視覺上的優(yōu)化,解決白屏問題,提高用戶體驗(yàn),所以通過上面冷啟動(dòng)的過程。能做的優(yōu)化如下:
減少onCreate()方法的工作量
不要讓Application參與業(yè)務(wù)的操作
不要在Application進(jìn)行耗時(shí)操作
不要以靜態(tài)變量的方式在Application保存數(shù)據(jù)
減少布局的復(fù)雜度和層級(jí)
減少主線程耗時(shí)
為什么冷啟動(dòng)會(huì)有白屏黑屏問題?原因在于加載主題樣式Theme中的windowBackground等屬性設(shè)置給MainActivity發(fā)生在inflate布局當(dāng)onCreate/onStart/onResume方法之前,而windowBackground背景被設(shè)置成了白色或者黑色,所以我們進(jìn)入app的第一個(gè)界面的時(shí)候會(huì)造成先白屏或黑屏一下再進(jìn)入界面。解決思路如下
1.給他設(shè)置windowBackground背景跟啟動(dòng)頁(yè)的背景相同,如果你的啟動(dòng)頁(yè)是張圖片那么可以直接給windowBackground這個(gè)屬性設(shè)置該圖片那么就不會(huì)有一閃的效果了
<style name=``"Splash_Theme"` `parent=``"@android:style/Theme.NoTitleBar"``>` <item name=``"android:windowBackground"``>@drawable/splash_bg</item>` <item name=``"android:windowNoTitle"``>``true``</item>` </style>`
2.采用世面的處理方法,設(shè)置背景是透明的,給人一種延遲啟動(dòng)的感覺。,將背景顏色設(shè)置為透明色,這樣當(dāng)用戶點(diǎn)擊桌面APP圖片的時(shí)候,并不會(huì)"立即"進(jìn)入APP,而且在桌面上停留一會(huì),其實(shí)這時(shí)候APP已經(jīng)是啟動(dòng)的了,只是我們心機(jī)的把Theme里的windowBackground的顏色設(shè)置成透明的,強(qiáng)行把鍋甩給了手機(jī)應(yīng)用廠商(手機(jī)反應(yīng)太慢了啦)
<style name=``"Splash_Theme"` `parent=``"@android:style/Theme.NoTitleBar"``>` <item name=``"android:windowIsTranslucent"``>``true``</item>` <item name=``"android:windowNoTitle"``>``true``</item>` </style>`
3.以上兩種方法是在視覺上顯得更快,但其實(shí)只是一種表象,讓應(yīng)用啟動(dòng)的更快,有一種思路,將Application中的不必要的初始化動(dòng)作實(shí)現(xiàn)懶加載,比如,在SpashActivity顯示后再發(fā)送消息到Application,去初始化,這樣可以將初始化的動(dòng)作放在后邊,縮短應(yīng)用啟動(dòng)到用戶看到界面的時(shí)
ANR的原因
1.耗時(shí)的網(wǎng)絡(luò)訪問
2.大量的數(shù)據(jù)讀寫
3.數(shù)據(jù)庫(kù)操作
4.硬件操作(比如camera)
5.調(diào)用thread的join()方法、sleep()方法、wait()方法或者等待線程鎖的時(shí)候
6.service binder的數(shù)量達(dá)到上限
7.system server中發(fā)生WatchDog ANR
8.service忙導(dǎo)致超時(shí)無響應(yīng)
9.其他線程持有鎖,導(dǎo)致主線程等待超時(shí)
10.其它線程終止或崩潰導(dǎo)致主線程一直等待
三級(jí)緩存原理
當(dāng)Android端需要獲得數(shù)據(jù)時(shí)比如獲取網(wǎng)絡(luò)中的圖片,首先從內(nèi)存中查找(按鍵查找),內(nèi)存中沒有的再?gòu)拇疟P文件或sqlite中去查找,若磁盤中也沒有才通過網(wǎng)絡(luò)獲取
jvm,jre以及jdk三者之間的關(guān)系?JDK(Java Development Kit)是針對(duì)Java開發(fā)員的產(chǎn)品,是整個(gè)Java的核心,包括了Java運(yùn)行環(huán)境JRE、Java工具和Java基礎(chǔ)類庫(kù)。
Java Runtime Environment(JRE)是運(yùn)行JAVA程序所必須的環(huán)境的集合,包含JVM標(biāo)準(zhǔn)實(shí)現(xiàn)及Java核心類庫(kù)。
JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫,是整個(gè)java實(shí)現(xiàn)跨平臺(tái)的最核心的部分,能夠運(yùn)行以Java語(yǔ)言寫作的軟件程序。
談?wù)勀銓?duì) JNIEnv 和 JavaVM 理解?
- JavaVm
JavaVM 是虛擬機(jī)在 JNI 層的代表,一個(gè)進(jìn)程只有一個(gè) JavaVM,所有的線程共用一個(gè) JavaVM。
- JNIEnv
JNIEnv 表示 Java 調(diào)用 native 語(yǔ)言的環(huán)境,是一個(gè)封裝了幾乎全部 JNI 方法的指針。
JNIEnv 只在創(chuàng)建它的線程生效,不能跨線程傳遞,不同線程的 JNIEnv 彼此獨(dú)立。
native 環(huán)境中創(chuàng)建的線程,如果需要訪問 JNI,必須要調(diào)用 AttachCurrentThread 關(guān)聯(lián),并使用 DetachCurrentThread 解除鏈接。
Serializable與Parcable的區(qū)別?
1.Serializable (java 自帶)
方法:對(duì)象繼承 Serializable類即可實(shí)現(xiàn)序列化,就是這么簡(jiǎn)單,也是它最吸引我們的地方
2.Parcelable(Android專用):Parcelable方式的實(shí)現(xiàn)原理是將一個(gè)完整的對(duì)象進(jìn)行分解,用起來比較麻煩
1)在使用內(nèi)存的時(shí)候,Parcelable比Serializable性能高,所以推薦使用Parcelable。
2)Serializable在序列化的時(shí)候會(huì)產(chǎn)生大量的臨時(shí)變量,從而引起頻繁的GC。
3)Parcelable不能使用在要將數(shù)據(jù)存儲(chǔ)在磁盤上的情況,因?yàn)镻arcelable不能很好的保證數(shù)據(jù)的持續(xù)性,在外界有變化的情況下。盡管Serializable效率低點(diǎn),但此時(shí)還是建議使用Serializable 。
4)android上應(yīng)該盡量采用Parcelable,效率至上,效率遠(yuǎn)高于Serializable
寶馬面試題
Handler消息機(jī)制。
自定義view步驟,如何自定義屬性。
布局的xml文件中 merge、include、viewstub關(guān)鍵字的使用。
MVP和MVC的區(qū)別。
DataBinding的使用。
谷歌最新技術(shù)的關(guān)注度?
自定義view中的onDraw()方法中,canvas和paint的api的功能。以及刷新功能的使用。
Synchronized關(guān)鍵字的各種使用。
如何繪制一個(gè)帶圓角的箭頭?你的實(shí)現(xiàn)思路是什么?
Java中Exception和Error的區(qū)別
1.Error類一般是指與虛擬機(jī)相關(guān)的問題,如系統(tǒng)崩潰,虛擬機(jī)錯(cuò)誤,內(nèi)存空間不足,方法調(diào)用棧溢等
2.Exception類表示程序可以處理的異常,可以捕獲且可能恢復(fù)。遇到這類異常,應(yīng)該盡可能處理異常,使程序恢復(fù)運(yùn)行,而不應(yīng)該隨意終止異常。Exception又分為可檢查異常和不可檢查異常,可檢查異常在源代碼里必須顯示的進(jìn)行捕獲處理,這是編譯期檢查的一部分。不可檢查異常就是所謂的運(yùn)行時(shí)異常,類似NullPointerException、ArrayIndexOutOfBoundsException之類,通常是可以編碼避免的邏輯錯(cuò)誤,具體可以根據(jù)需要來判斷是否需要捕獲,并不會(huì)在編譯期強(qiáng)制要求
3..exception和error都是繼承了throwable類,在java中只有throwable類型的實(shí)例才可以被拋出(throw)或者捕獲(catch),它是異常處理機(jī)制的基本組成類型
4.exception和error體現(xiàn)了java平臺(tái)設(shè)計(jì)者對(duì)不同異常情況的分類。exception是程序正常運(yùn)行中,可以預(yù)料的意外情況,并且應(yīng)該被捕獲,進(jìn)行相應(yīng)的處理
Android單線程模型
Android單線程模型的核心原則就是:只能在UI線程(Main Thread)中對(duì)UI進(jìn)行處理。當(dāng)一個(gè)程序第一次啟動(dòng)時(shí),Android會(huì)同時(shí)啟動(dòng)一個(gè)對(duì)應(yīng)的 主線程(Main Thread),主線程主要負(fù)責(zé)處理與UI相關(guān)的事件,如:用戶的按鍵事件,用戶接觸屏幕的事件以及屏幕繪圖事 件,并把相關(guān)的事件分發(fā)到對(duì)應(yīng)的組件進(jìn)行處理。所以主線程通常又被叫做UI線 程。在開發(fā)Android應(yīng)用時(shí)必須遵守單線程模型的原則: Android UI操作并不是線程安全的并且這些操作必須在UI線程中執(zhí)行。
Android的單線程模型有兩條原則:
1.不要阻塞UI線程。
2.不要在UI線程之外訪問Android UI toolkit(主要是這兩個(gè)包中的組件:android.widget and android.view
RecyclerView在很多方面能取代ListView,Google為什么沒把ListView劃上一條過時(shí)的橫線?
ListView采用的是RecyclerBin的回收機(jī)制在一些輕量級(jí)的List顯示時(shí)效率更高。
App啟動(dòng)流程
App啟動(dòng)時(shí),AMS會(huì)檢查這個(gè)應(yīng)用程序所需要的進(jìn)程是否存在,不存在就會(huì)請(qǐng)求Zygote進(jìn)程啟動(dòng)需要的應(yīng)用程序進(jìn)程,Zygote進(jìn)程接收到AMS請(qǐng)求并通過fock自身創(chuàng)建應(yīng)用程序進(jìn)程,這樣應(yīng)用程序進(jìn)程就會(huì)獲取虛擬機(jī)的實(shí)例,還會(huì)創(chuàng)建Binder線程池(ProcessState.startThreadPool())和消息循環(huán)(ActivityThread looper.loop),然后App進(jìn)程,通過Binder IPC向sytem_server進(jìn)程發(fā)起attachApplication請(qǐng)求;system_server進(jìn)程在收到請(qǐng)求后,進(jìn)行一系列準(zhǔn)備工作后,再通過Binder IPC向App進(jìn)程發(fā)送scheduleLaunchActivity請(qǐng)求;App進(jìn)程的binder線程(ApplicationThread)在收到請(qǐng)求后,通過handler向主線程發(fā)送LAUNCH_ACTIVITY消息;主線程在收到Message后,通過反射機(jī)制創(chuàng)建目標(biāo)Activity,并回調(diào)Activity.onCreate()等方法。到此,App便正式啟動(dòng),開始進(jìn)入Activity生命周期,執(zhí)行完onCreate/onStart/onResume方法,UI渲染結(jié)束后便可以看到App的主界面。
雙親委托模式
類加載器查找class所采用的是雙親委托模式,所謂雙親委托模式就是判斷該類是否已經(jīng)加載,如果沒有則不是自身去查找而是委托給父加載器進(jìn)行查找,這樣依次進(jìn)行遞歸,直到委托到最頂層的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了該Class,就會(huì)直接返回,如果沒找到,則繼續(xù)依次向下查找,如果還沒找到則最后交給自身去查找
雙親委托模式的好處
1.避免重復(fù)加載,如果已經(jīng)加載過一次Class,則不需要再次加載,而是直接讀取已經(jīng)加載的Class
2.更加安全,確保,java核心api中定義類型不會(huì)被隨意替換,比如,采用雙親委托模式可以使得系統(tǒng)在Java虛擬機(jī)啟動(dòng)時(shí)舊加載了String類,也就無法用自定義的String類來替換系統(tǒng)的String類,這樣便可以防止核心API庫(kù)被隨意篡改。
什么情況下會(huì)觸發(fā)類的初始化
遇到new,getstatic,putstatic,invokestatic這4條指令;
使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用;
初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類沒有進(jìn)行過初始化,則先初始化其父類(注意!如果其父類是接口的話,則不要求初始化父類);
當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類;
當(dāng)使用jdk1.7的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒有進(jìn)行過初始化,則先觸發(fā)其類初始化;
類的加載過程
類加載過程主要包含加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用、卸載七個(gè)方面,下面一一闡述。
1.加載:獲取定義此類的二進(jìn)制字節(jié)流,生成這個(gè)類的java.lang.Class對(duì)象
2.驗(yàn)證:保證Class文件的字節(jié)流包含的信息符合JVM規(guī)范,不會(huì)給JVM造成危害
3.準(zhǔn)備:準(zhǔn)備階段為變量分配內(nèi)存并設(shè)置類變量的初始化
4.解析:解析過程是將常量池內(nèi)的符號(hào)引用替換成直接引用
5.初始化:不同于準(zhǔn)備階段,本次初始化,是根據(jù)程序員通過程序制定的計(jì)劃去初始化類的變量和其他資源。這些資源有static{}塊,構(gòu)造函數(shù),父類的初始化等
6.使用:使用過程就是根據(jù)程序定義的行為執(zhí)行
7.卸載:卸載由GC完成。
8.三次握手時(shí)最后一次客戶端收到服務(wù)端SYN+ACK包時(shí),發(fā)送確認(rèn)信息給服務(wù)端,此時(shí)服務(wù)端沒有收到,那么兩端都處于什么狀態(tài)
當(dāng)?shù)谌挝帐质r(shí)的處理操作,可以看出當(dāng)失敗時(shí)服務(wù)器并不會(huì)重傳ack報(bào)文,而是直接發(fā)送RTS報(bào)文段,進(jìn)入CLOSED狀態(tài)。這樣做的目的是為了防止SYN洪泛攻擊。
9.BLocked和Waiting狀態(tài)的區(qū)別
10.wait的實(shí)現(xiàn)機(jī)制
11.C++重載的原理
消息隊(duì)列中的消息處理完之后,Looper進(jìn)入組賽狀態(tài),那么此時(shí)我想進(jìn)行一次垃圾回收,能不能做到?
可以。通過IdleHandler實(shí)現(xiàn),IdleHandler即在looper里面的message處理完了的時(shí)候去調(diào)用
IdleHandler源碼
/** * Callback interface for discovering when a thread is going to block * waiting for more messages.消息隊(duì)列進(jìn)入組賽狀態(tài)后會(huì)執(zhí)行 */ public static interface IdleHandler { /** * Called when the message queue has run out of messages and will now * wait for more. Return true to keep your idle handler active, false * to have it removed. This may be called if there are still messages * pending in the queue, but they are all scheduled to be dispatched * after the current time. *返回值為true,則保持此Idle一直在Handler中,一直運(yùn)行,否則,執(zhí)行一次后就從Handler線程中remove掉,只執(zhí)行一次。 */ boolean queueIdle(); }
系統(tǒng)源碼中IdleHandler的使用
void scheduleGcIdler() { if (!mGcIdlerScheduled) { mGcIdlerScheduled = true; Looper.myQueue().addIdleHandler(mGcIdler); } mH.removeMessages(H.GC_WHEN_IDLE); }
這個(gè)方法的調(diào)用發(fā)生在ActivityThread的Handler handleMessage中,可以看到,gc是在主線程消息隊(duì)列阻塞狀態(tài)(等待新的消息或者消息執(zhí)行完成)時(shí)進(jìn)行的,并且每次執(zhí)行g(shù)c有一個(gè)最小時(shí)間間隔5x1000
public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { ...... case GC_WHEN_IDLE: scheduleGcIdler(); break;
void scheduleGcIdler() { if (!mGcIdlerScheduled) { mGcIdlerScheduled = true; Looper.myQueue().addIdleHandler(mGcIdler); } mH.removeMessages(H.GC_WHEN_IDLE); } final class GcIdler implements MessageQueue.IdleHandler { @Override public final boolean queueIdle() { doGcIfNeeded(); return false; } } void doGcIfNeeded() { mGcIdlerScheduled = false; final long now = SystemClock.uptimeMillis(); //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime() // + "m now=" + now); //private static final long MIN_TIME_BETWEEN_GCS = 5*1000; if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) { //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!"); BinderInternal.forceGc("bg"); } }
Handler消息的優(yōu)先級(jí)-同步屏障機(jī)制(sync barrier)
同步屏障可以通過MessageQueue.postSyncBarrier函數(shù)來設(shè)置。該方法發(fā)送了一個(gè)沒有target的Message到Queue中,在next方法中獲取消息時(shí),如果發(fā)現(xiàn)沒有target的Message,則在一定的時(shí)間內(nèi)跳過同步消息,優(yōu)先執(zhí)行異步消息。再換句話說,同步屏障為Handler消息機(jī)制增加了一種簡(jiǎn)單的優(yōu)先級(jí)機(jī)制,異步消息的優(yōu)先級(jí)要高于同步消息。在創(chuàng)建Handler時(shí)有一個(gè)async參數(shù),傳true表示此handler發(fā)送的時(shí)異步消息。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保證UI繪制優(yōu)先執(zhí)行。
Linux管道機(jī)制
阻塞后的Looper如何再次被激活
Java中的泛型是什么 ? 使用泛型的好處是什么?
在集合中存儲(chǔ)對(duì)象并在使用前進(jìn)行類型轉(zhuǎn)換不方便。泛型防止了那種情況的發(fā)生。它提供了編譯期的類型安全,確保你只能把正確類型的對(duì)象放入 集合中,避免了在運(yùn)行時(shí)出現(xiàn)ClassCastException。
Java的泛型是如何工作的 ? 什么是類型擦除 ?
Java源代碼里面類型提供實(shí)現(xiàn)泛型功能,而編譯后Class文件類型就變成原生類型(即類型被擦除掉),而在引用處插入強(qiáng)制類型轉(zhuǎn)換以實(shí)現(xiàn)JVM對(duì)泛型的支持。本質(zhì)是Java泛型只是Java提供的一個(gè)語(yǔ)法糖,底層
的JVM并不提供支持,Java中的泛型屬于偽泛型。
但是編譯后的字節(jié)碼通過反射后還是可以獲取到泛型的真實(shí)類型信息,因?yàn)榉盒筒脸]有把保存泛型元數(shù)據(jù)擦除掉。
泛型是通過類型擦除來實(shí)現(xiàn)的,編譯器在編譯時(shí)擦除了所有類型相關(guān)的信息,所以在運(yùn)行時(shí)不存在任何類型相關(guān)的信息。例如 List<String>在運(yùn)行時(shí)僅用一個(gè)List來表示。這樣做的目的,是確保能和Java 5之前的版本開發(fā)二進(jìn)制類庫(kù)進(jìn)行兼容。你無法在運(yùn)行時(shí)訪問到類型參數(shù),因?yàn)榫幾g器已經(jīng)把泛型類型轉(zhuǎn)換成了原始類型。根據(jù)你對(duì)這個(gè)泛型問題的回答情況,你會(huì) 得到一些后續(xù)提問,比如為什么泛型是由類型擦除來實(shí)現(xiàn)的或者給你展示一些會(huì)導(dǎo)致編譯器出錯(cuò)的錯(cuò)誤泛型代碼。
什么是泛型中的限定通配符和非限定通配符 ?
這是另一個(gè)非常流行的Java泛型面試題。限定通配符對(duì)類型進(jìn)行了限制。有兩種限定通配符,一種是<? extends T>它通過確保類型必須是T的子類來設(shè)定類型的上界,另一種是<? super T>它通過確保類型必須是T的父類來設(shè)定類型的下界。泛型類型必須用限定內(nèi)的類型來進(jìn)行初始化,否則會(huì)導(dǎo)致編譯錯(cuò)誤。另一方面<?>表 示了非限定通配符,因?yàn)?lt;?>可以用任意類型來替代。
List<? extends T>和List <? super T>之間有什么區(qū)別 ?
這和上一個(gè)面試題有聯(lián)系,有時(shí)面試官會(huì)用這個(gè)問題來評(píng)估你對(duì)泛型的理解,而不是直接問你什么是限定通配符和非限定通配符。這兩個(gè)List的聲明都是 限定通配符的例子,List<? extends T>可以接受任何繼承自T的類型的List,而List<? super T>可以接受任何T的父類構(gòu)成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>。在本段出現(xiàn)的連接中可以找到更多信息。
為什么Java泛型是偽泛型
Java 的泛型是偽泛型, 也就是騙騙編譯器的。運(yùn)行期的泛型類型,被擦除了,因此,在運(yùn)行期,ArrayList<String> 和 ArrayList<int> 是相同的類型。Java源代碼里面類型提供實(shí)現(xiàn)泛型功能,而編譯后Class文件類型就變成原生類型(即類型被擦除掉),而在引用處插入強(qiáng)制類型轉(zhuǎn)換以實(shí)現(xiàn)JVM對(duì)泛型的支持。本質(zhì)是Java泛型只是Java提供的一個(gè)語(yǔ)法糖,底層
的JVM并不提供支持,Java中的泛型屬于偽泛型。
相關(guān)文章
- 這篇文章主要介紹了華為Android三面成功通過,面試官都問了什么,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-26
- 這篇文章主要介紹了Android 一線大廠面試總結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-08
- 這篇文章主要介紹了2019年必備的Android面試題及參考答案,整理匯總了Android開發(fā)中常見的各種知識(shí)點(diǎn)、技術(shù)細(xì)節(jié)與注意事項(xiàng),需要的朋友可以參考下2019-10-30
- 這篇文章主要介紹了2019 Android 面試真題集錦,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)2019-08-28
2019 金三銀四:阿里P9架構(gòu)的Android大廠面試題總結(jié)
面試是一道坎,很多人會(huì)恐懼面試,即使是工作很多年的老鳥,可能仍存在面試的焦慮。 今天介紹了阿里P9架構(gòu)的Android大廠面試題總結(jié)的相關(guān)資料,小編覺得挺不錯(cuò)的,現(xiàn)在分2019-05-05這20道題,聽說只有大廠的Android工程師能全對(duì)
這篇文章主要介紹了這20道題,聽說只有大廠的Android工程師能全對(duì),趕快一起來測(cè)試一下自己把,看看是不是都掌握了2020-05-25