Android面試題(轉載)

諸葛佩奇發表於2018-07-10

Android基本知識點 1、常規知識點 1、 Android類載入器 我們知道不管是外掛化還是元件化,都是基於系統的ClassLoader來設計的。只不過Android平臺上虛擬機器執行的是Dex位元組碼,一種對class檔案優化的產物,傳統Class檔案是一個Java原始碼檔案會生成一個.class檔案,而Android是把所有Class檔案進行合併,優化,然後生成一個最終的class.dex,目的是把不同class檔案重複的東西只需保留一份,如果我們的Android應用不進行分dex處理,最後一個應用的apk只會有一個dex檔案。

Android中常用的有兩種類載入器,DexClassLoader和PathClassLoader,它們都繼承於BaseDexClassLoader。區別在於呼叫父類構造器時,DexClassLoader多傳了一個optimizedDirectory引數,這個目錄必須是內部儲存路徑,用來快取系統建立的Dex檔案。而PathClassLoader該引數為null,只能載入內部儲存目錄的Dex檔案。所以我們可以用DexClassLoader去載入外部的apk

2、 Service λ Service是在main Thread中執行,Service中不能執行耗時操作(網路請求,拷貝資料庫,大檔案)。 λ 可以在Androidmanifest.xml中設定Service所在的程式,讓Service在另外的程式中執行。 λ Service執行的操作最多是20s,BroadcastReceiver是10s,Activity是5s。 λ Activity通過bindService(Intent,ServiceConnection,flag)與Service繫結。 λ Activity可以通過startService和bindService啟動Service。 λ IntentService IntentService是一個抽象類,繼承自Service,內部存在一個ServiceHandler(Handler)和HandlerThread(Thread)。IntentService是處理非同步請求的一個類,在IntentService中有一個工作執行緒(HandlerThread)來處理耗時操作,啟動IntentService的方式和普通的一樣,不過當執行完任務之後,IntentService會自動停止。另外可以多次啟動IntentService,每一個耗時操作都會以工作佇列的形式在IntentService的onHandleIntent回撥中執行,並且每次執行一個工作執行緒。它的本質是:封裝了一個HandlerThread和Handler的非同步框架。

2.1、生命週期

2.2、startService生命週期 當我們通過呼叫了Context的startService方法後,我們便啟動了Service,通過startService方法啟動的Service會一直無限期地執行下去,只有在外部呼叫Context的stopService或Service內部呼叫Service的stopSelf方法時,該Service才會停止執行並銷燬。

λ onCreate

onCreate: 執行startService方法時,如果Service沒有執行的時候會建立該Service並執行Service的onCreate回撥方法;如果Service已經處於執行中,那麼執行startService方法不會執行Service的onCreate方法。也就是說如果多次執行了Context的startService方法啟動Service,Service方法的onCreate方法只會在第一次建立Service的時候呼叫一次,以後均不會再次呼叫。我們可以在onCreate方法中完成一些Service初始化相關的操作。

λ onStartCommand 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 如果返回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。

λ onBind Service中的onBind方法是抽象方法,所以Service類本身就是抽象類,也就是onBind方法是必須重寫的,即使我們用不到。在通過startService使用Service時,我們在重寫onBind方法時,只需要將其返回null即可。onBind方法主要是用於給bindService方法呼叫Service時才會使用到。

λ onDestroy onDestroy: 通過startService方法啟動的Service會無限期執行,只有當呼叫了Context的stopService或在Service內部呼叫stopSelf方法時,Service才會停止執行並銷燬,在銷燬的時候會執行Service回撥函式。

2.3、bindService生命週期

3、 fragemnt 3.1、建立方式 (1)靜態建立 首先我們同樣需要註冊一個xml檔案,然後建立與之對應的java檔案,通過onCreatView()的返回方法進行關聯,最後我們需要在Activity中進行配置相關引數即在Activity的xml檔案中放上fragment的位置。 (2)動態建立具體步驟

  1. 建立待新增的fragment例項
  2. 獲取FragmentManager,在Activity中可以直接通過呼叫 getSupportFragmentManager()方法得到。
  3. 開啟一個事務,通過呼叫beginTransaction()方法開啟。
  4. 向容器內新增或替換fragment,一般使用repalce()方法實現,需要傳入容器的id和待新增的fragment例項。
  5. 提交事務,呼叫commit()方法來完成。

3.2、Adapter對比 FragmnetPageAdapter在每次切換頁面時,只是將Fragment進行分離,適合頁面較少的Fragment使用以儲存一些記憶體,對系統記憶體不會多大影響。

FragmentPageStateAdapter在每次切換頁面的時候,是將Fragment進行回收,適合頁面較多的Fragment使用,這樣就不會消耗更多的記憶體 3.3、生命週期

(1)動態載入: 動態載入時,Activity的onCreate()呼叫完,才開始載入fragment並呼叫其生命週期方法,所以在第一個生命週期方法onAttach()中便能獲取Activity以及Activity的佈局的元件;

(2)靜態載入: 1.靜態載入時,Activity的onCreate()呼叫過程中,fragment也在載入,所以fragment無法獲取到Activity的佈局中的元件,但為什麼能獲取到Activity呢? 2.原來在fragment呼叫onAttach()之前其實還呼叫了一個方法onInflate(),該方法被呼叫時fragment已經是和Activity相互結合了,所以可以獲取到對方,但是Activity的onCreate()呼叫還未完成,故無法獲取Activity的元件; 3.Activity的onCreate()呼叫完成是,fragment會呼叫onActivityCreated()生命週期方法,因此在這兒開始便能獲取到Activity的佈局的元件;

3.4、與Activity通訊 fragment不通過建構函式進行傳值的原因是因為橫屏切換的時候獲取不到值。

  1. Activity向Fragment傳值: 步驟:
  2. 要傳的值,放到bundle物件裡;
  3. 在Activity中建立該Fragment的物件fragment,通過呼叫fragment.setArguments()傳遞到fragment中;
  4. 在該Fragment中通過呼叫getArguments()得到bundle物件,就能得到裡面的值。
  5. Fragment向Activity傳值: 第一種: 在Activity中呼叫getFragmentManager()得到fragmentManager,,呼叫findFragmentByTag(tag)或者通過findFragmentById(id) FragmentManager fragmentManager = getFragmentManager(); Fragment fragment = fragmentManager.findFragmentByTag(tag);

第二種: 通過回撥的方式,定義一個介面(可以在Fragment類中定義),介面中有一個空的方法,在fragment中需要的時候呼叫介面的方法,值可以作為引數放在這個方法中,然後讓Activity實現這個介面,必然會重寫這個方法,這樣值就傳到了Activity中

  1. Fragment與Fragment之間是如何傳值的: 第一種: 通過findFragmentByTag得到另一個的Fragment的物件,這樣就可以呼叫另一個的方法了。 第二種: 通過介面回撥的方式。 第三種: 通過setArguments,getArguments的方式。

3.5、api區別

(add) 一種是add方式來進行show和add,這種方式你切換fragment不會讓fragment重新重新整理,只會呼叫onHiddenChanged(boolean isHidden)。

