天天看點

中進階Android大廠面試秘籍,為你保駕護航金三銀四,直通大廠(上)前言修煉原則Android中進階

前言

當下,正面臨着近幾年來的最嚴重的網際網路寒冬,聽得最多的一句話便是:相見于江湖~🤣。縮減HC、裁員不絕于耳,大家都是人心惶惶,年前如此,年後想必肯定又是一場更為慘烈的江湖厮殺。但部落客始終相信,寒冬之中,人才更是尤為珍貴。隻要有過硬的操作和裝備,在逆風局下,同樣也能來一波收割翻盤。

部落客也是年前經曆了一番厮殺,最終拿到多家大廠的 offer。在閉關修煉的過程中,自己整理出了一套面試秘籍供自己反複研究,後來給了多位有需要的兄台,均表示相當靠譜,理應在這寒冬之中回報于社會。于是決定花點精力整理成文,讓大家能比較系統的反複學習,快速提升自己。

面試固然有技巧,但絕不是僞造與吹流弊,通過一段短時間沉下心來閉關修煉,出山收割,步入大廠,薪資翻番,豈不爽哉?🤓

修煉原則

想必大家很厭煩筆試和考察知識點。因為其實在平時實戰中,講究的是開發效率,很少會去刻意記下一些細節和深挖知識點,腦海中都是一些分散的知識點,無法系統性地關聯成網,一直處于似曾相識的狀态。、

以如此的狀态,定然是無法在面試的戰場上縱橫的。其實面試就猶如考試,大家回想下聯考之前所做的事,無非就是 了解 和 系統性關聯記憶。本秘籍的知識點較多,花點時間一個個了解并記憶後,自然也就融會貫通,無所畏懼。

由于本秘籍為了便于記憶,快速達到應試狀态,類似于複習知識大綱。知識點會盡量的精簡與提煉知識脈絡,并不去展開深入細節,面面俱到。有興趣或者有疑問的童鞋可以自行谷歌下對應知識點的詳細内容。

Android中進階

1.Activity的生命周期

主要有onCreate()、onStart()、onResume()、onPause()、onStop()、onDestroy()和onRestart()等7個方法。

啟動一個A Activity,

分别執行onCreate()、onStart()、onResume()方法。

從A Activity打開B Activity

分别執行A onPause()、B onCreate()、B onStart()、B onResume()、A onStop()方法。

關閉B Activity

分别執行B onPause()、A onRestart()、A onStart()、A onResume()、B onStop()、B onDestroy()方法。

橫豎屏切換A Activity

清單檔案中不設定android:configChanges屬性時,先銷毀onPause()、onStop()、onDestroy()再重新建立onCreate()、onStart()、onResume()方法,

設定orientation|screenSize(一定要同時出現)屬性值時,不走生命周期方法,隻會執行onConfigurationChanged()方法。

Activity之間的切換

可以看出onPause()、onStop()這兩個方法比較特殊,切換的時候onPause()方法不要加入太多耗時操作否則會影響體驗。

2.Activity的啟動過程(不要回答生命周期)

app啟動的過程有兩種情況

第一種是從桌面launcher上點選相應的應用圖示

第二種是在activity中通過調用startActivity來啟動一個新的activity。

我們建立一個新的項目,預設的根activity都是MainActivity,而所有的activity都是儲存在堆棧中的,我們啟動一個新的activity就會放在上一個activity上面,而我們從桌面點選應用圖示的時候,由于launcher本身也是一個應用,當我們點選圖示的時候,系統就會調用startActivitySately(),一般情況下,我們所啟動的activity的相關資訊都會儲存在intent中,比如action,category等等。我們在安裝這個應用的時候,系統也會啟動一個PackaManagerService的管理服務,這個管理服務會對AndroidManifest.xml檔案進行解析,進而得到應用程式中的相關資訊,比如service,activity,Broadcast等等,然後獲得相關元件的資訊。當我們點選應用圖示的時候,就會調用startActivitySately()方法,而這個方法内部則是調用startActivty(),而startActivity()方法最終還是會調用startActivityForResult()這個方法。而在startActivityForResult()這個方法。因為startActivityForResult()方法是有傳回結果的,是以系統就直接給一個-1,就表示不需要結果傳回了。而startActivityForResult()這個方法實際是通過Instrumentation類中的execStartActivity()方法來啟動activity,Instrumentation這個類主要作用就是監控程式和系統之間的互動。而在這個execStartActivity()方法中會擷取ActivityManagerService的代理對象,通過這個代理對象進行啟動activity。啟動會就會調用一個checkStartActivityResult()方法,如果說沒有在配置清單中配置有這個元件,就會在這個方法中抛出異常了。當然最後是調用的是Application.scheduleLaunchActivity()進行啟動activity,而這個方法中通過擷取得到一個ActivityClientRecord對象,而這個ActivityClientRecord通過handler來進行消息的發送,系統内部會将每一個activity元件使用ActivityClientRecord對象來進行描述,而ActivityClientRecord對象中儲存有一個LoaderApk對象,通過這個對象調用handleLaunchActivity來啟動activity元件,而頁面的生命周期方法也就是在這個方法中進行調用。

