Android面試必看之基礎(chǔ)知識篇(持續(xù)更新中……)

面試專題我放在git上了,地址Github 歡迎fork然后一起更新

Activity的面試基礎(chǔ)詳解

1,activity的四種狀態(tài)

running、paused、stopped、killed

2,Android的進程優(yōu)先級

前臺 / 可見 / 服務(wù) / 后臺 / 空

3,Android的任務(wù)棧,啟動模式

棧:先進后出

  • standard (sidadende) 默認(rèn)模式,每次啟動會創(chuàng)建新實例

  • singleTop 棧頂復(fù)用模式 ,新activity在棧頂,就復(fù)用,并回調(diào)onNewIntent方法;如果存在且不再棧頂,那么Activity會被重新創(chuàng)建;
    使用場景:瀏覽器的書簽,通訊消息聊天界面

  • singleTask 棧內(nèi)復(fù)用模式,檢查整個棧內(nèi)如果有那就復(fù)用,并回調(diào)onNewIntent方法;此模式啟動 Activity A,系統(tǒng)首先會尋找是否存在 A 想要的任務(wù)棧,如果不存在,就會重新創(chuàng)建一個任務(wù)棧,然后把創(chuàng)建好 A 的實例放到棧中;
    使用場景:某個Activity當(dāng)做主界面的時候

  • singleInstance 單實例模式,加強版singleTask,該Activity只能單獨位于一個任務(wù)棧且該棧只有它一個
    使用場景:比如瀏覽器BrowserActivity很耗內(nèi)存,很多app都會要調(diào)用它,這樣就可以把該Activity設(shè)置成單例模式。比如:鬧鐘鬧鈴。

4,Scheme跳轉(zhuǎn)協(xié)議

通過自定義scheme協(xié)議,方便跳轉(zhuǎn)app中的各個頁面
通過scheme協(xié)議,服務(wù)器可以定制化告訴App跳轉(zhuǎn)到那個頁面
通過通知欄消息定制化跳轉(zhuǎn)頁面,通過H5頁面跳轉(zhuǎn)頁面等。

5,生命周期

啟動: onCreate (用戶不可見) -> onStart (用戶可見但不在前臺在后臺,無法與用戶交互) -> onResume (用戶可見,在前臺并獲得焦點)
點擊Home回主界面(Activity不可見) -> onPause -> onStop
再次回到原Activity -> onRestart -> onStart -> onResume
退出Activity -> onPause -> onStop -> onDestory

  • onCreate:創(chuàng)建,常用來初始化工作,比如調(diào)用 setContentView 加載界面布局資源,初始化 Activity 所需數(shù)據(jù)等;
  • onDestroy:表即將被銷毀,這是 Activity 生命周期中的最后一個回調(diào),常做回收工作、資源釋放;
  • onStart:啟動,此時 Activity 可見但不在前臺,還處于后臺,無法與用戶交互;
  • onStop:即將停止,可以做一些稍微重量級的回收工作,比如注銷廣播接收器、關(guān)閉網(wǎng)絡(luò)連接等,同樣不能太耗時;
  • onResume:獲得焦點,此時 Activity 可見且在前臺并開始活動,這是與 onStart 的區(qū)別所在;
  • onPause:正在停止,可做存儲數(shù)據(jù)、停止動畫等工作,但是不能太耗時,因為會影響到新 Activity的顯示,onPause 必須先執(zhí)行完,新 Activity 的 onResume 才會執(zhí)行;
  • onRestart: 重新啟動,一般情況下,當(dāng)前Acitivty 從不可見重新變?yōu)榭梢姇r,OnRestart 就會被調(diào)用;

6,Activity A 啟動另一個 Activity B 會調(diào)用哪些方法?如果 B 是透明主題的又或則是個 DialogActivity 呢 ?

Activity A 啟動另一個 Activity B,回調(diào)如下
Activity A 的 onPause() → Activity B 的 onCreate() →onStart() → onResume() → Activity A 的 onStop();
如果 B 是透明主題又或則是個 DialogActivity,則不會回調(diào) A 的onStop;

7,說下 onSaveInstanceState()方法的作用 ? 何時會被調(diào)用?

異常情況下(系統(tǒng)配置發(fā)生改變時導(dǎo)致 Activity被殺死并重新創(chuàng)建、資源內(nèi)存不足導(dǎo)致低優(yōu)先級的 Activity 被殺死)

  • 系統(tǒng)會調(diào)用 onSaveInstanceState 來保存當(dāng)前 Activity 的狀態(tài),此方法調(diào)用在 onStop 之前,與 onPause 沒有既定的時序關(guān)系;
  • 當(dāng) Activity 被重建后,系統(tǒng)會調(diào)用 onRestoreInstanceState,并且把 onSaveInstanceState方法所保存的 Bundle 對象同時傳參給onRestoreInstanceState和 onCreate(),因此可以通過這兩個方法判斷Activity 是否被重建,調(diào)用在 onStart 之后;


    image.png

8,了解哪些 Activity 常用的標(biāo)記位 Flags?

  • FLAG_ACTIVITY_NEW_TASK : 對應(yīng) singleTask 啟動模式,其效果和在 XML 中指定該啟動模式相同;

  • FLAG_ACTIVITY_SINGLE_TOP : 對應(yīng) singleTop 啟動模式,其效果和在 XML 中指定該啟動模式相同;

  • FLAG_ACTIVITY_CLEAR_TOP : 具有此標(biāo)記位的 Activity,當(dāng)它啟動時,在同一個任務(wù)棧中所有位于它上面的 Activity 都要出棧。

這個標(biāo)記位一般會和 singleTask 模式一起出現(xiàn),在這種情況下,被啟動 Activity 的實例如果已經(jīng)存在,那么系統(tǒng)就會回調(diào)onNewIntent。如果被啟動的 Activity 采用 standard 模式啟動,那么它以及連同它之上的 Activity 都要出棧,系統(tǒng)會創(chuàng)建新的Activity 實例并放入棧中;

  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS : 具有這個標(biāo)記的Activity 不會出現(xiàn)在歷史 Activity 列表中;

9,說下 Activity 跟 window,view 之間的關(guān)系?

Activity 創(chuàng)建時通過 attach()初始化了一個 Window 也就是PhoneWindow,一個 PhoneWindow 持有一個 DecorView 的實例,

DecorView 本身是一個 FrameLayout,繼承于 View,Activty 通過setContentView 將 xml 布局控件不斷 addView()添加到 View 中,

最終顯示到 Window 于我們交互;

Activity剪窗花的人(控制的);Window窗戶(承載的一個模型);View窗花(要顯示的視圖View);LayoutInflater剪刀---將布局(圖紙)剪成窗花。

這個問題真的很不好回答。所以這里先來個算是比較恰當(dāng)?shù)谋扔鱽硇稳菹滤鼈兊年P(guān)系吧。Activity像一個工匠(控制單元),Window像窗戶(承載模型),View像窗花(顯示視圖)LayoutInflater像剪刀,Xml配置像窗花圖紙。

1:Activity構(gòu)造的時候會初始化一個Window,準(zhǔn)確的說是PhoneWindow。
2:這個PhoneWindow有一個“ViewRoot”,這個“ViewRoot”是一個View或者說ViewGroup,是最初始的根視圖。
3:“ViewRoot”通過addView方法來一個個的添加View。比如TextView,Button等
4:這些View的事件監(jiān)聽,是由WindowManagerService來接受消息,并且回調(diào)Activity函數(shù)。比如onClickListener,onKeyDown等。

10,橫豎屏切換的 Activity 生命周期變化?

  • 不設(shè)置 Activity 的 android:configChanges 時,切屏?xí)N毀當(dāng)前Activity,然后重新加載調(diào)用各個生命周期,切橫屏?xí)r會執(zhí)行一次,切豎屏?xí)r會執(zhí)行兩次; onPause()→onStop()→onDestory()→onCreate()→onStart()→onResume()

  • 設(shè)置 Activity 的 android:configChanges="orientation",經(jīng)過機型測試

     在 Android5.1 即 API 23 級別下,切屏還是會重新調(diào)用各個生命周期,切橫、豎屏?xí)r只會執(zhí)行一次
     在 Android 9 即 API 28 級別下,切屏不會重新調(diào)用各個生命周期,只會執(zhí)行 onConfigurationChanged 方法
    