(replace) 而用replace方式會使fragment重新重新整理,因為add方式是將fragment隱藏了而不是銷燬再建立,replace方式每次都是重新建立。

(commit)/(commitAllowingStateLoss) 兩者都可以提交fragment的操作,唯一的不同是第二種方法,允許丟失一些介面的狀態和資訊,幾乎所有的開發者都遇到過這樣的錯誤:無法在activity呼叫了onSaveInstanceState之後再執行commit(),這種異常時可以理解的,介面被系統回收(介面已經不存在),為了在下次開啟的時候恢復原來的樣子,系統為我們儲存介面的所有狀態,這個時候我們再去修改介面理論上肯定是不允許的,所以為了避免這種異常,要使用第二種方法。

(BackStack)

3.x、懶載入 我們經常在使用fragment時,常常會結合著viewpager使用,那麼我們就會遇到一個問題,就是初始化fragment的時候,會連同我們寫的網路請求一起執行,這樣非常消耗效能,最理想的方式是,只有使用者點開或滑動到當前fragment時,才進行請求網路的操作。因此,我們就產生了懶載入這樣一個說法。

Viewpager配合fragment使用,預設載入前兩個fragment。很容易造成網路丟包、阻塞等問題。

在Fragment中有一個setUserVisibleHint這個方法,而且這個方法是優於onCreate()方法的,它會通過isVisibleToUser告訴我們當前Fragment我們是否可見,我們可以在可見的時候再進行網路載入。

從log上看setUserVisibleHint()的呼叫早於onCreateView,所以如果在setUserVisibleHint()要實現懶載入的話,就必須要確保View以及其他變數都已經初始化結束,避免空指標。

使用步驟:

  1. 申明一個變數isPrepare=false,isVisible=false,標明當前頁面是否被建立了
  2. 在onViewCreated週期內設定isPrepare=true
  3. 在setUserVisibleHint(boolean isVisible)判斷是否顯示,設定isVisible=true
  4. 判斷isPrepare和isVisible,都為true開始載入資料,然後恢復isPrepare和isVisible為false,防止重複載入

參考自:---- 面試總結(5):Fragment的懶載入

4、Activity 4.1、 Activity啟動流程 參考連結- Activity啟動流程概要

使用者從Launcher程式點選應用圖示可啟動應用的入口Activity,Activity啟動時需要多個程式之間的互動,Android系統中有一個zygote程式專用於孵化Android框架層和應用層程式的程式。還有一個system_server程式,該程式裡執行了很多binder service,例如ActivityManagerService,PackageManagerService,WindowManagerService,這些binder service分別執行在不同的執行緒中,其中ActivityManagerService負責管理Activity棧,應用程式,task。

(1) 點選Launcher圖示來啟動Activity

使用者在Launcher程式裡點選應用圖示時,會通知ActivityManagerService啟動應用的入口Activity,ActivityManagerService發現這個應用還未啟動,則會通知Zygote程式孵化出應用程式,然後在這個dalvik應用程式裡執行ActivityThread的main方法。應用程式接下來通知ActivityManagerService應用程式已啟動,ActivityManagerService儲存應用程式的一個代理物件,這樣ActivityManagerService可以通過這個代理物件控制應用程式,然後ActivityManagerService通知應用程式建立入口Activity的例項,並執行它的生命週期方法。

4.2、Activity生命週期 參考連結:https://blog.csdn.net/javazejian/article/details/51932554

(1)Activity的形態

λ Active/Running: Activity處於活動狀態,此時Activity處於棧頂,是可見狀態,可與使用者進行互動。 λ Paused: 當Activity失去焦點時,或被一個新的非全屏的Activity,或被一個透明的Activity放置在棧頂時,Activity就轉化為Paused狀態。但我們需要明白,此時Activity只是失去了與使用者互動的能力,其所有的狀態資訊及其成員變數都還存在,只有在系統記憶體緊張的情況下,才有可能被系統回收掉。 λ Stopped: 當一個Activity被另一個Activity完全覆蓋時,被覆蓋的Activity就會進入Stopped狀態,此時它不再可見,但是跟Paused狀態一樣保持著其所有狀態資訊及其成員變數。 λ Killed: 當Activity被系統回收掉時,Activity就處於Killed狀態。

Activity會在以上四種形態中相互切換,至於如何切換,這因使用者的操作不同而異。瞭解了Activity的4種形態後,我們就來聊聊Activity的生命週期。

所謂的典型的生命週期就是在有使用者參與的情況下,Activity經歷從建立,執行,停止,銷燬等正常的生命週期過程。

onCreate : 該方法是在Activity被建立時回撥,它是生命週期第一個呼叫的方法,我們在建立Activity時一般都需要重寫該方法,然後在該方法中做一些初始化的操作,如通過setContentView設定介面佈局的資源,初始化所需要的元件資訊等。

onStart : 此方法被回撥時表示Activity正在啟動,此時Activity已處於可見狀態,只是還沒有在前臺顯示,因此無法與使用者進行互動。可以簡單理解為Activity已顯示而我們無法看見擺了。

onResume : 當此方法回撥時,則說明Activity已在前臺可見,可與使用者互動了(處於前面所說的Active/Running形態),onResume方法與onStart的相同點是兩者都表示Activity可見,只不過onStart回撥時Activity還是後臺無法與使用者互動,而onResume則已顯示在前臺,可與使用者互動。當然從流程圖,我們也可以看出當Activity停止後(onPause方法和onStop方法被呼叫),重新回到前臺時也會呼叫onResume方法,因此我們也可以在onResume方法中初始化一些資源,比如重新初始化在onPause或者onStop方法中釋放的資源。

onPause : 此方法被回撥時則表示Activity正在停止(Paused形態),一般情況下onStop方法會緊接著被回撥。但通過流程圖我們還可以看到一種情況是onPause方法執行後直接執行了onResume方法,這屬於比較極端的現象了,這可能是使用者操作使當前Activity退居後臺後又迅速地再回到到當前的Activity,此時onResume方法就會被回撥。當然,在onPause方法中我們可以做一些資料儲存或者動畫停止或者資源回收的操作,但是不能太耗時,因為這可能會影響到新的Activity的顯示——onPause方法執行完成後,新Activity的onResume方法才會被執行。

onStop : 一般在onPause方法執行完成直接執行,表示Activity即將停止或者完全被覆蓋(Stopped形態),此時Activity不可見,僅在後臺執行。同樣地,在onStop方法可以做一些資源釋放的操作(不能太耗時)。

onRestart :表示Activity正在重新啟動,當Activity由不可見變為可見狀態時,該方法被回撥。這種情況一般是使用者開啟了一個新的Activity時,當前的Activity就會被暫停(onPause和onStop被執行了),接著又回到當前Activity頁面時,onRestart方法就會被回撥。

onDestroy :此時Activity正在被銷燬,也是生命週期最後一個執行的方法,一般我們可以在此方法中做一些回收工作和最終的資源釋放。