3.Activity之間的通信方式

Intent

借助類的靜态變量

借助全局變量/Application

借助外部工具

借助SharedPreference

使用Android資料庫SQLite

赤裸裸的使用File

Android剪切闆

借助Service

4.橫豎屏切換的時候,Activity 各種情況下的生命周期

分兩種情況:

1.不設定Activity的android:configChanges,或設定Activity的android:configChanges=“orientation”,或設定Activity的android:configChanges=“orientation|keyboardHidden”,切屏會重新調用各個生命周期,切橫屏時會執行一次,切豎屏時會執行一次。

橫豎屏切換造成 activity 的生命周期

onPause()-onSaveInstanceState()-onStop()-onDestroy()-onCreat()-onStart()-onRestoreInstanceState()-onResume()即會導緻 activity 的銷毀和重建 。

2.配置 android:configChanges=“orientation|keyboardHidden|screenSize”,才不會銷毀 activity,且隻調用 onConfigurationChanged方法。

onSaveInstanceState() 與onRestoreIntanceState() 資源相關的系統配置發生改變或者資源不足時(例如螢幕旋轉),目前 Activity 會銷毀,并且在 onStop 之前回調 onSaveInstanceState 儲存資料,在重新建立 Activity 的時候在onStart 之後回調 onRestoreInstanceState。其中 Bundle 資料會傳到 onCreate(不一定有資料)和 onRestoreInstanceState(一定有資料)。

使用者或者程式員主動去銷毀一個 Activity 的時候不會回調(如代碼中 finish()或使用者按下 back,不會回調),其他情況都會調用,來儲存界面資訊。

5.兩個Activity 之間跳轉時必然會執行的是哪幾個方法?

a. 正常情況下 Activity A 跳轉到 Activity B 時:

A調用 onCreate() 方法 -> onStart() 方法 -> onResume() 方法,此時 A 前台可見。當 A 跳轉到 B 時,A 調用 onPause() 方法,然後調用新的 Activity B 中的 onCreate() 方法 -> onStart() 方法 -> onResume() 方法。最後 A 再調用onStop()方法。

b. 當 Activity B 為透明主題時:

除了最後 Activity A 不調用 onStop() 方法之外,其它都和 a 中的一樣。

6.Activity狀态儲存于恢複

當 Activity 在異常情況( 系統記憶體不足或者系統配置發生了改變等 )被銷毀重建後, 在銷毀的時候 Activity 會調用 onSaveInstanceState() 方法用于儲存 Activity 相關的狀态和資料,然後在重建後的 Activity 的中我們可以通過 onCreate() 或者 onRestoreInstanceState() 方法恢複資料,這裡我們需要注意的是如果通過 onCreate() 方法恢複,那麼得先判斷它的 intent 參數 是否為空,如果在 onRestoreInstanceState() 方法恢複就不會,因為隻要 onRestoreInstanceState() 方法被調用就說明一定有資料,不會為空。Google 推薦使用 onRestoreInstanceState() 方法。

7.Fragment的生命周期方法

主要有onAttach()、onCreate()、onCreateView()、onActivityCreated()、onstart()、onResume()、onPause()、onStop()、onDestroyView()、onDestroy()、onDetach()等11個方法。

切換到該Fragment

分别執行onAttach()、onCreate()、onCreateView()、onActivityCreated()、onstart()、onResume()方法。

鎖屏

分别執行onPause()、onStop()方法。

亮屏

分别執行onstart()、onResume()方法。

覆寫切換到其他Fragment

分别執行onPause()、onStop()、onDestroyView()方法。

從其他Fragment回到之前Fragment

分别執行onCreateView()、onActivityCreated()、onstart()、onResume()方法。

8.Service生命周期?

service 啟動方式有兩種

一種是通過startService()方式進行啟動

另一種是通過bindService()方式進行啟動。

不同的啟動方式他們的生命周期是不一樣.

通過startService()這種方式啟動的service

生命周期是這樣:

調用startService() --> onCreate()–> onStartConmon()–> onDestroy()。

這種方式啟動的話,需要注意一下幾個問題

