Android 一些基礎知識整理(一)

1 在子線程可以刷新 UI 嗎
 

不行,在子線程中報異常ViewRootImpl$CalledFromWErongThreadException

提示 Only the original thread that created a view hierarchy can touch its views 中文翻譯爲只有在創建視圖層次的原始線程才能更改其視圖。

通過源碼得知ViewRootImpI 是通過 Activity 創建的,並不是由 ViewGroup 創建,那麼View 的刷新動作必須要在創建 ViewRootImpI 對象的線程中執行,而 ViewRootImpI 的創建時機是在 Activity 創建 DecorView (Window 中的第一個 View)的時候,也是系統幫我們啓動 Activity,所以 ViewRootImpI 創建時所處的線程是主線程,所以我們不能在子線程刷新 UI,因爲 View 是在主線程中創建的,源碼已經很清楚地告訴我們了,View 的創建和改變必須要在同一個線程。

2 原生和 H5 交互的流程:


首先開啓支持 JavaScript ,並且加載一遍網頁,這個是前提條件
原生調用 H5:通過 loadUrl 來調用 JS 方法 (WebView.loadUrl("javascript:xxxxx");)
H5 調用原生:創建一個普通的方法並且添加註解(@JavascriptInterface),然後調用 addJavascriptInterface 將這個方法注入給 WebView

 

3 new Message 和 Message.obtain() 區別


這兩種最本質的區別是,第一種是通過 new 的形式創建一個 Message 對象,而另一種是通過複用的形式來獲取一個 Message 對象。在使用 Handler 發送 Message 的情況下,建議採用第二種方式,因爲第二種方式在消息頻繁的情況下,所表現的性能較優,如果每次都創建 Message 對象,會造成不必要的資源浪費。

4 IntentService 和 Service 有什麼區別:


1. Service 運行在主線程,而 IntentService 運行子線程

2. Service 必須手動調用 stopSelf 才能關閉服務,而 IntentService 執行完畢之後會自動關閉服務

自定義打包名稱

android.applicationVariants.all {
    variant->
        variant.outputs.all {
            output->
                def appName ="building_"
                def outputFile=output.outputFile
                if (outputFile!=null && outputFile.name.contains('release')){
                    def  fileName="building_v${variant.versionName}"+new Date().format("_MMddss")+".apk"
                    outputFileName=fileName;
                }else if (outputFile!=null && outputFile.name.contains('debug')){
                    def  fileName="building_v${variant.versionName}"+new Date().format("_MMdd")+".apk"
                    outputFileName=fileName;
                }
        }
}

 

5 數據存儲的方式


大致分爲兩種:

1. 本地存儲(文件、SP、SQLite、MMKV)

2. 網絡存儲(普通傳輸、加密傳輸)

在實際開發中,我們會將一些配置文件存放在本地,比如應用設置,用戶信息等保存到本地中,而一些比較重要的數據,則通過接口發送給後臺保存,對於登陸密碼或者支付密碼還需要進行加密操作。

 

6 Activity 四種啓動模式


standard(標準模式):拿來主義,start 多少次就創建多少個 Activity

singleTop(單一棧頂):如果欲啓動的 Activity 是棧頂,則不會重複創建。

singleTask(單一任務棧):如果欲啓動的 Activity 有實例在任務棧,就會將在這個 Activity 上層的 Activity 清除,這樣即能保證棧中的唯一性,又能達到複用的效果。

singleInstance(單一實例):這種模式比較特殊,Activity 會運行在單獨的任務棧中,整個手機中只有一個實例存在。

7 請簡述 View 事件分發機制


View 事件一般要經過三個流程:分發(dispatchTouchEvent)、攔截(onInterceptTouchEvent)、消費(onTouchEvent)
dispatchTouchEvent:View 會先執行 OnTouchListener 中 onTouch 方法,如果這個監聽器的方法返回 true 則不會將事件交由自身的 onTouchEvent 進行消費。而 ViewGroup 跟 View 不同的是,它是直接將事件交由觸摸位置上的子 View 的  dispatchTouchEvent 方法處理。

onInterceptTouchEvent:這個是 ViewGroup 獨有的方法,在 dispatchTouchEvent 方法中調用,一般情況下返回 false(也就是不攔截),ViewGroup 如果想攔截觸摸事件可以重寫此方法返回 true,將事件交由自身的 onTouchEvent 方法處理。

onTouchEvent:一次觸摸事件會產生 down(按下)、move(移動)、up(擡起)三個事件,這個方法將決定事件產生的效果,比如狀態選擇器的 Drawable 變換就是在這個方法中觸發的,還有最常用的點擊事件 onClick 也是在這裏回調的。