小結:到這裡我們來個小結,當Activity啟動時,依次會呼叫onCreate(),onStart(),onResume(),而當Activity退居後臺時(不可見,點選Home或者被新的Activity完全覆蓋),onPause()和onStop()會依次被呼叫。當Activity重新回到前臺(從桌面回到原Activity或者被覆蓋後又回到原Activity)時,onRestart(),onStart(),onResume()會依次被呼叫。當Activity退出銷燬時(點選back鍵),onPause(),onStop(),onDestroy()會依次被呼叫,到此Activity的整個生命週期方法回撥完成。現在我們再回頭看看之前的流程圖,應該是相當清晰了吧。嗯,這就是Activity整個典型的生命週期過程。下篇我們再來聊聊Activity的異常生命週期。

2、 view部分知識點 2.1、DecorView淺析

Android View原始碼解讀:淺談DecorView與ViewRootImpl

DecorView為整個Window介面的最頂層View,它只有一個子元素LinearLayout。代表整個Window介面,包含通知欄、標題欄、內容顯示欄三塊區域。其中LinearLayout中有兩個FrameLayout子元素。

1

λ (20)標題欄FrameLayout 其中(20)為標題欄顯示介面,只有一個TextView顯示應用的名稱。 λ (21)內容欄FrameLayout 其中(21)位內容欄顯示介面,就是setContentView()方法載入的佈局介面。

DecorView的作用 1、 DecorView是頂級View,本質是一個FrameLayout 2、 它包含兩部分,標題欄和內容欄,都是FrameLayout 3、 內容欄id是content,也就是activity中設定setContentView的部分,最終將佈局新增到id為content的FrameLayout中。 4、 獲取content:ViewGroup content=findViewById(R.android.id.content) 5、 獲取設定的View:content.getChildAt(0).

使用總結 1、 每個Activity都包含一個Window物件,Window物件通常是由PhoneWindow實現的。 2、 PhoneWindow:將DecorView設定為整個應用視窗的根View,是Window的實現類。它是Android中的最基本的視窗系統,每個Activity均會建立一個PhoneWindow物件,是Activity和整個View系統互動的介面。 3、 DecorView:是頂層檢視,將要顯示的具體內容呈現在PhoneWindow上,DecorView是當前Activity所有View的祖先,它並不會向使用者呈現任何東西。

2.2、View的事件分發 圖解 Android 事件分發機制

λ ViewGroup事件分發

當一個點選事件產生後,它的傳遞過程將遵循如下順序: 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將會被呼叫

λ View的事件分發 dispatchTouchEvent -> onTouch(setOnTouchListener) -> onTouchEvent -> onClick λ onTouch和onTouchEvent的區別 兩者都是在dispatchTouchEvent中呼叫的,onTouch優先於onTouchEvent,如果onTouch返回true,那麼onTouchEvent則不執行,及onClick也不執行。

2.3、View的繪製 λ onMeasure(int widthMeasureSpec, int heightMeasureSpec)

在xml佈局檔案中,我們的layout_width和layout_height引數可以不用寫具體的尺寸,而是wrap_content或者是match_parent。這兩個設定並沒有指定真正的大小,可是我們繪製到螢幕上的View必須是要有具體的寬高的,正是因為這個原因,我們必須自己去處理和設定尺寸。當然了,View類給了預設的處理,但是如果View類的預設處理不滿足我們的要求,我們就得重寫onMeasure函式啦~。

一個int整數,裡面放了測量模式和尺寸大小。int型資料佔用32個bit,而google實現的是,將int資料的前面2個bit用於區分不同的佈局模式,後面30個bit存放的是尺寸的資料。

match_parent—>EXACTLY。怎麼理解呢?match_parent就是要利用父View給我們提供的所有剩餘空間,而父View剩餘空間是確定的,也就是這個測量模式的整數裡面存放的尺寸。

wrap_content—>AT_MOST。怎麼理解:就是我們想要將大小設定為包裹我們的view內容,那麼尺寸大小就是父View給我們作為參考的尺寸,只要不超過這個尺寸就可以啦,具體尺寸就根據我們的需求去設定。

固定尺寸(如100dp)—>EXACTLY。使用者自己指定了尺寸大小,我們就不用再去幹涉了,當然是以指定的大小為主啦。

λ onDraw λ 自定義屬性

名稱空間:"http://schemas.android.com/apk/res-auto"

2.4、ViewGroup的繪製 自定義ViewGroup可就沒那麼簡單啦~,因為它不僅要管好自己的,還要兼顧它的子View。我們都知道ViewGroup是個View容器,它裝納child View並且負責把child View放入指定的位置。

1、 首先,我們得知道各個子View的大小吧,只有先知道子View的大小,我們才知道當前的ViewGroup該設定為多大去容納它們。

2、 根據子View的大小,以及我們的ViewGroup要實現的功能,決定出ViewGroup的大小

3、 ViewGroup和子View的大小算出來了之後,接下來就是去擺放了吧,具體怎麼去擺放呢?這得根據你定製的需求去擺放了,比如,你想讓子View按照垂直順序一個挨著一個放,或者是按照先後順序一個疊一個去放,這是你自己決定的。

4、 已經知道怎麼去擺放還不行啊,決定了怎麼擺放就是相當於把已有的空間”分割”成大大小小的空間,每個空間對應一個子View,我們接下來就是把子View對號入座了,把它們放進它們該放的地方去。

λ onLayout

3、系統原理 3.1、打包原理 Android的包檔案APK分為兩個部分:程式碼和資源,所以打包方面也分為資源打包和程式碼打包兩個方面,這篇文章就來分析資源和程式碼的編譯打包原理。 具體說來:

  1. 通過AAPT工具進行資原始檔(包括AndroidManifest.xml、佈局檔案、各種xml資源等)的打包,生成R.java檔案。
  2. 通過AIDL工具處理AIDL檔案,生成相應的Java檔案。
  3. 通過Javac工具編譯專案原始碼,生成Class檔案。
  4. 通過DX工具將所有的Class檔案轉換成DEX檔案,該過程主要完成Java位元組碼轉換成Dalvik位元組碼,壓縮常量池以及清除冗餘資訊等工作。
  5. 通過ApkBuilder工具將資原始檔、DEX檔案打包生成APK檔案。
  6. 利用KeyStore對生成的APK檔案進行簽名。
  7. 如果是正式版的APK,還會利用ZipAlign工具進行對齊處理,對齊的過程就是將APK檔案中所有的資原始檔舉例檔案的起始距離都偏移4位元組的整數倍,這樣通過記憶體對映訪問APK檔案 的速度會更快。

3.2、安裝流程

  1. 複製APK到/data/app目錄下,解壓並掃描安裝包。
  2. 資源管理器解析APK裡的資原始檔。
  3. 解析AndroidManifest檔案,並在/data/data/目錄下建立對應的應用資料目錄。
  4. 然後對dex檔案進行優化,並儲存在dalvik-cache目錄下。
  5. 將AndroidManifest檔案解析出的四大元件資訊註冊到PackageManagerService中。
  6. 安裝完成後,傳送廣播。