官方糾正后,原話如下

如果您的應(yīng)用面向 Android 3.2 即 API 級別 13 或更高級別(按照 minSdkVersion 和 targetSdkVersion屬性所聲明的級別),則還應(yīng)聲明 "screenSize" 配置,因為當(dāng)設(shè)備在橫向與縱向之間切換時,該配置也會發(fā)生變化。即便是在 Android 3.2 或更高版本的設(shè)備上運行,此配置變更也不會重新啟動Activity

  • 設(shè)置 Activity 的android:configChanges="orientation|keyboardHidden|screenSize"時,機型測試通過,切屏不會重新調(diào)用各個生命周期,只會執(zhí)行 onConfigurationChanged 方法;

11,如何啟動其他應(yīng)用的 Activity?

在保證有權(quán)限訪問的情況下,通過隱式 Intent 進行目標(biāo)Activity 的 IntentFilter 匹配,原則是:
一個 intent 只有同時匹配某個 Activity 的 intentfilter 中的 action、category、data 才算完全匹配,才能啟動該 Activity;
一個 Activity 可以有多個 intent-filter,一個 intent只要成功匹配任意一組 intent-filter,就可以啟動該Activity;

12,Activity 的啟動過程?

  1. 點擊 App 圖標(biāo)后通過 startActivity 遠程調(diào)用到 AMS 中,AMS 中將新啟動的 activity 以 activityrecord 的結(jié)構(gòu)壓入 activity棧中,并通過遠程 binder 回調(diào)到原進程,使得原進程進入 pause狀態(tài),原進程 pause 后通知 AMS 我 pause 了
  2. 此時 AMS 再根據(jù)棧中 Activity 的啟動 intent 中的 flag 是否含有 new_task 的標(biāo)簽判斷是否需要啟動新進程,啟動新進程通過startProcessXXX 的函數(shù)
  3. 啟動新進程后通過反射調(diào)用 ActivityThread 的 main 函數(shù),main函數(shù)中調(diào)用 looper.prepar 和 lopper.loop 啟動消息隊列循環(huán)機制。最后遠程告知 AMS 我啟動了。AMS 回調(diào)handleLauncherAcitivyt 加載 activity。在handlerLauncherActivity 中會通過反射調(diào)用 Application 的onCreate 和 activity 的 onCreate 以及通過handleResumeActivity 中反射調(diào)用 Activity 的 onResume


    image.png

13,Activity之間傳值有哪幾種方法?

1,通過Intent直接傳參數(shù)
2,通過Bundle封裝數(shù)據(jù)傳指
3,通過startActivityforResult回調(diào),接口回調(diào)
4,通過發(fā)送廣播的形式,數(shù)據(jù)需要序列化
5,通過存儲介質(zhì),比如數(shù)據(jù)庫,sharedpreference,文件等
6,通過事件總線EventBus形式

14,設(shè)備橫豎屏切換的時候,接下來會發(fā)生什么?

1、不設(shè)置Activity的android:configChanges時,切屏?xí)匦抡{(diào)用各個生命周期,切橫屏?xí)r會執(zhí)行一次,切豎屏?xí)r會執(zhí)行兩次

2、設(shè)置Activity的android:configChanges=”orientation”時,切屏還是會重新調(diào)用各個生命周期,切橫、豎屏?xí)r只會執(zhí)行一次

3、設(shè)置Activity的android:configChanges=”orientation|keyboardHidden”時,切屏不會重新調(diào)用各個生命周期,只會執(zhí)行onConfigurationChanged方法

Fragment的面試詳解

1,F(xiàn)ragment為什么被稱為第五大組件

Fragment一開始是用于平板的擴展頁面,后面全部應(yīng)用于Activity內(nèi)部切換
Fragment有生命周期,切依附于Activity

2,F(xiàn)ragment的加載到Activity的2種方式

  • 添加Fragment到Activity的布局文件xml中
  • 動態(tài)在Activity中添加Fragment

3,F(xiàn)ragmentPagerAdapter與FragmentStatePagerAdapter的區(qū)別?

二者都繼承 PagerAdapter

  • 前者適用于頁面較少,數(shù)據(jù)相對靜態(tài)的頁面,F(xiàn)ragment數(shù)量較少的情況,其destroyItem方法里是detach,并不會回收內(nèi)存
  • 后者適用于頁面較多,數(shù)據(jù)動態(tài)性較大,占用內(nèi)存較多,多Fragment的情況,其destoryItem里面是remove,會釋放內(nèi)存;頁面不可見會移除Fragment釋放資源

viewpager中,fragment嵌套fragment的時候必須使用FragmentStatePageAdapter才起作用

4,F(xiàn)ragment的生命周期

Fragment生命周期比Activity多5個方法,F(xiàn)ragment里沒有onRestart
onAttach,onDetach,onCreateView,onDestoryView
onViewCreated和onActivityCreated

image.png

5,F(xiàn)ragment的通信

  • 在frgment中調(diào)用Activity中的方法,getActivity
  • 在Activity中調(diào)用Fragment中的方法,接口回調(diào)
  • 在Fragment中調(diào)用Fragment中的方法,findFragmentById

6,getFragmentManager、getSupportFragmentManager 、getChildFragmentManager 之間的區(qū)別?

  • getFragmentManager()所得到的是所在 fragment 的父容器的管理器, getChildFragmentManager()所得到的是在fragment 里面子容器的管理器, 如果是 fragment 嵌套fragment,那么就需要利用getChildFragmentManager();
  • 因為 Fragment 是 3.0 Android 系統(tǒng) API 版本才出現(xiàn)的組件,所以 3.0 以上系統(tǒng)可以直接調(diào)用getFragmentManager()來獲取 FragmentManager()對象,而 3.0 以下則需要調(diào)用 getSupportFragmentManager() 來間接獲??;

7,F(xiàn)ragment中的add與replace的區(qū)別(Fragment重疊)

  • add 不會重新初始化fragment,replace每次都會。所以如果在fragment生命周期內(nèi)獲取數(shù)據(jù),使用replace會重復(fù)獲取。
  • 添加相同的 fragment 時,replace 不會有任何變化,add會報 IllegalStateException 異常
  • replace 先 remove 掉相同 id 的所有 fragment,然后在add 當(dāng)前的這個 fragment,而 add 是覆蓋前一個fragment。所以如果使用 add 一般會伴隨 hide()和show(),避免布局重疊;
  • 使用 add,如果應(yīng)用放在后臺,或以其他方式被系統(tǒng)銷毀,再打開時,hide()中引用的 fragment 會銷毀,所以依然會出現(xiàn)布局重疊 bug,可以使用 replace 或使用 add時,添加一個 tag 參數(shù);

Service面試詳解

參考:https://blog.csdn.net/javazejian/article/details/52709857

1,Service是什么?

Service是一個一種可以在后臺執(zhí)行長時間運行操作而沒有用戶界面的應(yīng)用組件,無法做耗時的操作

2,Service和Thread的區(qū)別

service運行在主線程中,無法做耗時的操作

thread是線程的最小單元,一般指耗時線程

3,Service啟動方式

  • startService
  1. 定義一個類繼承Service
  2. 在Manifest.xml文件中配置該service
  3. 使用context的startService方法啟動
  4. 不再使用時,調(diào)用stopService方法停止該服務(wù)
  • bindService
  1. 創(chuàng)建bindservice服務(wù)端,繼承自service并在類中,創(chuàng)建一個實現(xiàn)IBinder接口的實例對象并提供公共方法給客戶端調(diào)用
  2. 從onBind回調(diào)方法返回此Binder實例
  3. 在客戶端中,從onServiceConnected回調(diào)方法接受Binder,并私用提供的方法調(diào)用綁定服務(wù)。

4,IntentService面試詳解

如果有一個任務(wù),可以分成很多個子任務(wù),需要按照順序來完成,如果需要放到一個服務(wù)中完成,那么使用IntentService是最好的選擇。