總結:三個事件方法各有各的作用,分發是想把事件傳遞下去,攔截是不希望事件傳遞下去,而消費是對事件的一系列處理。通過這三個方法,我們可以控制觸摸事件交由誰去處理,整個事件機制採用了責任鏈設計模式,事件會一層一層往下傳遞。

 

8 請簡述 View 繪製流程


一般 View 繪製要經過三個步驟:測量、擺放、繪製

onMeasure:View 會測量自身的寬高、ViewGroup 會測量子 View 的寬高,其中測量有兩個主要的值,測量模式和測量大小,這兩個值其實由一個 32 位 int 值組成,高 2 位爲測量模式,低 30 位爲測量大小。而測量模式有三種:自適應(wrap_content)、精確值(match_parent、固定值)、未指明(View 想多大就多大,在 ListView、ScrollView 等在測量子佈局的時候會用)。

onLayout:這個是 ViewGroup 獨有的方法,用於決定子控件的位置,同時也可以對 View 的寬高進行調整。

onDraw:這個是 View 獨有的方法,當 View 調用 invalidate 會先觸發 draw 方法,然後依次繪製背景、內容、前景、滾動條。而我們平時最常用的 onDraw 方法就是這四個步驟的其中一個:繪製內容。

總結:onMeasure 得出是繪製區域的大小,onLayout 得出是繪製區域的位置,而 onDraw 是對指定區域進行繪製,這樣 View 就能根據我們想要的方式繪製到屏幕上了。

9 內存優化


1. 不用的對象及時釋放,即指向 null

2. 在遍歷得到想要的位置之後要跳出循環

3. 儘量不要在循環或者遞歸中 new 對象

4. 在頻繁 new 對象的地方使用享元設計模式

5. 第三方框架懶加載或者使用到的時候再初始化

6. 儘量使用 Parcelable(通過接口序列化),減少使用 Serializable(反射次數多)

7. 減少避免使用反射(EventBus APT 插件可以減少反射次數)

8. 減少 findViewById 次數(不要在 onBindViewHolder 中進行 findViewById,而是在 ViewHolder 構造函數中進行)

9. 在數據量小的時候,應該選用 ArrayMap 和 SparseArray 來代替 HashMap(因爲它們的數據結構很簡單,都是由數組組成,而 HashMap 涉及了鏈表和紅黑樹)

10. 在 ViewPager 中 Fragment 比較少的情況下,應當使用 FragmentPagerAdapter,否則應當使用 FragmentStatePagerAdapter( FragmentPagerAdapter 只是簡單走了一遍 Fragment生命週期,並沒有真正從 ViewPager 裏面移除掉,而 FragmentStatePagerAdapter 會保存 Fragment 的狀態,然後把它從 ViewPager 中移除掉,然後下次使用的時候重新添加並且恢復它原有的狀態。可以看出 FragmentStatePagerAdapter 對 Fragment 內存管理機制還是做得比較完善的)

11. 及時釋放資源(IO、SQLite)

12. 監聽器反註冊(EditText、BroadcastReceiver)

13. WebView 生命週期優化(回調 onResume、onPause、Destroy)

14. WebView 獨立進程優化(WebView 本身就是一個複雜的 View,消耗內存和性能比較大,放在獨立進程中可以減小 APP 有一定機率因爲內存溢出導致的崩潰)

10 三級緩存是哪三級,分別有什麼用


內存緩存:速度快,優先讀取,但要管理好內存

本地緩存:速度其次,內存中沒有,纔讀本地

網絡緩存:速度最慢,本地也沒有,才訪問網絡

11 簡述一下 ANR 實現原理


ANR 檢測是由系統服務來完成,每當主線程接收到操作之後,系統會使用 Handler 會發送一個延遲消息,當這個操作完成之後會將這個延遲消息移除,如果這個延遲消息沒有被移除,那麼就證明應用沒有及時響應,同時也會觸發系統向用戶發送 ANR 警告。

12 如何避免內存泄漏:


出現內存的泄漏最多的兩個原因:

1. 使用靜態的 Activity

2. 不恰當使用 Handler

解決這兩個內存泄漏的方案:

1. 儘量使用 Application 作爲靜態 Context 對象,如果一定要用 Activity,不能直接引用,而是需要使用弱引用或者軟引用

2. Handler 是開發中最常用的類,伴隨使用頻率的增高,內存泄漏發生的情況也比較多,往往是我們使用不當導致的。我們應當在 Activity 銷燬的時候 removeAllMessage,又或者將 Activity 弱引用或者軟引用持有。