第一:當我們通過startService被調用以後,多次在調用startService(),onCreate()方法也隻會被調用一次,而onStartConmon()會被多次調用,當我們調用stopService()的時候,onDestroy()就會被調用,進而銷毀服務。

第二:當我們通過startService啟動時候,通過intent傳值,在onStartConmon()方法中擷取值的時候,一定要先判斷intent是否為null。

通過bindService()方式進行綁定,這種方式綁定service

生命周期:

bindService–>onCreate()–>onBind()–>unBind()–>onDestroy()

bindservice 這種方式進行啟動service好處是更加便利activity中操作service,如果要在activity中調用,在需要在activity擷取ServiceConnection對象,通過ServiceConnection來擷取service中内部類的類對象,然後通過這個類對象就可以調用類中的方法,當然這個類需要繼承Binder對象

9.service和activity怎麼進行資料互動?

1.通過 broadcast:通過廣播發送消息到 activitry

2.通過 Binder:通過與 activity 進行綁定

(1)添加一個繼承 Binder 的内部類,并添加相應的邏輯方法。

(2)重寫 Service 的 onBind 方法,傳回我們剛剛定義的那個内部類執行個體。

(3)Activity 中建立一個 ServiceConnection 的匿名内部類,并且 重 寫 裡 面 的 onServiceConnected 方 法 和onServiceDisconnected 方法,這兩個方法分别會在活動與服務成功綁定以及解除綁定的時候調用(在onServiceConnected方法中,我們可以得到一個剛才那個 service 的 binder 對象,通過對這個 binder 對象進行向下轉型,得到我們那個自定義的 Binder 執行個體,有了這個執行個體,做可以調用這個執行個體裡面的具體方法進行需要的操作了)。

10.Android啟動Service的兩種方式是什麼? 它們的适用情況是什麼?

如果背景服務開始後基本可以獨立運作的話,可以用startService。音樂播放器就可以這樣用。它們會一直運作直到你調用 stopSelf或者stopService。你可以通過發送Intent或者接收Intent來與正在運作的背景服務通信,但大部分時間,你隻是啟動服務并讓它獨立運作。如果你需要與背景服務通過一個持續的連接配接來比較頻繁地通信,建議使用bind()。比如你需要定位服務不停地把更新後的地理位置傳給UI。Binder比Intent開發起來複雜一些,但如果真的需要,你也隻能使用它。

startService:

  生命周期與調用者不同。啟動後若調用者未調用stopService而直接退出,Service仍會運作。

bindService:

  生命周期與調用者綁定,調用者一旦退出,Service就會調用unBind->onDestroy

11.Broadcast注冊方式與差別

此處延伸:什麼情況下用動态注冊

Broadcast廣播,注冊方式主要有兩種.

第一種是靜态注冊,也可成為常駐型廣播,這種廣播需要在Androidmanifest.xml中進行注冊,這中方式注冊的廣播,不受頁面生命周期的影響,即使退出了頁面,也可以收到廣播這種廣播一般用于想開機自啟動啊等等,由于這種注冊的方式的廣播是常駐型廣播,是以會占用CPU的資源。

第二種是動态注冊,而動态注冊的話,是在代碼中注冊的,這種注冊方式也叫非常駐型廣播,受到生命周期的影響,退出頁面後,就不會收到廣播,我們通常運用在更新UI方面。這種注冊方式優先級較高。最後需要解綁,否會會記憶體洩露

廣播是分為有序廣播和無序廣播。

11.講解一下Context

Context是一個抽象基類。在翻譯為上下文,也可以了解為環境,是提供一些程式的運作環境基礎資訊。Context下有兩個子類,ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實作類。而ContextWrapper又有三個直接的子類, ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity,是以Activity和Service以及Application的Context是不一樣的,隻有Activity需要主題,Service不需要主題。

Context一共有三種類型,分别是Application、Activity和Service。這三個類雖然分别各種承擔着不同的作用,但它們都屬于Context的一種,而它們具體Context的功能則是由ContextImpl類去實作的,是以在絕大多數場景下,Activity、Service和Application這三種類型的Context都是可以通用的。

不過有幾種場景比較特殊,比如啟動Activity,還有彈出Dialog。出于安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啟動必須要建立在另一個Activity的基礎之上,也就是以此形成的傳回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),是以在這種場景下,我們隻能使用Activity類型的Context,否則将會出錯。

getApplicationContext()和getApplication()方法得到的對象都是同一個application對象,隻是對象的類型不一樣。

Context數量 = Activity數量 + Service數量 + 1 (1為Application)

12.Service的onCreate回調在UI線程中嗎?

Service生命周期的各個回調和其他的應用元件一樣,是跑在主線程中,會影響

到你的UI操作或者阻塞主線程中的其他事情