Service是運行在主線程當(dāng)中的,所以在service里面編寫耗時的操作代碼,則會卡主線程會ANR。為了解決這樣的問題,谷歌引入了IntentService

  • 一種特殊的service,繼承自service并且本身就是一個抽象類
  • 內(nèi)部通過HandlerThread和Handler實現(xiàn)異步操作,所以它可以做耗時的操作,內(nèi)部適中也是通過handler異步
  • 本質(zhì)就是一個封裝了HandlerThred和handler的異步框架
  • 它創(chuàng)建了一個獨立的工作線程來處理所有一個一個的intent,創(chuàng)建了一個工作隊列來逐個發(fā)送intent給onHandlerIntent()
  • 不需要主動調(diào)用stopSelf()來結(jié)束服務(wù),因為源碼里面自己實現(xiàn)了自動關(guān)閉
  • 默認(rèn)實現(xiàn)了onBind()返回的null,默認(rèn)實現(xiàn)的onStartCommand()的目的是將intent插入到工作隊列

總結(jié):使用IntentService的好處有哪些。首先,省去了手動開線程的麻煩;第二,不用手動停止service;第三,由于設(shè)計了工作隊列,可以啟動多次---startService(),但是只有一個service實例和一個工作線程。一個一個熟悉怒執(zhí)行。

5,Service的生命周期

image.png

BroadcastReceiver面試詳解

1,廣播定義(類似觀察者模式)

BroadcastReceiver是一種全局監(jiān)聽器,用來實現(xiàn)系統(tǒng)中不同組件之間的通信。有時候也會用來作為傳輸少量而且發(fā)送頻率低的數(shù)據(jù),但是如果數(shù)據(jù)的發(fā)送頻率比較高或者數(shù)量比較大就不建議用廣播接收者來接收了,因為這樣的效率很不好,因為BroadcastReceiver接收數(shù)據(jù)的開銷還是比較大的。

2,使用場景

  • 同一個app具有多個進程的不同組件之間的消息通訊
  • 不同app之間的組件之間消息通訊

eg:

1)App全局監(jiān)聽:在AndroidManifest中靜態(tài)注冊的廣播接收器,一般我們在收到該消息后,需要做一些相應(yīng)的動作,而這些動作與當(dāng)前App的組件,比如Activity或者Service的是否運行無關(guān),比如我們在集成第三方Push SDK時,一般都會添加一個靜態(tài)注冊的BroadcastReceiver來監(jiān)聽Push消息,當(dāng)有Push消息過來時,會在后臺做一些網(wǎng)絡(luò)請求或者發(fā)送通知等等。

2)組件局部監(jiān)聽:這種主要是在Activity或者Service中使用registerReceiver()動態(tài)注冊的廣播接收器,因為當(dāng)我們收到一些特定的消息,比如網(wǎng)絡(luò)連接發(fā)生變化時,我們可能需要在當(dāng)前Activity頁面給用戶一些UI上的提示,或者將Service中的網(wǎng)絡(luò)請求任務(wù)暫停。所以這種動態(tài)注冊的廣播接收器適合特定組件的特定消息處理。

3,廣播的種類

  • Normal Broadcast :Context.sendBroadcast 普通的廣播,完全異步,可以在同一時刻被所有接收者接收到,消息傳遞效率高且無法中斷廣播的傳播
  • System Broadcast : Context.sendOrderedBroadcast 有序廣播; 發(fā)送有序廣播后,廣播接收者將按預(yù)先聲明的優(yōu)先級依次接收Broadcast。優(yōu)先級高的優(yōu)先接收到廣播,而在其onReceiver()執(zhí)行過程中,廣播不會傳播到下一個接收者,此時當(dāng)前的廣播接收者可以abortBroadcast()來終止廣播繼續(xù)向下傳播,也可以將intent中的數(shù)據(jù)進行修改設(shè)置,然后將其傳播到下一個廣播接收者。 sendOrderedBroadcast(intent, null);//發(fā)送有序廣播
  • 粘性廣播:sendStickyBroadcast()來發(fā)送該類型的廣播信息,這種的廣播的最大特點是,當(dāng)粘性廣播發(fā)送后,最后的一個粘性廣播會滯留在操作系統(tǒng)中。如果在粘性廣播發(fā)送后的一段時間里,如果有新的符合廣播的動態(tài)注冊的廣播接收者注冊,將會收到這個廣播消息,雖然這個廣播是在廣播接收者注冊之前發(fā)送的,另外一點,對于靜態(tài)注冊的廣播接收者來說,這個等同于普通廣播。
  • Local Broadcast : 只在自身App內(nèi)傳播 本地廣播

4,實現(xiàn)廣播Receiver

靜態(tài)注冊:注冊完成就一直運行,Menifest文件中
動態(tài)注冊:跟隨Activity的生命周期,必須要在onDestory中銷毀,否則內(nèi)存泄露,因為可以一直接收到

5,內(nèi)部實現(xiàn)機制

自定義廣播接受者,并復(fù)寫onRecvice方法
通過Binder機制向AMS(Activity Manager Service)進行注冊
廣播發(fā)送者通過Binder機制向AMS發(fā)送廣播;
AMS查找符合相應(yīng)條件(IntentFilter/Permission等)的BroadcastReceiver,將廣播相應(yīng)的消息循環(huán)隊列中
消息循環(huán)拿到廣播后,回調(diào)onReceive方法

6,本地廣播LocalBroadcastManager詳解

關(guān)于優(yōu)勢:

  • 使用它發(fā)送的廣播只能在自身的App內(nèi)傳播,不用擔(dān)心泄露隱私數(shù)據(jù)
  • 其他App無法對你的App發(fā)送該廣播,不用擔(dān)心安全漏洞
  • 比系統(tǒng)全局廣播高效,為甚?系統(tǒng)廣播需要加入更多的廣播池,加載流程多

為何它高效

  • 主要因為它內(nèi)部是通過Handler實現(xiàn)的,它的sendBroadcast方法就是通過handler發(fā)送一個message實現(xiàn)的。
  • 系統(tǒng)廣播是通過Binder實現(xiàn)比handler要復(fù)雜低效,用handler實現(xiàn)保障了其他應(yīng)用就無法向本應(yīng)用發(fā)廣播,而本應(yīng)用內(nèi)發(fā)廣播不會離開應(yīng)用本身
  • 內(nèi)部協(xié)作靠2個Map集合,mReceivers和mActions,當(dāng)然還有一個List集合mPendingBroadcast,主要就是存儲待接收的廣播對象

本地廣播可以用來做什么? 比如,做一個殺不死的服務(wù)-監(jiān)聽火的App,比如微信,友盟和極光的廣播來啟動自己

本地廣播是不能用靜態(tài)注冊的;靜態(tài)注冊的目的--程序停止后也能監(jiān)聽

Android中的動畫

1,幾種動畫?

幀動畫:指通過指定每一幀的圖片和播放時間,有序的進行播放而形成動畫效果,比如想聽的律動條。

補間動畫:指通過指定View的初始狀態(tài)、變化時間、方式,通過一系列的算法去進行圖形變換,從而形成動畫效果,主要有Alpha、Scale、Translate、Rotate四種效果。注意:只是在視圖層實現(xiàn)了動畫效果,并沒有真正改變View的屬性,比如滑動列表,改變標(biāo)題欄的透明度。

屬性動畫:在Android3.0的時候才支持,通過不斷的改變View的屬性,不斷的重繪而形成動畫效果。相比于視圖動畫,View的屬性是真正改變了。比如view的旋轉(zhuǎn),放大,縮小。

2,Android 動畫框架實現(xiàn)原理

傳統(tǒng)的動畫框架:View.startAnimation();

弊端:移動后不能點擊。原因?跟實現(xiàn)機制有關(guān)系。

所有的透明度、旋轉(zhuǎn)、平移、縮放動畫,都是在view不斷刷新調(diào)用draw的情況下實現(xiàn)的。

調(diào)用的canvas.translate(xxx),canvas.scaleX(xxx)…. Xxx:matrix像素矩陣來控制動畫的數(shù)據(jù)。記得看源碼,結(jié)合多只縮放的demo看源碼。

3,屬性動畫實現(xiàn)原理

工作原理:在一定時間間隔內(nèi),通過不斷對值進行改變,并不斷將該值賦給對象的屬性,從而實現(xiàn)該對象在該屬性上的動畫效果。

  • ValueAnimator:通過不斷控制值的變化(初始值->結(jié)束值),將值手動賦值給對象的屬性,再不斷調(diào)用View的invalidate()方法,去不斷onDraw重繪view,達到動畫的效果。