4、 第三方庫解析 4.1、Retrofit網路請求框架 參考-》使用與原始碼解析

概念:Retrofit是一個基於RESTful的HTTP網路請求框架的封裝,其中網路請求的本質是由OKHttp完成的,而Retrofit僅僅負責網路請求介面的封裝。

原理:App應用程式通過Retrofit請求網路,實際上是使用Retrofit介面層封裝請求引數,Header、URL等資訊,之後由OKHttp完成後續的請求,在伺服器返回資料之後,OKHttp將原始的結果交給Retrofit,最後根據使用者的需求對結果進行解析。

  1. android-async-http
  2. okhttp 4.2、圖片載入庫對比 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

2.介紹: Picasso :和Square的網路庫一起能發揮最大作用,因為Picasso可以選擇將網路請求的快取部分交給了okhttp實現。

Glide:模仿了Picasso的API,而且在他的基礎上加了很多的擴充套件(比如gif等支援),Glide預設的Bitmap格式是RGB_565,比    Picasso預設的ARGB_8888格式的記憶體開銷要小一半;Picasso快取的是全尺寸的(只快取一種),而Glide快取的是跟ImageView尺寸相同的(即56*56和128*128是兩個快取) 。

FB的圖片載入框架Fresco:最大的優勢在於5.0以下(最低2.3)的bitmap載入。在5.0以下系統,Fresco將圖片放到一個特別的記憶體區域(Ashmem區)。當然,在圖片不顯示的時候,佔用的記憶體會自動被釋放。這會使得APP更加流暢,減少因圖片記憶體佔用而引發的OOM。為什麼說是5.0以下,因為在5.0以後系統預設就是儲存在Ashmem區了。
複製程式碼

3.總結: Picasso所能實現的功能,Glide都能做,無非是所需的設定不同。但是Picasso體積比起Glide小太多如果專案中網路請求本身用的就是okhttp或者retrofit(本質還是okhttp),那麼建議用Picasso,體積會小很多(Square全家桶的幹活)。Glide的好處是大型的圖片流,比如gif、Video,如果你們是做美拍、愛拍這種視訊類應用,建議使用。 Fresco在5.0以下的記憶體優化非常好,代價就是體積也非常的大,按體積算Fresco>Glide>Picasso 不過在使用起來也有些不便(小建議:他只能用內建的一個ImageView來實現這些功能,用起來比較麻煩,我們通常是根據Fresco自己改改,直接使用他的Bitmap層)

4.3、各種json解析庫使用 參考連結:https://www.cnblogs.com/kunpengit/p/4001680.html (1)Google的Gson Gson是目前功能最全的Json解析神器,Gson當初是為因應Google公司內部需求而由Google自行研發而來,但自從在2008年五月公開發布第一版後已被許多公司或使用者應用。Gson的應用主要為toJson與fromJson兩個轉換函式,無依賴,不需要例外額外的jar,能夠直接跑在JDK上。而在使用這種物件轉換之前需先建立好物件的型別以及其成員才能成功的將JSON字串成功轉換成相對應的物件。類裡面只要有get和set方法,Gson完全可以將複雜型別的json到bean或bean到json的轉換,是JSON解析的神器。Gson在功能上面無可挑剔,但是效能上面比FastJson有所差距。

(2)阿里巴巴的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

5、熱點技術 參考連結- Android元件化方案

5.1、元件化 (1)概念: 元件化:是將一個APP分成多個module,每個module都是一個元件,也可以是一個基礎庫供元件依賴,開發中可以單獨除錯部分元件,元件中不需要相互依賴但是可以相互呼叫,最終釋出的時候所有元件以lib的形式被主APP工程依賴打包成一個apk。

(2)由來: 1、 APP版本迭代,新功能不斷增加,業務變得複雜,維護成本高 2、 業務耦合度高,程式碼臃腫,團隊內部多人協作開發困難 3、 Android編譯程式碼卡頓,單一工程下程式碼耦合嚴重,修改一處需要重新編譯打包,耗時耗力。 4、 方便單元測試,單獨改一個業務模組,不需要著重關注其他模組。

(3)優勢: 1、 元件化將通用模組獨立出來,統一管理,以提高複用,將頁面拆分為粒度更小的元件,元件內部出了包含UI實現,還可以包含資料層和邏輯層 2、 每個元件度可以獨立編譯、加快編譯速度、獨立打包。 3、 每個工程內部的修改,不會影響其他工程。 4、 業務庫工程可以快速拆分出來,整合到其他App中。 5、 迭代頻繁的業務模組採用元件方式,業務線研發可以互不干擾、提升協作效率,並控制產品質量,加強穩定性。 6、 並行開發,團隊成員只關注自己的開發的小模組,降低耦合性,後期維護方便等。

(4)考慮問題: 模式切換:如何使得APP在單獨除錯跟整體除錯自由切換 元件化後的每一個業務的module都可以是一個單獨的APP(isModuleRun=false), release 包的時候各個業務module作為lib依賴,這裡完全由一個變數控制,在根專案 gradle.properties裡面isModuleRun=true。isModuleRun狀態不同,載入application和AndroidManifest都不一樣,以此來區分是獨立的APK還是lib。 在build.grade裡面配置:

資源衝突:當我們建立了多個Module的時候,如何解決相同資原始檔名合併的衝突 業務Module和BaseModule資原始檔名稱重複會產生衝突,解決方案在 每個 module 都有 app_name,為了不讓資源名重名,在每個元件的 build.gradle 中增加 resourcePrefix “xxx_強行檢查資源名稱字首。固定每個元件的資源字首。但是 resourcePrefix 這個值只能限定 xml 裡面的資源,並不能限定圖片資源。

依賴關係:多個Module之間如何引用一些共同的library以及工具類

元件通訊:元件化之後,Module之間是相互隔離的,如何進行UI跳轉以及方法呼叫 阿里巴巴ARouter

各業務Module之前不需要任何依賴可以通過路由跳轉,完美解決業務之間耦合。

入口引數:我們知道元件之間是有聯絡的,所以在單獨除錯的時候如何拿到其它的Module傳遞過來的引數

Application:

當元件單獨執行的時候,每個Module自成一個APK,那麼就意味著會有多個Application,很顯然我們不願意重複寫這麼多程式碼,所以我們只需要定義一個BaseApplication即可,其它的Application直接繼承此BaseApplication就OK了,BaseApplication裡面還可定義公用的引數。

得到APP元件化

5.2、外掛化 參考連結- 外掛化入門

(1)概述 提到外掛化,就不得不提起方法數超過65535的問題,我們可以通過Dex分包來解決,同時也可以通過使用外掛化開發來解決。外掛化的概念就是由宿主APP去載入以及執行外掛APP。

(2優點) 1、 在一個大的專案裡面,為了明確的分工,往往不同的團隊負責不同的外掛APP,這樣分工更加明確。各個模組封裝成不同的外掛APK,不同模組可以單獨編譯,提高了開發效率。 2、 解決了上述的方法數超過限制的問題。 3、 可以通過上線新的外掛來解決線上的BUG,達到“熱修復”的效果。 4、 減小了宿主APK的體積。

