https://github.com/whatshappen/Android_Question
面試題1.
Android
開發(fā)過程中的版本適配問題?
Android4.4
適配:
uri
轉(zhuǎn)path
需要適配Android5.0
適配:
分包適配 -〉在5.0
及以上在app
的gradle
文件中配置multiDexEnabled true
即可色难,但是5.0
以下需要倒入jar
七蜘,然后在Application
的attch
方法中進行初始化Android6.0:
權(quán)限適配 -〉敏感權(quán)限動態(tài)申請;Android7.0:
Uri.fromFile()
適配 -〉使用FileProvider
進行適配遮咖;
Android
出于安全考慮關(guān)閉了網(wǎng)絡(luò)/拍照/錄像系統(tǒng)廣播;Android8.0:
Service
啟動方式適配 -〉需要使用startForegroundService()
啟動服務(wù);
Notification
適配 -〉添加了渠道和組的概念非春;
軟件安裝適配 -〉Android8.0
去掉了“允許未知來源”選項,需要用戶手動確定缓屠,所以安裝程序需要在AndroidManifest.xml
文件中添加REQUEST_INSTALL_PACKAGES
權(quán)限奇昙;
廣播適配 -〉AndroidManifest.xml
中注冊的廣播不能使用隱式,需要明確指定敌完。
權(quán)限適配-〉讀寫權(quán)限分離
面試題2.
關(guān)于協(xié)程的概念
簡單的介紹:協(xié)程又稱微線程储耐,是一個線程執(zhí)行。協(xié)程看上去也是子程序滨溉,但是不同的是可以在子程序內(nèi)部中斷轉(zhuǎn)而去執(zhí)行其他子程序什湘,然后在合適的時候再返回中斷位置繼續(xù)執(zhí)行。
協(xié)程特點:
執(zhí)行效率高:沒有多線程的線程間切換的開銷晦攒;
不需要多線程的鎖機制:因為只有一個線程闽撤,所以不需要
面試題3.
synchronized
和lock
的區(qū)別?
synchronized
會主動釋放鎖脯颜,而lock
需要手動調(diào)用unlock
釋放鎖哟旗;
synchronized
是java
內(nèi)置的關(guān)鍵字,而lock
是個java
類;
面試題4.
Handler
機制如何保證消息不錯亂热幔?消息延遲是如何實現(xiàn)的乐设?Handler、Looper绎巨、MessageQueue
三者對應(yīng)關(guān)系近尚?內(nèi)存泄漏如何避免?Looper
中的死循環(huán)為什么不會引器主線程ANR
场勤?
1.handler
機制中多個handler
共有一個looper
不會錯亂是因為在handler
發(fā)送消息的時候戈锻,會將當(dāng)前的handler
對象綁定到message
的target
屬性上,然后在Looper
取到消息后通過msg.target
拿到之前的handler
對象和媳,然后調(diào)用handler
的handleMessage
方法格遭。
2.消息延遲的原理:handler
發(fā)送延遲消息,會將當(dāng)前的延遲時間綁定到msg
的when
屬性上留瞳,然后在循環(huán)MessageQUeue
獲取msg
時判斷如果當(dāng)前有延遲就進行阻塞拒迅,通過計時器計算時間,時間通過系統(tǒng)啟動計算時間她倘,然后等待阻塞時間結(jié)束之后將其喚醒璧微,在阻塞過程中會將之后的消息放在消息隊列的頭部去處理。
3.同一個線程中可以有多個Handler硬梁,只有一個Looper
前硫,而MessageQueue
在looper
中初始化的,所以也只有一個MessageQueue
荧止。因此對應(yīng)關(guān)系是:Handler:Looper = 多對一屹电,Looper:MeesageQueue = 一對一,Handler:MessageQueue = 多對一
跃巡。
4.Handler
的內(nèi)存泄漏是由于Handler
持有外部類的引用危号,使其無法釋放。
解決辦法:(1)定義成靜態(tài)內(nèi)部類瓷炮,使其不持有外部類的引用葱色;(2)可以使用弱引用;
還需要在外部類銷毀的時候娘香,移除所有的消息。
5.可以說整個應(yīng)用的生命周期都是在looper.loop()
控制之下的(在應(yīng)用啟動的入口main函數(shù)中初始化ActivityThread办龄,Handler烘绽,Looper
,然后通過handler
和looper
去控制初始化應(yīng)用)俐填。而looper.loop
采用的是Linux
的管道機制安接,在沒有消息的時候會進入阻塞狀態(tài),釋放CPU
執(zhí)行權(quán)英融,等待被喚醒盏檐。真正會卡死主線程的操作是在回調(diào)方法onCreate/onStart/onResume
等操作時間過長歇式,會導(dǎo)致掉幀,甚至發(fā)生ANR胡野,looper.loop
本身不會導(dǎo)致應(yīng)用卡死材失。
面試題5.
開發(fā)過程中如果想替換第三方jar
中的某個class
文件,或者在開發(fā)時你的class
文件與jar
中的重名硫豆,但是你想使用自己的應(yīng)該如何解決龙巨?如果你替換掉某個方法又該怎么解決?
可以獲取到j(luò)ar的源碼或者將jar
反編譯獲取到java
項目熊响,然后替換掉自己想要的.java
文件或者方法旨别;
方式二:可以通過類加載器將目標(biāo)class
替換成自己的class
;
面試題6.
IO
與NIO
的區(qū)別汗茄?
第一點:IO
是面向流的秸弛,NIO
是面向緩沖區(qū)的。
IO
面向流意味著每次從流中讀一個或多個字節(jié)洪碳,直至讀取所有字節(jié)胆屿,它們沒有被緩存在任何地方。此外偶宫,它不能前后移動流中的數(shù)據(jù)非迹。
NIO
是面向緩存的。數(shù)據(jù)讀取到一個緩沖區(qū)纯趋,需要時可在緩沖區(qū)中前后移動憎兽。這就增加了處理過程中的靈活性。但是吵冒,還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數(shù)據(jù)纯命。而且要確保當(dāng)更多的數(shù)據(jù)讀入緩沖區(qū)時,不要覆蓋緩沖區(qū)中未處理的數(shù)據(jù)痹栖。
第二點:IO
的各種流是阻塞的亿汞。這意味著,當(dāng)一個線程調(diào)用read()
或 write()
時揪阿,該線程被阻塞疗我,直到有一些數(shù)據(jù)被讀取,或數(shù)據(jù)完全寫入南捂。該線程在此期間不能再干任何事情吴裤。NIO
的非阻塞模式,使一個線程從某通道發(fā)送請求讀取數(shù)據(jù)溺健,但是它僅能得到目前可用的數(shù)據(jù)麦牺,如果目前沒有數(shù)據(jù)可用時,就什么都不會獲取,而不是保持線程阻塞剖膳,在數(shù)據(jù)可讀之前魏颓,該線程可以繼續(xù)做其他的事情。 非阻塞寫也是如此吱晒。一個線程請求寫入一些數(shù)據(jù)到某通道甸饱,但不需要等待它完全寫入,這個線程同時可以去做別的事情枕荞。 線程通常將非阻塞IO的空閑時間用于在其它通道上執(zhí)行IO
操作柜候,所以一個單獨的線程現(xiàn)在可以管理多個輸入和輸出通道(channel)
。
面試題7.
單例模式有幾種寫法以及各自的優(yōu)劣躏精?
1.餓漢式:
public class SingleInstance {
private static SingleInstance mInstance = new SingleInstance();
private SingleInstance(){}
public static SingleInstance getInstance(){
return mInstance;
}
}
缺點:存在內(nèi)存損耗問題渣刷,如果當(dāng)前類沒有用到也會被實例化
2.懶漢式:
public class SingleInstance {
private static SingleInstance mInstance = null;
private SingleInstance(){}
public static SingleInstance getInstance(){
if(mInstance==null){
synchronized (SingleInstance.class){
if(mInstance==null){
mInstance = new SingleInstance();
}
}
}
return mInstance;
}
}
缺點:加了synchronized
鎖會影響性能
有次被問到為什么要有兩次空判斷?
第一次空判斷和好理解矗烛,可以很大程度上減少鎖機制的次數(shù)辅柴;
第二次判空是因為,如果a瞭吃,b兩個線程都到了synchronized處碌嘀,而假設(shè)a拿到了鎖,進入到代碼塊中創(chuàng)建了對象歪架,然后釋放了鎖股冗,由于b線程在等待鎖,所以a釋放后和蚪,會被b拿到止状,因此此時判空就保證了實例的唯一性。
3.靜態(tài)內(nèi)部類:
public class SingleInstance {
private SingleInstance(){}
public static SingleInstance getInstance(){
return Builder.mInstance;
}
private static class Builder{
private static SingleInstance mInstance = new SingleInstance();
}
}
優(yōu)點:解決了內(nèi)存浪費問題攒霹,同時也避免了加鎖性能問題
為什么這種寫法是線程安全的怯疤?
因為類加載過程是安全的,而靜態(tài)變量是隨著類的加載進行初始化的催束。
4.枚舉形式:
public enum SingleInstance {
INSTANCE;
}
優(yōu)點:不存在反射和反序列化的問題集峦。
缺點:通過查看枚舉類生成的class文件發(fā)現(xiàn),有多少變量抠刺,就會在靜態(tài)代碼塊中創(chuàng)建多少對象塔淤,所以不建議使用。
定義一些有意義的常量矫付,如果不用枚舉凯沪,怎么解決?
可以使用注解的形式买优,例如:
@IntDef({DataType.INT, DataType.STRING, DataType.FLOAT, DataType.DOUBLE, DataType.OBJECT})
public @interface DataType {
int INT = 0;
int STRING = 1;
int FLOAT = 2;
int DOUBLE = 3;
int OBJECT = 4;
}
面試題8.
ArrayList 和LinketList區(qū)別?hashmap的實現(xiàn)原理?hashmap與hashtable的區(qū)別杀赢?
ArrayList與LinketList差別:
ArrayList
基于數(shù)組實現(xiàn)烘跺,所以get
,set
操作效率較高脂崔;
LinketList
基于鏈表實現(xiàn)(雙向鏈表)滤淳,所以add,remove
操作效率較高砌左;
如何實現(xiàn)高效率的查詢和插入結(jié)構(gòu)脖咐?
二叉樹或者散列表
HashMap實現(xiàn)原理:
hashmap
是由數(shù)組+鏈表結(jié)構(gòu)現(xiàn)實的。獲取到key
的hashcode
汇歹,然后對數(shù)組長度取余屁擅,找到對應(yīng)的數(shù)組位置index
,然后在對應(yīng)的鏈表中判斷是否有當(dāng)前key
产弹,從而進行查詢/添加/替換等操作派歌。
HashMap與HashTable區(qū)別:
面試題9.
gson序列化數(shù)據(jù)時如何排除某個字段憋活?
方式一:給字段加上 transient 修飾符
方式二:排除Modifier指定類型的字段黍少。這個方法需要用GsonBuilder定制一個GSON實例。
方式三:使用@expose注解谈况。沒有被 @expose 標(biāo)注的字段會被排除
面試題10.
ButterKnife
與Xutils
注解的區(qū)別斤斧?以及Retrofit中的注解是如何處理的早抠?
ButterKnife
采用的是編譯時注解,在編譯時生成輔助類撬讽,在運行時通過輔助類完成操作蕊连。編譯時注解運行效率較高,不需要反射操作锐秦。
XUtils采用的是運行時注解咪奖,在運行時通過反射進行操作。運行時注解相對效率較低酱床。
Retrofit
與EventBus
采用的都是運行時注解羊赵,也就是通過反射技術(shù)處理的。
面試題11.
jvm
的類加載機制扇谣?
類加載分類:
BootstrapClassLoader
(負責(zé)加載java_home
中的jre/lib/rt.jar
中的class
昧捷,不是ClassLoader
的子類)
ExtensionClassLoader
(負責(zé)加載java平臺中擴展的一些jar中的class)
AppClassLoader
(負責(zé)加載classpath
中指定的jar
或class
文件)
CustomClassLoader
(自定義的classloader
)
JVM的類加載機制采用的是雙親委派模型。
類加載過程:
由底層類加載器開始查找是否已經(jīng)加載罐寨,如果底層已經(jīng)加載靡挥,則視為已經(jīng)加載,上層就無需再加載鸯绿,避免重復(fù)加載跋破。如果沒有加載簸淀,則向上層類加載器查找,以此類推毒返,直到頂層類加載器租幕。如果最后發(fā)現(xiàn)頂層類加載器也沒有加載,則先交由頂層類加載器嘗試加載拧簸,如果無法加載劲绪,則交由下層類加器加載,直至底層類加載器盆赤,如果還是無法加載贾富,則JVM
會拋出相應(yīng)的類加載異常。
面試題12.
列舉一些git
版本控制的常用操作符牺六?
面試題13.
AsyncTask
的原理以及弊端颤枪?AsyncTask
為什么要求在主線程加載,對象為什么要在主線程創(chuàng)建兔乞?
面試題14.
Android
開發(fā)中的屏幕適配方案汇鞭?
sw
(smallestWidth
最小寬度)適配;
通過修改系統(tǒng)的density
值進行適配庸追;
面試題15.
多線程中sleep
和wait
的區(qū)別霍骄?
sleep
是Thread
的靜態(tài)方法;wait
是Object
中的方法淡溯;
sleep
過程中不會釋放鎖读整,不會讓出系統(tǒng)資源;wait
會釋放鎖資源咱娶,將其放入等待池中米间,讓出系統(tǒng)資源,讓cpu
可以執(zhí)行其他線程膘侮;
sleep
之后可以主動釋放鎖屈糊;wait
需要手動去notify
;
面試題16.
輸出字符串中的第一個不重復(fù)的字符琼了,例如:
“hello
”輸出 ‘h’
“abbac”
輸出 ‘c’
“abdabe”
輸出‘d’
利用LinketHashMap
數(shù)組的有序性和鍵的唯一性來處理:
private void printChar(String source) {
if (source == null) {
return;
}
String soureTrim = source.replaceAll(" ", "");//去掉字符串中的所有空格
char[] chars = soureTrim.toCharArray();//拿到字符串對應(yīng)的char[]
int length = chars.length;
//用map鍵的唯一性去處理記錄重復(fù)數(shù)據(jù)逻锐,而選擇LinkedHashMap是為了保證有序
LinkedHashMap<Character, Integer> map = new LinkedHashMap<>();
//循環(huán)檢測或插入數(shù)據(jù),然后通過value的值記錄當(dāng)前字符出現(xiàn)次數(shù)
for (int i = 0; i < length; i++) {
char key = chars[i];
Integer value = map.get(key);
if (value == null) {
map.put(key, 1);
} else {
map.put(key, value+1);
}
}
//value=1雕薪,說明只出現(xiàn)一次
Set<Character> keys = map.keySet();
for (Character key : keys) {
Integer integer = map.get(key);
if (integer == 1) {
System.out.println("current frist only char is = " + key);
break;
}
}
}
面試題17.
對有序int數(shù)組去重昧诱,并輸出去重后的長度,并打印出來所袁,要求時間復(fù)雜度為O(n)
盏档,空間復(fù)雜度為O(1)。
例如:int[] array = {-1,0,0,2,4,4,4,6};
長度為:5燥爷,打印結(jié)果為:-1蜈亩,0懦窘,2,4勺拣,6
面試題18.
假設(shè)有A奶赠,B鱼填,C三個線程药有,在A線程的執(zhí)行過程中去執(zhí)行B線程,并且等待B線程的執(zhí)行結(jié)果苹丸,然后去執(zhí)行C線程愤惰,然后當(dāng)C線程執(zhí)行完成后,返回結(jié)果給A線程赘理。不阻塞線程宦言,如何實現(xiàn)?(相關(guān)描述我也記不太清了商模,可能有些不準(zhǔn)確奠旺,考點就是Future)
面試題19.
ThreadLocal
作用?
ThreadLocal
是一個線程內(nèi)的數(shù)據(jù)存儲類施流,可以通過它在指定線程中存儲數(shù)據(jù)响疚,并且只有在當(dāng)前線程可以獲取到存儲的數(shù)據(jù)。通常當(dāng)某些數(shù)據(jù)以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本時使用瞪醋。
通過查看源碼可以知道忿晕,se
t方法會通過values()
方法拿到當(dāng)前線程的ThreadLocal
數(shù)據(jù)(Thread
類中有個成員變量專門存儲ThreadLocal
數(shù)據(jù):ThreadLocal.Values localValues)
,在localValues
內(nèi)部有個數(shù)組Object[] table
银受,用于存儲ThreadLocal
的值践盼,而位置存儲在ThreadLocal
的reference
的下一個位置。
而get
方法就是通過當(dāng)前線程的reference
拿到localValues
中table
的位置宾巍,然后index+1
獲取數(shù)據(jù)咕幻。