13.請介紹下AsyncTask的内部實作,适用的場景是?

AsyncTask内部也是Handler機制來完成的,隻不過Android提供了執行架構來提供線程池來執行相應地任務,因為線程池的大小問題,是以AsyncTask隻應該用來執行耗時時間較短的任務,比如HTTP請求,大規模的下載下傳和資料庫的更改不适用于AsyncTask,因為會導緻線程池堵塞,沒有線程來執行其他的任務,導緻的情形是會發生,AsyncTask根本執行不了的問題。

由于篇幅有限,隻能分享部分面試題,更多面試題及答案可以我的【Github】閱讀下載下傳哦~無償分享給大家,算是一個感恩回饋吧
中進階Android大廠面試秘籍,為你保駕護航金三銀四,直通大廠(上)前言修煉原則Android中進階

14.說下你所知道的設計模式與使用場景

建造者模式:

觀察者模式:

代理模式:

門面模式:

單例模式:

生産者消費者模式:

15.Java語言的特點與OOP思想

這個通過對比來描述,比如面向對象和面向過程的對比,針對這兩種思想的對比,還可以舉個開發中的例子,比如播放器的實作,面向過程的實作方式就是将播放視訊的這個功能分解成多個過程,比如,加載視訊位址,擷取視訊資訊,初始化解碼器,選擇合适的解碼器進行解碼,讀取解碼後的幀進行視訊格式轉換和音頻重采樣,然後讀取幀進行播放,這是一個完整的過程,這個過程中不涉及類的概念,而面向對象最大的特點就是類,封裝繼承和多态是核心,同樣的以播放器為例,一面向對象的方式來實作,将會針對每一個功能封裝出一個對象,每一個功能對應一個對象,由這個對象來完成對應的功能,并且遵循單一職責原則,一個對象隻做它相關的事情

##16.說下java中的線程建立方式,線程池的工作原理。

Java中有三種建立線程的方式,或者說四種

1.繼承Thread類實作多線程

2.實作Runnable接口

3.實作Callable接口

4.通過線程池

17.線程池的工作原理:

線程池可以減少建立和銷毀線程的次數,進而減少系統資源的消耗,當一個任務送出到線程池時.

a. 首先判斷核心線程池中的線程是否已經滿了,如果沒滿,則建立一個核心線程執行任務,否則進入下一步.

b. 判斷工作隊列是否已滿,沒有滿則加入工作隊列,否則執行下一步.

c. 判斷線程數是否達到了最大值,如果不是,則建立非核心線程執行任務,否則執行飽和政策,預設抛出異常.

18.說下handler原理

Handler,Message,looper和MessageQueue構成了安卓的消息機制,handler建立後可以通過sendMessage将消息加入消息隊列,然後looper不斷的将消息從MessageQueue中取出來,回調到Hander的handleMessage方法,進而實作線程的通信。

從兩種情況來說,第一在UI線程建立Handler,此時我們不需要手動開啟looper,因為在應用啟動時,在ActivityThread的main方法中就建立了一個目前主線程的looper,并開啟了消息隊列,消息隊列是一個無限循環,為什麼無限循環不會ANR?因為可以說,應用的整個生命周期就是運作在這個消息循環中的,安卓是由事件驅動的,Looper.loop不斷的接收處理事件,每一個點選觸摸或者Activity每一個生命周期都是在Looper.loop的控制之下的,looper.loop一旦結束,應用程式的生命周期也就結束了。

我們可以想想什麼情況下會發生ANR?

第一,事件沒有得到處理

第二,事件正在處理,但是沒有及時完成,而對事件進行處理的就是looper,是以隻能說事件的處理如果阻塞會導緻ANR,而不能說looper的無限循環會ANR

另一種情況就是在子線程建立Handler,此時由于這個線程中沒有預設開啟的消息隊列,是以我們需要手動調用looper.prepare(),并通過looper.loop開啟消息

主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,并且往管道檔案寫資料,主線程即被喚醒,從管道檔案讀取資料,主線程被喚醒隻是為了讀取消息,當消息讀取完畢,再次睡眠。是以loop的循環并不會對CPU性能有過多的消耗。

19.記憶體洩漏的場景和解決辦法

1.非靜态内部類的靜态執行個體

非靜态内部類會持有外部類的引用,如果非靜态内部類的執行個體是靜态的,就會長期的維持着外部類的引用,組織被系統回收,解決辦法是使用靜态内部類

2.多線程相關的匿名内部類和非靜态内部類

匿名内部類同樣會持有外部類的引用,如果線上程中執行耗時操作就有可能發生記憶體洩漏,導緻外部類無法被回收,直到耗時任務結束,解決辦法是在頁面退出時結束線程中的任務