(3缺點) 外掛化開發的APP不能在Google Play上線,也就是沒有海外市場。

6、螢幕適配 6.1、基本概念 螢幕尺寸 含義:手機對角線的物理尺寸 單位:英寸(inch),1英寸=2.54cm Android手機常見的尺寸有5寸、5.5寸、6寸等等

螢幕解析度 含義:手機在橫向、縱向上的畫素點數總和 一般描述成螢幕的”寬x高”=AxB 含義:螢幕在橫向方向(寬度)上有A個畫素點,在縱向方向 (高)有B個畫素點 例子:1080x1920,即寬度方向上有1080個畫素點,在高度方向上有1920個畫素點 單位:px(pixel),1px=1畫素點

UI設計師的設計圖會以px作為統一的計量單位

Android手機常見的解析度:320x480、480x800、720x1280、1080x1920

螢幕畫素密度 含義:每英寸的畫素點數 單位:dpi(dots per ich) 假設裝置內每英寸有160個畫素,那麼該裝置的螢幕畫素密度=160dpi

6.2、適配方法 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圖片

6.3、連結 https://blog.csdn.net/lanxingfeifei/article/details/52161833

7、效能優化 參考連結:Android 效能監測工具,優化記憶體、卡頓、耗電、APK大小的方法

1、 穩定(記憶體溢位、崩潰) 2、 流暢(卡頓) 3、 耗損(耗電、流量) 4、 安裝包(APK瘦身)

影響穩定性的原因很多,比如記憶體使用不合理、程式碼異常場景考慮不周全、程式碼邏輯不合理等,都會對應用的穩定性造成影響。其中最常見的兩個場景是:Crash 和 ANR,這兩個錯誤將會使得程式無法使用。所以做好Crash全域性監控,處理閃退同時把崩潰資訊、異常資訊收集記錄起來,以便後續分析;合理使用主執行緒處理業務,不要在主執行緒中做耗時操作,防止ANR程式無響應發生。

(一)穩定——記憶體優化 (1)Memory Monitor 工具: 它是Android Studio自帶的一個記憶體監視工具,它可以很好地幫助我們進行記憶體實時分析。通過點選Android Studio右下角的Memory Monitor標籤,開啟工具可以看見較淺藍色代表free的記憶體,而深色的部分代表使用的記憶體從記憶體變換的走勢圖變換,可以判斷關於記憶體的使用狀態,例如當記憶體持續增高時,可能發生記憶體洩漏;當記憶體突然減少時,可能發生GC等,如下圖所示。

(2) LeakCanary工具: 這個工具是Square公司在Github開源的。

(3)Android Lint 工具: Android Lint Tool 是Android Sutido種整合的一個Android程式碼提示工具,它可以給你佈局、程式碼提供非常強大的幫助。硬編碼會提示以級別警告,例如:在佈局檔案中寫了三層冗餘的LinearLayout佈局、直接在TextView中寫要顯示的文字、字型大小使用dp而不是sp為單位,就會在編輯器右邊看到提示。

(二)流暢——卡頓優化

卡頓的場景通常是發生在使用者互動體驗最直接的方面。影響卡頓的兩大因素,分別是介面繪製和資料處理。

介面繪製:主要原因是繪製的層級深、頁面複雜、重新整理不合理,由於這些原因導致卡頓的場景更多出現在 UI 和啟動後的初始介面以及跳轉到頁面的繪製上。 資料處理:導致這種卡頓場景的原因是資料處理量太大,一般分為三種情況,一是資料在處理 UI 執行緒,二是資料處理佔用 CPU 高,導致主執行緒拿不到時間片,三是記憶體增加導致 GC 頻繁,從而引起卡頓。

(1)佈局優化 在Android種系統對View進行測量、佈局和繪製時,都是通過對View數的遍歷來進行操作的。如果一個View數的高度太高就會嚴重影響測量、佈局和繪製的速度。Google也在其API文件中建議View高度不宜哦過10層。現在版本種Google使用RelativeLayout替代LineraLayout作為預設根佈局,目的就是降低LineraLayout巢狀產生布局樹的高度,從而提高UI渲染的效率。

λ 佈局複用,使用標籤重用layout; λ 提高顯示速度,使用延遲View載入; λ 減少層級,使用標籤替換父級佈局; λ 注意使用wrap_content,會增加measure計算成本; λ 刪除控制元件中無用屬性;

(2)繪製優化 過度繪製是指在螢幕上的某個畫素在同一幀的時間內被繪製了多次。在多層次重疊的 UI 結構中,如果不可見的 UI 也在做繪製的操作,就會導致某些畫素區域被繪製了多次,從而浪費了多餘的 CPU 以及 GPU 資源。如何避免過度繪製? 佈局上的優化。移除 XML 中非必須的背景,移除 Window 預設的背景、按需顯示佔位背景圖片

自定義View優化。使用 canvas.clipRect() 幫助系統識別那些可見的區域,只有在這個區域內才會被繪製。

(3)啟動優化 應用一般都有閃屏頁SplashActivity,優化閃屏頁的 UI 佈局,可以通過 Profile GPU Rendering 檢測丟幀情況。

(三)節省——耗電優化 在 Android5.0 以前,關於應用電量消耗的測試即麻煩又不準確,而5.0 之後Google專門引入了一個獲取裝置上電量消耗資訊的API—— Battery Historian。Battery Historian 是一款由 Google 提供的 Android 系統電量分析工具,直觀地展示出手機的電量消耗過程,通過輸入電量分析檔案,顯示消耗情況。

最後提供一些可供參考耗電優化的方法:

(1)計算優化。演算法、for迴圈優化、Switch..case替代if..else、避開浮點運算。 浮點運算:計算機裡整數和小數形式就是按普通格式進行儲存,例如1024、3.1415926等等,這個沒什麼特點,但是這樣的數精度不高,表達也不夠全面,為了能夠有一種數的通用表示法,就發明了浮點數。浮點數的表示形式有點像科學計數法(.×10^),它的表示形式是0.×10^,在計算機中的形式為 .* e ±**),其中前面的星號代表定點小數,也就是整數部分為0的純小數,後面的指數部分是定點整數。利用這樣的形式就能表示出任意一個整數和小數,例如1024就能表示成0.1024×10^4,也就是 .1024e+004,3.1415926就能表示成0.31415926×10^1,也就是 .31415926e+001,這就是浮點數。浮點數進行的運算就是浮點運算。浮點運算比常規運算更復雜,因此計算機進行浮點運算速度要比進行常規運算慢得多。

(2)避免 Wake Lock 使用不當。 Wake Lock是一種鎖的機制,主要是相對系統的休眠而言的,,只要有人拿著這個鎖,系統就無法進入休眠意思就是我的程式給CPU加了這個鎖那系統就不會休眠了,這樣做的目的是為了全力配合我們程式的執行。有的情況如果不這麼做就會出現一些問題,比如微信等及時通訊的心跳包會在熄屏不久後停止網路訪問等問題。所以微信裡面是有大量使用到了Wake_Lock鎖。系統為了節省電量,CPU在沒有任務忙的時候就會自動進入休眠。有任務需要喚醒CPU高效執行的時候,就會給CPU加Wake_Lock鎖。大家經常犯的錯誤,我們很容易去喚醒CPU來工作,但是很容易忘記釋放Wake_Lock。