主要的三種方法:

a) ValueAnimator.ofInt(int values):估值器是整型估值器IntEaluator

b) ValueAnimator.ofFloat(float values):估值器是浮點型估值器FloatEaluator

c) ValueAnimator.ofObject(ObjectEvaluator, start, end):將初始值以對象的形式過渡到結(jié)束值,通過操作對象實現(xiàn)動畫效果,需要實現(xiàn)Interpolator接口,自定義估值器

估值器TypeEvalutor,設(shè)置動畫如何從初始值過渡到結(jié)束值的邏輯。插值器(Interpolator)決定值的變化模式(勻速、加速等);估值器(TypeEvalutor)決定值的具體變化數(shù)值。

// 自定義估值器,需要實現(xiàn)TypeEvaluator接口

public class ObjectEvaluator implements TypeEvaluator{   
// 復(fù)寫evaluate(),在evaluate()里寫入對象動畫過渡的邏輯     
@Override       
public Object evaluate(float fraction, Object startValue, Object endValue) {           // 參數(shù)說明         
        // fraction:表示動畫完成度(根據(jù)它來計算當(dāng)前動畫的值)        
       // startValue、endValue:動畫的初始值和結(jié)束值        
         ... 
        // 寫入對象動畫過渡的邏輯             
      return value;          
     // 返回對象動畫過渡的邏輯計算后的值   
  } }
  • ObjectAnimator:直接對對象的屬性值進行改變操作,從而實現(xiàn)動畫效果

ObjectAnimator繼承自ValueAnimator類,底層的動畫實現(xiàn)機制還是基本值的改變。它是不斷控制值的變化,再不斷自動賦給對象的屬性,從而實現(xiàn)動畫效果。這里的自動賦值,是通過調(diào)用對象屬性的set/get方法進行自動賦值,屬性動畫初始值如果有就直接取,沒有則調(diào)用屬性的get()方法獲取,當(dāng)值更新變化時,通過屬性的set()方法進行賦值。每次賦值都是調(diào)用view的postInvalidate()/invalidate()方法不斷刷新視圖(實際調(diào)用了onDraw()方法進行了重繪視圖)。

//Object 需要操作的對象; propertyName 需要操作的對象的屬性; values動畫初始值&結(jié)束值,
//如果是兩個值,則從a->b值過渡,如果是三值,則從a->b->c

ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String propertyName, float ...values);

如果采用ObjectAnimator類實現(xiàn)動畫,操作的對象的屬性必須有g(shù)et()和set()方法。

其他用法:

1)AnimatorSet組合動畫

AnimatorSet.play(Animator anim)   :播放當(dāng)前動畫 
AnimatorSet.after(long delay)   :將現(xiàn)有動畫延遲x毫秒后執(zhí)行 
AnimatorSet.with(Animator anim)   :將現(xiàn)有動畫和傳入的動畫同時執(zhí)行 
AnimatorSet.after(Animator anim)   :將現(xiàn)有動畫插入到傳入的動畫之后執(zhí)行 
AnimatorSet.before(Animator anim) :  將現(xiàn)有動畫插入到傳入的動畫之前執(zhí)行
 
  1. ViewPropertyAnimator直接對屬性操作,View.animate()返回的是一個ViewPropertyAnimator對象,之后的調(diào)用方法都是基于該對象的操作,調(diào)用每個方法返回值都是它自身的實例

View.animate().alpha(0f).x(500).y(500).setDuration(500).setInterpolator()

3)設(shè)置動畫監(jiān)聽

Animation.addListener(new AnimatorListener() {           
         @Override           
          public void onAnimationStart(Animation animation) {//動畫開始時執(zhí)行           }            
          @Override           
          public void onAnimationRepeat(Animation animation) {//動畫重復(fù)時執(zhí)行           }          
          @Override           
          public void onAnimationCancel()(Animation animation) {//動畫取消時執(zhí)行           }           
          @Override           
          public void onAnimationEnd(Animation animation) {//動畫結(jié)束時執(zhí)行           }      
 });
image.png

4,補間動畫實現(xiàn)原理

主要有四種AlpahAnimation\ ScaleAnimation\ RotateAnimation\ TranslateAnimation四種,對透明度、縮放、旋轉(zhuǎn)、位移四種動畫。
在調(diào)用View.startAnimation時,先調(diào)用View.setAnimation(Animation)方法給自己設(shè)置一個Animation對象,再調(diào)用invalidate來重繪自己。在View.draw(Canvas, ViewGroup, long)方法中進行了getAnimation(), 并調(diào)用了drawAnimation(ViewGroup, long, Animation, boolean)方法,此方法調(diào)用Animation.getTranformation()方法,再調(diào)用applyTranformation()方法,該方法中主要是對Transformation.getMatrix().setTranslate/setRotate/setAlpha/setScale來設(shè)置相應(yīng)的值,這個方法系統(tǒng)會以60FPS的頻率進行調(diào)用。
具體是在調(diào)Animation.start()方法中會調(diào)用animationHandler.start()方法,從而調(diào)用了scheduleAnimation()方法,這里會調(diào)用mChoreographer.postCallback(Choregrapher.CALLBACK_ANIMATION, this, null)放入事件隊列中,等待doFrame()來消耗事件。

當(dāng)一個 ChildView 要重畫時,它會調(diào)用其成員函數(shù) invalidate() 函數(shù)將通知其 ParentView 這個 ChildView 要重畫,這個過程一直向上遍歷到 ViewRoot,當(dāng) ViewRoot 收到這個通知后就會調(diào)用ViewRoot 中的 draw 函數(shù)從而完成繪制。View::onDraw() 有一個畫布參數(shù) Canvas, 畫布顧名思義就是畫東西的地方,Android 會為每一個 View 設(shè)置好畫布,View 就可以調(diào)用 Canvas 的方法,比如:drawText, drawBitmap, drawPath 等等去畫內(nèi)容。每一個 ChildView 的畫布是由其 ParentView 設(shè)置的,ParentView 根據(jù) ChildView 在其內(nèi)部的布局來調(diào)整 Canvas,其中畫布的屬性之一就是定義和 ChildView 相關(guān)的坐標(biāo)系,默認(rèn)是橫軸為 X 軸,從左至右,值逐漸增大,豎軸為 Y 軸,從上至下,值逐漸增大。


image.png

Android 補間動畫就是通過 ParentView 來不斷調(diào)整 ChildView 的畫布坐標(biāo)系來實現(xiàn)的,在ParentView的dispatchDraw方法會被調(diào)用。

dispatchDraw() { 
      .... 
      Animation a = ChildView.getAnimation() 
      Transformation tm = a.getTransformation(); 
      Use tm to set ChildView's Canvas; Invalidate(); 
      .... 
}

這里有兩個類:Animation 和 Transformation,這兩個類是實現(xiàn)動畫的主要的類,Animation 中主要定義了動畫的一些屬性比如開始時間、持續(xù)時間、是否重復(fù)播放等,這個類主要有兩個重要的函數(shù):getTransformation 和 applyTransformation,在 getTransformation 中 Animation 會根據(jù)動畫的屬性來產(chǎn)生一系列的差值點,然后將這些差值點傳給 applyTransformation,這個函數(shù)將根據(jù)這些點來生成不同的 Transformation,Transformation 中包含一個矩陣和 alpha 值,矩陣是用來做平移、旋轉(zhuǎn)和縮放動畫的,而 alpha 值是用來做 alpha 動畫的(簡單理解的話,alpha 動畫相當(dāng)于不斷變換透明度或顏色來實現(xiàn)動畫),調(diào)用 dispatchDraw 時會調(diào)用 getTransformation 來得到當(dāng)前的 Transformation。某一個 View 的動畫的繪制并不是由他自己完成的而是由它的父 view 完成。

1)補間動畫TranslateAnimation,View位置移動了,可是點擊區(qū)域還在原來的位置,為什么?

View在做動畫是,根據(jù)動畫時間的插值,計算出一個Matrix,不停的invalidate,在onDraw中的Canvas上使用這個計算出來的Matrix去draw view的內(nèi)容。某個view的動畫繪制并不是由它自己完成,而是由它的父view完成,使它的父view畫布進行了移動,而點擊時還是點擊原來的畫布。使得它看起來變化了。

數(shù)據(jù)庫面試相關(guān)

1,數(shù)據(jù)庫的操作類有那些,如何導(dǎo)入外部數(shù)據(jù)庫?

讀懂題目。如果碰到問題比較模糊的時候可以適當(dāng)問問面試官。

配合面試官來面試:面試是一個相互了解的過程,要充分利用面試的題目和時間把自己的能力和技術(shù)展現(xiàn)出來,面試官能夠看到你的真實技術(shù)。

1) 使用數(shù)據(jù)庫的方式有哪些?

(1) openOrCreateDatabase(String path);

(2) 繼承SqliteOpenHelper類對數(shù)據(jù)庫及其版本進行管理(onCreate,onUpgrade)

當(dāng)在程序當(dāng)中調(diào)用這個類的方法getWritableDatabase()或者getReadableDatabase();的時候才會打開數(shù)據(jù)庫。如果當(dāng)時沒有數(shù)據(jù)庫文件的時候,系統(tǒng)就會自動生成一個數(shù)據(jù)庫。

2) 操作的類型:增刪改查CRUD

直接操作SQL語句:SQliteDatabase.execSQL(sql);

面向?qū)ο蟮牟僮鞣绞剑篠QLiteDatabase.insert(table, nullColumnHack, ContentValues);