3.Handler記憶體洩漏

Handler導緻的記憶體洩漏也可以被歸納為非靜态内部類導緻的,Handler内部message是被存儲在MessageQueue中的,有些message不能馬上被處理,存在的時間會很長,導緻handler無法被回收,如果handler是非靜态的,就會導緻它的外部類無法被回收,

解決辦法是

1.使用靜态handler,外部類引用使用弱引用處理

2.在退出頁面時移除消息隊列中的消息

4.Context導緻記憶體洩漏

根據場景确定使用Activity的Context還是Application的Context,因為二者生命周期不同,對于不必須使用Activity的Context的場景(Dialog),一律采用Application的Context,單例模式是最常見的發生此洩漏的場景,比如傳入一個Activity的Context被靜态類引用,導緻無法回收

5.靜态View導緻洩漏

使用靜态View可以避免每次啟動Activity都去讀取并渲染View,但是靜态View會持有Activity的引用,導緻無法回收

解決辦法是在Activity銷毀的時候将靜态View設定為null(View一旦被加載到界面中将會持有一個Context對象的引用,在這個例子中,這個context對象是我們的Activity,聲明一個靜态變量引用這個View,也就引用了activity)

6.WebView導緻的記憶體洩漏

WebView隻要使用一次,記憶體就不會被釋放,是以WebView都存在記憶體洩漏的問題,通常的解決辦法是為WebView單開一個程序,使用AIDL進行通信,根據業務需求在合适的時機釋放掉

7.資源對象未關閉導緻

如Cursor,File等,内部往往都使用了緩沖,會造成記憶體洩漏,一定要確定關閉它并将引用置為null

8.集合中的對象未清理

集合用于儲存對象,如果集合越來越大,不進行合理的清理,尤其是入股集合是靜态的

9.Bitmap導緻記憶體洩漏

bitmap是比較占記憶體的,是以一定要在不使用的時候及時進行清理,避免靜态變量持有大的bitmap對象

10.監聽器未關閉

很多需要register和unregister的系統服務要在合适的時候進行unregister,手動添加的listener也需要及時移除

20.如何避免OOM?

1.使用更加輕量的資料結構:

如使用ArrayMap/SparseArray替代HashMap,HashMap更耗記憶體,因為它需要額外的執行個體對象來記錄Mapping操作,SparseArray更加高效,因為它避免了Key Value的自動裝箱,和裝箱後的解箱操作

2.枚舉的使用

可以用靜态常量或者注解@IntDef替代

3.Bitmap優化:

a.尺寸壓縮:

通過InSampleSize設定合适的縮放

b.顔色品質:

設定合适的format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差異.

c.inBitmap:

使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經存在的記憶體區域,新解碼的Bitmap會嘗試去使用之前那張Bitmap在Heap中所占據的pixel data記憶體區域,而不是去問記憶體重新申請一塊區域來存放Bitmap。利用這種特性,即使是上千張的圖檔,也隻會僅僅隻需要占用螢幕所能夠顯示的圖檔數量的記憶體大小,但複用存在一些限制,具體展現在:在Android 4.4之前隻能重用相同大小的Bitmap的記憶體,而Android 4.4及以後版本則隻要後來的Bitmap比之前的小即可。使用inBitmap參數前,每建立一個Bitmap對象都會配置設定一塊記憶體供其使用,而使用了inBitmap參數後,多個Bitmap可以複用一塊記憶體,這樣可以提高性能

d.StringBuilder替代String: 在有些時候,代碼中會需要使用到大量的字元串拼接的操作,這種時候有必要考慮使用StringBuilder來替代頻繁的“+”