(3)使用 Job Scheduler 管理後臺任務。 在Android 5.0 API 21 中,google提供了一個叫做JobScheduler API的元件,來處理當某個時間點或者當滿足某個特定的條件時執行一個任務的場景,例如當使用者在夜間休息時或裝置接通電源介面卡連線WiFi啟動下載更新的任務。這樣可以在減少資源消耗的同時提升應用的效率。 (四)安裝包——APK瘦身 (1)安裝包的組成結構

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 尋找資源。

(2)減少安裝包大小 λ 程式碼混淆。使用IDE 自帶的 proGuard 程式碼混淆器工具 ,它包括壓縮、優化、混淆等功能。 λ 資源優化。比如使用 Android Lint 刪除冗餘資源,資原始檔最少化等。 λ 圖片優化。比如利用 PNG優化工具 對圖片做壓縮處理。推薦目前最先進的壓縮工具Googlek開源庫zopfli。如果應用在4.0版本以上,推薦使用 WebP圖片格式。 λ 避免重複或無用功能的第三方庫。例如,百度地圖接入基礎地圖即可、訊飛語音無需接入離線、圖片庫Glide\Picasso等。 λ 外掛化開發。比如功能模組放在伺服器上,按需下載,可以減少安裝包大小。 λ 可以使用微信開源資原始檔混淆工具——AndResGuard 。一般可以壓縮apk的1M左右大。

7.1、冷啟動

參考連結:https://www.jianshu.com/p/03c0fd3fc245 8、MVP模式架構 8.1、MVP模式 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核心優點。

9、虛擬機器 9.1、Android Dalvik虛擬機器和ART虛擬機器對比 λ Dalvik Android4.4及以前使用的都是Dalvik虛擬機器,我們知道Apk在打包的過程中會先將java等原始碼通過javac編譯成.class檔案,但是我們的Dalvik虛擬機器只會執行.dex檔案,這個時候dx會將.class檔案轉換成Dalvik虛擬機器執行的.dex檔案。Dalvik虛擬機器在啟動的時候會先將.dex檔案轉換成快速執行的機器碼,又因為65535這個問題,導致我們在應用冷啟動的時候有一個合包的過程,最後導致的一個結果就是我們的app啟動慢,這就是Dalvik虛擬機器的JIT特性(Just In Time)。

λ ART 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處理器

需要了解的知識點

熟悉Android效能分析工具、UI卡頓、APP啟動、包瘦身和記憶體效能優化 熟悉Android APP架構設計,模組化、元件化、外掛化開發 熟練掌握Java、設計模式、網路、多執行緒技術 Java基本知識點 1、Java的類載入過程 jvm將.class類檔案資訊載入到記憶體並解析成對應的class物件的過程,注意:jvm並不是一開始就把所有的類載入進記憶體中,只是在第一次遇到某個需要執行的類才會載入,並且只載入一次 主要分為三部分:1、載入,2、連結(1.驗證,2.準備,3.解析),3、初始化 1:載入 類載入器包括 BootClassLoader、ExtClassLoader、APPClassLoader 2:連結 驗證:(驗證class檔案的位元組流是否符合jvm規範) 準備:為類變數分配記憶體,並且進行賦初值 解析:將常量池裡面的符號引用(變數名)替換成直接引用(記憶體地址)過程,在解析階段,jvm會把所有的類名、方法名、欄位名、這些符號引用替換成具體的記憶體地址或者偏移量。 3:初始化 主要對類變數進行初始化,執行類構造器的過程,換句話說,只對static修試的變數或者語句進行初始化。

範例:Person person = new Person();為例進行說明。 注意:Java程式設計思想中的類的初始化過程

  1. 找到People.class檔案,將它載入到記憶體
  2. 在堆記憶體中分配記憶體地址
  3. 初始化
  4. 將堆記憶體地址指給棧記憶體中的p變數

2、String、StringBuilder、StringBuffer StringBuffer裡面的很多方法新增了synchronized關鍵字,是可以表徵執行緒安全的,所以多執行緒情況下使用它。 執行速度: StringBuilder > StringBuffer > String StringBuilder犧牲了效能來換取速度的,這兩個是可以直接在原物件上面進行修改,省去了建立新物件和回收老物件的過程,而String是字串常量(final)修試,另外兩個是字串變數,常量物件一旦建立就不可以修改,變數是可以進行修改的,所以對於String字串的操作包含下面三個步驟:

  1. 建立一個新物件,名字和原來的一樣
  2. 在新物件上面進行修改
  3. 原物件被垃圾回收掉

3、JVM記憶體結構 Java物件例項化過程中,主要使用到虛擬機器棧、Java堆和方法區。Java檔案經過編譯之後首先會被載入到jvm方法區中,jvm方法區中很重的一個部分是執行時常量池,用以儲存class檔案類的版本、欄位、方法、介面等描述資訊和編譯期間的常量和靜態常量。 3.1、JVM基本結構

  1. 類載入器classLoader,在JVM啟動時或者類執行時將需要的.class檔案載入到記憶體中。

  2. 執行引擎,負責執行class檔案中包含的位元組碼指令。

  3. 本地方法介面,主要是呼叫C/C++實現的本地方法及返回結果。

  4. 記憶體區域(執行時資料區),是在JVM執行的時候操作所分配的記憶體區, 主要分為以下五個部分:

  5. 方法區:用於儲存類結構資訊的地方,包括常量池、靜態變數、建構函式等。

  6. Java堆(heap):儲存Java例項或者物件的地方。這塊是gc的主要區域。

  7. Java棧(stack):Java棧總是和執行緒關聯的,每當建立一個執行緒時,JVM就會為這個執行緒建立一個對應的Java棧。在這個java棧中又會包含多個棧幀,每執行一個方法就建立一個棧幀,用於儲存區域性變數表、操作棧、方法返回值等。每一個方法從呼叫直至執行完成的過程,就對應一個棧幀在java棧中入棧到出棧的過程。所以java棧是執行緒私有的。

  8. 程式計數器:用於儲存當前執行緒執行的記憶體地址,由於JVM是多執行緒執行的,所以為了保證執行緒切換回來後還能恢復到原先狀態,就需要一個獨立的計數器,記錄之前中斷的地方,可見程式計數器也是執行緒私有的。

  9. 本地方法棧:和Java棧的作用差不多,只不過是為JVM使用到的native方法服務的。 3.2、JVM原始碼分析 https://www.jianshu.com/nb/12554212 4、GC機制 垃圾收集器一般完成兩件事

  10. 檢測出垃圾

  11. 回收垃圾