如何導(dǎo)入外部數(shù)據(jù)庫?

一般外部數(shù)據(jù)庫文件可能放在SD卡或者res/raw或者assets目錄下面。

寫一個DBManager的類來管理,數(shù)據(jù)庫文件搬家,先把數(shù)據(jù)庫文件復(fù)制到”/data/data/包名/databases/”目錄下面,然后通過db.openOrCreateDatabase(db文件),打開數(shù)據(jù)庫使用。

我上一個項目就是這么做的,由于app上架之前就有一些初始數(shù)據(jù)需要內(nèi)置,也會碰到數(shù)據(jù)的升級等問題,我是這么做的…… 同時我碰到最有意思的問題就是關(guān)于數(shù)據(jù)庫并發(fā)操作的問題,比如:多線程操作數(shù)據(jù)庫的時候,我采取的是封裝使用互斥鎖來解決……

Webview面試詳解

1,常見的坑

  • android api 16以及以前的版本存在遠程代碼執(zhí)行安全漏洞,主要是因為程序沒有正確的限制使用Webview.addJavascriptInterface方法,

遠程攻擊者可以通過使用Java Reflection Api利用該漏洞執(zhí)行任意Java對象的方法,16后面的版本改了之后申明@JavascriptInterace注解就可以了

  • Webview在布局文件中的使用,webvew卸載其他容器中時
  • JsBridge ,調(diào)用原生相互通訊
  • webviewClient.onPageFinished(會跳轉(zhuǎn)其他url的時候不斷調(diào)用) -> WebChromeClient.onProgressChanged(優(yōu)先調(diào)用)
  • 后臺耗電(需要在onDestory里把webview 銷毀掉)
  • Webview硬件加速導(dǎo)致頁面渲染問題;只能通過暫時關(guān)閉加速

2,內(nèi)存泄露問題

  • 獨立進程,涉及到了進程間的通訊(推薦)
  • 動態(tài)添加webview,對傳入的webview中使用的Context使用弱引用,動態(tài)添加webview意思在布局創(chuàng)建個ViewGroup用來放置Webview,Activity創(chuàng)建時add進來,在Activity停止時remove掉

Binder面試詳解

1,Binder機制的簡單理解

在Android系統(tǒng)的Binder機制中,是有Client,Service,ServiceManager,Binder驅(qū)動程序組成的,其中Client,service,Service Manager運行在用戶空間,Binder驅(qū)動程序是運行在內(nèi)核空間的。而Binder就是把這4種組件粘合在一塊的粘合劑,其中核心的組件就是Binder驅(qū)動程序,Service Manager提供輔助管理的功能,而Client和Service正是在Binder驅(qū)動程序和Service Manager提供的基礎(chǔ)設(shè)施上實現(xiàn)C/S 之間的通信。其中Binder驅(qū)動程序提供設(shè)備文件/dev/binder與用戶控件進行交互,

Client、Service,Service Manager通過open和ioctl文件操作相應(yīng)的方法與Binder驅(qū)動程序進行通信。而Client和Service之間的進程間通信是通過Binder驅(qū)動程序間接實現(xiàn)的。而Binder Manager是一個守護進程,用來管理Service,并向Client提供查詢Service接口的能力。

2,為什么使用Binder(已有的跨進程都不適合Android)

  • Android使用的Linux內(nèi)核擁有非常多的跨進程通訊機制(管道,消息隊列,信號,信號量,共享內(nèi)存,socket )

擴展:https://blog.csdn.net/qq_26626709/article/details/52206067

  • 性能 (相對Socket更高效)
  • 安全(跟socket的只一個url就鏈接的不安全相比,binder支持雙方做調(diào)用時身份校驗)

3,binder通信模型

通訊錄,serviceManager,binder驅(qū)動(類似電話基站)

image.png

怎么調(diào)用?(SM = ServiceManager)

先去serviceManager注冊一個方法,然后通過binder驅(qū)動來轉(zhuǎn)換成代理對象返回給客戶端完成注冊。

image.png

4,到底什么是Binder?

通常意義下,Binder指的是一種通信機制

對于Server進程來說,Binder指的是Binder本地對象

對于Client來說,Binder指的是Binder代理對象

對于傳輸過程而言,Binder是可以進行垮進程傳遞的對象

Android中實現(xiàn)IBinder這個接口就可以垮進程使用

5,AIDL解決了什么問題?

AIDL的全稱:Android Interface Definition Language,安卓接口定義語言。

由于Android系統(tǒng)中的進程之間不能共享內(nèi)存,所以需要提供一些機制在不同的進程之間進行數(shù)據(jù)通信。

遠程過程調(diào)用:RPC—Remote Procedure Call。 安卓就是提供了一種IDL的解決方案來公開自己的服務(wù)接口。

AIDL:可以理解為雙方的一個協(xié)議合同。雙方都要持有這份協(xié)議---文本協(xié)議 xxx.aidl文件

(安卓內(nèi)部編譯的時候會將aidl協(xié)議翻譯生成一個xxx.java文件---代理模式:Binder驅(qū)動有關(guān)的,Linux底層通訊有關(guān)的。)

在系統(tǒng)源碼里面有大量用到aidl,比如系統(tǒng)服務(wù)。電視機頂盒系統(tǒng)開發(fā)。你的服務(wù)要暴露給別的開發(fā)者來使用。

6,Android開發(fā)中何時使用多進程?使用多進程的好處是什么?

即時通訊或者社交應(yīng)用,webview可以單獨給進程

在啟動一個不可見的輕量級私有進程,在后臺收發(fā)消息,或者做一些耗時的事情,或者開機啟動這個進程,然后做監(jiān)聽等。還有就是防止主進程被殺守護進程,守護進程和主進程之間相互監(jiān)視,有一方被殺就重新啟動它。

7,Android中進程間通信有那些實現(xiàn)方式?

Intent,Binder(AIDL),Messenger,BroadcastReceiver

8,binder的內(nèi)存拷貝過程

相比其他的IPC通信,比如消息機制、共享內(nèi)存、管道、信號量等,Binder僅需一次內(nèi)存拷貝,即可讓目標(biāo)進程讀取到更新數(shù)據(jù),同共享內(nèi)存一樣相當(dāng)高效,其他的IPC通信機制大多需要2次內(nèi)存拷貝。Binder內(nèi)存拷貝的原理為:進程A為Binder客戶端,在IPC調(diào)用前,需將其用戶空間的數(shù)據(jù)拷貝到Binder驅(qū)動的內(nèi)核空間,由于進程B在打開Binder設(shè)備(/dev/binder)時,已將Binder驅(qū)動的內(nèi)核空間映射(mmap)到自己的進程空間,所以進程B可以直接看到Binder驅(qū)動內(nèi)核空間的內(nèi)容改動

Handler和AsyncTask面試詳解

1,什么是Handler?

Handler是線程的消息通訊的橋梁,主要用來發(fā)送消息及處理消息。

