1.手畫一下Android系統(tǒng)架構(gòu)圖,描述一下各個層次的作用?
Android系統(tǒng)架構(gòu)圖
從上到下依次分為六層:
應(yīng)用框架層
進程通信層
系統(tǒng)服務(wù)層
Android運行時層
硬件抽象層
Linux內(nèi)核層
而我們經(jīng)常說的五層:
*應(yīng)用層
*FrameWork層
*Libarary 層
*Linux層
2.Activity如與Service通信涉馅?
可以通過bindService的方式旭蠕,先在Activity里實現(xiàn)一個ServiceConnection接口,并將該接口傳遞給bindService()方法亭枷,在ServiceConnection接口的onServiceConnected()方法
里執(zhí)行相關(guān)操作袭艺。
3.Service的生命周期與啟動方法有什么區(qū)別?
startService():開啟Service叨粘,調(diào)用者退出后Service仍然存在猾编。
bindService():開啟Service瘤睹,調(diào)用者退出后Service也隨即退出。
Service生命周期:
- 只是用startService()啟動服務(wù):onCreate() -> onStartCommand() -> onDestory
注意:重復(fù)調(diào)用startService()只會調(diào)用onStartCommand(),onCreat() 在沒onDestry之前多次startService() 只會執(zhí)行一次答倡。
- 只是用bindService()綁定服務(wù):onCreate() -> onBind() -> onUnBind() -> onDestory
注意:重復(fù)調(diào)用bindService()只會執(zhí)行一次 onCreat()和onbind(),服務(wù)端判定這個客戶端已經(jīng)綁定過服務(wù)了不需要重復(fù)綁定轰传。
- 同時使用startService()啟動服務(wù)與bindService()綁定服務(wù):onCreate() -> onStartCommnad() -> onBind() -> onUnBind() -> onDestory
注意:注意在stopService() 要先進行解綁 unbindService()。
4廣播分為哪幾種瘪撇,應(yīng)用場景是什么获茬?
普通廣播:調(diào)用sendBroadcast()發(fā)送,最常用的廣播倔既。
有序廣播:調(diào)用sendOrderedBroadcast()恕曲,發(fā)出去的廣播會被廣播接受者按照順序接收,廣播接收者按照Priority屬性值從大-小排序渤涌,Priority屬性相同者佩谣,動態(tài)注冊的廣播優(yōu)先,廣播接收者還可以
選擇對廣播進行截斷和修改歼捏。
5.廣播的兩種注冊方式有什么區(qū)別稿存?
靜態(tài)注冊:常駐系統(tǒng),不受組件生命周期影響瞳秽,即便應(yīng)用退出瓣履,廣播還是可以被接收,耗電练俐、占內(nèi)存袖迎。
動態(tài)注冊:非常駐,跟隨組件的生命變化腺晾,組件結(jié)束燕锥,廣播結(jié)束。在組件結(jié)束前悯蝉,需要先移除廣播归形,否則容易造成內(nèi)存泄漏。
6.廣播發(fā)送和接收的原理了解嗎鼻由?
繼承BroadcastReceiver暇榴,重寫onReceive()方法。
通過Binder機制向ActivityManagerService注冊廣播蕉世。
通過Binder機制向ActivityMangerService發(fā)送廣播蔼紧。
ActivityManagerService查找符合相應(yīng)條件的廣播(IntentFilter/Permission)的BroadcastReceiver,將廣播發(fā)送到BroadcastReceiver所在的消息隊列中狠轻。
BroadcastReceiver所在消息隊列拿到此廣播后奸例,回調(diào)它的onReceive()方法。
7.ContentProvider向楼、ContentResolver與ContentObserver之間的關(guān)系是什么查吊?
ContentProvider:管理數(shù)據(jù)谐区,提供數(shù)據(jù)的增刪改查操作,數(shù)據(jù)源可以是數(shù)據(jù)庫菩貌、文件卢佣、XML重荠、網(wǎng)絡(luò)等箭阶,ContentProvider為這些數(shù)據(jù)的訪問提供了統(tǒng)一的接口,可以用來做進程間數(shù)據(jù)共享戈鲁。
ContentResolver:ContentResolver可以不同URI操作不同的ContentProvider中的數(shù)據(jù)仇参,外部進程可以通過ContentResolver與ContentProvider進行交互。
ContentObserver:觀察ContentProvider中的數(shù)據(jù)變化婆殿,并將變化通知給外界诈乒。
8.遇到過哪些關(guān)于Fragment的問題,如何處理的婆芦?
getActivity()空指針:這種情況一般發(fā)生在在異步任務(wù)里調(diào)用getActivity()怕磨,而Fragment已經(jīng)onDetach(),此時就會有空指針消约,解決方案是在Fragment里使用
一個全局變量mActivity肠鲫,在onAttach()方法里賦值,這樣可能會引起內(nèi)存泄漏或粮,但是異步任務(wù)沒有停止的情況下本身就已經(jīng)可能內(nèi)存泄漏导饲,相比直接crash,這種方式
顯得更妥當(dāng)一些氯材。Fragment視圖重疊:在類onCreate()的方法加載Fragment,并且沒有判斷saveInstanceState==null或if(findFragmentByTag(mFragmentTag) == null),導(dǎo)致重復(fù)加載了同一個Fragment導(dǎo)致重疊朴摊。(PS:replace情況下诬烹,如果沒有加入回退棧,則不判斷也不會造成重疊冗尤,但建議還是統(tǒng)一判斷下)
9.Android里的Intent傳遞的數(shù)據(jù)有大小限制嗎听盖,如何解決?
Intent傳遞數(shù)據(jù)大小的限制大概在1M左右生闲,超過這個限制就會靜默崩潰媳溺。處理方式如下:
進程內(nèi):EventBus,文件緩存碍讯、磁盤緩存悬蔽。
進程間:通過ContentProvider進行款進程數(shù)據(jù)共享和傳遞。
10.描述一下Android的事件分發(fā)機制捉兴?
Android事件分發(fā)機制的本質(zhì):事件從哪個對象發(fā)出蝎困,經(jīng)過哪些對象录语,最終由哪個對象處理了該事件。此處對象指的是Activity禾乘、Window與View澎埠。
Android事件的分發(fā)順序:Activity(Window) -> ViewGroup -> View
Android事件的分發(fā)主要由三個方法來完成,如下所示:
11.描述一下View的繪制原理始藕?
View的繪制流程主要分為三步:
onMeasure:測量視圖的大小蒲稳,從頂層父View到子View遞歸調(diào)用measure()方法,measure()調(diào)用onMeasure()方法伍派,onMeasure()方法完成繪制工作江耀。
onLayout:確定視圖的位置,從頂層父View到子View遞歸調(diào)用layout()方法诉植,父View將上一步measure()方法得到的子View的布局大小和布局參數(shù)祥国,將子View放在合適的位置上。
onDraw:繪制最終的視圖晾腔,首先ViewRoot創(chuàng)建一個Canvas對象舌稀,然后調(diào)用onDraw()方法進行繪制。onDraw()方法的繪制流程為:① 繪制視圖背景灼擂。② 繪制畫布的圖層壁查。 ③ 繪制View內(nèi)容。
④ 繪制子視圖缤至,如果有的話潮罪。⑤ 還原圖層。⑥ 繪制滾動條领斥。
12.requestLayout()嫉到、invalidate()與postInvalidate()有什么區(qū)別?
requestLayout():該方法會遞歸調(diào)用父窗口的requestLayout()方法月洛,直到觸發(fā)ViewRootImpl的performTraversals()方法何恶,此時mLayoutRequestede為true,會觸發(fā)onMesaure()與onLayout()方法嚼黔,不一定
會觸發(fā)onDraw()方法细层。invalidate():該方法遞歸調(diào)用父View的invalidateChildInParent()方法,直到調(diào)用ViewRootImpl的invalidateChildInParent()方法唬涧,最終觸發(fā)ViewRootImpl的performTraversals()方法疫赎,此時mLayoutRequestede為false,不會
觸發(fā)onMesaure()與onLayout()方法碎节,當(dāng)時會觸發(fā)onDraw()方法捧搞。postInvalidate():該方法功能和invalidate()一樣,只是它可以在非UI線程中調(diào)用。
一般說來需要重新布局就調(diào)用requestLayout()方法胎撇,需要重新繪制就調(diào)用invalidate()方法介粘。
13了解APK的打包流程嗎,描述一下晚树?
Android的包文件APK分為兩個部分:代碼和資源姻采,所以打包方面也分為資源打包和代碼打包兩個方面,這篇文章就來分析資源和代碼的編譯打包原理爵憎。
APK整體的的打包流程如下圖所示:
具體說來:
通過AAPT工具進行資源文件(包括AndroidManifest.xml慨亲、布局文件、各種xml資源等)的打包纲堵,生成R.java文件巡雨。
通過AIDL工具處理AIDL文件,生成相應(yīng)的Java文件席函。
通過Javac工具編譯項目源碼,生成Class文件冈涧。
通過DX工具將所有的Class文件轉(zhuǎn)換成DEX文件茂附,該過程主要完成Java字節(jié)碼轉(zhuǎn)換成Dalvik字節(jié)碼,壓縮常量池以及清除冗余信息等工作督弓。
通過ApkBuilder工具將資源文件营曼、DEX文件打包生成APK文件。
利用KeyStore對生成的APK文件進行簽名愚隧。
如果是正式版的APK蒂阱,還會利用ZipAlign工具進行對齊處理,對齊的過程就是將APK文件中所有的資源文件舉例文件的起始距離都偏移4字節(jié)的整數(shù)倍狂塘,這樣通過內(nèi)存映射訪問APK文件
的速度會更快录煤。
14.了解APK的安裝流程嗎,描述一下荞胡?
APK的安裝流程如下所示:
復(fù)制APK到/data/app目錄下妈踊,解壓并掃描安裝包。
資源管理器解析APK里的資源文件泪漂。
解析AndroidManifest文件廊营,并在/data/data/目錄下創(chuàng)建對應(yīng)的應(yīng)用數(shù)據(jù)目錄。
然后對dex文件進行優(yōu)化萝勤,并保存在dalvik-cache目錄下露筒。
將AndroidManifest文件解析出的四大組件信息注冊到PackageManagerService中。
安裝完成后敌卓,發(fā)送廣播慎式。
15.當(dāng)點擊一個應(yīng)用圖標(biāo)以后,都發(fā)生了什么,描述一下這個過程瞬捕?
點擊應(yīng)用圖標(biāo)后會去啟動應(yīng)用的LauncherActivity鞍历,如果LancerActivity所在的進程沒有創(chuàng)建,還會創(chuàng)建新進程肪虎,整體的流程就是一個Activity的啟動流程劣砍。
Activity的啟動流程圖(放大可查看)如下所示:
整個流程涉及的主要角色有:
Instrumentation: 監(jiān)控應(yīng)用與系統(tǒng)相關(guān)的交互行為。
AMS:組件管理調(diào)度中心扇救,什么都不干刑枝,但是什么都管。
ActivityStarter:Activity啟動的控制器迅腔,處理Intent與Flag對Activity啟動的影響装畅,具體說來有:1 尋找符合啟動條件的Activity,如果有多個沧烈,讓用戶選擇掠兄;2 校驗啟動參數(shù)的合法性;3 返回int參數(shù)锌雀,代表Activity是否啟動成功蚂夕。
ActivityStackSupervisior:這個類的作用你從它的名字就可以看出來,它用來管理任務(wù)棧腋逆。
ActivityStack:用來管理任務(wù)棧里的Activity婿牍。
ActivityThread:最終干活的人,是ActivityThread的內(nèi)部類惩歉,Activity等脂、Service、BroadcastReceiver的啟動撑蚌、切換上遥、調(diào)度等各種操作都在這個類里完成。
注:這里單獨提一下ActivityStackSupervisior锨并,這是高版本才有的類露该,它用來管理多個ActivityStack,早期的版本只有一個ActivityStack對應(yīng)著手機屏幕第煮,后來高版本支持多屏以后解幼,就有了多個ActivityStack,于是就引入了ActivityStackSupervisior用來管理多個ActivityStack包警。
整個流程主要涉及四個進程:
調(diào)用者進程撵摆,如果是在桌面啟動應(yīng)用就是Launcher應(yīng)用進程。
ActivityManagerService等所在的System Server進程害晦,該進程主要運行著系統(tǒng)服務(wù)組件特铝。
Zygote進程暑中,該進程主要用來fork新進程。
新啟動的應(yīng)用進程鲫剿,該進程就是用來承載應(yīng)用運行的進程了鳄逾,它也是應(yīng)用的主線程(新創(chuàng)建的進程就是主線程),處理組件生命周期灵莲、界面繪制等相關(guān)事情雕凹。
有了以上的理解,整個流程可以概括如下:
點擊桌面應(yīng)用圖標(biāo)政冻,Launcher進程將啟動Activity(MainActivity)的請求以Binder的方式發(fā)送給了AMS枚抵。
AMS接收到啟動請求后,交付ActivityStarter處理Intent和Flag等信息明场,然后再交給ActivityStackSupervisior/ActivityStack
處理Activity進棧相關(guān)流程汽摹。同時以Socket方式請求Zygote進程fork新進程。Zygote接收到新進程創(chuàng)建請求后fork出新進程苦锨。
在新進程里創(chuàng)建ActivityThread對象逼泣,新創(chuàng)建的進程就是應(yīng)用的主線程,在主線程里開啟Looper消息循環(huán)逆屡,開始處理創(chuàng)建Activity圾旨。
ActivityThread利用ClassLoader去加載Activity、創(chuàng)建Activity實例魏蔗,并回調(diào)Activity的onCreate()方法。這樣便完成了Activity的啟動痹筛。
16.BroadcastReceiver與LocalBroadcastReceiver有什么區(qū)別莺治?
BroadcastReceiver 是跨應(yīng)用廣播,利用Binder機制實現(xiàn)帚稠。
LocalBroadcastReceiver 是應(yīng)用內(nèi)廣播谣旁,利用Handler實現(xiàn),利用了IntentFilter的match功能滋早,提供消息的發(fā)布與接收功能榄审,實現(xiàn)應(yīng)用內(nèi)通信,效率比較高杆麸。
17.Android Handler機制是做什么的搁进,原理了解嗎?
Android消息循環(huán)流程圖如下所示:
主要涉及的角色如下所示:
Message:消息昔头,分為硬件產(chǎn)生的消息(例如:按鈕饼问、觸摸)和軟件產(chǎn)生的消息。
MessageQueue:消息隊列揭斧,主要用來向消息池添加消息和取走消息莱革。
Looper:消息循環(huán)器,主要用來把消息分發(fā)給相應(yīng)的處理者。
Handler:消息處理器盅视,主要向消息隊列發(fā)送各種消息以及處理各種消息捐名。
整個消息的循環(huán)流程還是比較清晰的,具體說來:
Handler通過sendMessage()發(fā)送消息Message到消息隊列MessageQueue闹击。
Looper通過loop()不斷提取觸發(fā)條件的Message镶蹋,并將Message交給對應(yīng)的target handler來處理。
target handler調(diào)用自身的handleMessage()方法來處理Message拇砰。
事實上梅忌,在整個消息循環(huán)的流程中,并不只有Java層參與除破,很多重要的工作都是在C++層來完成的牧氮。我們來看下這些類的調(diào)用關(guān)系。
注:虛線表示關(guān)聯(lián)關(guān)系瑰枫,實線表示調(diào)用關(guān)系踱葛。
在這些類中MessageQueue是Java層與C++層維系的橋梁,MessageQueue與Looper相關(guān)功能都通過MessageQueue的Native方法來完成光坝,而其他虛線連接的類只有關(guān)聯(lián)關(guān)系尸诽,并沒有直接調(diào)用的關(guān)系,它們發(fā)生關(guān)聯(lián)的橋梁是MessageQueue盯另。
18.Android Binder機制是做什么的性含,為什么選用Binder,原理了解嗎鸳惯?
Android Binder是用來做進程通信(IPC)的商蕴,Android的各個應(yīng)用以及系統(tǒng)服務(wù)都運行在獨立的進程中,它們的通信都依賴于Binder芝发。
為什么選用Binder绪商,在討論這個問題之前,我們知道Android也是基于Linux內(nèi)核辅鲸,Linux現(xiàn)有的進程通信手段有以下幾種:
管道:在創(chuàng)建時分配一個page大小的內(nèi)存格郁,緩存區(qū)大小比較有限;
消息隊列:信息復(fù)制兩次独悴,額外的CPU消耗例书;不合適頻繁或信息量大的通信;
共享內(nèi)存:無須復(fù)制绵患,共享緩沖區(qū)直接付附加到進程虛擬地址空間雾叭,速度快;但進程間的同步問題操作系統(tǒng)無法實現(xiàn)落蝙,必須各進程利用同步工具解決织狐;
套接字:作為更通用的接口暂幼,傳輸效率低,主要用于不通機器或跨網(wǎng)絡(luò)的通信移迫;
信號量:常作為一種鎖機制旺嬉,防止某進程正在訪問共享資源時,其他進程也訪問該資源厨埋。因此邪媳,主要作為進程間以及同一進程內(nèi)不同線程之間的同步手段。
信號: 不適用于信息交換荡陷,更適用于進程中斷控制雨效,比如非法內(nèi)存訪問,殺死某個進程等废赞;
既然有現(xiàn)有的IPC方式徽龟,為什么重新設(shè)計一套Binder機制呢。主要是出于以上三個方面的考量:
高性能:從數(shù)據(jù)拷貝次數(shù)來看Binder只需要進行一次內(nèi)存拷貝唉地,而管道据悔、消息隊列、Socket都需要兩次耘沼,共享內(nèi)存不需要拷貝极颓,Binder的性能僅次于共享內(nèi)存。
穩(wěn)定性:上面說到共享內(nèi)存的性能優(yōu)于Binder群嗤,那為什么不適用共享內(nèi)存呢菠隆,因為共享內(nèi)存需要處理并發(fā)同步問題,控制負(fù)責(zé)狂秘,容易出現(xiàn)死鎖和資源競爭浸赫,穩(wěn)定性較差。而Binder基于C/S架構(gòu)赃绊,客戶端與服務(wù)端彼此獨立,穩(wěn)定性較好羡榴。
安全性:Android是一個開放式的平臺碧查,所以確保應(yīng)用程序安全是很重要的。Android對每一個安裝應(yīng)用都分配了UID/PID,其中進程的UID是可用來鑒別進程身份校仑。傳統(tǒng)的只能由用戶在數(shù)據(jù)包里填寫UID/PID忠售,這樣不可靠,容易被惡意程序利用迄沫。而我們要求由內(nèi)核來添加可靠的UID稻扬。
所以,出于高性能羊瘩、穩(wěn)定性泰佳、安全性盼砍。android建立了一套新的進程間通信方式。
原理:
1).首先逝她,Binder分為Client和Server兩個進程浇坐。
注意,Client和Server是相對的黔宛。誰發(fā)消息近刘,誰就是Client,誰接收消息臀晃,誰就是Server觉渴。
舉個例子,兩個進程A和B之間使用Binder通信徽惋,進程A發(fā)消息給進程B案淋,那么這時候A是Binder Client,B是Binder Server寂曹;進程B發(fā)消息給進程A哎迄,那么這時候B是Binder Client,A是Binder Server——其實這么說雖然簡單了隆圆,但還是不太嚴(yán)謹(jǐn)漱挚,我們先這么理解著。
2).其次渺氧,我們看下面這個圖(摘自田維術(shù)的博客)旨涝,基本說明白了Binder的組成解構(gòu):
圖中的IPC就是進程間通信的意思。
圖中的ServiceManager侣背,負(fù)責(zé)把Binder Server注冊到一個容器中白华。
有人把ServiceManager比喻成電話局,存儲著每個住宅的座機電話贩耐,還是很恰當(dāng)?shù)幕⌒取埲o李四打電話,撥打電話號碼潮太,會先轉(zhuǎn)接到電話局管搪,電話局的接線員查到這個電話號碼的地址,因為李四的電話號碼之前在電話局注冊過铡买,所以就能撥通更鲁;沒注冊,就會提示該號碼不存在奇钞。
對照著Android Binder機制澡为,對著上面這圖,張三就是Binder Client景埃,李四就是Binder Server媒至,電話局就是ServiceManager顶别,電話局的接線員在這個過程中做了很多事情,對應(yīng)著圖中的Binder驅(qū)動.
3).接下來我們看Binder通信的過程塘慕,還是摘自田維術(shù)博客的一張圖:
注:圖中的SM也就是ServiceManager筋夏。
我們看到,Client想要直接調(diào)用Server的add方法图呢,是不可以的条篷,因為它們在不同的進程中,這時候就需要Binder來幫忙了蛤织。
首先是Server在SM這個容器中注冊赴叹。
其次,Client想要調(diào)用Server的add方法指蚜,就需要先獲取Server對象乞巧, 但是SM不會把真正的Server對象返回給Client,而是把Server的一個代理對象返回給Client摊鸡,也就是Proxy绽媒。
然后,Client調(diào)用Proxy的add方法免猾,SM會幫他去調(diào)用Server的add方法是辕,并把結(jié)果返回給Client。
以上這3步猎提,Binder驅(qū)動出了很多力获三,但我們不需要知道Binder驅(qū)動的底層實現(xiàn),涉及到C++的代碼了——把有限的時間去做更有意義的事情。
19.描述一下Activity的生命周期,這些生命周期是如何管理的容贝?
Activity與Fragment生命周期如下所示:
[圖片上傳失敗...(image-82b331-1540037333887)]
讀者可以從上圖看出,Activity有很多種狀態(tài)贞谓,狀態(tài)之間的變化也比較復(fù)雜,在眾多狀態(tài)中葵诈,只有三種是常駐狀態(tài):
Resumed(運行狀態(tài)):Activity處于前臺经宏,用戶可以與其交互。
Paused(暫停狀態(tài)):Activity被其他Activity部分遮擋驯击,無法接受用戶的輸入。
Stopped(停止?fàn)顟B(tài)):Activity被完全隱藏耐亏,對用戶不可見徊都,進入后臺。
其他的狀態(tài)都是中間狀態(tài)广辰。
我們再來看看生命周期變化時的整個調(diào)度流程暇矫,生命周期調(diào)度流程圖如下所示:
[圖片上傳失敗...(image-b64535-1540037333887)]
所以你可以看到主之,整個流程是這樣的:
比方說我們點擊跳轉(zhuǎn)一個新Activity,這個時候Activity會入棧李根,同時它的生命周期也會從onCreate()到onResume()開始變換槽奕,這個過程是在ActivityStack里完成的,ActivityStack
是運行在Server進程里的房轿,這個時候Server進程就通過ApplicationThread的代理對象ApplicationThreadProxy向運行在app進程ApplicationThread發(fā)起操作請求粤攒。ApplicationThread接收到操作請求后,因為它是運行在app進程里的其他線程里囱持,所以ApplicationThread需要通過Handler向主線程ActivityThread發(fā)送操作消息夯接。
主線程接收到ApplicationThread發(fā)出的消息后,調(diào)用主線程ActivityThread執(zhí)行響應(yīng)的操作纷妆,并回調(diào)Activity相應(yīng)的周期方法盔几。
注:這里提到了主線程ActivityThread,更準(zhǔn)確來說ActivityThread不是線程掩幢,因為它沒有繼承Thread類或者實現(xiàn)Runnable接口逊拍,它是運行在應(yīng)用主線程里的對象,那么應(yīng)用的主線程
到底是什么呢际邻?從本質(zhì)上來講啟動啟動時創(chuàng)建的進程就是主線程芯丧,線程和進程處理是否共享資源外,沒有其他的區(qū)別枯怖,對于Linux來說注整,它們都只是一個struct結(jié)構(gòu)體。
20.Activity的通信方式有哪些度硝?
startActivityForResult
EventBus
LocalBroadcastReceiver
21.Android應(yīng)用里有幾種Context對象?
Context類圖如下所示:
[圖片上傳失敗...(image-45b1fa-1540037333887)]
可以發(fā)現(xiàn)Context是個抽象類肿轨,它的具體實現(xiàn)類是ContextImpl,ContextWrapper是個包裝類蕊程,內(nèi)部的成員變量mBase指向的也是個ContextImpl對象椒袍,ContextImpl完成了
實際的功能,Activity藻茂、Service與Application都直接或者間接的繼承ContextWrapper驹暑。
22.描述一下進程和Application的生命周期?
一個安裝的應(yīng)用對應(yīng)一個LoadedApk對象辨赐,對應(yīng)一個Application對象优俘,對于四大組件,Application的創(chuàng)建和獲取方式也是不盡相同的掀序,具體說來:
Activity:通過LoadedApk的makeApplication()方法創(chuàng)建帆焕。
Service:通過LoadedApk的makeApplication()方法創(chuàng)建。
靜態(tài)廣播:通過其回調(diào)方法onReceive()方法的第一個參數(shù)指向Application不恭。
ContentProvider:無法獲取Application叶雹,因此此時Application不一定已經(jīng)初始化财饥。
23.Android哪些情況會導(dǎo)致內(nèi)存泄漏,如何分析內(nèi)存泄漏折晦?
常見的產(chǎn)生內(nèi)存泄漏的情況如下所示:
持有靜態(tài)的Context(Activity)引用钥星。
持有靜態(tài)的View引用,
內(nèi)部類&匿名內(nèi)部類實例無法釋放(有延遲時間等等)满着,而內(nèi)部類又持有外部類的強引用谦炒,導(dǎo)致外部類無法釋放,這種匿名內(nèi)部類常見于監(jiān)聽器漓滔、Handler编饺、Thread、TimerTask
資源使用完成后沒有關(guān)閉响驴,例如:BraodcastReceiver透且,ContentObserver,F(xiàn)ile豁鲤,Cursor秽誊,Stream,Bitmap琳骡。
不正確的單例模式锅论,比如單例持有Activity。
集合類內(nèi)存泄漏楣号,如果一個集合類是靜態(tài)的(緩存HashMap)最易,只有添加方法,沒有對應(yīng)的刪除方法炫狱,會導(dǎo)致引用無法被釋放藻懒,引發(fā)內(nèi)存泄漏。
錯誤的覆寫了finalize()方法视译,finalize()方法執(zhí)行執(zhí)行不確定嬉荆,可能會導(dǎo)致引用無法被釋放。
查找內(nèi)存泄漏可以使用Android Profiler工具或者利用LeakCanary工具酷含。
24.Android有哪幾種進程鄙早,是如何管理的?
Android的進程主要分為以下幾種:
前臺進程
用戶當(dāng)前操作所必需的進程椅亚。如果一個進程滿足以下任一條件限番,即視為前臺進程:
托管用戶正在交互的 Activity(已調(diào)用 Activity 的 onResume() 方法)
托管某個 Service,后者綁定到用戶正在交互的 Activity
托管正在“前臺”運行的 Service(服務(wù)已調(diào)用 startForeground())
托管正執(zhí)行一個生命周期回調(diào)的 Service(onCreate()呀舔、onStart() 或 onDestroy())
托管正執(zhí)行其 onReceive() 方法的 BroadcastReceiver
通常扳缕,在任意給定時間前臺進程都為數(shù)不多。只有在內(nèi)存不足以支持它們同時繼續(xù)運行這一萬不得已的情況下,系統(tǒng)才會終止它們躯舔。 此時,設(shè)備往往已達到內(nèi)存分頁狀態(tài)省古,因此需要終止一些前臺進程來確保用戶界面正常響應(yīng)粥庄。
可見進程
沒有任何前臺組件、但仍會影響用戶在屏幕上所見內(nèi)容的進程豺妓。 如果一個進程滿足以下任一條件惜互,即視為可見進程:
托管不在前臺、但仍對用戶可見的 Activity(已調(diào)用其 onPause() 方法)琳拭。例如训堆,如果前臺 Activity 啟動了一個對話框,允許在其后顯示上一 Activity白嘁,則有可能會發(fā)生這種情況坑鱼。
托管綁定到可見(或前臺)Activity 的 Service。
可見進程被視為是極其重要的進程絮缅,除非為了維持所有前臺進程同時運行而必須終止鲁沥,否則系統(tǒng)不會終止這些進程。
服務(wù)進程
正在運行已使用 startService() 方法啟動的服務(wù)且不屬于上述兩個更高類別進程的進程耕魄。盡管服務(wù)進程與用戶所見內(nèi)容沒有直接關(guān)聯(lián)画恰,但是它們通常在執(zhí)行一些用戶關(guān)
心的操作(例如,在后臺播放音樂或從網(wǎng)絡(luò)下載數(shù)據(jù))吸奴。因此允扇,除非內(nèi)存不足以維持所有前臺進程和可見進程同時運行,否則系統(tǒng)會讓服務(wù)進程保持運行狀態(tài)则奥。
后臺進程
包含目前對用戶不可見的 Activity 的進程(已調(diào)用 Activity 的 onStop() 方法)考润。這些進程對用戶體驗沒有直接影響,系統(tǒng)可能隨時終止它們逞度,以回收內(nèi)存供前臺進程额划、可見進程或服務(wù)進程使用。 通常會有很多后臺進程在運行档泽,因此它們會保存在 LRU (最近最少使用)列表中俊戳,以確保包含用戶最近查看的 Activity 的進程最后一個被終止。如果某個 Activity 正確實現(xiàn)了生命周期方法馆匿,并保存了其當(dāng)前狀態(tài)抑胎,則終止其進程不會對用戶體驗產(chǎn)生明顯影響,因為當(dāng)用戶導(dǎo)航回該 Activity 時渐北,Activity 會恢復(fù)其所有可見狀態(tài)阿逃。
空進程
不含任何活動應(yīng)用組件的進程。保留這種進程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啟動時間恃锉。 為使總體系統(tǒng)資源在進程緩存和底層內(nèi)核緩存之間保持平衡搀菩,系統(tǒng)往往會終止這些進程。
ActivityManagerService負(fù)責(zé)根據(jù)各種策略算法計算進程的adj值破托,然后交由系統(tǒng)內(nèi)核進行進程的管理肪跋。
25.SharePreference性能優(yōu)化,可以做進程同步嗎土砂?
在Android中, SharePreferences是一個輕量級的存儲類州既,特別適合用于保存軟件配置參數(shù)。使用SharedPreferences保存數(shù)據(jù)萝映,其背后是用xml文件存放數(shù)據(jù)吴叶,文件
存放在/data/data/ < package name > /shared_prefs目錄下.
之所以說SharedPreference是一種輕量級的存儲方式,是因為它在創(chuàng)建的時候會把整個文件全部加載進內(nèi)存序臂,如果SharedPreference文件比較大蚌卤,會帶來以下問題:
第一次從sp中獲取值的時候,有可能阻塞主線程贸宏,使界面卡頓造寝、掉幀。
解析sp的時候會產(chǎn)生大量的臨時對象吭练,導(dǎo)致頻繁GC诫龙,引起界面卡頓。
這些key和value會永遠(yuǎn)存在于內(nèi)存之中鲫咽,占用大量內(nèi)存签赃。
優(yōu)化建議
不要存放大的key和value,會引起界面卡分尸、頻繁GC锦聊、占用內(nèi)存等等。
毫不相關(guān)的配置項就不要放在在一起箩绍,文件越大讀取越慢孔庭。
讀取頻繁的key和不易變動的key盡量不要放在一起,影響速度材蛛,如果整個文件很小圆到,那么忽略吧,為了這點性能添加維護成本得不償失卑吭。
不要亂edit和apply芽淡,盡量批量修改一次提交,多次apply會阻塞主線程豆赏。
盡量不要存放JSON和HTML挣菲,這種場景請直接使用JSON富稻。
SharedPreference無法進行跨進程通信,MODE_MULTI_PROCESS只是保證了在API 11以前的系統(tǒng)上白胀,如果sp已經(jīng)被讀取進內(nèi)存椭赋,再次獲取這個SharedPreference的時候,如果有這個flag或杠,會重新讀一遍文件纹份,僅此而已。
26.如何做SQLite升級廷痘?
數(shù)據(jù)庫升級增加表和刪除表都不涉及數(shù)據(jù)遷移,但是修改表涉及到對原有數(shù)據(jù)進行遷移件已。升級的方法如下所示:
將現(xiàn)有表命名為臨時表笋额。
創(chuàng)建新表。
將臨時表的數(shù)據(jù)導(dǎo)入新表篷扩。
刪除臨時表兄猩。
如果是跨版本數(shù)據(jù)庫升級,可以由兩種方式鉴未,如下所示:
逐級升級枢冤,確定相鄰版本與現(xiàn)在版本的差別,V1升級到V2,V2升級到V3铜秆,依次類推淹真。
跨級升級,確定每個版本與現(xiàn)在數(shù)據(jù)庫的差別连茧,為每個case編寫專門升級大代碼核蘸。
27.進程保護如何做,如何喚醒其他進程啸驯?
進程笨驮活主要有兩個思路:
提升進程的優(yōu)先級,降低進程被殺死的概率罚斗。
拉活已經(jīng)被殺死的進程徙鱼。
如何提升優(yōu)先級,如下所示:
監(jiān)控手機鎖屏事件针姿,在屏幕鎖屏?xí)r啟動一個像素的Activity袱吆,在用戶解鎖時將Activity銷毀掉,前臺Activity可以將進程變成前臺進程搓幌,優(yōu)先級升級到最高杆故。
如果拉活
利用廣播拉活A(yù)ctivity。
28.理解序列化嗎溉愁,Android為什么引入Parcelable处铛?
所謂序列化就是將對象變成二進制流饲趋,便于存儲和傳輸。
Serializable是java實現(xiàn)的一套序列化方式撤蟆,可能會觸發(fā)頻繁的IO操作奕塑,效率比較低,適合將對象存儲到磁盤上的情況家肯。
Parcelable是Android提供一套序列化機制龄砰,它將序列化后的字節(jié)流寫入到一個共性內(nèi)存中,其他對象可以從這塊共享內(nèi)存中讀出字節(jié)流讨衣,并反序列化成對象换棚。因此效率比較高,適合在對象間或者進程間傳遞信息反镇。
29.如何計算一個Bitmap占用內(nèi)存的大小固蚤,怎么保證加載Bitmap不產(chǎn)生內(nèi)存溢出?
Bitamp 占用內(nèi)存大小 = 寬度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一個像素所占的內(nèi)存.
注:這里inDensity表示目標(biāo)圖片的dpi(放在哪個資源文件夾下)歹茶,inTargetDensity表示目標(biāo)屏幕的dpi夕玩,所以你可以發(fā)現(xiàn)inDensity和inTargetDensity會對Bitmap的寬高
進行拉伸,進而改變Bitmap占用內(nèi)存的大小惊豺。
在Bitmap里有兩個獲取內(nèi)存占用大小的方法燎孟。
getByteCount():API12 加入,代表存儲 Bitmap 的像素需要的最少內(nèi)存尸昧。
getAllocationByteCount():API19 加入揩页,代表在內(nèi)存中為 Bitmap 分配的內(nèi)存大小,代替了 getByteCount() 方法彻磁。
在不復(fù)用 Bitmap 時碍沐,getByteCount() 和 getAllocationByteCount 返回的結(jié)果是一樣的。在通過復(fù)用 Bitmap 來解碼圖片時衷蜓,那么 getByteCount() 表示新解碼圖片占用內(nèi)存的大
小累提,getAllocationByteCount() 表示被復(fù)用 Bitmap真實占用的內(nèi)存大小(即 mBuffer 的長度)磁浇。
為了保證在加載Bitmap的時候不產(chǎn)生內(nèi)存溢出斋陪,可以受用BitmapFactory進行圖片壓縮,主要有以下幾個參數(shù):
BitmapFactory.Options.inPreferredConfig:將ARGB_8888改為RGB_565置吓,改變編碼方式无虚,節(jié)約內(nèi)存。
BitmapFactory.Options.inSampleSize:縮放比例衍锚,可以參考Luban那個庫友题,根據(jù)圖片寬高計算出合適的縮放比例。
BitmapFactory.Options.inPurgeable:讓系統(tǒng)可以內(nèi)存不足時回收內(nèi)存戴质。
30.Android如何在不壓縮的情況下加載高清大圖度宦?
使用BitmapRegionDecoder進行布局加載踢匣。
31.Android里的內(nèi)存緩存和磁盤緩存是怎么實現(xiàn)的?
內(nèi)存緩存基于LruCache實現(xiàn),磁盤緩存基于DiskLruCache實現(xiàn)戈抄。這兩個類都基于Lru算法和LinkedHashMap來實現(xiàn)离唬。
LRU算法可以用一句話來描述,如下所示:
LRU是Least Recently Used的縮寫划鸽,最近最久未使用算法输莺,從它的名字就可以看出,它的核心原則是如果一個數(shù)據(jù)在最近一段時間沒有使用到裸诽,那么它在將來被
訪問到的可能性也很小嫂用,則這類數(shù)據(jù)項會被優(yōu)先淘汰掉。
LruCache的原理是利用LinkedHashMap持有對象的強引用丈冬,按照Lru算法進行對象淘汰尸折。具體說來假設(shè)我們從表尾訪問數(shù)據(jù),在表頭刪除數(shù)據(jù)殷蛇,當(dāng)訪問的數(shù)據(jù)項在鏈表中存在時,則將該數(shù)據(jù)項移動到表尾橄浓,否則在表尾新建一個數(shù)據(jù)項粒梦。當(dāng)鏈表容量超過一定閾值,則移除表頭的數(shù)據(jù)荸实。
之前匀们,我們會使用內(nèi)存緩存技術(shù)實現(xiàn),也就是軟引用或弱引用准给,在Android 2.3(APILevel 9)開始泄朴,垃圾回收器會更傾向于回收持有軟引用或弱引用的對象,這讓軟引用和弱引用變得不再可靠露氮。
為什么會選擇LinkedHashMap呢祖灰?
這跟LinkedHashMap的特性有關(guān),LinkedHashMap的構(gòu)造函數(shù)里有個布爾參數(shù)accessOrder畔规,當(dāng)它為true時局扶,LinkedHashMap會以訪問順序為序排列元素,否則以插入順序為序排序元素叁扫。
DiskLruCache與LruCache原理相似三妈,只是多了一個journal文件來做磁盤文件的管理和迎神,如下所示:
注:這里的緩存目錄是應(yīng)用的緩存目錄/data/data/pckagename/cache莫绣,未root的手機可以通過以下命令進入到該目錄中或者將該目錄整體拷貝出來:
我們來分析下這個文件的內(nèi)容:
第一行:libcore.io.DiskLruCache畴蒲,固定字符串。
第二行:1对室,DiskLruCache源碼版本號模燥。
第三行:1咖祭,App的版本號,通過open()方法傳入進去的涧窒。
第四行:1心肪,每個key對應(yīng)幾個文件,一般為1.
第五行:空行
第六行及后續(xù)行:緩存操作記錄纠吴。
第六行及后續(xù)行表示緩存操作記錄硬鞍,關(guān)于操作記錄,我們需要了解以下三點:
DIRTY 表示一個entry正在被寫入戴已。寫入分兩種情況固该,如果成功會緊接著寫入一行CLEAN的記錄;如果失敗糖儡,會增加一行REMOVE記錄伐坏。注意單獨只有DIRTY狀態(tài)的記錄是非法的。
當(dāng)手動調(diào)用remove(key)方法的時候也會寫入一條REMOVE記錄握联。
READ就是說明有一次讀取的記錄桦沉。
CLEAN的后面還記錄了文件的長度,注意可能會一個key對應(yīng)多個文件金闽,那么就會有多個數(shù)字纯露。
32.PathClassLoader與DexClassLoader有什么區(qū)別?
PathClassLoader:只能加載已經(jīng)安裝到Android系統(tǒng)的APK文件代芜,即/data/app目錄埠褪,Android默認(rèn)的類加載器。
DexClassLoader:可以加載任意目錄下的dex挤庇、jar钞速、apk、zip文件嫡秕。
33.WebView優(yōu)化了解嗎渴语,如何提高WebView的加載速度?
為什么WebView加載會慢呢昆咽?
這是因為在客戶端中遵班,加載H5頁面之前,需要先初始化WebView潮改,在WebView完全初始化完成之前狭郑,后續(xù)的界面加載過程都是被阻塞的。
優(yōu)化手段圍繞著以下兩個點進行:
預(yù)加載WebView汇在。
加載WebView的同時翰萨,請求H5頁面數(shù)據(jù)。
因此常見的方法是:
全局WebView糕殉。
客戶端代理頁面請求亩鬼。WebView初始化完成后向客戶端請求數(shù)據(jù)殖告。
asset存放離線包。
除此之外還有一些其他的優(yōu)化手段:
腳本執(zhí)行慢雳锋,可以讓腳本最后運行黄绩,不阻塞頁面解析。
DNS與鏈接慢玷过,可以讓客戶端復(fù)用使用的域名與鏈接爽丹。
React框架代碼執(zhí)行慢,可以將這部分代碼拆分出來辛蚊,提前進行解析粤蝎。
34.Java和JS的相互調(diào)用怎么實現(xiàn),有做過什么優(yōu)化嗎袋马?
jockeyjs:https://github.com/tcoulter/jockeyjs
對協(xié)議進行統(tǒng)一的封裝和處理初澎。
35.JNI了解嗎掖肋,Java與C++如何相互調(diào)用虫埂?
Java調(diào)用C++
在Java中聲明Native方法(即需要調(diào)用的本地方法)
編譯上述 Java源文件javac(得到 .class文件)
3肆汹。 通過 javah 命令導(dǎo)出JNI的頭文件(.h文件)使用 Java需要交互的本地代碼 實現(xiàn)在 Java中聲明的Native方法
編譯.so庫文件
通過Java命令執(zhí)行 Java程序衅鹿,最終實現(xiàn)Java調(diào)用本地代碼
C++調(diào)用Java
從classpath路徑下搜索ClassMethod這個類,并返回該類的Class對象帘瞭。
獲取類的默認(rèn)構(gòu)造方法ID统捶。
查找實例方法的ID七蜘。
創(chuàng)建該類的實例霉囚。
調(diào)用對象的實例方法。
36.了解插件化和熱修復(fù)嗎匕积,它們有什么區(qū)別盈罐,理解它們的原理嗎?
插件化:插件化是體現(xiàn)在功能拆分方面的闪唆,它將某個功能獨立提取出來盅粪,獨立開發(fā),獨立測試悄蕾,再插入到主應(yīng)用中票顾。依次來較少主應(yīng)用的規(guī)模。
熱修復(fù):熱修復(fù)是體現(xiàn)在bug修復(fù)方面的帆调,它實現(xiàn)的是不需要重新發(fā)版和重新安裝奠骄,就可以去修復(fù)已知的bug。
熱修復(fù)原理:利用PathClassLoader和DexClassLoader去加載與bug類同名的類番刊,替換掉bug類含鳞,進而達到修復(fù)bug的目的,原理是在app打包的時候阻止類打上CLASS_ISPREVERIFIED標(biāo)志芹务,然后在熱修復(fù)的時候動態(tài)改變BaseDexClassLoader對象間接引用的dexElements蝉绷,這樣才能搶先代替Bug類鸭廷,完成系統(tǒng)不加載舊的Bug類.
插件化:插件化只是增肌新的功能類或者是資源文件,所以不涉及搶先加載舊的類這樣的使命熔吗,就避過了阻止相關(guān)類去打上CLASS_ISPREVERIFIED標(biāo)志和還有在熱修復(fù)時動態(tài)改變BaseDexClassLoader對象間接引用的dexElements.
所以插件化比熱修復(fù)簡單辆床,熱修復(fù)是在插件化的基礎(chǔ)上在進行替舊的Bug類
37.如何做性能優(yōu)化?
節(jié)制的使用Service桅狠,當(dāng)啟動一個Service時讼载,系統(tǒng)總是傾向于保留這個Service依賴的進程,這樣會造成系統(tǒng)資源的浪費垂攘,可以使用IntentService维雇,執(zhí)行完成任務(wù)后會自動停止。
當(dāng)界面不可見時釋放內(nèi)存晒他,可以重寫Activity的onTrimMemory()方法吱型,然后監(jiān)聽TRIM_MEMORY_UI_HIDDEN這個級別,這個級別說明用戶離開了頁面陨仅,可以考慮釋放內(nèi)存和資源津滞。
避免在Bitmap浪費過多的內(nèi)存,使用壓縮過的圖片灼伤,也可以使用Fresco等庫來優(yōu)化對Bitmap顯示的管理触徐。
使用優(yōu)化過的數(shù)據(jù)集合SparseArray代替HashMap,HashMap為每個鍵值都提供一個對象入口狐赡,使用SparseArray可以免去基本對象類型轉(zhuǎn)換為引用數(shù)據(jù)類想的時間撞鹉。
38.如果防止過度繪制,如何做布局優(yōu)化颖侄?
使用include復(fù)用布局文件鸟雏。
使用merge標(biāo)簽避免嵌套布局。
使用stub標(biāo)簽僅在需要的時候在展示出來览祖。
39.如何提交代碼質(zhì)量孝鹊?
- 避免創(chuàng)建不必要的對象,盡可能避免頻繁的創(chuàng)建臨時對象展蒂,例如在for循環(huán)內(nèi)又活,減少GC的次數(shù)。
如果有需要拼接的字符串锰悼,那么可以優(yōu)先考慮使用StringBuffer或者StringBuilder來進行拼接柳骄,而不是加號連接符,因為使用加號連接符會創(chuàng)建多余的對象箕般,拼接的字符串越長夹界,加號連接符的性能越低。
當(dāng)一個方法的返回值是String的時候,通常需要去判斷一下這個String的作用是什么可柿,如果明確知道調(diào)用方會將返回的String再進行拼接操作的話鸠踪,可以考慮返回一個StringBuffer對象來代替,因為這樣可以將一個對象的引用進行返回复斥,而返回String的話就是創(chuàng)建了一個短生命周期的臨時對象营密。
盡可能地少創(chuàng)建臨時對象,越少的對象意味著越少的GC操作目锭。
盡量使用基本數(shù)據(jù)類型代替引用數(shù)據(jù)類型评汰。
靜態(tài)方法調(diào)用效率高于動態(tài)方法,也可以避免創(chuàng)建額外對象痢虹。
對于基本數(shù)據(jù)類型和String類型的常量要使用static final修飾被去,這樣常量會在dex文件的初始化器中進行初始化,使用的時候可以直接使用奖唯。
有final 修飾和無final修飾區(qū)別
static int intVal = 42;
static String strVal = "Hello, world!";
編譯器會為上面的代碼生成一個初始方法惨缆,稱為方法,該方法會在定義類第一次被使用的時候調(diào)用丰捷。這個方法會將42的值賦值到intVal當(dāng)中坯墨,從字符串常量表中提取一個引用賦值到strVal上。當(dāng)賦值完成后病往,我們就可以通過字段搜尋的方式去訪問具體的值了捣染。
final進行優(yōu)化:
static final int intVal = 42;
static final String strVal = "Hello, world!";
這樣,定義類就不需要方法了停巷,因為所有的常量都會在dex文件的初始化器當(dāng)中進行初始化耍攘。當(dāng)我們調(diào)用intVal時可以直接指向42的值,而調(diào)用strVal會用一種相對輕量級的字符串常量方式畔勤,而不是字段搜尋的方式蕾各。
這種優(yōu)化方式只對基本數(shù)據(jù)類型以及String類型的常量有效,對于其他數(shù)據(jù)類型的常量是無效的硼被。
- 多使用系統(tǒng)API,例如數(shù)組拷貝System.arrayCopy()方法渗磅,要比我們用for循環(huán)效率快9倍以上嚷硫,因為系統(tǒng)API很多都是通過底層的匯編模式執(zhí)行的,效率比較高始鱼。
6.使用增強型for循環(huán)語法
static class Counter {
int mCount;
}
Counter[] mArray = ...
public void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; ++i) {
sum += mArray[i].mCount;
} }
public void one() {
int sum = 0;
Counter[] localArray = mArray;
int len = localArray.length;
for (int i = 0; i < len; ++i) {
sum += localArray[i].mCount;
}
}
public void two() {
int sum = 0;
for (Counter a : mArray) {
sum += a.mCount;
}
}
zero()最慢仔掸,每次都要計算mArray的長度,one()相對快得多医清,two()fangfa在沒有JIT(Just In Time Compiler)的設(shè)備上是運行最快的起暮,而在有JIT的設(shè)備上運行效率和one()方法不相上下,需要注意這種寫法需要JDK1.5之后才支持会烙。
Tips:ArrayList手寫的循環(huán)比增強型for循環(huán)更快负懦,其他的集合沒有這種情況筒捺。因此默認(rèn)情況下使用增強型for循環(huán),而遍歷ArrayList使用傳統(tǒng)的循環(huán)方式纸厉。
6).多使用系統(tǒng)封裝好的API
系統(tǒng)提供不了的Api完成不了我們需要的功能才應(yīng)該自己去寫系吭,因為使用系統(tǒng)的Api很多時候比我們自己寫的代碼要快得多,它們的很多功能都是通過底層的匯編模式執(zhí)行的颗品。 舉個例子肯尺,實現(xiàn)數(shù)組拷貝的功能,使用循環(huán)的方式來對數(shù)組中的每一個元素一一進行賦值當(dāng)然可行躯枢,但是直接使用系統(tǒng)中提供的System.arraycopy()方法會讓執(zhí)行效率快9倍以上则吟。
7).避免在內(nèi)部調(diào)用Getters/Setters方法
面向?qū)ο笾蟹庋b的思想是不要把類內(nèi)部的字段暴露給外部,而是提供特定的方法來允許外部操作相應(yīng)類的內(nèi)部字段锄蹂。但在Android中氓仲,字段搜尋比方法調(diào)用效率高得多,我們直接訪問某個字段可能要比通過getters方法來去訪問這個字段快3到7倍败匹。但是編寫代碼還是要按照面向?qū)ο笏季S的寨昙,我們應(yīng)該在能優(yōu)化的地方進行優(yōu)化,比如避免在內(nèi)部調(diào)用getters/setters方法掀亩。
40.有沒有遇到64k問題舔哪,為什么,如何解決槽棍?
在DEX文件中捉蚤,method、field炼七、class等的個數(shù)使用short類型來做索引缆巧,即兩個字節(jié)(65535),method豌拙、field陕悬、class等均有此限制。
APK在安裝過程中會調(diào)用dexopt將DEX文件優(yōu)化成ODEX文件按傅,dexopt使用LinearAlloc來存儲應(yīng)用信息捉超,關(guān)于LinearAlloc緩沖區(qū)大小,不同的版本經(jīng)歷了4M/8M/16M的限制唯绍,超出
緩沖區(qū)時就會拋出INSTALL_FAILED_DEXOPT錯誤拼岳。
解決方案是Google的MultiDex方案,具體參見:配置方法數(shù)超過 64K 的應(yīng)用况芒。
41.MVC惜纸、MVP與MVVM之間的對比分析?
[圖片上傳失敗...(image-a3d14e-1540037333884)]
MVC:PC時代就有的架構(gòu)方案,在Android上也是最早的方案耐版,Activity/Fragment這些上帝角色既承擔(dān)了V的角色祠够,也承擔(dān)了C的角色,小項目開發(fā)起來十分順手椭更,大項目就會遇到
耦合過重哪审,Activity/Fragment類過大等問題。MVP:為了解決MVC耦合過重的問題虑瀑,MVP的核心思想就是提供一個Presenter將視圖邏輯I和業(yè)務(wù)邏輯相分離湿滓,達到解耦的目的。
MVVM:使用ViewModel代替Presenter舌狗,實現(xiàn)數(shù)據(jù)與View的雙向綁定叽奥,這套框架最早使用的data-binding將數(shù)據(jù)綁定到xml里,這么做在大規(guī)模應(yīng)用的時候是不行的痛侍,不過數(shù)據(jù)綁定是
一個很有用的概念朝氓,后續(xù)Google又推出了ViewModel組件與LiveData組件。ViewModel組件規(guī)范了ViewModel所處的地位主届、生命周期赵哲、生產(chǎn)方式以及一個Activity下多個Fragment共享ViewModel數(shù)據(jù)的問題。LiveData組件則提供了在Java層面View訂閱ViewModel數(shù)據(jù)源的實現(xiàn)方案君丁。
42.類的加載過程枫夺,Person person = new Person();為例進行說明。
1).因為new用到了Person.class绘闷,所以會先找到Person.class文件橡庞,并加載到內(nèi)存中;
2).執(zhí)行該類中的static代碼塊,如果有的話印蔗,給Person.class類進行初始化;
3).在堆內(nèi)存中開辟空間分配內(nèi)存地址;
4).在堆內(nèi)存中建立對象的特有屬性扒最,并進行默認(rèn)初始化;
5).對屬性進行顯示初始化;
6).對對象進行構(gòu)造代碼塊初始化;
7).對對象進行與之對應(yīng)的構(gòu)造函數(shù)進行初始化;
8).將內(nèi)存地址付給棧內(nèi)存中的p變量
43.JVM相關(guān)知識
JVM基本構(gòu)成
從上圖可知,JVM主要包括四個部分:
1.類加載器(ClassLoader):在JVM啟動時或者在類運行時將需要的class加載到JVM中华嘹。(下圖表示了從java源文件到JVM的整個過程吧趣,可配合理解。
2.執(zhí)行引擎:負(fù)責(zé)執(zhí)行class文件中包含的字節(jié)碼指令耙厚;
3.內(nèi)存區(qū)(也叫運行時數(shù)據(jù)區(qū)):是在JVM運行的時候操作所分配的內(nèi)存區(qū)强挫。運行時內(nèi)存區(qū)主要可以劃分為5個區(qū)域,如圖:
方法區(qū)(Method Area):用于存儲類結(jié)構(gòu)信息的地方颜曾,包括常量池纠拔、靜態(tài)變量秉剑、構(gòu)造函數(shù)等泛豪。雖然JVM規(guī)范把方法區(qū)描述為堆的一個邏輯部分, 但它卻有個別名non-heap(非堆),所以大家不要搞混淆了诡曙。方法區(qū)還包含一個運行時常量池臀叙。
java堆(Heap):存儲java實例或者對象的地方。這塊是GC的主要區(qū)域价卤。從存儲的內(nèi)容我們可以很容易知道劝萤,方法區(qū)和堆是被所有java線程共享的。
java棧(Stack):java椛麒担總是和線程關(guān)聯(lián)在一起床嫌,每當(dāng)創(chuàng)建一個線程時,JVM就會為這個線程創(chuàng)建一個對應(yīng)的java棧胸私。在這個java棧中又會包含多個棧幀厌处,每運行一個方法就創(chuàng)建一個棧幀,用于存儲局部變量表岁疼、操作棧阔涉、方法返回值等。每一個方法從調(diào)用直至執(zhí)行完成的過程捷绒,就對應(yīng)一個棧幀在java棧中入棧到出棧的過程瑰排。所以java棧是現(xiàn)成私有的。
程序計數(shù)器(PC Register):用于保存當(dāng)前線程執(zhí)行的內(nèi)存地址暖侨。由于JVM程序是多線程執(zhí)行的(線程輪流切換)椭住,所以為了保證線程切換回來后,還能恢復(fù)到原先狀態(tài)它碎,就需要一個獨立的計數(shù)器函荣,記錄之前中斷的地方,可見程序計數(shù)器也是線程私有的扳肛。
本地方法棧(Native Method Stack):和java棧的作用差不多傻挂,只不過是為JVM使用到的native方法服務(wù)的。
4.本地方法接口:主要是調(diào)用C或C++實現(xiàn)的本地方法及返回結(jié)果挖息。
44.GC原理
垃圾收集器一般必須完成兩件事:檢測出垃圾金拒;回收垃圾。怎么檢測出垃圾套腹?一般有以下幾種方法:
給一個對象添加引用計數(shù)器绪抛,每當(dāng)有個地方引用它,計數(shù)器就加1电禀;引用失效就減1幢码。好了,問題來了尖飞,如果我有兩個對象A和B症副,互相引用店雅,除此之外,沒有其他任何對象引用它們贞铣,實際上這兩個對象已經(jīng)無法訪問闹啦,即是我們說的垃圾對象。但是互相引用辕坝,計數(shù)不為0窍奋,導(dǎo)致無法回收,所以還有另一種方法:
以根集對象為起始點進行搜索酱畅,如果有對象不可達的話琳袄,即是垃圾對象。這里的根集一般包括java棧中引用的對象纺酸、方法區(qū)常良池中引用的對象挚歧、本地方法中引用的對象等。
總之吁峻,JVM在做垃圾回收的時候滑负,會檢查堆中的所有對象是否會被這些根集對象引用,不能夠被引用的對象就會被垃圾收集器回收用含。一般回收算法也有如下幾種:
1).標(biāo)記-清除(Mark-sweep)
2).復(fù)制(Copying
3).標(biāo)記-整理(Mark-Compact)
4).分代收集算法
45.類的加載器矮慕,雙親機制,Android的類加載器
類的加載器
大家都知道啄骇,當(dāng)我們寫好一個Java程序之后痴鳄,不是管是CS還是BS應(yīng)用,都是由若干個.class文件組織而成的一個完整的Java應(yīng)用程序缸夹,當(dāng)程序在運行時痪寻,即會調(diào)用該程序的一個入口函數(shù)來調(diào)用系統(tǒng)的相關(guān)功能,而這些功能都被封裝在不同的class文件當(dāng)中虽惭,所以經(jīng)常要從這個class文件中要調(diào)用另外一個class文件中的方法橡类,如果另外一個文件不存在的,則會引發(fā)系統(tǒng)異常芽唇。而程序在啟動的時候顾画,并不會一次性加載程序所要用的所有class文件,而是根據(jù)程序的需要匆笤,通過Java的類加載機制(ClassLoader)來動態(tài)加載某個class文件到內(nèi)存當(dāng)中的研侣,從而只有class文件被載入到了內(nèi)存之后,才能被其它class所引用炮捧。所以ClassLoader就是用來動態(tài)加載class文件到內(nèi)存當(dāng)中用的庶诡。
雙親機制
ClassLoader使用的是雙親委托模型來搜索類的,每個ClassLoader實例都有一個父類加載器的引用(不是繼承的關(guān)系咆课,是一個包含的關(guān)系)末誓,虛擬機內(nèi)置的類加載器(Bootstrap ClassLoader)本身沒有父類加載器璧函,但可以用作其它ClassLoader實例的的父類加載器。當(dāng)一個ClassLoader實例需要加載某個類時基显,它會試圖親自搜索某個類之前,先把這個任務(wù)委托給它的父類加載器善炫,這個過程是由上至下依次檢查的撩幽,首先由最頂層的類加載器Bootstrap ClassLoader試圖加載,如果沒加載到箩艺,則把任務(wù)轉(zhuǎn)交給Extension ClassLoader試圖加載窜醉,如果也沒加載到,則轉(zhuǎn)交給App ClassLoader 進行加載艺谆,如果它也沒有加載得到的話榨惰,則返回給委托的發(fā)起者,由它到指定的文件系統(tǒng)或網(wǎng)絡(luò)等URL中加載該類静汤。如果它們都沒有加載到這個類時琅催,則拋出ClassNotFoundException異常。否則將這個找到的類生成一個類的定義虫给,并將它加載到內(nèi)存當(dāng)中藤抡,最后返回這個類在內(nèi)存中的Class實例對象。
因為這樣可以避免重復(fù)加載抹估,當(dāng)父親已經(jīng)加載了該類的時候缠黍,就沒有必要子ClassLoader再加載一次∫撸考慮到安全因素瓷式,我們試想一下,如果不使用這種委托模式语泽,那我們就可以隨時使用自定義的String來動態(tài)替代java核心api中定義的類型贸典,這樣會存在非常大的安全隱患,而雙親委托的方式踱卵,就可以避免這種情況瓤漏,因為String已經(jīng)在啟動時就被引導(dǎo)類加載器(Bootstrcp ClassLoader)加載,所以用戶自定義的ClassLoader永遠(yuǎn)也無法加載一個自己寫的String颊埃,除非你改變JDK中ClassLoader搜索類的默認(rèn)算法蔬充。
JVM在判定兩個class是否相同時,不僅要判斷兩個類名是否相同班利,而且要判斷是否由同一個類加載器實例加載的饥漫。只有兩者同時滿足的情況下,JVM才認(rèn)為這兩個class是相同的罗标。就算兩個class是同一份class字節(jié)碼庸队,如果被兩個不同的ClassLoader實例所加載积蜻,JVM也會認(rèn)為它們是兩個不同class。比如網(wǎng)絡(luò)上的一個Java類org.classloader.simple.NetClassLoaderSimple彻消,javac編譯之后生成字節(jié)碼文件NetClassLoaderSimple.class竿拆,ClassLoaderA和ClassLoaderB這兩個類加載器并讀取了NetClassLoaderSimple.class文件,并分別定義出了java.lang.Class實例來表示這個類宾尚,對于JVM來說丙笋,它們是兩個不同的實例對象,但它們確實是同一份字節(jié)碼文件煌贴,如果試圖將這個Class實例生成具體的對象進行轉(zhuǎn)換時御板,就會拋運行時異常java.lang.ClassCaseException,提示這是兩個不同的類型牛郑。
Android類加載器
對于Android而言怠肋,最終的apk文件包含的是dex類型的文件,dex文件是將class文件重新打包淹朋,打包的規(guī)則又不是簡單地壓縮笙各,而是完全對class文件內(nèi)部的各種函數(shù)表,變量表進行優(yōu)化础芍,產(chǎn)生一個新的文件酪惭,即dex文件。因此加載這種特殊的Class文件就需要特殊的類加載器DexClassLoader者甲。
45.集合框架春感,list,map虏缸,set都有哪些具體的實現(xiàn)類鲫懒,區(qū)別都是什么?
1.List,Set都是繼承自Collection接口,Map則不是;
2.List特點:元素有放入順序刽辙,元素可重復(fù);
Set特點:元素?zé)o放入順序窥岩,元素不可重復(fù),重復(fù)元素會覆蓋掉宰缤,(注意:元素雖然無放入順序颂翼,但是元素在set中的位置是有該元素的HashCode決定的,其位置其實是固定的慨灭,加入Set 的Object必須定義equals()方法;
另外list支持for循環(huán)朦乏,也就是通過下標(biāo)來遍歷,也可以用迭代器氧骤,但是set只能用迭代呻疹,因為他無序,無法用下標(biāo)來取得想要的值)筹陵。
3.Set和List對比:
Set:檢索元素效率低下刽锤,刪除和插入效率高镊尺,插入和刪除不會引起元素位置改變。
List:和數(shù)組類似并思,List可以動態(tài)增長庐氮,查找元素效率高,插入刪除元素效率低宋彼,因為會引起其他元素位置改變弄砍。
4.Map適合儲存鍵值對的數(shù)據(jù)。
5.線程安全集合類與非線程安全集合類
LinkedList宙暇、ArrayList、HashSet是非線程安全的议泵,Vector是線程安全的;
HashMap是非線程安全的占贫,HashTable是線程安全的;
StringBuilder是非線程安全的,StringBuffer是線程安全的先口。
下面是這些類具體的使用介紹:
ArrayList與LinkedList的區(qū)別和適用場景
優(yōu)點:ArrayList是實現(xiàn)了基于動態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu),因為地址連續(xù)型奥,一旦數(shù)據(jù)存儲好了,查詢操作效率會比較高(在內(nèi)存里是連著放的)碉京。
缺點:因為地址連續(xù)厢汹, ArrayList要移動數(shù)據(jù),所以插入和刪除操作效率比較低。
優(yōu)點:LinkedList基于鏈表的數(shù)據(jù)結(jié)構(gòu),地址是任意的谐宙,所以在開辟內(nèi)存空間的時候不需要等一個連續(xù)的地址烫葬,對于新增和刪除操作add和remove,LinedList比較占優(yōu)勢凡蜻。LinkedList 適用于要頭尾操作或插入指定位置的場景搭综。
缺點:因為LinkedList要移動指針,所以查詢操作性能比較低。
適用場景分析:
當(dāng)需要對數(shù)據(jù)進行對此訪問的情況下選用ArrayList划栓,當(dāng)需要對數(shù)據(jù)進行多次增加刪除修改時采用LinkedList兑巾。
ArrayList與Vector的區(qū)別和適用場景
ArrayList有三個構(gòu)造方法:
public ArrayList(int initialCapacity)//構(gòu)造一個具有指定初始容量的空列表。
public ArrayList()//構(gòu)造一個初始容量為10的空列表忠荞。
public ArrayList(Collection<? extends E> c)//構(gòu)造一個包含指定 collection 的元素的列表
Vector有四個構(gòu)造方法:
public Vector()//使用指定的初始容量和等于零的容量增量構(gòu)造一個空向量蒋歌。
public Vector(int initialCapacity)//構(gòu)造一個空向量,使其內(nèi)部數(shù)據(jù)數(shù)組的大小委煤,其標(biāo)準(zhǔn)容量增量為零堂油。
public Vector(Collection<? extends E> c)//構(gòu)造一個包含指定 collection 中的元素的向量
public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量構(gòu)造一個空的向量
ArrayList和Vector都是用數(shù)組實現(xiàn)的,主要有這么三個區(qū)別:
1).Vector是多線程安全的碧绞,線程安全就是說多線程訪問同一代碼称诗,不會產(chǎn)生不確定的結(jié)果。而ArrayList不是头遭,這個可以從源碼中看出寓免,Vector類中的方法很多有synchronized進行修飾癣诱,這樣就導(dǎo)致了Vector在效率上無法與ArrayList相比;
2).兩個都是采用的線性連續(xù)空間存儲元素,但是當(dāng)空間不足的時候胳赌,兩個類的增加方式是不同兢卵。
3).Vector可以設(shè)置增長因子,而ArrayList不可以实抡。
4).Vector是一種老的動態(tài)數(shù)組,是線程同步的欢策,效率很低吆寨,一般不贊成使用。
適用場景:
1.Vector是線程同步的踩寇,所以它也是線程安全的啄清,而ArrayList是線程異步的,是不安全的俺孙。如果不考慮到線程的安全因素辣卒,一般用ArrayList效率比較高。
2.如果集合中的元素的數(shù)目大于目前集合數(shù)組的長度時睛榄,在集合中使用數(shù)據(jù)量比較大的數(shù)據(jù)荣茫,用Vector有一定的優(yōu)勢。
HashSet與Treeset的適用場景
1.TreeSet 是二叉樹(紅黑樹的樹據(jù)結(jié)構(gòu))實現(xiàn)的,Treeset中的數(shù)據(jù)是自動排好序的场靴,不允許放入null值啡莉。
2.HashSet 是哈希表實現(xiàn)的,HashSet中的數(shù)據(jù)是無序的,可以放入null旨剥,但只能放入一個null票罐,兩者中的值都不能重復(fù),就如數(shù)據(jù)庫中唯一約束泞边。
3.HashSet要求放入的對象必須實現(xiàn)HashCode()方法该押,放入的對象,是以hashcode碼作為標(biāo)識的阵谚,而具有相同內(nèi)容的String對象蚕礼,hashcode是一樣,所以放入的內(nèi)容不能重復(fù)梢什。但是同一個類的對象可以放入不同的實例奠蹬。
適用場景分析:
HashSet是基于Hash算法實現(xiàn)的,其性能通常都優(yōu)于TreeSet嗡午。為快速查找而設(shè)計的Set囤躁,我們通常都應(yīng)該使用HashSet,在我們需要排序的功能時,我們才使用TreeSet狸演。
HashMap與TreeMap言蛇、HashTable的區(qū)別及適用場景
HashMap:基于哈希表(散列表)實現(xiàn)。使用HashMap要求添加的鍵類明確定義了hashCode()和equals()[可以重寫hashCode()和equals()]宵距,為了優(yōu)化HashMap空間的使用腊尚,您可以調(diào)優(yōu)初始容量和負(fù)載因子。其中散列表的沖突處理主要分兩種满哪,一種是開放定址法婿斥,另一種是鏈表法。HashMap的實現(xiàn)中采用的是鏈表法哨鸭。
TreeMap:非線程安全基于紅黑樹實現(xiàn)民宿。TreeMap沒有調(diào)優(yōu)選項,因為該樹總處于平衡狀態(tài)像鸡。
適用場景分析:
HashMap和HashTable:HashMap去掉了HashTable的contains方法活鹰,但是加上了containsValue()和containsKey()方法。HashTable同步的坟桅,而HashMap是非同步的华望,效率上比HashTable要高蕊蝗。HashMap允許空鍵值仅乓,而HashTable不允許。
HashMap:適用于Map中插入蓬戚、刪除和定位元素夸楣。
Treemap:適用于按自然順序或自定義順序遍歷鍵(key)。
(ps:其實我們工作的過程中對集合的使用是很頻繁的,稍加注意并總結(jié)積累一下,在面試的時候應(yīng)該會回答的很輕松)
46.concurrentHashmap原理子漩,原子類
ConcurrentHashMap作為一種線程安全且高效的哈希表的解決方案豫喧,尤其其中的"分段鎖"的方案,相比HashTable的全表鎖在性能上的提升非常之大.
47.volatile原理
在《Java并發(fā)編程:核心理論》一文中幢泼,我們已經(jīng)提到過可見性紧显、有序性及原子性問題,通常情況下我們可以通過Synchronized關(guān)鍵字來解決這些個問題缕棵,不過如果對Synchronized原理有了解的話孵班,應(yīng)該知道Synchronized是一個比較重量級的操作,對系統(tǒng)的性能有比較大的影響招驴,所以篙程,如果有其他解決方案,我們通常都避免使用Synchronized來解決問題别厘。而volatile關(guān)鍵字就是Java中提供的另一種解決可見性和有序性問題的方案虱饿。對于原子性,需要強調(diào)一點,也是大家容易誤解的一點:對volatile變量的單次讀/寫操作可以保證原子性的氮发,如long和double類型變量渴肉,但是并不能保證i++這種操作的原子性,因為本質(zhì)上i++是讀折柠、寫兩次操作宾娜。
48.多線程的使用場景
使用多線程就一定效率高嗎? 有時候使用多線程并不是為了提高效率扇售,而是使得CPU能夠同時處理多個事件前塔。
1).為了不阻塞主線程,啟動其他線程來做好事的事情,比如APP中耗時操作都不在UI中做.
2).實現(xiàn)更快的應(yīng)用程序,即主線程專門監(jiān)聽用戶請求,子線程用來處理用戶請求,以獲得大的吞吐量.感覺這種情況下,多線程的效率未必高承冰。 這種情況下的多線程是為了不必等待华弓, 可以并行處理多條數(shù)據(jù)。
比如JavaWeb的就是主線程專門監(jiān)聽用戶的HTTP請求困乒,然后啟動子線程去處理用戶的HTTP請求寂屏。
3).某種雖然優(yōu)先級很低的服務(wù),但是卻要不定時去做娜搂。
比如Jvm的垃圾回收迁霎。
4.)某種任務(wù),雖然耗時百宇,但是不耗CPU的操作時考廉,開啟多個線程,效率會有顯著提高携御。
比如讀取文件昌粤,然后處理。 磁盤IO是個很耗費時間啄刹,但是不耗CPU計算的工作涮坐。 所以可以一個線程讀取數(shù)據(jù),一個線程處理數(shù)據(jù)誓军「ざ铮肯定比一個線程讀取數(shù)據(jù),然后處理效率高昵时。 因為兩個線程的時候充分利用了CPU等待磁盤IO的空閑時間捷雕。
49.線程池的相關(guān)知識
Android中的線程池都是之間或間接通過配置ThreadPoolExecutor來實現(xiàn)不同特性的線程池.Android中最常見的四類具有不同特性的線程池分別為FixThreadPool、CachedThreadPool债查、SingleThreadPool非区、ScheduleThreadExecutor.
1).FixThreadPool
只有核心線程,并且數(shù)量固定的,也不會被回收,所有線程都活動時,因為隊列沒有限制大小,新任務(wù)會等待執(zhí)行.
優(yōu)點:更快的響應(yīng)外界請求.
2).SingleThreadPool
只有一個核心線程,確保所有的任務(wù)都在同一線程中按順序完成.因此不需要處理線程同步的問題.
3).CachedThreadPool
只有非核心線程,最大線程數(shù)非常大,所有線程都活動時,會為新任務(wù)創(chuàng)建新線程,否則會利用空閑線程(60s空閑時間,過了就會被回收,所以線程池中有0個線程的可能)處理任務(wù).
優(yōu)點:任何任務(wù)都會被立即執(zhí)行(任務(wù)隊列SynchronousQueue相當(dāng)于一個空集合);比較適合執(zhí)行大量的耗時較少的任務(wù).
4).ScheduledThreadPool
核心線程數(shù)固定,非核心線程(閑著沒活干會被立即回收)數(shù)沒有限制.
優(yōu)點:執(zhí)行定時任務(wù)以及有固定周期的重復(fù)任務(wù)
50.JAVA常量池
a.當(dāng)數(shù)值范圍為-128~127時:如果兩個new出來Integer對象,即使值相同盹廷,通過“==”比較結(jié)果為false征绸,但兩個對象直接賦值,則通過“==”比較結(jié)果為“true,這一點與String非常相似管怠。
b.當(dāng)數(shù)值不在-128~127時淆衷,無論通過哪種方式,即使兩個對象的值相等渤弛,通過“==”比較祝拯,其結(jié)果為false;
c.當(dāng)一個Integer對象直接與一個int基本數(shù)據(jù)類型通過“==”比較她肯,其結(jié)果與第一點相同佳头;
d.Integer對象的hash值為數(shù)值本身;
在Integer類中有一個靜態(tài)內(nèi)部類IntegerCache晴氨,在IntegerCache類中有一個Integer數(shù)組康嘉,用以緩存當(dāng)數(shù)值范圍為-128~127時的Integer對象。
51.簡單介紹一下java中的泛型籽前,泛型擦除以及相關(guān)的概念亭珍。
泛型是Java SE 1.5的新特性,泛型的本質(zhì)是參數(shù)化類型枝哄,也就是說所操作的數(shù)據(jù)類型被指定為一個參數(shù)肄梨。這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中挠锥,分別稱為泛型類众羡、泛型接口、泛型方法瘪贱。 Java語言引入泛型的好處是安全簡單纱控。
在Java SE 1.5之前辆毡,沒有泛型的情況的下菜秦,通過對類型Object的引用來實現(xiàn)參數(shù)的“任意化”,“任意化”帶來的缺點是要做顯式的強制類型轉(zhuǎn)換舶掖,而這種轉(zhuǎn)換是要求開發(fā)者對實際參數(shù)類型可以預(yù)知的情況下進行的球昨。對于強制類型轉(zhuǎn)換錯誤的情況,編譯器可能不提示錯誤眨攘,在運行的時候才出現(xiàn)異常主慰,這是一個安全隱患。
泛型的好處是在編譯的時候檢查類型安全鲫售,并且所有的強制轉(zhuǎn)換都是自動和隱式的共螺,提高代碼的重用率。
1情竹、泛型的類型參數(shù)只能是類類型(包括自定義類)藐不,不能是簡單類型。
2、同一種泛型可以對應(yīng)多個版本(因為參數(shù)類型是不確定的)雏蛮,不同版本的泛型類實例是不兼容的涎嚼。
3、泛型的類型參數(shù)可以有多個挑秉。
4法梯、泛型的參數(shù)類型可以使用extends語句,例如犀概。習(xí)慣上稱為“有界類型”立哑。
5、泛型的參數(shù)類型還可以是通配符類型姻灶。例如Class classType = Class.forName("java.lang.String");
泛型擦除以及相關(guān)的概念
Java中的泛型基本上都是在編譯器這個層次來實現(xiàn)的刁憋。在生成的Java字節(jié)碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數(shù)木蹬,會在編譯器在編譯的時候去掉至耻。這個過程就稱為類型擦除。
類型擦除引起的問題及解決方法
1镊叁、先檢查尘颓,在編譯,以及檢查編譯的對象和引用傳遞的問題
2晦譬、自動類型轉(zhuǎn)換
3疤苹、類型擦除與多態(tài)的沖突和解決方法
4、泛型類型變量不能是基本數(shù)據(jù)類型
5敛腌、運行時類型查詢
6卧土、異常中使用泛型的問題
7、數(shù)組(這個不屬于類型擦除引起的問題)
9像樊、類型擦除后的沖突
10尤莺、泛型在靜態(tài)方法和靜態(tài)類中的問題
以上整理出自 安卓巴士Android開發(fā)者門戶和鴻洋公眾號和自己的理解,有不正確的地方生棍,歡迎指出颤霎。
還有些優(yōu)秀的面試總結(jié)
2019校招Android面試題解1.0(上篇)
http://www.reibang.com/p/718aa3c1a70b
2019校招Android面試題解1.0(中篇)
http://www.reibang.com/p/2dd855aa1938
2019校招Android面試題解1.0(下篇)
http://www.reibang.com/p/168e52336b53