垃圾檢測方法:

  1. 引用計數法:給每個物件新增引用計數器,每個地方引用它,計數器就+1,失效時-1。如果兩個物件互相引用時,就導致無法回收。
  2. 可達性分析演算法:以根集物件為起始點進行搜尋,如果物件不可達的話就是垃圾物件。根集(Java棧中引用的物件、方法區中常量池中引用的物件、本地方法中引用的物件等。JVM在垃圾回收的時候,會檢查堆中所有物件是否被這些根集物件引用,不能夠被引用的物件就會被垃圾回收器回收。)

垃圾回收演算法:

  1. 標記-清除
  2. 複製
  3. 標記-整理
  4. 分帶收集演算法

5、類載入器 程式在啟動的時候,並不會一次性載入程式所要用的所有class檔案,而是根據程式的需要,通過Java的類載入機制(ClassLoader)來動態載入某個class檔案到記憶體當中的,從而只有class檔案被載入到了記憶體之後,才能被其它class所引用。所以ClassLoader就是用來動態載入class檔案到記憶體當中用的。

5.1、雙親委派原理 每個ClassLoader例項都有一個父類載入器的引用(不是繼承關係,是一個包含的關係),虛擬機器內建的類載入器(Bootstrap ClassLoader)本身沒有父類載入器,但是可以用做其他ClassLoader例項的父類載入器。

當一個ClassLoader 例項需要載入某個類時,它會試圖在親自搜尋這個類之前先把這個任務委託給它的父類載入器,這個過程是由上而下依次檢查的,首先由頂層的類載入器Bootstrap CLassLoader進行載入,如果沒有載入到,則把任務轉交給Extension CLassLoader檢視載入,如果也沒有找到,則轉交給AppCLassLoader進行載入,還是沒有的話,則交給委託的發起者,由它到指定的檔案系統或者網路等URL中進行載入類。還沒有找到的話,則會丟擲CLassNotFoundException異常。否則將這個類生成一個類的定義,並將它載入到記憶體中,最後返回這個類在記憶體中的Class例項物件。 5.2、 為什麼使用雙親委託模型 JVM在判斷兩個class是否相同時,不僅要判斷兩個類名是否相同,還要判斷是否是同一個類載入器載入的。

  1. 避免重複載入,父類已經載入了,則子CLassLoader沒有必要再次載入。
  2. 考慮安全因素,假設自定義一個String類,除非改變JDK中CLassLoader的搜尋類的預設演算法,否則使用者自定義的CLassLoader如法載入一個自己寫的String類,因為String類在啟動時就被引導類載入器Bootstrap CLassLoader載入了。

6、集合 Java集合類主要由兩個介面派生出:Collection和Map,這兩個介面是Java集合的根介面。

Collection介面是集合類的根介面,Java中沒有提供這個介面的直接的實現類。但是卻讓其被繼承產生了兩個介面,就是 Set和List。Set中不能包含重複的元素。List是一個有序的集合,可以包含重複的元素,提供了按索引訪問的方式。