handler通過發(fā)送和處理Message和Runnable對象來關(guān)聯(lián)相對應(yīng)線程的MessageQueue

  • 可以讓對應(yīng)的Message和Runnable在未來的某個時間點進行相應(yīng)處理
  • 讓自己想要處理的耗時操作放在子線程,讓更新UI的操作放在主線程
image.png

2,Handler的使用方法

  • post(Runnable)其實底層調(diào)用的還是sendMessage,主要是系統(tǒng)自己封裝了
  • sendMessage(message)

3,Handler機制原理

image.png

handler,它的作用就是實現(xiàn)線程之間的通信。

handler整個流程中,主要有四個對象,handler,Message,MessageQueue,Looper。當(dāng)應(yīng)用創(chuàng)建的時候,就會在主線程中創(chuàng)建handler對象,

我們通過要傳送的消息保存到Message中,handler通過調(diào)用sendMessage方法將Message發(fā)送到MessageQueue中,Looper對象就會不斷的調(diào)用loop()方法

不斷的從MessageQueue中取出Message交給handler進行處理。從而實現(xiàn)線程之間的通信。

4,解決Handler的內(nèi)存泄露

原因:靜態(tài)內(nèi)部類持有外部類的匿名引用,導(dǎo)致外部activity無法釋放

解決:handler內(nèi)部持有外部activity的弱引用,并把handler改為靜態(tài)內(nèi)部類,mHandler.removeCallback

5,什么是AsyncTask

本質(zhì)上就是一個封裝了線程池和handler的異步框架,無法做耗時操作

怎么使用:三個參數(shù)(),5個方法(onPreExecute前期操作,doInBackground,onPostExecute)

6,AsyncTask的機制原理

它本質(zhì)上是一個靜態(tài)的線程池,它派生出來的子類可以實現(xiàn)不同的異步任務(wù),這些任務(wù)都是提交到靜態(tài)的線程池中執(zhí)行。

線程池中的工作線程執(zhí)行doInBackground(mParams)方法執(zhí)行異步任務(wù)

當(dāng)任務(wù)狀態(tài)改變之后,工作線程會向UI線程發(fā)送消息,AsyncTask內(nèi)部的InternalHandler想要這些消息,并調(diào)用相關(guān)的回調(diào)函數(shù)

7,AsyncTask的注意事項

內(nèi)存泄露:跟handler一樣

生命周期:不調(diào)用cancel,無法銷毀,可能導(dǎo)致崩潰

8,HandlerThread面試詳解,背景,它是什么,有什么優(yōu)缺點?

開啟Thread子線程進行耗時操作,多次創(chuàng)建和銷毀線程是很耗系統(tǒng)資源的,為了解決這個問題,google搞了一個HandlerThread;

它就是一個線程類,繼承了Thread,有內(nèi)部Looper對象,可以進行l(wèi)ooper循環(huán),通過獲取HandlerThread的looper對象傳遞給Handler對象,

可以在handlerMessage方法中執(zhí)行異步任務(wù)。

  • 優(yōu)點:不會有堵塞,減少了性能的消耗
  • 缺陷:不能同時進行多任務(wù)的處理,需要等待,處理效率低

與線程池注重并發(fā)不同,HandlerThread是一個串行隊列,HandlerThread背后只有一個線程。

9,HandlerThread源碼解析

10,Handler、 Thread 和 HandlerThread 的差別

View 的繪制與分發(fā),自定義等

1,view樹的繪制流程

measure( 測量大?。?> layout(安置位置)-> draw (繪制)自上而下進行遍歷

2,measure的重要方法

View的measure過程由ViewGroup傳遞而來,在調(diào)用View.measure方法之前,會首先根據(jù)View自身的LayoutParams和父布局的MeasureSpec確定子view的MeasureSpec,然后將view寬高對應(yīng)的measureSpec傳遞到measure方法中,那么子view的MeasureSpec獲取規(guī)則是怎樣的?分幾種情況進行說明

  • 父布局是EXACTLY模式:

    a.子view寬或高是個確定值,那么子view的size就是這個確定值,mode是EXACTLY(是不是說子view寬高可以超過父view?見下一個)

    b.子view寬或高設(shè)置為match_parent,那么子view的size就是占滿父容器剩余空間,模式就是EXACTLY

    c.子view寬或高設(shè)置為wrap_content,那么子view的size就是占滿父容器剩余空間,不能超過父容器大小,模式就是AT_MOST

  • 父布局是AT_MOST模式:

a.子view寬或高是個確定值,那么子view的size就是這個確定值,mode是EXACTLY

b.子view寬或高設(shè)置為match_parent,那么子view的size就是占滿父容器剩余空間,不能超過父容器大小,模式就是AT_MOST

c.子view寬或高設(shè)置為wrap_content,那么子view的size就是占滿父容器剩余空間,不能超過父容器大小,模式就是AT_MOST

  • 父布局是UNSPECIFIED模式:

a.子view寬或高是個確定值,那么子view的size就是這個確定值,mode是EXACTLY

b.子view寬或高設(shè)置為match_parent,那么子view的size就是0,模式就是UNSPECIFIED

c.子view寬或高設(shè)置為wrap_content,那么子view的size就是0,模式就是UNSPECIFIED

獲取到寬高的MeasureSpec后,傳入view的measure方法中來確定view的寬高,這個時候還要分情況

1.當(dāng)MeasureSpec的mode是UNSPECIFIED,此時view的寬或者高要看view有沒有設(shè)置背景,如果沒有設(shè)置背景,就返回設(shè)置的minWidth或minHeight,這兩個值如果沒有設(shè)置默認(rèn)就是0,如果view設(shè)置了背景,就取minWidth或minHeight和背景這個drawable固有寬或者高中的最大值返回

2.當(dāng)MeasureSpec的mode是AT_MOST和EXACTLY,此時view的寬高都返回從MeasureSpec中獲取到的size值,這個值的確定見上邊的分析。因此如果要通過繼承view實現(xiàn)自定義view,一定要重寫onMeasure方法對wrap_conten屬性做處理,否則,他的match_parent和wrap_content屬性效果就是一樣的

measure(ViewGroup.LayoutParams,MeasureSpec測量規(guī)格,如何測量,三種模式(父容器,固定大小,子容器))

  • UNSPECIFIED:不對View大小做限制,如:ListView,ScrollView
  • EXACTLY:確切的大小,如:100dp或者march_parent
  • AT_MOST:大小不可超過某數(shù)值,如:wrap_content

onMeasure(2個參數(shù),寬和高)

setMeasuredDimension(設(shè)置測量的大小,一定會調(diào)用,寬和高)

3,layout方法

view中的onLayout方法是抽象方法,viewgroup繼承view必須實現(xiàn)onLayout方法

layout方法的作用是用來確定view本身的位置,onLayout方法用來確定所有子元素的位置,當(dāng)ViewGroup的位置確定之后,它在onLayout中會遍歷所有的子元素并調(diào)用其layout方法,在子元素的layout方法中onLayout方法又會被調(diào)用。layout方法的流程是,首先通過setFrame方法確定view四個頂點的位置,然后view在父容器中的位置也就確定了,接著會調(diào)用onLayout方法,確定子元素的位置,onLayout是個空方法,需要繼承者去實現(xiàn)。

getMeasuredHeight和getHeight方法有什么區(qū)別?getMeasuredHeight(測量高度)形成于view的measure過程,getHeight(最終高度)形成于layout過程,在有些情況下,view需要measure多次才能確定測量寬高,在前幾次的測量過程中,得出的測量寬高有可能和最終寬高不一致,但是最終來說,還是會相同,有一種情況會導(dǎo)致兩者值不一樣,如下,此代碼會導(dǎo)致view的最終寬高比測量寬高大100px

public void layout(int l,int t,int r, int b){
  super.layout(l,t,r+100,b+100);
}

4,draw

View的繪制過程遵循如下幾步:

a.繪制背景 background.draw(canvas)
b.繪制自己(onDraw)
c.繪制children(dispatchDraw)
d.繪制裝飾(onDrawScrollBars)

View繪制過程的傳遞是通過dispatchDraw來實現(xiàn)的,它會遍歷所有的子元素的draw方法,如此draw事件就一層一層的傳遞下去了

ps:view有一個特殊的方法setWillNotDraw,如果一個view不需要繪制內(nèi)容,即不需要重寫onDraw方法繪制,可以開啟這個標(biāo)記,系統(tǒng)會進行相應(yīng)的優(yōu)化。默認(rèn)情況下,View沒有開啟這個標(biāo)記,默認(rèn)認(rèn)為需要實現(xiàn)onDraw方法繪制,當(dāng)我們繼承ViewGroup實現(xiàn)自定義控件,并且明確知道不需要具備繪制功能時,可以開啟這個標(biāo)記,如果我們重寫了onDraw,那么要顯示的關(guān)閉這個標(biāo)記

子view寬高可以超過父view?能

1.android:clipChildren = "false" 這個屬性要設(shè)置在父 view 上。代表其中的子View 可以超出屏幕。

2.子view 要有具體的大小,一定要比父view 大 才能超出。比如 父view 高度 100px 子view 設(shè)置高度150px。子view 比父view大,這樣超出的屬性才有意義。(高度可以在代碼中動態(tài)賦值,但不能用wrap_content / match_partent)。

3.對父布局還有要求,要求使用linearLayout(反正我用RelativeLayout 是不行)。你如果必須用其他布局可以在需要超出的view上面套一個linearLayout 外面再套其他的布局。

4.最外面的布局如果設(shè)置的padding 不能超出

5,自定義view需要注意的幾點

1.讓view支持wrap_content屬性,在onMeasure方法中針對AT_MOST模式做專門處理,否則wrap_content會和match_parent效果一樣(繼承ViewGroup也同樣要在onMeasure中做這個判斷處理)

if(widthMeasureSpec == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST){   
      setMeasuredDimension(200,200); // wrap_content情況下要設(shè)置一個默認(rèn)值,最終的值需要計算得到剛好包裹內(nèi)容的寬高值 
}else if(widthMeasureSpec == MeasureSpec.AT_MOST){     
    setMeasuredDimension(200,heightMeasureSpec ); 
}else if(heightMeasureSpec == MeasureSpec.AT_MOST){     
    setMeasuredDimension(heightMeasureSpec ,200); 
}

2.讓view支持padding(onDraw的時候,寬高減去padding值,margin由父布局控制,不需要view考慮),自定義ViewGroup需要考慮自身的padding和子view的margin造成的影響

3.在view中盡量不要使用handler,使用view本身的post方法
4.在onDetachedFromWindow中及時停止線程或動畫
5.view帶有滑動嵌套情形時,處理好滑動沖突

擴展:優(yōu)化自定義view
減少在onDraw里面大量計算和對象創(chuàng)建,大量內(nèi)存分配
盡量少用invalidate()次數(shù)
view里面耗時的操作layout,減少requestLayout()避免讓UI系統(tǒng)重新遍歷整棵樹
如果有一個很復(fù)雜的布局,不如將這個復(fù)雜的布局直接使用自己寫的viewgroup來實現(xiàn),減少了一個樹的層次關(guān)系,全部都是自己測量和layout,打到優(yōu)化的目的,facebook經(jīng)常這么搞

6,介紹下實現(xiàn)一個自定義view的基本流程

1、自定義View的屬性 編寫attr.xml文件
2、在layout布局文件中引用,同時引用命名空間
3、在View的構(gòu)造方法中獲得我們自定義的屬性 ,在自定義控件中進行讀?。?gòu)造方法拿到attr.xml文件值)
4、重寫onMesure
5、重寫onDraw

