您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關Android有哪些面試題,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
在Android開發中,不管是插件化還是組件化,都是基于Android系統的類加載器ClassLoader來設計的。只不過Android平臺上虛擬機運行的是Dex字節碼,一種對class文件優化的產物,傳統Class文件是一個Java源碼文件會生成一個.class文件,而Android是把所有Class文件進行合并、優化,然后再生成一個最終的class.dex,目的是把不同class文件重復的東西只需保留一份,在早期的Android應用開發中,如果不對Android應用進行分dex處理,那么最后一個應用的apk只會有一個dex文件。
Android中常用的類加載器有兩種,DexClassLoader和PathClassLoader,它們都繼承于BaseDexClassLoader。區別在于調用父類構造器時,DexClassLoader多傳了一個optimizedDirectory參數,這個目錄必須是內部存儲路徑,用來緩存系統創建的Dex文件。而PathClassLoader該參數為null,只能加載內部存儲目錄的Dex文件。所以我們可以用DexClassLoader去加載外部的apk文件,這也是很多插件化技術的基礎。
理解Android的Service,可以從以下幾個方面來理解:
IntentService是一個抽象類,繼承自Service,內部存在一個ServiceHandler(Handler)和HandlerThread(Thread)。IntentService是處理異步請求的一個類,在IntentService中有一個工作線程(HandlerThread)來處理耗時操作,啟動IntentService的方式和普通的一樣,不過當執行完任務之后,IntentService會自動停止。另外可以多次啟動IntentService,每一個耗時操作都會以工作隊列的形式在IntentService的onHandleIntent回調中執行,并且每次執行一個工作線程。IntentService的本質是:封裝了一個HandlerThread和Handler的異步框架。
Service 作為 Android四大組件之一,應用非常廣泛。和Activity一樣,Service 也有一系列的生命周期回調函數,具體如下圖。
通常,啟動Service有兩種方式,startService和bindService方式。
當我們通過調用了Context的startService方法后,我們便啟動了Service,通過startService方法啟動的Service會一直無限期地運行下去,只有在外部調用Context的stopService或Service內部調用Service的stopSelf方法時,該Service才會停止運行并銷毀。
onCreate: 執行startService方法時,如果Service沒有運行的時候會創建該Service并執行Service的onCreate回調方法;如果Service已經處于運行中,那么執行startService方法不會執行Service的onCreate方法。也就是說如果多次執行了Context的startService方法啟動Service,Service方法的onCreate方法只會在第一次創建Service的時候調用一次,以后均不會再次調用。我們可以在onCreate方法中完成一些Service初始化相關的操作。
onStartCommand: 在執行了startService方法之后,有可能會調用Service的onCreate方法,在這之后一定會執行Service的onStartCommand回調方法。也就是說,如果多次執行了Context的startService方法,那么Service的onStartCommand方法也會相應的多次調用。onStartCommand方法很重要,我們在該方法中根據傳入的Intent參數進行實際的操作,比如會在此處創建一個線程用于下載數據或播放音樂等。
public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) { }
當Android面臨內存匱乏的時候,可能會銷毀掉你當前運行的Service,然后待內存充足的時候可以重新創建Service,Service被Android系統強制銷毀并再次重建的行為依賴于Service中onStartCommand方法的返回值。我們常用的返回值有三種值,START_NOT_STICKY
、START_STICKY
和START_REDELIVER_INTENT
,這三個值都是Service中的靜態常量。
如果返回START_NOT_STICKY,表示當Service運行的進程被Android系統強制殺掉之后,不會重新創建該Service,當然如果在其被殺掉之后一段時間又調用了startService,那么該Service又將被實例化。那什么情境下返回該值比較恰當呢?如果我們某個Service執行的工作被中斷幾次無關緊要或者對Android內存緊張的情況下需要被殺掉且不會立即重新創建這種行為也可接受,那么我們便可將 onStartCommand的返回值設置為START_NOT_STICKY。舉個例子,某個Service需要定時從服務器獲取最新數據:通過一個定時器每隔指定的N分鐘讓定時器啟動Service去獲取服務端的最新數據。當執行到Service的onStartCommand時,在該方法內再規劃一個N分鐘后的定時器用于再次啟動該Service并開辟一個新的線程去執行網絡操作。假設Service在從服務器獲取最新數據的過程中被Android系統強制殺掉,Service不會再重新創建,這也沒關系,因為再過N分鐘定時器就會再次啟動該Service并重新獲取數據。
如果返回START_STICKY,表示Service運行的進程被Android系統強制殺掉之后,Android系統會將該Service依然設置為started狀態(即運行狀態),但是不再保存onStartCommand方法傳入的intent對象,然后Android系統會嘗試再次重新創建該Service,并執行onStartCommand回調方法,但是onStartCommand回調方法的Intent參數為null,也就是onStartCommand方法雖然會執行但是獲取不到intent信息。如果你的Service可以在任意時刻運行或結束都沒什么問題,而且不需要intent信息,那么就可以在onStartCommand方法中返回START_STICKY,比如一個用來播放背景音樂功能的Service就適合返回該值。
如果返回START_REDELIVER_INTENT,表示Service運行的進程被Android系統強制殺掉之后,與返回START_STICKY的情況類似,Android系統會將再次重新創建該Service,并執行onStartCommand回調方法,但是不同的是,Android系統會再次將Service在被殺掉之前最后一次傳入onStartCommand方法中的Intent再次保留下來并再次傳入到重新創建后的Service的onStartCommand方法中,這樣我們就能讀取到intent參數。只要返回START_REDELIVER_INTENT,那么onStartCommand重的intent一定不是null。如果我們的Service需要依賴具體的Intent才能運行(需要從Intent中讀取相關數據信息等),并且在強制銷毀后有必要重新創建運行,那么這樣的Service就適合返回START_REDELIVER_INTENT。
Service中的onBind方法是抽象方法,所以Service類本身就是抽象類,也就是onBind方法是必須重寫的,即使我們用不到。在通過startService使用Service時,我們在重寫onBind方法時,只需要將其返回null即可。onBind方法主要是用于給bindService方法調用Service時才會使用到。
onDestroy: 通過startService方法啟動的Service會無限期運行,只有當調用了Context的stopService或在Service內部調用stopSelf方法時,Service才會停止運行并銷毀,在銷毀的時候會執行Service回調函數。
bindService方式啟動Service主要有以下幾個生命周期函數:
首次創建服務時,系統將調用此方法。如果服務已在運行,則不會調用此方法,該方法只調用一次。
當另一個組件通過調用startService()請求啟動服務時,系統將調用此方法。
當服務不再使用且將被銷毀時,系統將調用此方法。
當另一個組件通過調用bindService()與服務綁定時,系統將調用此方法。
當另一個組件通過調用unbindService()與服務解綁時,系統將調用此方法。
當舊的組件與服務解綁后,另一個新的組件與服務綁定,onUnbind()返回true時,系統將調用此方法。
首先我們需要創建一個xml文件,然后創建與之對應的java文件,通過onCreatView()的返回方法進行關聯,最后我們需要在Activity中進行配置相關參數即在Activity的xml文件中放上fragment的位置。
<fragment android:name="xxx.BlankFragment" android:layout_width="match_parent" android:layout_height="match_parent"> </fragment>
動態創建Fragment主要有以下幾個步驟:
FragmnetPageAdapter在每次切換頁面時,只是將Fragment進行分離,適合頁面較少的Fragment使用以保存一些內存,對系統內存不會多大影響。
FragmentPageStateAdapter在每次切換頁面的時候,是將Fragment進行回收,適合頁面較多的Fragment使用,這樣就不會消耗更多的內存
Activity的生命周期如下圖:
動態加載時,Activity的onCreate()調用完,才開始加載fragment并調用其生命周期方法,所以在第一個生命周期方法onAttach()中便能獲取Activity以及Activity的布局的組件;
1.靜態加載時,Activity的onCreate()調用過程中,fragment也在加載,所以fragment無法獲取到Activity的布局中的組件,但為什么能獲取到Activity呢?
2.原來在fragment調用onAttach()之前其實還調用了一個方法onInflate(),該方法被調用時fragment已經是和Activity相互結合了,所以可以獲取到對方,但是Activity的onCreate()調用還未完成,故無法獲取Activity的組件;
3.Activity的onCreate()調用完成是,fragment會調用onActivityCreated()生命周期方法,因此在這兒開始便能獲取到Activity的布局的組件;
fragment不通過構造函數進行傳值的原因是因為橫屏切換的時候獲取不到值。
Activity向Fragment傳值,要傳的值放到bundle對象里;
在Activity中創建該Fragment的對象fragment,通過調用setArguments()傳遞到fragment中;
在該Fragment中通過調用getArguments()得到bundle對象,就能得到里面的值。
在Activity中調用getFragmentManager()得到fragmentManager,,調用findFragmentByTag(tag)或者通過findFragmentById(id),例如:
FragmentManager fragmentManager = getFragmentManager(); Fragment fragment = fragmentManager.findFragmentByTag(tag);
通過回調的方式,定義一個接口(可以在Fragment類中定義),接口中有一個空的方法,在fragment中需要的時候調用接口的方法,值可以作為參數放在這個方法中,然后讓Activity實現這個接口,必然會重寫這個方法,這樣值就傳到了Activity中
通過findFragmentByTag得到另一個的Fragment的對象,這樣就可以調用另一個的方法了。
通過接口回調的方式。
通過setArguments,getArguments的方式。
一種是add方式來進行show和add,這種方式你切換fragment不會讓fragment重新刷新,只會調用onHiddenChanged(boolean isHidden)。
而用replace方式會使fragment重新刷新,因為add方式是將fragment隱藏了而不是銷毀再創建,replace方式每次都是重新創建。
兩者都可以提交fragment的操作,唯一的不同是第二種方法,允許丟失一些界面的狀態和信息,幾乎所有的開發者都遇到過這樣的錯誤:無法在activity調用了onSaveInstanceState之后再執行commit(),這種異常時可以理解的,界面被系統回收(界面已經不存在),為了在下次打開的時候恢復原來的樣子,系統為我們保存界面的所有狀態,這個時候我們再去修改界面理論上肯定是不允許的,所以為了避免這種異常,要使用第二種方法。
我們經常在使用fragment時,常常會結合著viewpager使用,那么我們就會遇到一個問題,就是初始化fragment的時候,會連同我們寫的網絡請求一起執行,這樣非常消耗性能,最理想的方式是,只有用戶點開或滑動到當前fragment時,才進行請求網絡的操作。因此,我們就產生了懶加載這樣一個說法。
Viewpager配合fragment使用,默認加載前兩個fragment。很容易造成網絡丟包、阻塞等問題。
在Fragment中有一個setUserVisibleHint這個方法,而且這個方法是優于onCreate()方法的,它會通過isVisibleToUser告訴我們當前Fragment我們是否可見,我們可以在可見的時候再進行網絡加載。
從log上看setUserVisibleHint()的調用早于onCreateView,所以如果在setUserVisibleHint()要實現懶加載的話,就必須要確保View以及其他變量都已經初始化結束,避免空指針。
使用步驟:
申明一個變量isPrepare=false,isVisible=false,標明當前頁面是否被創建了
在onViewCreated周期內設置isPrepare=true
在setUserVisibleHint(boolean isVisible)判斷是否顯示,設置isVisible=true
判斷isPrepare和isVisible,都為true開始加載數據,然后恢復isPrepare和isVisible為false,防止重復加載。
關于Android Fragment的懶加載,可以參考下面的鏈接:Fragment的懶加載
用戶從Launcher程序點擊應用圖標可啟動應用的入口Activity,Activity啟動時需要多個進程之間的交互,Android系統中有一個zygote進程專用于孵化Android框架層和應用層程序的進程。還有一個system_server進程,該進程里運行了很多binder service。例如ActivityManagerService,PackageManagerService,WindowManagerService,這些binder service分別運行在不同的線程中,其中ActivityManagerService負責管理Activity棧,應用進程,task。
用戶在Launcher程序里點擊應用圖標時,會通知ActivityManagerService啟動應用的入口Activity,ActivityManagerService發現這個應用還未啟動,則會通知Zygote進程孵化出應用進程,然后在這個dalvik應用進程里執行ActivityThread的main方法。應用進程接下來通知ActivityManagerService應用進程已啟動,ActivityManagerService保存應用進程的一個代理對象,這樣ActivityManagerService可以通過這個代理對象控制應用進程,然后ActivityManagerService通知應用進程創建入口Activity的實例,并執行它的生命周期方法。
Android繪制流程窗口啟動流程分析
4.2、Activity生命周期
Activity處于活動狀態,此時Activity處于棧頂,是可見狀態,可與用戶進行交互。
當Activity失去焦點時,或被一個新的非全屏的Activity,或被一個透明的Activity放置在棧頂時,Activity就轉化為Paused狀態。但我們需要明白,此時Activity只是失去了與用戶交互的能力,其所有的狀態信息及其成員變量都還存在,只有在系統內存緊張的情況下,才有可能被系統回收掉。
當一個Activity被另一個Activity完全覆蓋時,被覆蓋的Activity就會進入Stopped狀態,此時它不再可見,但是跟Paused狀態一樣保持著其所有狀態信息及其成員變量。
當Activity被系統回收掉時,Activity就處于Killed狀態。
Activity會在以上四種形態中相互切換,至于如何切換,這因用戶的操作不同而異。了解了Activity的4種形態后,我們就來聊聊Activity的生命周期。
所謂的典型的生命周期就是在有用戶參與的情況下,Activity經歷從創建,運行,停止,銷毀等正常的生命周期過程。
該方法是在Activity被創建時回調,它是生命周期第一個調用的方法,我們在創建Activity時一般都需要重寫該方法,然后在該方法中做一些初始化的操作,如通過setContentView設置界面布局的資源,初始化所需要的組件信息等。
此方法被回調時表示Activity正在啟動,此時Activity已處于可見狀態,只是還沒有在前臺顯示,因此無法與用戶進行交互。可以簡單理解為Activity已顯示而我們無法看見擺了。
當此方法回調時,則說明Activity已在前臺可見,可與用戶交互了(處于前面所說的Active/Running形態),onResume方法與onStart的相同點是兩者都表示Activity可見,只不過onStart回調時Activity還是后臺無法與用戶交互,而onResume則已顯示在前臺,可與用戶交互。當然從流程圖,我們也可以看出當Activity停止后(onPause方法和onStop方法被調用),重新回到前臺時也會調用onResume方法,因此我們也可以在onResume方法中初始化一些資源,比如重新初始化在onPause或者onStop方法中釋放的資源。
此方法被回調時則表示Activity正在停止(Paused形態),一般情況下onStop方法會緊接著被回調。但通過流程圖我們還可以看到一種情況是onPause方法執行后直接執行了onResume方法,這屬于比較極端的現象了,這可能是用戶操作使當前Activity退居后臺后又迅速地再回到到當前的Activity,此時onResume方法就會被回調。當然,在onPause方法中我們可以做一些數據存儲或者動畫停止或者資源回收的操作,但是不能太耗時,因為這可能會影響到新的Activity的顯示——onPause方法執行完成后,新Activity的onResume方法才會被執行。
一般在onPause方法執行完成直接執行,表示Activity即將停止或者完全被覆蓋(Stopped形態),此時Activity不可見,僅在后臺運行。同樣地,在onStop方法可以做一些資源釋放的操作(不能太耗時)。
表示Activity正在重新啟動,當Activity由不可見變為可見狀態時,該方法被回調。這種情況一般是用戶打開了一個新的Activity時,當前的Activity就會被暫停(onPause和onStop被執行了),接著又回到當前Activity頁面時,onRestart方法就會被回調。
此時Activity正在被銷毀,也是生命周期最后一個執行的方法,一般我們可以在此方法中做一些回收工作和最終的資源釋放。
到這里我們來個小結,當Activity啟動時,依次會調用onCreate(),onStart(),onResume(),而當Activity退居后臺時(不可見,點擊Home或者被新的Activity完全覆蓋),onPause()和onStop()會依次被調用。當Activity重新回到前臺(從桌面回到原Activity或者被覆蓋后又回到原Activity)時,onRestart(),onStart(),onResume()會依次被調用。當Activity退出銷毀時(點擊back鍵),onPause(),onStop(),onDestroy()會依次被調用,到此Activity的整個生命周期方法回調完成。現在我們再回頭看看之前的流程圖,應該是相當清晰了吧。嗯,這就是Activity整個典型的生命周期過程。
Android的Activity、PhoneWindow和DecorView的關系可以用下面的圖表示:
例如,有下面一個視圖,DecorView為整個Window界面的最頂層View,它只有一個子元素LinearLayout。代表整個Window界面,包含通知欄、標題欄、內容顯示欄三塊區域。其中LinearLayout中有兩個FrameLayout子元素。
DecorView是頂級View,本質是一個FrameLayout它包含兩部分,標題欄和內容欄,都是FrameLayout。內容欄id是content,也就是activity中設置setContentView的部分,最終將布局添加到id為content的FrameLayout中。
獲取content:ViewGroup content=findViewById(android.id.content)
獲取設置的View:getChildAt(0).
每個Activity都包含一個Window對象,Window對象通常是由PhoneWindow實現的。
PhoneWindow:將DecorView設置為整個應用窗口的根View,是Window的實現類。它是Android中的最基本的窗口系統,每個Activity均會創建一個PhoneWindow對象,是Activity和整個View系統交互的接口。
DecorView:是頂層視圖,將要顯示的具體內容呈現在PhoneWindow上,DecorView是當前Activity所有View的祖先,它并不會向用戶呈現任何東西。
View的事件分發機制可以使用下圖表示:
如上圖,圖分為3層,從上往下依次是Activity、ViewGroup、View。
當一個點擊事件產生后,它的傳遞過程將遵循如下順序:
Activity -> Window -> View
事件總是會傳遞給Activity,之后Activity再傳遞給Window,最后Window再傳遞給頂級的View,頂級的View在接收到事件后就會按照事件分發機制去分發事件。如果一個View的onTouchEvent返回了FALSE,那么它的父容器的onTouchEvent將會被調用,依次類推,如果所有都不處理這個事件的話,那么Activity將會處理這個事件。
對于ViewGroup的事件分發過程,大概是這樣的:如果頂級的ViewGroup攔截事件即onInterceptTouchEvent返回true的話,則事件會交給ViewGroup處理,如果ViewGroup的onTouchListener被設置的話,則onTouch將會被調用,否則的話onTouchEvent將會被調用,也就是說:兩者都設置的話,onTouch將會屏蔽掉onTouchEvent,在onTouchEvent中,如果設置了onClickerListener的話,那么onClick將會被調用。如果頂級ViewGroup不攔截的話,那么事件將會被傳遞給它所在的點擊事件的子view,這時候子view的dispatchTouchEvent將會被調用
dispatchTouchEvent -> onTouch(setOnTouchListener) -> onTouchEvent -> onClick
onTouch和onTouchEvent的區別
兩者都是在dispatchTouchEvent中調用的,onTouch優先于onTouchEvent,如果onTouch返回true,那么onTouchEvent則不執行,及onClick也不執行。
在xml布局文件中,我們的layout_width和layout_height參數可以不用寫具體的尺寸,而是wrap_content或者是match_parent。這兩個設置并沒有指定真正的大小,可是我們繪制到屏幕上的View必須是要有具體的寬高的,正是因為這個原因,我們必須自己去處理和設置尺寸。當然了,View類給了默認的處理,但是如果View類的默認處理不滿足我們的要求,我們就得重寫onMeasure函數啦~。
onMeasure函數是一個int整數,里面放了測量模式和尺寸大小。int型數據占用32個bit,而google實現的是,將int數據的前面2個bit用于區分不同的布局模式,后面30個bit存放的是尺寸的數據。
onMeasure函數的使用如下圖:
MeasureSpec有三種測量模式:
match_parent—>EXACTLY。怎么理解呢?match_parent就是要利用父View給我們提供的所有剩余空間,而父View剩余空間是確定的,也就是這個測量模式的整數里面存放的尺寸。
wrap_content—>AT_MOST。怎么理解:就是我們想要將大小設置為包裹我們的view內容,那么尺寸大小就是父View給我們作為參考的尺寸,只要不超過這個尺寸就可以啦,具體尺寸就根據我們的需求去設定。
固定尺寸(如100dp)—>EXACTLY。用戶自己指定了尺寸大小,我們就不用再去干涉了,當然是以指定的大小為主啦。
自定義ViewGroup可就沒那么簡單啦~,因為它不僅要管好自己的,還要兼顧它的子View。我們都知道ViewGroup是個View容器,它裝納child View并且負責把child View放入指定的位置。
首先,我們得知道各個子View的大小吧,只有先知道子View的大小,我們才知道當前的ViewGroup該設置為多大去容納它們。
根據子View的大小,以及我們的ViewGroup要實現的功能,決定出ViewGroup的大小
ViewGroup和子View的大小算出來了之后,接下來就是去擺放了吧,具體怎么去擺放呢?這得根據你定制的需求去擺放了,比如,你想讓子View按照垂直順序一個挨著一個放,或者是按照先后順序一個疊一個去放,這是你自己決定的。
已經知道怎么去擺放還不行啊,決定了怎么擺放就是相當于把已有的空間”分割”成大大小小的空間,每個空間對應一個子View,我們接下來就是把子View對號入座了,把它們放進它們該放的地方去。
自定義ViewGroup可以參考:Android自定義ViewGroup
Android的包文件APK分為兩個部分:代碼和資源,所以打包方面也分為資源打包和代碼打包兩個方面,這篇文章就來分析資源和代碼的編譯打包原理。
具體說來:
Android apk的安裝過程主要氛圍以下幾步:
可以使用下面的圖表示:
概念:Retrofit是一個基于RESTful的HTTP網絡請求框架的封裝,其中網絡請求的本質是由OKHttp完成的,而Retrofit僅僅負責網絡請求接口的封裝。
原理:App應用程序通過Retrofit請求網絡,實際上是使用Retrofit接口層封裝請求參數,Header、URL等信息,之后由OKHttp完成后續的請求,在服務器返回數據之后,OKHttp將原始的結果交給Retrofit,最后根據用戶的需求對結果進行解析。
1.在retrofit中通過一個接口作為http請求的api接口
public interface NetApi { @GET("repos/{owner}/{repo}/contributors") Call<ResponseBody> contributorsBySimpleGetCall(@Path("owner") String owner, @Path("repo") String repo); }
2.創建一個Retrofit實例
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build();
3.調用api接口
NetApi repo = retrofit.create(NetApi.class); //第三步:調用網絡請求的接口獲取網絡請求 retrofit2.Call<ResponseBody> call = repo.contributorsBySimpleGetCall("username", "path"); call.enqueue(new Callback<ResponseBody>() { //進行異步請求 @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { //進行異步操作 } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { //執行錯誤回調方法 } });
retrofit執行的原理如下:
1.首先,通過method把它轉換成ServiceMethod。
2.然后,通過serviceMethod,args獲取到okHttpCall對象。
3.最后,再把okHttpCall進一步封裝并返回Call對象。
首先,創建retrofit對象的方法如下:
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build();
在創建retrofit對象的時候用到了build()方法,該方法的實現如下:
public Retrofit build() { if (baseUrl == null) { throw new IllegalStateException("Base URL required."); } okhttp3.Call.Factory callFactory = this.callFactory; if (callFactory == null) { callFactory = new OkHttpClient(); //設置kHttpClient } Executor callbackExecutor = this.callbackExecutor; if (callbackExecutor == null) { callbackExecutor = platform.defaultCallbackExecutor(); //設置默認回調執行器 } // Make a defensive copy of the adapters and add the default Call adapter. List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories); adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); // Make a defensive copy of the converters. List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories); return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories, callbackExecutor, validateEagerly); //返回新建的Retrofit對象 }
該方法返回了一個Retrofit對象,通過retrofit對象創建網絡請求的接口的方式如下:
NetApi repo = retrofit.create(NetApi.class);
retrofit對象的create()方法的實現如下:‘
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); //直接調用該方法 } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); //通過平臺對象調用該方法 } ServiceMethod serviceMethod = loadServiceMethod(method); //獲取ServiceMethod對象 OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); //傳入參數生成okHttpCall對象 return serviceMethod.callAdapter.adapt(okHttpCall); //執行okHttpCall } }); }
Picasso:120K
Glide:475K
Fresco:3.4M
Android-Universal-Image-Loader:162K
圖片函數庫的選擇需要根據APP的具體情況而定,對于嚴重依賴圖片緩存的APP,例如壁紙類,圖片社交類APP來說,可以選擇最專業的Fresco。對于一般的APP,選擇Fresco會顯得比較重,畢竟Fresco3.4M的體量擺在這。根據APP對圖片的顯示和緩存的需求從低到高,我們可以對以上函數庫做一個排序。
Picasso < Android-Universal-Image-Loader < Glide < Fresco
Picasso :和Square的網絡庫一起能發揮最大作用,因為Picasso可以選擇將網絡請求的緩存部分交給了okhttp實現。
Glide:模仿了Picasso的API,而且在他的基礎上加了很多的擴展(比如gif等支持),Glide默認的Bitmap格式是RGB_565,比 Picasso默認的ARGB_8888格式的內存開銷要小一半;Picasso緩存的是全尺寸的(只緩存一種),而Glide緩存的是跟ImageView尺寸相同的(即5656和128128是兩個緩存) 。
FB的圖片加載框架Fresco:最大的優勢在于5.0以下(最低2.3)的bitmap加載。在5.0以下系統,Fresco將圖片放到一個特別的內存區域(Ashmem區)。當然,在圖片不顯示的時候,占用的內存會自動被釋放。這會使得APP更加流暢,減少因圖片內存占用而引發的OOM。為什么說是5.0以下,因為在5.0以后系統默認就是存儲在Ashmem區了。
Picasso所能實現的功能,Glide都能做,無非是所需的設置不同。但是Picasso體積比起Glide小太多如果項目中網絡請求本身用的就是okhttp或者retrofit(本質還是okhttp),那么建議用Picasso,體積會小很多(Square全家桶的干活)。Glide的好處是大型的圖片流,比如gif、Video,如果你們是做美拍、愛拍這種視頻類應用,建議使用。
Fresco在5.0以下的內存優化非常好,代價就是體積也非常的大,按體積算Fresco>Glide>Picasso
不過在使用起來也有些不便(小建議:他只能用內置的一個ImageView來實現這些功能,用起來比較麻煩,我們通常是根據Fresco自己改改,直接使用他的Bitmap層)
參考鏈接:https://www.cnblogs.com/kunpengit/p/4001680.html
Gson是目前功能最全的Json解析神器,Gson當初是為因應Google公司內部需求而由Google自行研發而來,但自從在2008年五月公開發布第一版后已被許多公司或用戶應用。Gson的應用主要為toJson與fromJson兩個轉換函數,無依賴,不需要例外額外的jar,能夠直接跑在JDK上。而在使用這種對象轉換之前需先創建好對象的類型以及其成員才能成功的將JSON字符串成功轉換成相對應的對象。類里面只要有get和set方法,Gson完全可以將復雜類型的json到bean或bean到json的轉換,是JSON解析的神器。Gson在功能上面無可挑剔,但是性能上面比FastJson有所差距。
Fastjson是一個Java語言編寫的高性能的JSON處理器,由阿里巴巴公司開發。
無依賴,不需要例外額外的jar,能夠直接跑在JDK上。FastJson在復雜類型的Bean轉換Json上會出現一些問題,可能會出現引用的類型,導致Json轉換出錯,需要制定引用。FastJson采用獨創的算法,將parse的速度提升到極致,超過所有json庫。
綜上Json技術的比較,在項目選型的時候可以使用Google的Gson和阿里巴巴的FastJson兩種并行使用,如果只是功能要求,沒有性能要求,可以使用google的Gson,如果有性能上面的要求可以使用Gson將bean轉換json確保數據的正確,使用FastJson將Json轉換Bean
參考鏈接- Android組件化方案
組件化:是將一個APP分成多個module,每個module都是一個組件,也可以是一個基礎庫供組件依賴,開發中可以單獨調試部分組件,組件中不需要相互依賴但是可以相互調用,最終發布的時候所有組件以lib的形式被主APP工程依賴打包成一個apk。
組件化后的每一個業務的module都可以是一個單獨的APP(isModuleRun=false), release 包的時候各個業務module作為lib依賴,這里完全由一個變量控制,在根項目 gradle.properties里面isModuleRun=true。isModuleRun狀態不同,加載application和AndroidManifest都不一樣,以此來區分是獨立的APK還是lib。
當我們創建了多個Module的時候,如何解決相同資源文件名合并的沖突,業務Module和BaseModule資源文件名稱重復會產生沖突,解決方案在于:
每個 module 都有 app_name,為了不讓資源名重名,在每個組件的 build.gradle 中增加 resourcePrefix “xxx_強行檢查資源名稱前綴。固定每個組件的資源前綴。但是 resourcePrefix 這個值只能限定 xml 里面的資源,并不能限定圖片資源。
多個Module之間如何引用一些共同的library以及工具類
組件化之后,Module之間是相互隔離的,如何進行UI跳轉以及方法調用,具體可以使用阿里巴巴ARouter或者美團的WMRouter等路由框架。
各業務Module之前不需要任何依賴可以通過路由跳轉,完美解決業務之間耦合。
我們知道組件之間是有聯系的,所以在單獨調試的時候如何拿到其它的Module傳遞過來的參數
當組件單獨運行的時候,每個Module自成一個APK,那么就意味著會有多個Application,很顯然我們不愿意重復寫這么多代碼,所以我們只需要定義一個BaseApplication即可,其它的Application直接繼承此BaseApplication就OK了,BaseApplication里面還可定義公用的參數。
關于如何進行組件化,可以參考:安居客Android項目架構演進
參考鏈接- 插件化入門
提到插件化,就不得不提起方法數超過65535的問題,我們可以通過Dex分包來解決,同時也可以通過使用插件化開發來解決。插件化的概念就是由宿主APP去加載以及運行插件APP。
在一個大的項目里面,為了明確的分工,往往不同的團隊負責不同的插件APP,這樣分工更加明確。各個模塊封裝成不同的插件APK,不同模塊可以單獨編譯,提高了開發效率。
解決了上述的方法數超過限制的問題。可以通過上線新的插件來解決線上的BUG,達到“熱修復”的效果。
減小了宿主APK的體積。
插件化開發的APP不能在Google Play上線,也就是沒有海外市場。
含義:手機對角線的物理尺寸 單位:英寸(inch),1英寸=2.54cm
Android手機常見的尺寸有5寸、5.5寸、6寸,6.5寸等等
含義:手機在橫向、縱向上的像素點數總和
一般描述成屏幕的”寬x高”=AxB 含義:屏幕在橫向方向(寬度)上有A個像素點,在縱向方向
(高)有B個像素點 例子:1080x1920,即寬度方向上有1080個像素點,在高度方向上有1920個像素點
單位:px(pixel),1px=1像素點
UI設計師的設計圖會以px作為統一的計量單位
Android手機常見的分辨率:320x480、480x800、720x1280、1080x1920
含義:每英寸的像素點數 單位:dpi(dots per ich)
假設設備內每英寸有160個像素,那么該設備的屏幕像素密度=160dpi
1.支持各種屏幕尺寸: 使用wrap_content, match_parent, weight.要確保布局的靈活性并適應各種尺寸的屏幕,應使用 “wrap_content”、“match_parent” 控制某些視圖組件的寬度和高度。
2.使用相對布局,禁用絕對布局。
3.使用LinearLayout的weight屬性
假如我們的寬度不是0dp(wrap_content和0dp的效果相同),則是match_parent呢?
android:layout_weight的真實含義是:如果View設置了該屬性并且有效,那么該 View的寬度等于原有寬度(android:layout_width)加上剩余空間的占比。
從這個角度我們來解釋一下上面的現象。在上面的代碼中,我們設置每個Button的寬度都是match_parent,假設屏幕寬度為L,那么每個Button的寬度也應該都為L,剩余寬度就等于L-(L+L)= -L。
Button1的weight=1,剩余寬度占比為1/(1+2)= 1/3,所以最終寬度為L+1/3*(-L)=2/3L,Button2的計算類似,最終寬度為L+2/3(-L)=1/3L。
4.使用.9圖片
參考鏈接:今日頭條屏幕適配方案終極版
參考鏈接:Android 性能監測工具,優化內存、卡頓、耗電、APK大小的方法
Android的性能優化,主要是從以下幾個方面進行優化的:
穩定(內存溢出、崩潰)
流暢(卡頓)
耗損(耗電、流量)
安裝包(APK瘦身)
影響穩定性的原因很多,比如內存使用不合理、代碼異常場景考慮不周全、代碼邏輯不合理等,都會對應用的穩定性造成影響。其中最常見的兩個場景是:Crash 和 ANR,這兩個錯誤將會使得程序無法使用。所以做好Crash全局監控,處理閃退同時把崩潰信息、異常信息收集記錄起來,以便后續分析;合理使用主線程處理業務,不要在主線程中做耗時操作,防止ANR程序無響應發生。
它是Android Studio自帶的一個內存監視工具,它可以很好地幫助我們進行內存實時分析。通過點擊Android Studio右下角的Memory Monitor標簽,打開工具可以看見較淺藍色代表free的內存,而深色的部分代表使用的內存從內存變換的走勢圖變換,可以判斷關于內存的使用狀態,例如當內存持續增高時,可能發生內存泄漏;當內存突然減少時,可能發生GC等,如下圖所示。
LeakCanary工具:
LeakCanary是Square公司基于MAT開發的一款監控Android內存泄漏的開源框架。其工作的原理是:
監測機制利用了Java的WeakReference和ReferenceQueue,通過將Activity包裝到WeakReference中,被WeakReference包裝過的Activity對象如果被回收,該WeakReference引用會被放到ReferenceQueue中,通過監測ReferenceQueue里面的內容就能檢查到Activity是否能夠被回收(在ReferenceQueue中說明可以被回收,不存在泄漏;否則,可能存在泄漏,LeakCanary是執行一遍GC,若還未在ReferenceQueue中,就會認定為泄漏)。
如果Activity被認定為泄露了,就抓取內存dump文件(Debug.dumpHprofData);之后通過HeapAnalyzerService.runAnalysis進行分析內存文件分析;接著通過HeapAnalyzer (checkForLeak—findLeakingReference—findLeakTrace)來進行內存泄漏分析。最后通過DisplayLeakService進行內存泄漏的展示。
Android Lint Tool 是Android Sutido種集成的一個Android代碼提示工具,它可以給你布局、代碼提供非常強大的幫助。硬編碼會提示以級別警告,例如:在布局文件中寫了三層冗余的LinearLayout布局、直接在TextView中寫要顯示的文字、字體大小使用dp而不是sp為單位,就會在編輯器右邊看到提示。
卡頓的場景通常是發生在用戶交互體驗最直接的方面。影響卡頓的兩大因素,分別是界面繪制和數據處理。
界面繪制:主要原因是繪制的層級深、頁面復雜、刷新不合理,由于這些原因導致卡頓的場景更多出現在 UI 和啟動后的初始界面以及跳轉到頁面的繪制上。
數據處理:導致這種卡頓場景的原因是數據處理量太大,一般分為三種情況,一是數據在處理 UI 線程,二是數據處理占用 CPU 高,導致主線程拿不到時間片,三是內存增加導致 GC 頻繁,從而引起卡頓。
在Android種系統對View進行測量、布局和繪制時,都是通過對View數的遍歷來進行操作的。如果一個View數的高度太高就會嚴重影響測量、布局和繪制的速度。Google也在其API文檔中建議View高度不宜哦過10層。現在版本種Google使用RelativeLayout替代LineraLayout作為默認根布局,目的就是降低LineraLayout嵌套產生布局樹的高度,從而提高UI渲染的效率。
布局復用,使用標簽重用layout;
提高顯示速度,使用延遲View加載;
減少層級,使用標簽替換父級布局;
注意使用wrap_content,會增加measure計算成本;
刪除控件中無用屬性;
過度繪制是指在屏幕上的某個像素在同一幀的時間內被繪制了多次。在多層次重疊的 UI 結構中,如果不可見的 UI 也在做繪制的操作,就會導致某些像素區域被繪制了多次,從而浪費了多余的 CPU 以及 GPU 資源。如何避免過度繪制?
布局上的優化。移除 XML 中非必須的背景,移除 Window 默認的背景、按需顯示占位背景圖片
自定義View優化。使用 canvas.clipRect() 幫助系統識別那些可見的區域,只有在這個區域內才會被繪制。
應用一般都有閃屏頁SplashActivity,優化閃屏頁的 UI 布局,可以通過 Profile GPU Rendering 檢測丟幀情況。
在 Android5.0 以前,關于應用電量消耗的測試即麻煩又不準確,而5.0 之后Google專門引入了一個獲取設備上電量消耗信息的API—— Battery Historian。Battery Historian 是一款由 Google 提供的 Android 系統電量分析工具,直觀地展示出手機的電量消耗過程,通過輸入電量分析文件,顯示消耗情況。
最后提供一些可供參考耗電優化的方法:
浮點運算:計算機里整數和小數形式就是按普通格式進行存儲,例如1024、3.1415926等等,這個沒什么特點,但是這樣的數精度不高,表達也不夠全面,為了能夠有一種數的通用表示法,就發明了浮點數。浮點數的表示形式有點像科學計數法(.×10***),它的表示形式是0.*****×10,在計算機中的形式為 .*** e ±**),其中前面的星號代表定點小數,也就是整數部分為0的純小數,后面的指數部分是定點整數。利用這樣的形式就能表示出任意一個整數和小數,例如1024就能表示成0.1024×10^4,也就是 .1024e+004,3.1415926就能表示成0.31415926×10^1,也就是 .31415926e+001,這就是浮點數。浮點數進行的運算就是浮點運算。浮點運算比常規運算更復雜,因此計算機進行浮點運算速度要比進行常規運算慢得多。
Wake Lock是一種鎖的機制,主要是相對系統的休眠而言的,,只要有人拿著這個鎖,系統就無法進入休眠意思就是我的程序給CPU加了這個鎖那系統就不會休眠了,這樣做的目的是為了全力配合我們程序的運行。有的情況如果不這么做就會出現一些問題,比如微信等及時通訊的心跳包會在熄屏不久后停止網絡訪問等問題。所以微信里面是有大量使用到了Wake_Lock鎖。系統為了節省電量,CPU在沒有任務忙的時候就會自動進入休眠。有任務需要喚醒CPU高效執行的時候,就會給CPU加Wake_Lock鎖。大家經常犯的錯誤,我們很容易去喚醒CPU來工作,但是很容易忘記釋放Wake_Lock。
在Android 5.0 API 21 中,google提供了一個叫做JobScheduler API的組件,來處理當某個時間點或者當滿足某個特定的條件時執行一個任務的場景,例如當用戶在夜間休息時或設備接通電源適配器連接WiFi啟動下載更新的任務。這樣可以在減少資源消耗的同時提升應用的效率。
assets文件夾。存放一些配置文件、資源文件,assets不會自動生成對應的 ID,而是通過 AssetManager 類的接口獲取。
res。res 是 resource 的縮寫,這個目錄存放資源文件,會自動生成對應的 ID 并映射到 .R 文件中,訪問直接使用資源 ID。
META-INF。保存應用的簽名信息,簽名信息可以驗證 APK 文件的完整性。
AndroidManifest.xml。這個文件用來描述 Android 應用的配置信息,一些組件的注冊信息、可使用權限等。
classes.dex。Dalvik 字節碼程序,讓 Dalvik 虛擬機可執行,一般情況下,Android 應用在打包時通過 Android SDK 中的 dx 工具將 Java 字節碼轉換為 Dalvik 字節碼。
resources.arsc。記錄著資源文件和資源 ID 之間的映射關系,用來根據資源 ID 尋找資源。
代碼混淆。使用IDE 自帶的 proGuard 代碼混淆器工具 ,它包括壓縮、優化、混淆等功能。
資源優化。比如使用 Android Lint 刪除冗余資源,資源文件最少化等。
圖片優化。比如利用 PNG優化工具 對圖片做壓縮處理。推薦目前最先進的壓縮工具Googlek開源庫zopfli。如果應用在0版本以上,推薦使用 WebP圖片格式。
避免重復或無用功能的第三方庫。例如,百度地圖接入基礎地圖即可、訊飛語音無需接入離線、圖片庫Glide\Picasso等。
插件化開發。比如功能模塊放在服務器上,按需下載,可以減少安裝包大小。
可以使用微信開源資源文件混淆工具——AndResGuard。一般可以壓縮apk的1M左右大。
參考鏈接:https://www.jianshu.com/p/03c0fd3fc245
冷啟動
在啟動應用時,系統中沒有該應用的進程,這時系統會創建一個新的進程分配給該應用;
熱啟動
在啟動應用時,系統中已有該應用的進程(例:按back鍵、home鍵,應用雖然會退出,但是該應用的進程還是保留在后臺);
區別
冷啟動:系統沒有該應用的進程,需要創建一個新的進程分配給應用,所以會先創建和初始化Application類,再創建和初始化MainActivity類(包括一系列的測量、布局、繪制),最后顯示在界面上。 熱啟動: 從已有的進程中來啟動,不會創建和初始化Application類,直接創建和初始化MainActivity類(包括一系列的測量、布局、繪制),最后顯示在界面上。
冷啟動流程
Zygote進程中fork創建出一個新的進程; 創建和初始化Application類、創建MainActivity; inflate布局、當onCreate/onStart/onResume方法都走完; contentView的measure/layout/draw顯示在界面上。
冷啟動優化
減少在Application和第一個Activity的onCreate()方法的工作量; 不要讓Application參與業務的操作; 不要在Application進行耗時操作; 不要以靜態變量的方式在Application中保存數據; 減少布局的復雜性和深度;
MVP架構由MVC發展而來。在MVP中,M代表Model,V代表View,P代表Presenter。
模型層(Model):主要是獲取數據功能,業務邏輯和實體模型。
視圖層(View):對應于Activity或Fragment,負責視圖的部分展示和業務邏輯用戶交互
控制層(Presenter):負責完成View層與Model層間的交互,通過P層來獲取M層中數據后返回給V層,使得V層與M層間沒有耦合。
在MVP中 ,Presenter層完全將View層和Model層進行了分離,把主要程序邏輯放在Presenter層實現,Presenter與具體的View層(Activity)是沒有直接的關聯,是通過定義接口來進行交互的,從而使得當View層(Activity)發生改變時,Persenter依然可以保持不變。View層接口類只應該只有set/get方法,及一些界面顯示內容和用戶輸入,除此之外不應該有多余的內容。絕不允許View層直接訪問Model層,這是與MVC最大區別之處,也是MVP核心優點。
Android4.4及以前使用的都是Dalvik虛擬機,我們知道Apk在打包的過程中會先將java等源碼通過javac編譯成.class文件,但是我們的Dalvik虛擬機只會執行.dex文件,這個時候dx會將.class文件轉換成Dalvik虛擬機執行的.dex文件。Dalvik虛擬機在啟動的時候會先將.dex文件轉換成快速運行的機器碼,又因為65535這個問題,導致我們在應用冷啟動的時候有一個合包的過程,最后導致的一個結果就是我們的app啟動慢,這就是Dalvik虛擬機的JIT特性(Just In Time)。
ART虛擬機是在Android5.0才開始使用的Android虛擬機,ART虛擬機必須要兼容Dalvik虛擬機的特性,但是ART有一個很好的特性AOT(ahead of time),這個特性就是我們在安裝APK的時候就將dex直接處理成可直接供ART虛擬機使用的機器碼,ART虛擬機將.dex文件轉換成可直接運行的.oat文件,ART虛擬機天生支持多dex,所以也不會有一個合包的過程,所以ART虛擬機會很大的提升APP冷啟動速度。
ART優點:
加快APP冷啟動速度
提升GC速度
提供功能全面的Debug特性
ART缺點:
APP安裝速度慢,因為在APK安裝的時候要生成可運行.oat文件
APK占用空間大,因為在APK安裝的時候要生成可運行.oat文件
arm處理器
關于ART更詳細的介紹,可以參考Android ART詳解
熟悉Android性能分析工具、UI卡頓、APP啟動、包瘦身和內存性能優化
熟悉Android APP架構設計,模塊化、組件化、插件化開發
熟練掌握Java、設計模式、網絡、多線程技術
jvm將.class類文件信息加載到內存并解析成對應的class對象的過程,注意:jvm并不是一開始就把所有的類加載進內存中,只是在第一次遇到某個需要運行的類才會加載,并且只加載一次
主要分為三部分:1、加載,2、鏈接(1.驗證,2.準備,3.解析),3、初始化
類加載器包括 BootClassLoader、ExtClassLoader、APPClassLoader
驗證:(驗證class文件的字節流是否符合jvm規范)
準備:為類變量分配內存,并且進行賦初值
解析:將常量池里面的符號引用(變量名)替換成直接引用(內存地址)過程,在解析階段,jvm會把所有的類名、方法名、字段名、這些符號引用替換成具體的內存地址或者偏移量。
主要對類變量進行初始化,執行類構造器的過程,換句話說,只對static修試的變量或者語句進行初始化。
范例:Person person = new Person();為例進行說明。
Java編程思想中的類的初始化過程主要有以下幾點:
StringBuffer里面的很多方法添加了synchronized關鍵字,是可以表征線程安全的,所以多線程情況下使用它。
執行速度:
StringBuilder > StringBuffer > String
StringBuilder犧牲了性能來換取速度的,這兩個是可以直接在原對象上面進行修改,省去了創建新對象和回收老對象的過程,而String是字符串常量(final)修試,另外兩個是字符串變量,常量對象一旦創建就不可以修改,變量是可以進行修改的,所以對于String字符串的操作包含下面三個步驟:
Java對象實例化過程中,主要使用到虛擬機棧、Java堆和方法區。Java文件經過編譯之后首先會被加載到jvm方法區中,jvm方法區中很重的一個部分是運行時常量池,用以存儲class文件類的版本、字段、方法、接口等描述信息和編譯期間的常量和靜態常量。
類加載器classLoader,在JVM啟動時或者類運行時將需要的.class文件加載到內存中。
執行引擎,負責執行class文件中包含的字節碼指令。
本地方法接口,主要是調用C/C++實現的本地方法及返回結果。
內存區域(運行時數據區),是在JVM運行的時候操作所分配的內存區,
主要分為以下五個部分,如下圖:
https://www.jianshu.com/nb/12554212
垃圾收集器一般完成兩件事
通常,Java對象的引用可以分為4類:強引用、軟引用、弱引用和虛引用。
強引用:通常可以認為是通過new出來的對象,即使內存不足,GC進行垃圾收集的時候也不會主動回收。
Object obj = new Object();
軟引用:在內存不足的時候,GC進行垃圾收集的時候會被GC回收。
Object obj = new Object(); SoftReference<Object> softReference = new SoftReference<>(obj);
弱引用:無論內存是否充足,GC進行垃圾收集的時候都會回收。
Object obj = new Object(); WeakReference<Object> weakReference = new WeakReference<>(obj);
虛引用:和弱引用類似,主要區別在于虛引用必須和引用隊列一起使用。
Object obj = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); PhantomReference<Object> phantomReference = new PhantomReference<>(obj, referenceQueue);
引用隊列:如果軟引用和弱引用被GC回收,JVM就會把這個引用加到引用隊列里,如果是虛引用,在回收前就會被加到引用隊列里。
引用計數法:給每個對象添加引用計數器,每個地方引用它,計數器就+1,失效時-1。如果兩個對象互相引用時,就導致無法回收。
可達性分析算法:以根集對象為起始點進行搜索,如果對象不可達的話就是垃圾對象。根集(Java棧中引用的對象、方法區中常量池中引用的對象、本地方法中引用的對象等。JVM在垃圾回收的時候,會檢查堆中所有對象是否被這些根集對象引用,不能夠被引用的對象就會被垃圾回收器回收。)
常見的垃圾回收算法有:
標記:首先標記所有需要回收的對象,在標記完成之后統計回收所有被標記的對象,它的標記過程即為上面的可達性分析算法。
清除:清除所有被標記的對象
缺點:
效率不足,標記和清除效率都不高
空間問題,標記清除之后會產生大量不連續的內存碎片,導致大對象分配無法找到足夠的空間,提前進行垃圾回收。
復制回收算法
將可用的內存按容量劃分為大小相等的2塊,每次只用一塊,當這一塊的內存用完了,就將存活的對象復制到另外一塊上面,然后把已使用過的內存空間一次清理掉。
缺點:
將內存縮小了原本的一般,代價比較高
大部分對象是“朝生夕滅”的,所以不必按照1:1的比例劃分。
現在商業虛擬機采用這種算法回收新生代,但不是按1:1的比例,而是將內存區域劃分為eden 空間、from 空間、to 空間 3 個部分。
其中 from 空間和 to 空間可以視為用于復制的兩塊大小相同、地位相等,且可進行角色互換的空間塊。from 和 to 空間也稱為 survivor 空間,即幸存者空間,用于存放未被回收的對象。
在垃圾回收時,eden 空間中的存活對象會被復制到未使用的 survivor 空間中 (假設是 to),正在使用的 survivor 空間 (假設是 from) 中的年輕對象也會被復制到 to 空間中 (大對象,或者老年對象會直接進入老年帶,如果 to 空間已滿,則對象也會直接進入老年代)。此時,eden 空間和 from 空間中的剩余對象就是垃圾對象,可以直接清空,to 空間則存放此次回收后的存活對象。這種改進的復制算法既保證了空間的連續性,又避免了大量的內存空間浪費。
在老年代的對象大都是存活對象,復制算法在對象存活率教高的時候,效率就會變得比較低。根據老年代的特點,有人提出了“標記-壓縮算法(Mark-Compact)”
標記過程與標記-清除的標記一樣,但后續不是對可回收對象進行清理,而是讓所有的對象都向一端移動,然后直接清理掉端邊界以外的內存。
這種方法既避免了碎片的產生,又不需要兩塊相同的內存空間,因此,其性價比比較高。
根據對象存活的周期不同將內存劃分為幾塊,一般是把Java堆分為老年代和新生代,這樣根據各個年代的特點采用適當的收集算法。
新生代每次收集都有大量對象死去,只有少量存活,那就選用復制算法,復制的對象數較少就可完成收集。
老年代對象存活率高,使用標記-壓縮算法,以提高垃圾回收效率。
程序在啟動的時候,并不會一次性加載程序所要用的所有class文件,而是根據程序的需要,通過Java的類加載機制(ClassLoader)來動態加載某個class文件到內存當中的,從而只有class文件被載入到了內存之后,才能被其它class所引用。所以ClassLoader就是用來動態加載class文件到內存當中用的。
每個ClassLoader實例都有一個父類加載器的引用(不是繼承關系,是一個包含的關系),虛擬機內置的類加載器(Bootstrap ClassLoader)本身沒有父類加載器,但是可以用做其他ClassLoader實例的父類加載器。
當一個ClassLoader 實例需要加載某個類時,它會試圖在親自搜索這個類之前先把這個任務委托給它的父類加載器,這個過程是由上而下依次檢查的,首先由頂層的類加載器Bootstrap CLassLoader進行加載,如果沒有加載到,則把任務轉交給Extension CLassLoader視圖加載,如果也沒有找到,則轉交給AppCLassLoader進行加載,還是沒有的話,則交給委托的發起者,由它到指定的文件系統或者網絡等URL中進行加載類。還沒有找到的話,則會拋出CLassNotFoundException異常。否則將這個類生成一個類的定義,并將它加載到內存中,最后返回這個類在內存中的Class實例對象。
JVM在判斷兩個class是否相同時,不僅要判斷兩個類名是否相同,還要判斷是否是同一個類加載器加載的。
避免重復加載,父類已經加載了,則子CLassLoader沒有必要再次加載。
考慮安全因素,假設自定義一個String類,除非改變JDK中CLassLoader的搜索類的默認算法,否則用戶自定義的CLassLoader如法加載一個自己寫的String類,因為String類在啟動時就被引導類加載器Bootstrap CLassLoader加載了。
關于Android的雙親委托機制,可以參考android classloader雙親委托模式
Java集合類主要由兩個接口派生出:Collection和Map,這兩個接口是Java集合的根接口。
Collection接口是集合類的根接口,Java中沒有提供這個接口的直接的實現類。但是卻讓其被繼承產生了兩個接口,就是 Set和List。Set中不能包含重復的元素。List是一個有序的集合,可以包含重復的元素,提供了按索引訪問的方式。
Map是Java.util包中的另一個接口,它和Collection接口沒有關系,是相互獨立的,但是都屬于集合類的一部分。Map包含了key-value對。Map不能包含重復的key,但是可以包含相同的value。
List,Set都是繼承自Collection接口,Map則不是;
List特點:元素有放入順序,元素可重復; Set特點:元素無放入順序,元素不可重復,重復元素會覆蓋掉,(注意:元素雖然無放入順序,但是元素在set中的位置是有該元素的HashCode決定的,其位置其實是固定的,加入Set 的Object必須定義equals()方法;
LinkedList、ArrayList、HashSet是非線程安全的,Vector是線程安全的;
HashMap是非線程安全的,HashTable是線程安全的;
Vector是多線程安全的,線程安全就是說多線程訪問同一代碼,不會產生不確定的結果。而ArrayList不是,這個可以從源碼中看出,Vector類中的方法很多有synchronized進行修飾,這樣就導致了Vector在效率上無法與ArrayList相比;
兩個都是采用的線性連續空間存儲元素,但是當空間不足的時候,兩個類的增加方式是不同。
Vector可以設置增長因子,而ArrayList不可以。
Vector是一種老的動態數組,是線程同步的,效率很低,一般不贊成使用。
HashSet底層通過HashMap來實現的,在往HashSet中添加元素是
public boolean add(E e) { return map.put(e, PRESENT)==null; } // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object();
在HashMap中進行查找是否存在這個key,value始終是一樣的,主要有以下幾種情況:
HashMap 非線程安全,基于哈希表(散列表)實現。使用HashMap要求添加的鍵類明確定義了hashCode()和equals()[可以重寫hashCode()和equals()],為了優化HashMap空間的使用,您可以調優初始容量和負載因子。其中散列表的沖突處理主要分兩種,一種是開放定址法,另一種是鏈表法。HashMap的實現中采用的是鏈表法。
TreeMap:非線程安全基于紅黑樹實現,TreeMap沒有調優選項,因為該樹總處于平衡狀態
當數值范圍為-128~127時:如果兩個new出來Integer對象,即使值相同,通過“”比較結果為false,但兩個對象直接賦值,則通過“”比較結果為“true,這一點與String非常相似。
當數值不在-128~127時,無論通過哪種方式,即使兩個對象的值相等,通過“”比較,其結果為false;
當一個Integer對象直接與一個int基本數據類型通過“”比較,其結果與第一點相同;
Integer對象的hash值為數值本身;
@Override public int hashCode() { return Integer.hashCode(value); }
在Integer類中有一個靜態內部類IntegerCache,在IntegerCache類中有一個Integer數組,用以緩存當數值范圍為-128~127時的Integer對象。
泛型是Java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。這種參數類型可以用在類、接口和方法的創建中,分別稱為泛型類、泛型接口、泛型方法。 Java語言引入泛型的好處是安全簡單。
泛型的好處是在編譯的時候檢查類型安全,并且所有的強制轉換都是自動和隱式的,提高代碼的重用率。
它提供了編譯期的類型安全,確保你只能把正確類型的對象放入 集合中,避免了在運行時出現ClassCastException。
使用Java的泛型時應注意以下幾點:
Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會在編譯器在編譯的時候去掉。這個過程就稱為類型擦除。
泛型是通過類型擦除來實現的,編譯器在編譯時擦除了所有類型相關的信息,所以在運行時不存在任何類型相關的信息。例如 List在運行時僅用一個List來表示。這樣做的目的,是確保能和Java 5之前的版本開發二進制類庫進行兼容。你無法在運行時訪問到類型參數,因為編譯器已經把泛型類型轉換成了原始類型。
限定通配符對類型進行了限制。
一種是<? extends T>它通過確保類型必須是T的子類來設定類型的上界,
另一種是<? super T>它通過確保類型必須是T的父類來設定類型的下界。
另一方面<?>表 示了非限定通配符,因為<?>可以用任意類型來替代。
例如List<? extends Number>可以接受List或List。
對任何一個不太熟悉泛型的人來說,這個Java泛型題目看起來令人疑惑,因為乍看起來String是一種Object,所以 List應當可以用在需要List的地方,但是事實并非如此。真這樣做的話會導致編譯錯誤。如 果你再深一步考慮,你會發現Java這樣做是有意義的,因為List可以存儲任何類型的對象包括String, Integer等等,而List卻只能用來存儲Strings。
Array事實上并不支持泛型,這也是為什么Joshua Bloch在Effective Java一書中建議使用List來代替Array,因為List可以提供編譯期的類型安全保證,而Array卻不能。
原始類型和帶參數類型之間的主要區別是,在編譯時編譯器不會對原始類型進行類型安全檢查,卻會對帶參數的類型進行檢查,通過使用Object作為類型,可以告知編譯器該方法可以接受任何類型的對象,比如String或Integer。這道題的考察點在于對泛型中原始類型的正確理解。它們之間的第二點區別是,你可以把任何帶參數的類型傳遞給原始類型List,但卻不能把List傳遞給接受 List的方法,因為會產生編譯錯誤。
List<?> 是一個未知類型的List,而List 其實是任意類型的List。你可以把List, List賦值給List<?>,卻不能把List賦值給 List。
JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。
Java反射機制主要提供了以下功能: 在運行時判斷任意一個對象所屬的類;在運行時構造任意一個類的對象;在運行時判斷任意一個類所具有的成員變量和方法;在運行時調用任意一個對象的方法;生成動態代理。
代理這個詞大家肯定已經非常熟悉,因為現實中接觸的很多,其實現實中的東西恰恰可以非常形象和直觀地反映出模式的抽象過程以及本質。現在房子不是吵得熱火朝天嗎?我們就以房子為例,來撥開代理的面紗。
假設你有一套房子要賣,一種方法是你直接去網上發布出售信息,然后直接帶要買房子的人來看房子、過戶等一直到房子賣出去,但是可能你很忙,你沒有時間去處理這些事情,所以你可以去找中介,讓中介幫你處理這些瑣碎事情,中介實際上就是你的代理。本來是你要做的事情,現在中介幫助你一一處理,對于買方來說跟你直接交易跟同中介直接交易沒有任何差異,買方甚至可能覺察不到你的存在,這實際上就是代理的一個最大好處。
接下來我們再深入考慮一下為什么你不直接買房子而需要中介?其實一個問題恰恰解答了什么時候該用代理模式的問題。
原因一:你可能在外地上班,買房子的人沒法找到你直接交易。
對應到我們程序設計的時候就是:客戶端無法直接操作實際對象。那么為什么無法直接操作?一種情況是你需要調用的對象在另外一臺機器上,你需要跨越網絡才能訪問,如果讓你直接coding去調用,你需要處理網絡連接、處理打包、解包等等非常復雜的步驟,所以為了簡化客戶端的處理,我們使用代理模式,在客戶端建立一個遠程對象的代理,客戶端就象調用本地對象一樣調用該代理,再由代理去跟實際對象聯系,對于客戶端來說可能根本沒有感覺到調用的東西在網絡另外一端,這實際上就是Web Service的工作原理。另一種情況雖然你所要調用的對象就在本地,但是由于調用非常耗時,你怕影響你正常的操作,所以特意找個代理來處理這種耗時情況,一個最容易理解的就是Word里面裝了很大一張圖片,在word被打開的時候我們肯定要加載里面的內容一起打開,但是如果等加載完這個大圖片再打開Word用戶等得可能早已經跳腳了,所以我們可以為這個圖片設置一個代理,讓代理慢慢打開這個圖片而不影響Word本來的打開的功能。申明一下我只是猜可能Word是這么做的,具體到底怎么做的,俺也不知道。
原因二:你不知道怎么辦過戶手續,或者說除了你現在會干的事情外,還需要做其他的事情才能達成目的。
對應到我們程序設計的時候就是:除了當前類能夠提供的功能外,我們還需要補充一些其他功能。最容易想到的情況就是權限過濾,我有一個類做某項業務,但是由于安全原因只有某些用戶才可以調用這個類,此時我們就可以做一個該類的代理類,要求所有請求必須通過該代理類,由該代理類做權限判斷,如果安全則調用實際類的業務開始處理。可能有人說為什么我要多加個代理類?我只需要在原來類的方法里面加上權限過濾不就完了嗎?在程序設計中有一個類的單一性原則問題,這個原則很簡單,就是每個類的功能盡可能單一。為什么要單一,因為只有功能單一這個類被改動的可能性才會最小,就拿剛才的例子來說,如果你將權限判斷放在當前類里面,當前這個類就既要負責自己本身業務邏輯、又要負責權限判斷,那么就有兩個導致該類變化的原因,現在如果權限規則一旦變化,這個類就必需得改,顯然這不是一個好的設計。
好了,原理的東西已經講得差不多了,要是再講個沒完可能大家要扔磚頭了。呵呵,接下來就看看怎么來實現代理。
https://zhuanlan.zhihu.com/p/27005757?utm_source=weibo&utm_medium=social
http://crazyandcoder.tech/2016/09/14/android 算法與數據結構-排序/
排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。
思想:
將第一個數和第二個數排序,然后構成一個有序序列
將第三個數插入進去,構成一個新的有序序列。
對第四個數、第五個數……直到最后一個數,重復第二步。
代碼:
首先設定插入次數,即循環次數,for(int i=1;i<length;i++),1個數的那次不用插入。
設定插入數和得到已經排好序列的最后一個數的位數。insertNum和j=i-1。
參考:Android開發中的一些設計模式
單例主要分為:懶漢式單例、餓漢式單例、登記式單例。
特點:
在計算機系統中,像線程池,緩存、日志對象、對話框、打印機等常被設計成單例。
Singleton通過將構造方法限定為private避免了類在外部被實例化,在同一個虛擬機范圍內,Singleton的唯一實例只能通過getInstance()方法訪問。(事實上,通過Java反射機制是能夠實例化構造方法為private的類的,那基本上會使所有的Java單例實現失效。
它是線程不安全的,并發情況下很有可能出現多個Singleton實例,要實現線程安全,有以下三種方式:
1.在getInstance方法上加上同步
2.雙重檢查鎖定
3.靜態內部類
這種方式對比前兩種,既實現了線程安全,又避免了同步帶來的性能影響。
餓漢式在創建類的同時就已經創建好了一個靜態的對象供系統使用,以后不再改變,所以天生是系統安全。
看完上述內容,你們對Android有哪些面試題有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。