e.避免在類似onDraw這樣的方法中建立對象,因為它會迅速占用大量記憶體,引起頻繁的GC`甚至記憶體抖動

f.減少記憶體洩漏也是一種避免OOM的方法

21.onRestart的調用場景

(1)按下home鍵之後,然後切換回來,會調用onRestart()。

(2)從本Activity跳轉到另一個Activity之後,按back鍵傳回原來Activity,會調用onRestart();

(3)從本Activity切換到其他的應用,然後再從其他應用切換回來,會調用onRestart();

22.如何實作程序保活

a: Service設定成START_STICKY kill後會被重新開機(等待5秒左右),重傳Intent,保持與重新開機前一樣

b: 通過 startForeground将程序設定為前台程序, 做前台服務,優先級和前台應用一個級别,除非在系統記憶體非常缺,否則此程序不會被kill

c: 雙程序Service: 讓2個程序互相保護對方,其中一個Service被清理後,另外沒被清理的程序可以立即重新開機程序

d: 用C編寫守護程序(即子程序) : Android系統中目前程序(Process)fork出來的子程序,被系統認為是兩個不同的程序。當父程序被殺死的時候,子程序仍然可以存活,并不受影響(Android5.0以上的版本不可行)聯系廠商,加入白名單

e.鎖屏狀态下,開啟一個一像素Activity

23.說下冷啟動與熱啟動是什麼,差別,如何優化,使用場景等。

app冷啟動:

當應用啟動時,背景沒有該應用的程序,這時系統會重新建立一個新的程序配置設定給該應用, 這個啟動方式就叫做冷啟動(背景不存在該應用程序)。冷啟動因為系統會重新建立一個新的程序配置設定給它,是以會先建立和初始化Application類,再建立和初始化MainActivity類(包括一系列的測量、布局、繪制),最後顯示在界面上。

app熱啟動:

當應用已經被打開, 但是被按下傳回鍵、Home鍵等按鍵時回到桌面或者是其他程式的時候,再重新打開該app時, 這個方式叫做熱啟動(背景已經存在該應用程序)。

熱啟動因為會從已有的程序中來啟動,是以熱啟動就不會走Application這步了,而是直接走MainActivity(包括一系列的測量、布局、繪制),是以熱啟動的過程隻需要建立和初始化一個MainActivity就行了,而不必建立和初始化Application

冷啟動的流程

當點選app的啟動圖示時,安卓系統會從Zygote程序中fork建立出一個新的程序配置設定給該應用,之後會依次建立和初始化Application類、建立MainActivity類、加載主題樣式Theme中的windowBackground等屬性設定給MainActivity以及配置Activity層級上的一些屬性、再inflate布局、當onCreate/onStart/onResume方法都走完了後最後才進行contentView的measure/layout/draw顯示在界面上

冷啟動的生命周期簡要流程:

Application構造方法 –>attachBaseContext()–>onCreate –>Activity構造方法 –>onCreate() –> 配置主體中的背景等操作 –>onStart()–> onResume()–> 測量、布局、繪制顯示

冷啟動的優化主要是視覺上的優化,解決白屏問題,提高使用者體驗,是以通過上面冷啟動的過程。能做的優化如下:

1、減少onCreate()方法的工作量

2、不要讓Application參與業務的操作

3、不要在Application進行耗時操作

4、不要以靜态變量的方式在Application儲存資料

5、減少布局的複雜度和層級

6、減少主線程耗時

24.為什麼冷啟動會有白屏黑屏問題?

原因在于加載主題樣式Theme中的windowBackground等屬性設定給MainActivity發生在inflate布局當onCreate/onStart/onResume方法之前,而windowBackground背景被設定成了白色或者黑色,是以我們進入app的第一個界面的時候會造成先白屏或黑屏一下再進入界面。解決思路如下

1.給他設定windowBackground背景跟啟動頁的背景相同,如果你的啟動頁是張圖檔那麼可以直接給windowBackground這個屬性設定該圖檔那麼就不會有一閃的效果了

<!--   <style name="AppFirstColdStartTheme" >

        //設定透明背景
        <item name="android:windowIsTranslucent">true</item>
      //  自定義背景顔色,可以先讓app 顯示其他背景logo等
        <item name="android:windowBackground">@drawable/main_bg_green</item>
       // 設定無标題
        <item name="android:windowNoTitle">true</item>
    </style>
           

2.采用世面的處理方法,設定背景是透明的,給人一種延遲啟動的感覺。,将背景顔色設定為透明色,這樣當使用者點選桌面APP圖檔的時候,并不會"立即"進入APP,而且在桌面上停留一會,其實這時候APP已經是啟動的了,隻是我們心機的把Theme裡的windowBackground的顔色設定成透明的,強行把鍋甩給了手機應用廠商(手機反應太慢了啦)

<!--   <style name="AppFirstColdStartTheme" >

        //設定透明背景
        <item name="android:windowIsTranslucent">true</item>
      //  自定義背景顔色,可以先讓app 顯示其他背景logo等
        <item name="android:windowIsTranslucent">true</item>
       // 設定無标題
        <item name="android:windowNoTitle">true</item>
    </style>
           

3.以上兩種方法是在視覺上顯得更快,但其實隻是一種表象,讓應用啟動的更快,有一種思路,将Application中的不必要的初始化動作實作懶加載,比如,在SpashActivity顯示後再發送消息到Application,去初始化,這樣可以将初始化的動作放在後邊,縮短應用啟動到使用者看到界面的時間

由于篇幅有限,隻能分享部分面試題,更多面試題及答案可以我的【Github】閱讀下載下傳哦~無償分享給大家,算是一個感恩回饋吧
中進階Android大廠面試秘籍,為你保駕護航金三銀四,直通大廠(上)前言修煉原則Android中進階

25.Android中的線程有那些,原理與各自特點

AsyncTask,HandlerThread,IntentService

AsyncTask原理:

内部是 Handler和兩個線程池實作的,Handler用于将線程切換到主線程,兩個線程池一個用于任務的排隊,一個用于執行任務,當AsyncTask執行execute方法時會封裝出一個FutureTask對象,将這個對象加入隊列中,如果此時沒有正在執行的任務,就執行它,執行完成之後繼續執行隊列中下一個任務,執行完成通過Handler将事件發送到主線程。AsyncTask必須在主線程初始化,因為内部的Handler是一個靜态對象,在AsyncTask類加載的時候他就已經被初始化了。在Android3.0開始,execute方法串行執行任務的,一個一個來,3.0之前是并行執行的。如果要在3.0上執行并行任務,可以調用executeOnExecutor方法

HandlerThread原理:

繼承自Thread,start開啟線程後,會在其run方法中會通過Looper建立消息隊列并開啟消息循環,這個消息隊列運作在子線程中,是以可以将HandlerThread中的Looper執行個體傳遞給一個Handler,進而保證這個Handler的handleMessage方法運作在子線程中,Android中使用HandlerThread的一個場景就是IntentService

IntentService原理:

繼承自Service,它的内部封裝了HandlerThread和Handler,可以執行耗時任務,同時因為它是一個服務,優先級比普通線程高很多,是以更适合執行一些高優先級的背景任務,HandlerThread底層通過Looper消息隊列實作的,是以它是順序的執行每一個任務。可以通過Intent的方式開啟IntentService,IntentService通過handler将每一個intent加入HandlerThread子線程中的消息隊列,通過looper按順序一個個的取出并執行,執行完成後自動結束自己,不需要開發者手動關閉

26.ANR的原因

1.耗時的網絡通路

2.大量的資料讀寫

3.資料庫操作

4.硬體操作(比如camera)

5.調用thread的join()方法、sleep()方法、wait()方法或者等待線程鎖的時候

6.service binder的數量達到上限

7.system server中發生WatchDog ANR

8.service忙導緻逾時無響應

9.其他線程持有鎖,導緻主線程等待逾時

10.其它線程終止或崩潰導緻主線程一直等待

27.介紹下實作一個自定義view的基本流程

1.自定義View的屬性 編寫attr.xml檔案。

  2.在layout布局檔案中引用,同時引用命名空間。

  3.在View的構造方法中獲得我們自定義的屬性 ,在自定義控件中進行讀取(構造方法拿到attr.xml檔案值)。

  4.重寫onMesure。

  5.重寫onDraw。

28.Android中touch事件的傳遞機制是怎樣的?

1.Touch事件傳遞的相關API有dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent。

  2.Touch事件相關的類有View.ViewGroup.Activity。

  3.Touch事件會被封裝成MotionEvent對象,該對象封裝了手勢按下.移動.松開等動作。

  4.Touch事件通常從Activity#dispatchTouchEvent發出,隻要沒有被消費,會一直往下傳遞,到最底層的View。

  5.如果Touch事件傳遞到的每個View都不消費事件,那麼Touch事件會反向向上傳遞,最終交由Activity#onTouchEvent處理。

  6.onInterceptTouchEvent為ViewGroup特有,可以攔截事件。

  7.Down事件到來時,如果一個View沒有消費該事件,那麼後續的MOVE/UP事件都不會再給它。

29.Android多線程的實作方式有哪些?

Thread

   可以與Loop和Handler共用建立消息處理隊列

AsyncTask

  可以作為線程池并行處理多任務

30.LinearLayout的onMeasure過程

LinearLayout會先做一個簡單橫縱方向判斷

需要注意的是在每次對child測量完畢後,都會調用child.getMeasuredHeight()/getMeasuredWidth()擷取該子視圖最終的高度,并将這個高度添加到mTotalLength中。

但是getMeasuredHeight暫時避開了lp.weight>0且高度為0子View,因為後面會将把剩餘高度按weight配置設定給相應的子View。是以可以得出以下結論:

(1)如果我們在LinearLayout中不使用weight屬性,将隻進行一次measure的過程。(如果使用weight屬性,則周遊一次wiew測量後,再周遊一次view測量)

(2)如果使用了weight屬性,LinearLayout在第一次測量時擷取所有子View的高度,之後再将剩餘高度根據weight加到weight>0的子View上。由此可見,weight屬性對性能是有影響的。

1)RelativeLayout慢于LinearLayout是因為它會讓子View調用2次measure過程,而LinearLayout隻需一次,但是有weight屬性存在時,LinearLayout也需要兩次measure。

2)在不響應層級深度的情況下,使用Linearlayout而不是RelativeLayout。

31.談談對接口與回調的了解

接口回調就是指: 可以把使用某一接口的類建立的對象的引用賦給該接口聲明的接口變量,那麼該接口變量就可以調用被類實作的接口的方法。實際上,當接口變量調用被類實作的接口中的方法時,就是通知相應的對象調用接口的方法,這一過程稱為對象功能的接口回調。

32.Android中View,SurfaceView和GLSurfaceView

View:顯示視圖,内置畫布,提供圖形繪制函數,觸屏事件,按鍵事件函數;必須在UI線程中更新畫面,速度較慢。

SurfaceView:基于View視圖進行拓展的視圖類,更适合2D遊戲的開發;是View的子類,類似雙緩機制,在新的線程中更新畫面,是以重新整理界面速度比View快。(雙緩機制:即前台緩存和背景緩存,背景緩存計算場景、産生畫面,前台緩存顯示背景緩存已畫好的畫面。)

GLSurfaceView:基于SurfaceView視圖再次進行擴充的視圖類,專用于3D遊戲開發的視圖;是SurfaceView的子類,OpenGL專用。(OpenGL:是一個開放的三維圖形軟體包。)

33.請介紹下ContentProvider 是如何實作資料共享的?

一個程式可以通過實作一個Content provider的抽象接口将自己的資料完全暴露出去,而且Content providers是以類似資料庫中表的方式将資料暴露。Content providers存儲和檢索資料,通過它可以讓所有的應用程式通路到,這也是應用程式之間唯一共享資料的方法。

要想使應用程式的資料公開化,可通過2種方法:建立一個屬于你自己的Content provider或者将你的資料添加到一個已經存在的Content provider中,前提是有相同資料類型并且有寫入Content provider的權限。

如何通過一套标準及統一的接口擷取其他應用程式暴露的資料?

Android提供了ContentResolver,外界的程式可以通過ContentResolver接口通路ContentProvider提供的資料。

34.Handler機制和底層實作

上面一共出現了幾種類,ActivityThread,Handler,MessageQueue,Looper,msg(Message),對這些類作簡要介紹:

ActivityThread:程式的啟動入口,該類就是我們說的主線程,它對Looper進行操作的。

Handler:字面意思是操控者,該類有比較重要的地方,就是通過handler來發送消息(sendMessage)到

MessageQueue和 操作控件的更新(handleMessage)。handler下面持有這MessageQueue和Looper的對象。

MessageQueue:字面意思是消息隊列,就是封裝Message類。對Message進行插入和取出操作。

Message:這個類是封裝消息體并被發送到MessageQueue中的,給類是通過連結清單實作的,其好處友善MessageQueue的插入和取出操作。還有一些字段是(int what,Object obj,int arg1,int arg2)。what是使用者定義的消息和代碼,以便接收者(handler)知道這個是關于什麼的。obj是用來傳輸任意對象的,arg1和arg2是用來傳遞一些簡單的整數類型的。

先擷取looper,如果沒有就建立

建立過程:

ActivityThread 執行looperMainPrepare(),該方法先執行個體化MessageQueue對象,然後執行個體化Looper對象,封裝mQueue和主線程,把自己放入ThreadLocal中

再執行loop()方法,裡面會重複死循環執行讀取MessageQueue。接着ActivityThread 執行Looper對象中的loop()方法)

此時調用sendMessage()方法,往MessageQueue中添加資料,其取出消息隊列中的handler,執行dispatchMessage(),進而執行handleMessage(),Message的資料結構是基于連結清單的

35.執行Looper為什麼要無限循環?

主線程中如果沒有looper進行循環,那麼主線程一運作完畢就會退出。那麼我們還能運作APP嗎,顯然,這是不可能的,Looper主要就是做消息循環,然後由Handler進行消息分發處理,一旦退出消息循環,那麼你的應用也就退出了。

36.主線程中的Looper.loop()一直無限循環為什麼不會造成ANR?

主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,并且往管道檔案寫資料,主線程即被喚醒,從管道檔案讀取資料,主線程被喚醒隻是為了讀取消息,當消息讀取完畢,再次睡眠。是以loop的循環并不會對CPU性能有過多的消耗。

由于篇幅有限,隻能分享部分面試題,更多面試題及答案可以我的【Github】閱讀下載下傳哦~無償分享給大家,算是一個感恩回饋吧
中進階Android大廠面試秘籍,為你保駕護航金三銀四,直通大廠(上)前言修煉原則Android中進階

繼續閱讀