過年回來到現(xiàn)在也一個(gè)月了,這段時(shí)間一直沒寫文章,這是因?yàn)槲覝?zhǔn)備換工作了,一直在面試,也面試了四五家,但是效果都不是很好,雖然如此,但也算收獲了一些經(jīng)驗(yàn),我就將我面試遇到的問題記錄下來,與大家一起分享吧。(本人是做游戲sdk的,所以一些問題會偏向于sdk的,如果不找sdk方向的工作可以忽略其中的一些問題)
一、面試基礎(chǔ)
1、自我介紹
這個(gè)大家自己可以好好看一下網(wǎng)上的一些攻略,自己組織一個(gè)好一點(diǎn)的自我介紹,主要是要把個(gè)人信息,之前做過什么介紹清楚,這個(gè)就看自我發(fā)揮了。
2、之前做過的項(xiàng)目或者工作經(jīng)歷,遇到了什么難點(diǎn),解決了什么問題,技術(shù)上得到了哪些提高(這個(gè)必問,希望大家準(zhǔn)備好)
這個(gè)問題是必問的,而且感覺還挺重要的,大家面試前先準(zhǔn)備好幾個(gè)技術(shù)難點(diǎn),哪怕項(xiàng)目中沒用過也沒關(guān)系。
3、介紹一下之前項(xiàng)目中常用的第三方框架
二、java部分
一、ArrayList和HashMap實(shí)現(xiàn)原理,他們的特性是什么。
ArrayList:底層數(shù)據(jù)結(jié)構(gòu)是Object數(shù)組,查詢快,增刪慢,查詢是根據(jù)數(shù)組下標(biāo)直接查詢速度快,增刪需要移動后邊的元素和擴(kuò)容,速度慢。線程不安全,元素單個(gè),效率高。(如果要線程安全的List集合可以用Vector)
HashMap:jdk1.8之前的數(shù)據(jù)結(jié)構(gòu)是數(shù)組+鏈表,1.8之后是數(shù)組+鏈表+紅黑樹,當(dāng)鏈表長度小于8的時(shí)候使用的還是數(shù)組+鏈表,當(dāng)鏈表長度超過8時(shí)轉(zhuǎn)換為紅黑樹,HashMap元素成對,元素可為空,線程不安全,new HashMap的時(shí)候初始化默認(rèn)大小為16。(如果要線程安全的Map集合可以用HashTable或ConcurrentHashMap)
二、java四大引用
強(qiáng)引用(StrongReference):JVM 寧可拋出 OOM ,也不會讓 GC 回收具有強(qiáng)引用的對象
軟引用(SoftReference):只有在內(nèi)存空間不足時(shí),才會被回收的對象
弱引用(WeakReference):在 GC 時(shí),一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會回收它的內(nèi)存
虛引用(PhantomReference):任何時(shí)候都可以被GC回收,當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會在回收對象的內(nèi)存之前,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。程序可以通過判斷引用隊(duì)列中是否存在該對象的虛引用,來了解這個(gè)對象是否將要被回收??梢杂脕碜鳛镚C回收Object的標(biāo)志。
三、java抽象類和接口,有什么區(qū)別
抽象類:在Java中被abstract關(guān)鍵字修飾的類稱為抽象類,被abstract關(guān)鍵字修飾的方法稱為抽象方法,抽象方法只有方法的聲明,沒有方法體。抽象類的特點(diǎn):
a、抽象類不能被實(shí)例化只能被繼承;
b、包含抽象方法的一定是抽象類,但是抽象類不一定含有抽象方法;
c、抽象類中的抽象方法的修飾符只能為public或者protected,默認(rèn)為public;
d、一個(gè)子類繼承一個(gè)抽象類,則子類必須實(shí)現(xiàn)父類抽象方法,否則子類也必須定義為抽象類;
e、抽象類可以包含屬性、方法、構(gòu)造方法,但是構(gòu)造方法不能用于實(shí)例化,主要用途是被子類調(diào)用。
接口:Java中接口使用interface關(guān)鍵字修飾,特點(diǎn)為:
a、接口可以包含變量、方法;變量被隱士指定為public static final,方法被隱士指定為public abstract(JDK1.8之前);
b、接口支持多繼承,即一個(gè)接口可以extends多個(gè)接口,間接的解決了Java中類的單繼承問題;
c、一個(gè)類可以實(shí)現(xiàn)多個(gè)接口;
d、JDK1.8中對接口增加了新的特性:(1)、默認(rèn)方法(default method):JDK 1.8允許給接口添加非抽象的方法實(shí)現(xiàn),但必須使用default關(guān)鍵字修飾;定義了default的方法可以不被實(shí)現(xiàn)子類所實(shí)現(xiàn),但只能被實(shí)現(xiàn)子類的對象調(diào)用;如果子類實(shí)現(xiàn)了多個(gè)接口,并且這些接口包含一樣的默認(rèn)方法,則子類必須重寫默認(rèn)方法;(2)、靜態(tài)方法(static method):JDK 1.8中允許使用static關(guān)鍵字修飾一個(gè)方法,并提供實(shí)現(xiàn),稱為接口靜態(tài)方法。接口靜態(tài)方法只能通過接口調(diào)用(接口名.靜態(tài)方法名)。
相同點(diǎn)
(1)都不能被實(shí)例化 (2)接口的實(shí)現(xiàn)類或抽象類的子類都只有實(shí)現(xiàn)了接口或抽象類中的方法后才能實(shí)例化。
不同點(diǎn)
(1)接口只有定義,不能有方法的實(shí)現(xiàn),java 1.8中可以定義default方法體,而抽象類可以有定義與實(shí)現(xiàn),方法可在抽象類中實(shí)現(xiàn)。
(2)實(shí)現(xiàn)接口的關(guān)鍵字為implements,繼承抽象類的關(guān)鍵字為extends。一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,但一個(gè)類只能繼承一個(gè)抽象類。所以,使用接口可以間接地實(shí)現(xiàn)多重繼承。
(3)接口強(qiáng)調(diào)特定功能的實(shí)現(xiàn),而抽象類強(qiáng)調(diào)所屬關(guān)系。
(4)接口成員變量默認(rèn)為public static final,必須賦初值,不能被修改;其所有的成員方法都是public、abstract的。抽象類中成員變量默認(rèn)default,可在子類中被重新定義,也可被重新賦值;抽象方法被abstract修飾,不能被private、static、synchronized和native等修飾,必須以分號結(jié)尾,不帶花括號。
四、設(shè)計(jì)模式六大原則,能否給我說說Android中至少3個(gè)用到設(shè)計(jì)模式的例子
1、單一職責(zé)原則:有且只有一個(gè)原因會引起類的變化。
2、接口隔離原則。
3、里氏替換原則。
4、依賴倒置原則。
5、迪米特法則。
6、開閉原則。
23種設(shè)計(jì)模式:
創(chuàng)建型5個(gè):單例模式、建造者模式、原型模式、工廠方法、抽象工廠
行為型11個(gè):策略模式、狀態(tài)模式、觀察者模式、中介者模式、訪問者模式、迭代器模式、模板方法、備忘錄模式、命令模式、解釋器模式、責(zé)任鏈模式
結(jié)構(gòu)型模式7個(gè):適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式
OKHttp內(nèi)部采用責(zé)任鏈模式來完成每個(gè)interceptor攔截器的調(diào)用
定義:使得多個(gè)對象都有機(jī)會處理請求,從而避免了請求的發(fā)送者和接收者之間的耦合關(guān)系。將 這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有對象處理該請求為止。
優(yōu)點(diǎn): 將請求者和處理者關(guān)系解耦,使代碼更加靈活。
缺點(diǎn): 在鏈中遍歷尋找請求處理者中,如果處理者太多,會影響性能。
RxJava的觀察者模式
定義:定義對象間一種一對多的依賴關(guān)系,使得每當(dāng)一對象狀態(tài)發(fā)生改變時(shí),所有依賴它的對象 會得到通知并自動更新。
應(yīng)用:listView的Adapter的notifyDataSetChanged方法,廣播,事件總線機(jī)制。
觀察者模式主要的作用是對象解耦,將觀察者和被觀察者分隔開,只依賴于observer(觀察員) 和observable(可觀察的)抽象。
優(yōu)點(diǎn): 1.觀察者和被觀察者是抽象耦合,以應(yīng)對業(yè)務(wù)變化; 2.增強(qiáng)系統(tǒng)的靈活性和可擴(kuò)展性。
缺點(diǎn): 在Java中通知是順序執(zhí)行,如果觀察者卡頓,會影響整體的執(zhí)行效率。這種情況下,一般建議采 用異步的方式。
listview/gridview的適配器模式
1.使用場景: 1.接口不兼容 2.想建立一個(gè)可以重復(fù)使用的類 3.需要統(tǒng)一的輸出接口,而輸入端類型不可知。
優(yōu)點(diǎn): 1.更好的復(fù)用性:復(fù)用現(xiàn)有的功能; 2.更好的擴(kuò)展性:擴(kuò)展現(xiàn)有的功能。
缺點(diǎn): 過多的使用適配器,會使系統(tǒng)凌亂不堪,不易于整體把握。如:明明調(diào)用的是A接口,內(nèi)部適配 的卻是B接口的實(shí)現(xiàn),如果系統(tǒng)出現(xiàn)太多這種情況,無異于一場災(zāi)難。
Context外觀模式
使用場景: 為復(fù)雜子系統(tǒng)提供一個(gè)簡單接口。
優(yōu)點(diǎn): 1.隱藏子系統(tǒng)實(shí)現(xiàn)細(xì)節(jié),減少客戶和子系統(tǒng)的耦合,能夠擁抱變化; 2.外觀類對子系統(tǒng)接口封裝,使子系統(tǒng)更加容易使用。
缺點(diǎn): 1.外觀類接口膨脹; 更多免費(fèi)Android開發(fā)視頻課程,2.外觀類沒有遵循開閉原則,當(dāng)業(yè)務(wù)出現(xiàn)變更時(shí),可能需要修改外觀類。
AlertDialog、Notification 源碼使用了建造者模式完成參數(shù)的初始化
優(yōu)點(diǎn):1.良好的封裝性,隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié); 2.建造者獨(dú)立,容易擴(kuò)展。
缺點(diǎn): 會產(chǎn)生多余的Builder對象和Director對象,消耗內(nèi)存。
安卓應(yīng)用主題是抽象工廠模式的最好體現(xiàn)
安卓應(yīng)用有兩套主題:LightTheme亮色主題和DarkTheme暗色主題。主題之下有各種與之相關(guān)的 UI元素。創(chuàng)建主題也要隨之創(chuàng)建各種UI元素,這就是抽象工廠模式的最好體現(xiàn)。
優(yōu)點(diǎn): 分離接口和實(shí)現(xiàn),面向接口編程。在具體實(shí)現(xiàn)中解耦,同時(shí)基于接口和實(shí)現(xiàn)的分離,使得產(chǎn)品類 切換更加容易,靈活。
缺點(diǎn): 1.類文件爆炸性增加 2.新的產(chǎn)品類不易擴(kuò)展
五、一個(gè)線程幾個(gè)loop
三、Android部分
1、65536錯(cuò)誤是怎么回事
該錯(cuò)誤主要是由于我們打包后classes.dex文件里方法數(shù)量超出的65536個(gè)。
預(yù)防方法主要是少寫方法,多用基類,能不用兼容包的就不要用兼容包的,引入jar或library工程時(shí)注意下它們的方法數(shù),但是如果實(shí)在沒辦法,就用這個(gè)方法解決:
第一步:在項(xiàng)目的grade文件里面的defaultConfig閉包下添加: multiDexEnabled true
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
multiDexEnabled true
第二步:在dependencies下添加依賴 compile 'com.android.support:multidex:1.0.0'
dependencies {
compile 'com.android.support:multidex:1.0.0'
第三步:自定義繼承于Application的類,并重寫protected void attachBaseContext(Context base)方法,調(diào)用 MultiDex.install(this)初始化,最后記得在Manifest清單里注冊自定義的application類,如圖:
public class MyApplication extends Application{
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
2、什么是內(nèi)存泄漏、什么是內(nèi)存溢出,該怎么應(yīng)對
內(nèi)存溢出(OOM):指的是申請不到足夠的內(nèi)存;
原因:1、內(nèi)存一次性開銷過大(加載巨圖)。2、內(nèi)存持續(xù)性開銷(循環(huán)創(chuàng)建對象)。3、內(nèi)存回收不及時(shí)(內(nèi)存開銷過快,GC頻率跟不上開銷速度等)。4、內(nèi)存無法回收(內(nèi)存泄漏導(dǎo)致內(nèi)存溢出)。
解決方案:1、調(diào)整圖像大小。2、盡可能不在循環(huán)中申請內(nèi)存。3、及時(shí)回收圖像。
內(nèi)存泄露(Leak):無法釋放已申請的內(nèi)存;
在Java中,判斷對象是否仍在使用的方法是:引用計(jì)數(shù)法,可達(dá)性分析。
原因:1、靜態(tài)變量(單例模式)。2、監(jiān)聽器。3、內(nèi)部類(內(nèi)部類持有外部引用)。4、資源對象未關(guān)閉。5、容器中的對象沒有清理。6、webview。
避免內(nèi)存泄漏方法:
1、不要在匿名內(nèi)部類中進(jìn)行異步操作
2、將非靜態(tài)內(nèi)部類轉(zhuǎn)為靜態(tài)內(nèi)部類 + WeakReference(弱引用)的方式
3、使用Context時(shí),盡量使用Application 的 Context
4、盡量避免使用static 成員變量。另外可以考慮lazy初始化
5、為webView開啟另外一個(gè)進(jìn)程,通過AIDL與主線程進(jìn)行通信,WebView所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時(shí)機(jī)進(jìn)行銷毀,從而達(dá)到內(nèi)存的完整釋放
6、及時(shí)關(guān)閉資源。Bitmap 使用后調(diào)用recycle()方法
兩者關(guān)系:內(nèi)存泄露 → 剩余內(nèi)存不足 → 后續(xù)申請不到足夠內(nèi)存 →內(nèi)存溢出。
3、什么是Android的模塊化、組件化、插件化
模塊化:一個(gè)程序按照其功能做拆分,分成相互獨(dú)立的模塊(例如:登陸,注冊)。模塊化的具體實(shí)施方法分為插件化和組件化。
組件化:是將一個(gè)app分成多個(gè)模塊,每個(gè)模塊都是一個(gè)組件(module),開發(fā)的過程中我們可以讓這些組件相互依賴或者單獨(dú)調(diào)試部分組件,但是最終發(fā)布的時(shí)候?qū)⑦@些組件合并成一個(gè)統(tǒng)一的apk,這就是組件化開發(fā)。
插件化:插件化開發(fā)和組件化不同,插件化開發(fā)就是將整個(gè)app拆分成很多模塊,每個(gè)模塊都是一個(gè)apk(組件化的每個(gè)模塊是一個(gè)lib),最終打包的時(shí)候?qū)⑺拗鱝pk和插件apk分開打包,插件apk通過動態(tài)下發(fā)到宿主apk,這就是插件化。
4、淺述APK的打包流程
第一步 aapt階段: 資源打包工具,將res資源文件打包成R.java文件和res文件。
第二步 aidl階段: 這個(gè)階段處理.aidl文件,生成對應(yīng)的Java接口文件。
第三步 Java Compiler階段:通過Java Compiler編譯R.java、Java接口文件、Java源文件,生成.class文件。
第四步 dex階段: 通過dex2.jar將.class文件處理生成class.dex。
第五步 apkbuilder階段:將classes.dex、res文件夾、AndroidManifest.xml打包成apk文件。
第六步 Jarsigner階段:對apk文件加簽,形成一個(gè)可以運(yùn)行的apk。
5、打apk包時(shí),V1、V2簽名的區(qū)別是什么
v1簽名是對jar進(jìn)行簽名,V2簽名是對整個(gè)apk簽名:官方介紹就是:v2簽名是在整個(gè)APK文件的二進(jìn)制內(nèi)容上計(jì)算和驗(yàn)證的,v1是在歸檔文件中解壓縮文件內(nèi)容。
二者簽名所產(chǎn)生的結(jié)果:
v1:在v1中只對未壓縮的文件內(nèi)容進(jìn)行了驗(yàn)證,所以在APK簽名之后可以進(jìn)行很多修改——文件可以移動,甚至可以重新壓縮。即可以對簽名后的文件在進(jìn)行處理
v2:v2簽名驗(yàn)證了歸檔中的所有字節(jié),而不是單獨(dú)的ZIP條目,如果您在構(gòu)建過程中有任何定制任務(wù),包括篡改或處理APK文件,請確保禁用它們,否則您可能會使v2簽名失效,從而使您的APKs與Android 7.0和以上版本不兼容。
根據(jù)實(shí)際開發(fā)的經(jīng)驗(yàn)總結(jié):
一定可行的方案: 只使用 v1 方案
不一定可行的方案:同時(shí)使用 v1 和 v2 方案
對 7.0 以下一定不行的方案:只使用 v2 方案
6、Handler
什么是handler:
Handler是Android SDK來處理異步消息的核心類。
子線程與主線程通過Handler來進(jìn)行通信。子線程可以通過Handler來通知主線程進(jìn)行UI更新。
7、ListView和RecylerView的區(qū)別,以及如何優(yōu)化
1、 緩存不同:
ListView是2級緩存,RecyclerView比ListView多兩級緩存,支持多個(gè)離ItemView緩存,支持開發(fā)者自定義緩存處理邏輯,支持所有RecyclerView共用同一個(gè)RecyclerViewPool(緩存池)。
2、adapter不同
ListView有自帶的Adapter,例如ArrayAdapter等,而RecylerView所有的adapter必須由自己實(shí)現(xiàn)。
3、布局不同
ListView布局較為單一,只有縱向布局,RecylerView橫向、縱向、表格、瀑布流都可以實(shí)現(xiàn)。
4、刷新區(qū)別
ListView中通常使用全局刷新函數(shù)notifyDataSetChanged()來刷新ListView中的所有數(shù)據(jù),這是一個(gè)非常耗費(fèi)資源的行為,RecyclerView則可以實(shí)現(xiàn)數(shù)據(jù)的局部刷新,例如notifyItemChanged()函數(shù)等。
5、 動畫區(qū)別:
在RecyclerView封裝的類中已經(jīng)自帶了很多內(nèi)置的動畫API,而ListView則需要自己實(shí)現(xiàn)。
6、item點(diǎn)擊事件:
ListView提供了setOnItemClickListener()這樣的item點(diǎn)擊事件,而RecylerView沒有,需要自己實(shí)現(xiàn)。
ListView的優(yōu)化:
優(yōu)化方式一:
convertView的復(fù)用,進(jìn)行布局的復(fù)用。
優(yōu)化方式二:
ViewHolder的使用,避免每次都findviewById。
優(yōu)化方式三:
使用分段加載。
優(yōu)化方式四:
使用分頁加載。
RecylerView的優(yōu)化:
8、EventBus
EventBus是一種用于Android的事件發(fā)布-訂閱總線,它簡化了應(yīng)用程序內(nèi)各個(gè)組件之間進(jìn)行通信的復(fù)雜度,尤其是碎片之間進(jìn)行通信的問題,可以避免由于使用廣播通信而帶來的諸多不便。
三個(gè)角色:
Event:事件,它可以是任意類型,EventBus會根據(jù)事件類型進(jìn)行全局的通知。
Subscriber:事件訂閱者,在EventBus 3.0之前我們必須定義以onEvent開頭的那幾個(gè)方法,分別是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之后事件處理的方法名可以隨意取,不過需要加上注解@subscribe,并且指定線程模型,默認(rèn)是POSTING。
Publisher:事件的發(fā)布者,可以在任意線程里發(fā)布事件。一般情況下,使用EventBus.getDefault()就可以得到一個(gè)EventBus對象,然后再調(diào)用post(Object)方法即可。
四個(gè)線程:
POSTING:默認(rèn),表示事件處理函數(shù)的線程跟發(fā)布事件的線程在同一個(gè)線程。
MAIN:表示事件處理函數(shù)的線程在主線程(UI)線程,因此在這里不能進(jìn)行耗時(shí)操作。
BACKGROUND:表示事件處理函數(shù)的線程在后臺線程,因此不能進(jìn)行UI操作。如果發(fā)布事件的線程是主線程(UI線程),那么事件處理函數(shù)將會開啟一個(gè)后臺線程,如果果發(fā)布事件的線程是在后臺線程,那么事件處理函數(shù)就使用該線程。
ASYNC:表示無論事件發(fā)布的線程是哪一個(gè),事件處理函數(shù)始終會新建一個(gè)子線程運(yùn)行,同樣不能進(jìn)行UI操作。
9、ANR問題
ANR全稱:Application Not Responding,也就是應(yīng)用程序無響應(yīng)。
以下四個(gè)條件都可以造成ANR發(fā)生:
InputDispatching Timeout:5秒內(nèi)無法響應(yīng)屏幕觸摸事件或鍵盤輸入事件
BroadcastQueue Timeout :在執(zhí)行前臺廣播(BroadcastReceiver)的onReceive()函數(shù)時(shí)10秒沒有處理完成,后臺為60秒。
Service Timeout :前臺服務(wù)20秒內(nèi),后臺服務(wù)在200秒內(nèi)沒有執(zhí)行完畢。
ContentProvider Timeout :ContentProvider的publish在10s內(nèi)沒進(jìn)行完成。
造成ANR的原因及解決辦法:
1、主線程阻塞或主線程數(shù)據(jù)讀取。解決辦法:使用子線程處理耗時(shí)任務(wù)或者阻塞任務(wù)
2、CPU滿負(fù)荷,I/O阻塞。解決辦法:文件讀寫或者數(shù)據(jù)庫操作放在子線程。
3、內(nèi)存不足。解決辦法:優(yōu)化內(nèi)存,防止內(nèi)存泄漏。
4、各大組件ANR。解決辦法:各大組件的生命周期也應(yīng)避免耗時(shí)操作。
10、android設(shè)備的唯一標(biāo)識問題
沒有IMEI授權(quán)就用android_id和MAC地址,但是android6.0之后MAC地址統(tǒng)一返回02:00:00:00:00:00。
有IMEI授權(quán)就用imei碼。
android10不能用imei,用OAID吧。
以上的是主流的方法,還有MEID,ESN,IMSI等。
Android設(shè)備唯一標(biāo)識
11、sdk打渠道包方法
12、APP從啟動到看到第一個(gè)頁面