在Android開發中,無論是插件化仍是組件化,都是基於Android系統的類加載器ClassLoader來設計的。只不過Android平臺上虛擬機運行的是Dex字節碼,一種對class文件優化的產物,傳統Class文件是一個Java源碼文件會生成一個.class文件,而Android是把全部Class文件進行合併、優化,而後再生成一個最終的class.dex,目的是把不一樣class文件重複的東西只需保留一份,在早期的Android應用開發中,若是不對Android應用進行分dex處理,那麼最後一個應用的apk只會有一個dex文件。html
Android中經常使用的類加載器有兩種,DexClassLoader和PathClassLoader,它們都繼承於BaseDexClassLoader。區別在於調用父類構造器時,DexClassLoader多傳了一個optimizedDirectory參數,這個目錄必須是內部存儲路徑,用來緩存系統建立的Dex文件。而PathClassLoader該參數爲null,只能加載內部存儲目錄的Dex文件。因此咱們能夠用DexClassLoader去加載外部的apk文件,這也是不少插件化技術的基礎。 java
理解Android的Service,能夠從如下幾個方面來理解:android
IntentService是一個抽象類,繼承自Service,內部存在一個ServiceHandler(Handler)和HandlerThread(Thread)。IntentService是處理異步請求的一個類,在IntentService中有一個工做線程(HandlerThread)來處理耗時操做,啓動IntentService的方式和普通的同樣,不過當執行完任務以後,IntentService會自動中止。另外能夠屢次啓動IntentService,每個耗時操做都會以工做隊列的形式在IntentService的onHandleIntent回調中執行,而且每次執行一個工做線程。IntentService的本質是:封裝了一個HandlerThread和Handler的異步框架。git
Service 做爲 Android四大組件之一,應用很是普遍。和Activity同樣,Service 也有一系列的生命週期回調函數,具體以下圖。 github
一般,啓動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中的靜態常量。json
START_NOT_STICKY
若是返回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
若是返回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
若是返回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回調函數。
首次建立服務時,系統將調用此方法。若是服務已在運行,則不會調用此方法,該方法只調用一次。
當另外一個組件經過調用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的實例,並執行它的生命週期方法。
4.二、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的事件分發機制能夠使用下圖表示:
當一個點擊事件產生後,它的傳遞過程將遵循以下順序:
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函數的使用以下圖:
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層)
參考連接:www.cnblogs.com/kunpengit/p…
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 系統電量分析工具,直觀地展現出手機的電量消耗過程,經過輸入電量分析文件,顯示消耗狀況。
最後提供一些可供參考耗電優化的方法:
浮點運算:計算機裏整數和小數形式就是按普通格式進行存儲,例如102四、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左右大。
參考連接:www.jianshu.com/p/03c0fd3fc…
冷啓動 在啓動應用時,系統中沒有該應用的進程,這時系統會建立一個新的進程分配給該應用;
熱啓動 在啓動應用時,系統中已有該應用的進程(例:按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.準備,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運行的時候操做所分配的內存區, 主要分爲如下五個部分,以下圖:
垃圾收集器通常完成兩件事
一般,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卻不能。
JAVA反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。
Java反射機制主要提供瞭如下功能: 在運行時判斷任意一個對象所屬的類;在運行時構造任意一個類的對象;在運行時判斷任意一個類所具備的成員變量和方法;在運行時調用任意一個對象的方法;生成動態代理。
zhuanlan.zhihu.com/p/27005757?…
crazyandcoder.tech/2016/09/14/…
排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納所有的排序記錄,在排序過程當中須要訪問外存。
思想:
將第一個數和第二個數排序,而後構成一個有序序列 將第三個數插入進去,構成一個新的有序序列。 對第四個數、第五個數……直到最後一個數,重複第二步。 代碼:
首先設定插入次數,即循環次數,for(int i=1;i<length;i++),1個數的那次不用插入。 設定插入數和獲得已經排好序列的最後一個數的位數。insertNum和j=i-1。
單例主要分爲:懶漢式單例、餓漢式單例、登記式單例。
特色:
在計算機系統中,像線程池,緩存、日誌對象、對話框、打印機等常被設計成單例。
Singleton經過將構造方法限定爲private避免了類在外部被實例化,在同一個虛擬機範圍內,Singleton的惟一實例只能經過getInstance()方法訪問。(事實上,經過Java反射機制是可以實例化構造方法爲private的類的,那基本上會使全部的Java單例實現失效。
餓漢式在建立類的同時就已經建立好了一個靜態的對象供系統使用,之後再也不改變,因此天生是系統安全。