7,View的事件分發(fā)機制

1,為什么有事件分發(fā)機制?
view是樹形結(jié)構(gòu)的,view會重疊,點擊會無法判斷,所以需要事件分發(fā)來統(tǒng)一調(diào)度

2,三個重要的事件分發(fā)方法?

dispatchTouchEvent
onInterceptTouchEvent
onTouchEvent

3,事件分發(fā)流程

Activity->PhoneWindow->DecorView->ViewGroup->View

如果事件沒有被攔截,就拋給Activity

image.png
1、Touch事件傳遞的相關(guān)API有dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent
2、Touch事件相關(guān)的類有View、ViewGroup、Activity
3、Touch事件會被封裝成MotionEvent對象,該對象封裝了手勢按下、移動、松開等動作
4、Touch事件通常從Activity#dispatchTouchEvent發(fā)出,只要沒有被消費,會一直往下傳遞,到最底層的View。
5、如果Touch事件傳遞到的每個View都不消費事件,那么Touch事件會反向向上傳遞,最終交由Activity#onTouchEvent處理.
6、onInterceptTouchEvent為ViewGroup特有,可以攔截事件.
7、Down事件到來時,如果一個View沒有消費該事件,那么后續(xù)的MOVE/UP事件都不會再給它

8,View的加載流程

View隨著Activity的創(chuàng)建而加載,startActivity啟動一個Activity時,在ActivityThread的handleLaunchActivity方法中會執(zhí)行Activity的onCreate方法,這個時候會調(diào)用setContentView加載布局創(chuàng)建出DecorView并將我們的layout加載到DecorView中,當(dāng)執(zhí)行到handleResumeActivity時,Activity的onResume方法被調(diào)用,然后WindowManager會將DecorView設(shè)置給ViewRootImpl,這樣,DecorView就被加載到Window中了,此時界面還沒有顯示出來,還需要經(jīng)過View的measure,layout和draw方法,才能完成View的工作流程。我們需要知道View的繪制是由ViewRoot來負(fù)責(zé)的,每一個DecorView都有一個與之關(guān)聯(lián)的ViewRoot,這種關(guān)聯(lián)關(guān)系是由WindowManager維護的,將DecorView和ViewRoot關(guān)聯(lián)之后,ViewRootImpl的requestLayout會被調(diào)用以完成初步布局,通過scheduleTraversals方法向主線程發(fā)送消息請求遍歷,最終調(diào)用ViewRootImpl的performTraversals方法,這個方法會執(zhí)行View的measure layout 和draw流程

9,View的滑動方式

a.layout(left,top,right,bottom):通過修改View四個方向的屬性值來修改View的坐標(biāo),從而滑動View

b.offsetLeftAndRight() offsetTopAndBottom():指定偏移量滑動view

c.LayoutParams,改變布局參數(shù):layoutParams中保存了view的布局參數(shù),可以通過修改布局參數(shù)的方式滑動view

d.通過動畫來移動view:注意安卓的平移動畫不能改變view的位置參數(shù),屬性動畫可以

e.scrollTo/scrollBy:注意移動的是view的內(nèi)容,scrollBy(50,50)你會看到屏幕上的內(nèi)容向屏幕的左上角移動了,這是參考對象不同導(dǎo)致的,你可以看作是它移動的是手機屏幕,手機屏幕向右下角移動,那么屏幕上的內(nèi)容就像左上角移動了

f.scroller:scroller需要配置computeScroll方法實現(xiàn)view的滑動,scroller本身并不會滑動view,它的作用可以看作一個插值器,它會計算當(dāng)前時間點view應(yīng)該滑動到的距離,然后view不斷的重繪,不斷的調(diào)用computeScroll方法,這個方法是個空方法,所以我們重寫這個方法,在這個方法中不斷的從scroller中獲取當(dāng)前view的位置,調(diào)用scrollTo方法實現(xiàn)滑動的效果

10. Requestlayout, onlayout, onDraw, DrawChild 區(qū)別與聯(lián)系

RequestLayout()方法:會導(dǎo)致調(diào)用Measure()方法和layout()。將會根據(jù)標(biāo)志位判斷是否需要onDraw();

說明:只是對View樹重新布局layout過程包括measure()和layout()過程,如果view的l,t,r,b沒有必變,那就不會觸發(fā)onDraw;但是如果這次刷新是在動畫 里,mDirty非空,就會導(dǎo)致onDraw。

onLayout():擺放viewGroup里面的子控件
onDraw():繪制視圖本身;(每個View都需要重載該方法,ViewGroup不需要實現(xiàn)該方法)
drawChild(): 重新回調(diào)每一個子視圖的draw方法。child.draw(canvas, this, drawingTime);

11. invalidate()和 postInvalidate() 的區(qū)別及使用

View.invalidate():層層上傳到父級,直到傳遞到ViewRootImpl后出發(fā)了scheduleTraversals(),然后整個View樹開始重新按照View繪制流程進行重繪任務(wù)。
invalidate():在UI主線程當(dāng)中刷新View;
postInvalidate():在子線程當(dāng)中刷新View;其實最終調(diào)用的就是invalidate,原理依然是通過工作線程向主線程發(fā)送消息這一機制。