另外我們可以用第三方框架 LeakCanary(金絲鳥)來檢測應用是否發生內存泄漏,這個框架的原理是通過監聽 Activity 的生命週期,當 Activity 銷燬之後一段時間,金絲鳥會檢查弱引用中的 Activity 對象是否被置空(回收)了,如果是的話就證明這個 Activity 在使用的時候沒有產生內存泄漏。

13 什麼是內存抖動?如何避免內存抖動?


概念:內存抖動是因爲短時間內有大量的對象進出(創建和回收),隨着系統頻繁的 GC,使渲染(UI)線程被阻塞,從而導致程序顯示的畫面有短暫卡頓。 

避免:儘量避免在頻繁調用的地方 new 對象(比如循環遞歸,View.onDraw、RecyclerView.onBindView),如果需要最好把對象提取到外層(循環外或者提取成字段),針對一些可複用的對象(比如 Bitmap),建議使用對象池進行緩存。

14 請簡述內存溢出和內存泄漏有什麼區別


內存溢出是系統給應用分配的內存使用超標導致的,而分配的內存大小是根據屏幕大小而定的,一般屏幕越大分配的內存越大,我們需要做好內存優化和內存控制;而內存泄漏是指 GC 垃圾回收器無法回收對象,導致對象佔用的內存空間無法釋放,我們可以理解成這部分內存空間被佔用着,無法被系統重複利用。

 

15 請簡述 Handler 的工作原理


Handler 有兩個重要的組成部分,Looper(消息輪詢器) 和 MessageQueue(消息隊列)

Looper 是 Handler 實現的核心,Looper 在構造方法中會創建 MessageQueue,而 Handler 處理消息的時候會交給 MessageQueue.enqueueMessage 方法,而 MessageQueue 會將消息鏈表進行重新排序,再判斷 Looper 是否喚醒,如果 MessageQueue 中沒有正在消息輪詢,那麼 Looper 會一直處於休眠狀態,所以 MessageQueue 需要觸發 Looper 對自己的輪詢。(通過往管道寫入數據通知 Looper.looper)

16 Java 回收算法


1. 引用計數算法:對象引用對象會進行計數,當這個對象沒有被任何對象引用的時候,計數就爲零,這個對象就會被回收掉。但是這種算法是有缺陷的,當兩個對象相互引用的時候,會導致對象無法回收。

2. GC Root 可達分析算法:由於引用計數算法是有問題的,後面誕生了這種回收算法。當 GC Root (棧幀中的變量、靜態變量、常量、JNI 引用的對象)不可用時,其他引用這個 GC Root 的對象也將作爲無效對象被垃圾收集器回收。

 

17 Java 最常見的兩種查找算法:


1. 線性查找:這個,對數組進行遍歷操作

2. 二分法查找:這個算法必須要數組是有序的前提下,取數組中間的值進行比較,如果小於則取左邊的數值進行查找(否則向右查找),再獲取左邊的數值中間的值進行比較,以此類推,直到找到對應的值爲止。

 

18 代碼優化


1. 對常用的功能模塊進行封裝

2. 對重複的代碼考慮進行抽取

3. 關注編譯器給出的警告並正確處理

4. 通過不斷改進來優化代碼的寫法

5. 使用 AOP 降低一些代碼的耦合性

6. 代碼命名和文件命名要規範(阿里代碼規範手冊,駝峯命名)

7. 減少不必要的代碼註釋,儘量用規範的代碼代替註釋

8. 完善重點難點代碼的註釋,完善後臺接口代碼的註釋(接口作用,參數含義)

9. 代碼的擺放順序要有一定的規律(根據代碼類型和執行順序來定義)

19 請簡述 HashMap 的原理:


數據結構:HashMap 其實是一個對象數組,數據結構採用的是鏈表,當鏈表長度大於 8個 的時候,會切換成紅黑樹,如果紅黑樹長度小於 6 個會回退到鏈表。

存儲流程:HashMap 是先計算 Key 對象的 hashCode 值,因爲 hashCode 的值比較大,所以 HashMap 會用位運算對這個值壓縮到 16 (對象數組長度)以內的值,得出來的結果就是鏈表的位置。

容量擴容:HashMap 默認的數組容量是 16,其負載因子是 0.75,如果超過了 12 (16 * 0.75)個元素,會對數組進行雙倍擴容,也就是 32 (16 * 2)。擴容的過程比較簡單,但擴容是一個密集操作,HashMap 會重新計算每個元素的位置,然後給這些元素重新排序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章