Map是Java.util包中的另一個介面,它和Collection介面沒有關係,是相互獨立的,但是都屬於集合類的一部分。Map包含了key-value對。Map不能包含重複的key,但是可以包含相同的value。 6.1、區別 1、 List,Set都是繼承自Collection介面,Map則不是; 2、 List特點:元素有放入順序,元素可重複; Set特點:元素無放入順序,元素不可重複,重複元素會覆蓋掉,(注意:元素雖然無放入順序,但是元素在set中的位置是有該元素的HashCode決定的,其位置其實是固定的,加入Set 的Object必須定義equals()方法; 3、 LinkedList、ArrayList、HashSet是非執行緒安全的,Vector是執行緒安全的; 4、 HashMap是非執行緒安全的,HashTable是執行緒安全的; 5、 6、 6.2、List和Vector比較 1、 Vector是多執行緒安全的,執行緒安全就是說多執行緒訪問同一程式碼,不會產生不確定的結果。而ArrayList不是,這個可以從原始碼中看出,Vector類中的方法很多有synchronized進行修飾,這樣就導致了Vector在效率上無法與ArrayList相比; 2、 兩個都是採用的線性連續空間儲存元素,但是當空間不足的時候,兩個類的增加方式是不同。 3、 Vector可以設定增長因子,而ArrayList不可以。 4、 Vector是一種老的動態陣列,是執行緒同步的,效率很低,一般不贊成使用。

6.3、HashSet如何保證不重複 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始終是一樣的,PRESENT 1、 如果hash碼值不相同,說明是一個新元素,存; 2、 如果hash碼值相同,且equles判斷相等,說明元素已經存在,不存; 3、 如果hash碼值相同,且equles判斷不相等,說明元素不存在,存;

如果有元素和傳入物件的hash值相等,那麼,繼續進行equles()判斷,如果仍然相等,那麼就認為傳入元素已經存在,不再新增,結束,否則仍然新增;

6.4、HashSet與Treeset的適用場景

  1. HashSet是基於Hash演算法實現的,其效能通常都優於TreeSet。為快速查詢而設計的Set,我們通常都應該使用HashSet,在我們需要排序的功能時,我們才使用TreeSet。
  2. TreeSet 是二叉樹(紅黑樹的樹據結構)實現的,Treeset中的資料是自動排好序的,不允許放入null值
  3. HashSet 是雜湊表實現的,HashSet中的資料是無序的,可以放入null,但只能放入一個null,兩者中的值都不能重複,就如資料庫中唯一約束。
  4. HashSet是基於Hash演算法實現的,其效能通常都優於TreeSet。為快速查詢而設計的Set,我們通常都應該使用HashSet,在我們需要排序的功能時,我們才使用TreeSet。

6.5、HashMap與TreeMap、HashTable的區別及適用場景

  1. HashMap 非執行緒安全,基於雜湊表(雜湊表)實現。使用HashMap要求新增的鍵類明確定義了hashCode()和equals()[可以重寫hashCode()和equals()],為了優化HashMap空間的使用,您可以調優初始容量和負載因子。其中雜湊表的衝突處理主要分兩種,一種是開放定址法,另一種是連結串列法。HashMap的實現中採用的是連結串列法。
  2. TreeMap:非執行緒安全基於紅黑樹實現,TreeMap沒有調優選項,因為該樹總處於平衡狀態

7、 常量池 7.1、Interger中的128(-128~127) 1、 當數值範圍為-128~127時:如果兩個new出來Integer物件,即使值相同,通過“==”比較結果為false,但兩個物件直接賦值,則通過“==”比較結果為“true,這一點與String非常相似。 2、 當數值不在-128~127時,無論通過哪種方式,即使兩個物件的值相等,通過“==”比較,其結果為false; 3、 當一個Integer物件直接與一個int基本資料型別通過“==”比較,其結果與第一點相同; 4、 Integer物件的hash值為數值本身; @Override public int hashCode() { return Integer.hashCode(value); }

7.2、為什麼是-128-127? 在Integer類中有一個靜態內部類IntegerCache,在IntegerCache類中有一個Integer陣列,用以快取當數值範圍為-128~127時的Integer物件。

8、泛型 泛型是Java SE 1.5的新特性,泛型的本質是引數化型別,也就是說所操作的資料型別被指定為一個引數。這種引數型別可以用在類、介面和方法的建立中,分別稱為泛型類、泛型介面、泛型方法。 Java語言引入泛型的好處是安全簡單。

泛型的好處是在編譯的時候檢查型別安全,並且所有的強制轉換都是自動和隱式的,提高程式碼的重用率。

它提供了編譯期的型別安全,確保你只能把正確型別的物件放入 集合中,避免了在執行時出現ClassCastException。

1、 泛型的型別引數只能是類型別(包括自定義類),不能是簡單型別。 2、 同一種泛型可以對應多個版本(因為引數型別是不確定的),不同版本的泛型類例項是不相容的。 3、 泛型的型別引數可以有多個。 4、 泛型的引數型別可以使用extends語句,例如。習慣上稱為“有界型別”。 5、 泛型的引數型別還可以是萬用字元型別。例如Class<?> classType = Class.forName("java.lang.String");

8.1、泛型擦除 Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java位元組碼中是不包含泛型中的型別資訊的。使用泛型的時候加上的型別引數,會在編譯器在編譯的時候去掉。這個過程就稱為型別擦除。

泛型是通過型別擦除來實現的,編譯器在編譯時擦除了所有型別相關的資訊,所以在執行時不存在任何型別相關的資訊。例如 List在執行時僅用一個List來表示。這樣做的目的,是確保能和Java 5之前的版本開發二進位制類庫進行相容。你無法在執行時訪問到型別引數,因為編譯器已經把泛型型別轉換成了原始型別。

8.2、限定萬用字元 限定萬用字元對型別進行了限制。

  1. 一種是<? extends T>它通過確保型別必須是T的子類來設定型別的上界,
  2. 另一種是<? super T>它通過確保型別必須是T的父類來設定型別的下界。
  3. 另一方面表 示了非限定萬用字元,因為可以用任意型別來替代。

例如List<? extends Number>可以接受List或List。

8.3、泛型面試題 λ 你可以把List傳遞給一個接受List引數的方法嗎?

對任何一個不太熟悉泛型的人來說,這個Java泛型題目看起來令人疑惑,因為乍看起來String是一種Object,所以 List應當可以用在需要List的地方,但是事實並非如此。真這樣做的話會導致編譯錯誤。如 果你再深一步考慮,你會發現Java這樣做是有意義的,因為List可以儲存任何型別的物件包括String, Integer等等,而List卻只能用來儲存Strings。

λ Array中可以用泛型嗎? Array事實上並不支援泛型,這也是為什麼Joshua Bloch在Effective Java一書中建議使用List來代替Array,因為List可以提供編譯期的型別安全保證,而Array卻不能

λ Java中List和原始型別List之間的區別? 原始型別和帶引數型別之間的主要區別是,在編譯時編譯器不會對原始型別進行型別安全檢查,卻會對帶引數的型別進行檢 查,通過使用Object作為型別,可以告知編譯器該方法可以接受任何型別的物件,比如String或Integer。這道題的考察點在於對泛型中原始類 型的正確理解。它們之間的第二點區別是,你可以把任何帶引數的型別傳遞給原始型別List,但卻不能把List傳遞給接受 List的方法,因為會產生編譯錯誤。

List 是一個未知型別的List,而List 其實是任意型別的List。你可以把List, List賦值給List,卻不能把List賦值給 List。

9、反射 9.1、概念 JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

9.2、作用 Java反射機制主要提供了以下功能: 在執行時判斷任意一個物件所屬的類;在執行時構造任意一個類的物件;在執行時判斷任意一個類所具有的成員變數和方法;在執行時呼叫任意一個物件的方法;生成動態代理。

10、代理 代理這個詞大家肯定已經非常熟悉,因為現實中接觸的很多,其實現實中的東西恰恰可以非常形象和直觀地反映出模式的抽象過程以及本質。現在房子不是吵得熱火朝天嗎?我們就以房子為例,來撥開代理的面紗。

假設你有一套房子要賣,一種方法是你直接去網上釋出出售資訊,然後直接帶要買房子的人來看房子、過戶等一直到房子賣出去,但是可能你很忙,你沒有時間去處理這些事情,所以你可以去找中介,讓中介幫你處理這些瑣碎事情,中介實際上就是你的代理。本來是你要做的事情,現在中介幫助你一一處理,對於買方來說跟你直接交易跟同中介直接交易沒有任何差異,買方甚至可能覺察不到你的存在,這實際上就是代理的一個最大好處。

接下來我們再深入考慮一下為什麼你不直接買房子而需要中介?其實一個問題恰恰解答了什麼時候該用代理模式的問題。

原因一:你可能在外地上班,買房子的人沒法找到你直接交易。

對應到我們程式設計的時候就是:客戶端無法直接操作實際物件。那麼為什麼無法直接操作?一種情況是你需要呼叫的物件在另外一臺機器上,你需要跨越網路才能訪問,如果讓你直接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%20%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E6%8E%92%E5%BA%8F/

1、排序 排序有內部排序和外部排序,內部排序是資料記錄在記憶體中進行排序,而外部排序是因排序的資料很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。

1.1、 直接插入排序 思想:

  1. 將第一個數和第二個數排序,然後構成一個有序序列
  2. 將第三個數插入進去,構成一個新的有序序列。
  3. 對第四個數、第五個數……直到最後一個數,重複第二步。

程式碼:

  1. 首先設定插入次數,即迴圈次數,for(int i=1;i<length;i++),1個數的那次不用插入。
  2. 設定插入數和得到已經排好序列的最後一個數的位數。insertNum和j=i-1。

2、設計模式 2.1、單例設計模式 單例主要分為:懶漢式單例、餓漢式單例、登記式單例。 特點:

  1. 單例類只有一個例項
  2. 單例類必須自己建立自己的唯一例項
  3. 單例類必須給所有其他物件提供這一例項。

在計算機系統中,像執行緒池,快取、日誌物件、對話方塊、印表機等常被設計成單例。

1、 懶漢式單例: Singleton通過將構造方法限定為private避免了類在外部被例項化,在同一個虛擬機器範圍內,Singleton的唯一例項只能通過getInstance()方法訪問。(事實上,通過Java反射機制是能夠例項化構造方法為private的類的,那基本上會使所有的Java單例實現失效。

它是執行緒不安全的,併發情況下很有可能出現多個Singleton例項,要實現執行緒安全,有以下三種方式:

1.在getInstance方法上加上同步

2.雙重檢查鎖定

3.靜態內部類

這種方式對比前兩種,既實現了執行緒安全,又避免了同步帶來的效能影響。

2、 餓漢式單例:

餓漢式在建立類的同時就已經建立好了一個靜態的物件供系統使用,以後不再改變,所以天生是系統安全。

N、網路請求

其他 https://juejin.im/post/5a37171af265da432d282fd6

常用連結

  1. 2017年秋招Android面試的21個重難點
  2. https://github.com/Mr-YangCheng/ForAndroidInterview https://github.com/xiaole0310/interview-

相關文章