View.postInvalidate最終會調(diào)用ViewRootImpl.dispatchInvalidateDelayed()

public void postInvalidate() {         
          postInvalidateDelayed(0);     
}     
public void postInvalidateDelayed(long delayMilliseconds) {       
  // We try only with the AttachInfo because there's no point in invalidating         // if we are not attached to our window         
        final AttachInfo attachInfo = mAttachInfo;        
       if (attachInfo != null) {             
 attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this,delayMilliseconds);        
       }     
}     
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {         
          Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);        
          mHandler.sendMessageDelayed(msg, delayMilliseconds); 
//這里的mHandler是ViewRootHandler實例,在該Handler的handleMessage方法中調(diào)用了view.invalidate()方法。     
}
 public void handleMessage(Message msg) {             
          switch (msg.what) {             
                case MSG_INVALIDATE:               
                ((View) msg.obj).invalidate();         
                  break; 
          }
 }

12,LinearLayout對比RelativeLayout

性能對比:LinearLayout的性能要比RelativeLayout好。

因為RelativeLayout會測量兩次。而默認(rèn)情況下(沒有設(shè)置weight)LinearLayout只會測量一次。

為什么RelativeLayout會測量兩次?首先RelativeLayout中的子view排列方式是基于彼此依賴的關(guān)系,而這個依賴可能和布局中view的順序無關(guān),在確定每一個子view的位置的時候,就需要先給每一個子view排一下序。又因為RelativeLayout允許橫向和縱向相互依賴,所以需要橫向縱向分別進行一次排序測量。

13,RecyclerView在很多方面能取代ListView,Google為什么沒把ListView劃上一條過時的橫線?

ListView采用的是RecyclerBin的回收機制在一些輕量級的List顯示時效率更高。

14, RecyclerView 的緩存機制

四級緩存,主要三個類(Recycler,RecycledViewPool和ViewCacheExtension)
Recycler:管理已經(jīng)廢棄或者與RecyclerView分離的ViewHolder,里面有2個重要的成員
mAttachedScrap
mChangedScrap

RecyclerView的四級緩存包括:

一級緩存:mAttachedScrap、mChangedScrap
二級緩存:mCacheViews
三級緩存:mViewCacheExtension
四級緩存:mRecyclerPool

一級緩存為屏幕內(nèi)緩存,二級緩存為屏幕外緩存,三級緩存為自定義緩存,四級緩存為緩存池緩存。一二三即緩存直接不需要重新綁定View,四級緩存需要綁定Holder設(shè)置數(shù)據(jù)。禁用緩存可以使用ViewHolder的setIsRecyclable方法。

參考:https://blog.csdn.net/yoonerloop/article/details/84727902

15,SurfaceView,它是什么?他的繼承方式是什么?他與View的區(qū)別(從源碼角度,如加載,繪制等)。

SurfaceView中采用了雙緩沖機制,保證了UI界面的流暢性,同時SurfaceView不在主線程中繪制,而是另開辟一個線程去繪制,所以它不妨礙UI線程;

SurfaceView繼承于View,他和View主要有以下三點區(qū)別:

(1)View底層沒有雙緩沖機制,SurfaceView有;
(2)view主要適用于主動更新,而SurfaceView適用與被動的更新,如頻繁的刷新
(3)view會在主線程中去更新UI,而SurfaceView則在子線程中刷新;

SurfaceView的內(nèi)容不在應(yīng)用窗口上,所以不能使用變換(平移、縮放、旋轉(zhuǎn)等)。也難以放在ListView或者ScrollView中,不能使用UI控件的一些特性比如View.setAlpha()

View:顯示視圖,內(nèi)置畫布,提供圖形繪制函數(shù)、觸屏事件、按鍵事件函數(shù)等;必須在UI主線程內(nèi)更新畫面,速度較慢。

SurfaceView:基于view視圖進行拓展的視圖類,更適合2D游戲的開發(fā);是view的子類,類似使用雙緩機制,在新的線程中更新畫面所以刷新界面速度比view快,Camera預(yù)覽界面使用SurfaceView。

GLSurfaceView:基于SurfaceView視圖再次進行拓展的視圖類,專用于3D游戲開發(fā)的視圖;是SurfaceView的子類,openGL專用。

Android的構(gòu)建

1,android的構(gòu)建流程

Java的文件編譯成class 字節(jié)碼文件,然后把字節(jié)碼加依賴的第三方j(luò)ar文件一起打包成classes.dex 安卓devilk虛擬機可執(zhí)行的文件

再打包資源文件,再把dex文件和save文件合并成未簽名的包,然后簽名打包成一個完整的包

2,jenkins持續(xù)集成構(gòu)建

……待續(xù)

MVC/MVP/MVVM

1,MVC定義,acitivity又充當(dāng)了View和Controller

Android 插件化

1,由來

65536/64k 方法超過限制

2,解決的問題

動態(tài)加載APK,資源加載,代碼加載

Android熱更新

1,熱更新流程

  1. 線上檢查到嚴(yán)重的crash
  2. 拉出bugfix分支并分支上修復(fù)問題
  3. jenkins構(gòu)建和補丁生成
  4. app通過推送或者主動拉取補丁文件
  5. 將bugfix代碼合到主分支上

2,更新框架介紹:

Dexposed
AndFix
Nuwa

3,原理

java的加載器:PathClassLoader,DexClassLoader
機制:ClassLoader遍歷dexElements這個數(shù)組,
我們知道Java虛擬機 —— JVM 是加載類的class文件的,而Android虛擬機——Dalvik/ART VM 是加載類的dex文件,
而他們加載類的時候都需要ClassLoader,ClassLoader有一個子類BaseDexClassLoader,而BaseDexClassLoader下有一個
數(shù)組——DexPathList,是用來存放dex文件,當(dāng)BaseDexClassLoader通過調(diào)用findClass方法時,實際上就是遍歷數(shù)組,
找到相應(yīng)的dex文件,找到,則直接將它return。

而熱修復(fù)的解決方法就是將新的dex添加到該集合中,并且是在舊的dex的前面,所以就會優(yōu)先被取出來并且return返回。

進程?;?/h1>

1,Android進程的優(yōu)先級

Foreground Process 前臺進程
Visible process 可見進程
Service process 服務(wù)進程
Background process 后臺進程
Empty process 空進程

2,Android進程的回收策略

Low memory killer:通過一些比較復(fù)雜的評分機制,對進程進行打分,然后將分?jǐn)?shù)高的判定為bad進程,殺死并釋放內(nèi)存。

OOM _ODJ : 判別進程的優(yōu)先級。

3,進程?;罘桨?/h3>

利用系統(tǒng)廣播
利用service機制拉活
利用native進程拉活,不過后面被限制了
利用Jobscheduler機制拉活,5.0后
利用賬號同步機制拉活

a: Service設(shè)置成START_STICKY kill 后會被重啟(等待5秒左右),重傳Intent,保持與重啟前一樣
b: 通過 startForeground將進程設(shè)置為前臺進程, 做前臺服務(wù),優(yōu)先級和前臺應(yīng)用一個級別,除非在系統(tǒng)內(nèi)存非常缺,否則此進程不會被 kill
c: 雙進程Service: 讓2個進程互相保護對方,其中一個Service被清理后,另外沒被清理的進程可以立即重啟進程
d: 用C編寫守護進程(即子進程) : Android系統(tǒng)中當(dāng)前進程(Process)fork出來的子進程,被系統(tǒng)認(rèn)為是兩個不同的進程。當(dāng)父進程被殺死的時候,子進程仍然可以存活,并不受影響(Android5.0以上的版本不可行)聯(lián)系廠商,加入白名單
e.鎖屏狀態(tài)下,開啟一個一像素Activity

Git

1,工作區(qū)

2,git常用命令

git init
git status
git diff
git clone
git add
git commit

3,git工作流

fork操作

Proguard

1,Proguard技術(shù)點

壓縮(沒用到的資源和文件可以不被壓縮到包里),優(yōu)化(代碼字節(jié)碼不用的可以移除),混淆,預(yù)檢測

2,Proguard工作原理

EntryPoint的類

Android其他相關(guān)面試題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容