Android面試題(二)Android基礎3

 (三)常見的一些原理性問題

1、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的數據結構是基於鏈表的

2、Handler、Thread和HandlerThread的差別

①Handler:在android中負責發送和處理消息,通過它可以實現其他支線線程與主線程之間的消息通訊。

②Thread:Java進程中執行運算的最小單位,亦即執行處理機調度的基本單位。某一進程中一路單獨運行的程序。

③HandlerThread:一個繼承自Thread的類HandlerThread,Android中沒有對Java中的Thread進行任何封裝,而是提供了一個繼承自Thread的類HandlerThread類,這個類對Java的Thread做了很多便利的封裝。

3、ThreadLocal原理,實現及如何保證Local屬性?

  當需要使用多線程時,有個變量恰巧不需要共享,此時就不必使用synchronized這麼麻煩的關鍵字來鎖住,每個線程都相當於在堆內存中開闢一個空間,線程中帶有對共享變量的緩衝區,通過緩衝區將堆內存中的共享變量進行讀取和操作,ThreadLocal相當於線程內的內存,一個局部變量。每次可以對線程自身的數據讀取和操作,並不需要通過緩衝區與 主內存中的變量進行交互。並不會像synchronized那樣修改主內存的數據,再將主內存的數據複製到線程內的工作內存。ThreadLocal可以讓線程獨佔資源,存儲於線程內部,避免線程堵塞造成CPU吞吐下降。

  在每個Thread中包含一個ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的對象,value是獨享數據。

4、請解釋下在單線程模型中Message、Handler、Message Queue、Looper之間的關係

單的說,Handler獲取當前線程中的looper對象,looper用來從存放Message的MessageQueue中取出Message,再有Handler進行Message的分發和處理.

Message Queue(消息隊列):

用來存放通過Handler發佈的消息,通常附屬於某一個創建它的線程,可以通過Looper.myQueue()得到當前線程的消息隊列

Handler:

可以發佈或者處理一個消息或者操作一個Runnable,通過Handler發佈消息,消息將只會發送到與它關聯的消息隊列,然也只能處理該消息隊列中的消息

Looper:

是Handler和消息隊列之間通訊橋樑,程序組件首先通過Handler把消息傳遞給Looper,Looper把消息放入隊列。Looper也把消息隊列裏的消息廣播給所有的

Handler:Handler接受到消息後調用handleMessage進行處理

Message:

消息的類型,在Handler類中的handleMessage方法中得到單個的消息進行處理。

在單線程模型下,爲了線程通信問題,Android設計了一個Message Queue(消息隊列), 線程間可以通過該Message Queue並結合Handler和Looper組件進行信息交換。

5、請描述一下View事件傳遞分發機制

https://blog.csdn.net/github_37130188/article/details/89112087

6、Touch事件傳遞流程

https://www.jianshu.com/p/62b638e1712a

7、事件分發中的onTouch 和onTouchEvent 有什麼區別,又該如何使用?

 

Touch事件傳遞流程

1.Touch事件類型

 Touch事件被封裝成MotionEvent,用戶當前的touch事件主要類型有:

       ACTION_DOWN: 表示用戶開始觸摸

     ACTION_MOVE: 表示用戶在移動(手指或者其他)

     ACTION_UP:表示用戶擡起了手指

     ACTION_CANCEL:表示手勢被取消了

     ACTION_OUTSIDE: 表示用戶觸碰超出了正常的UI邊界.

     ACTION_POINTER_DOWN:有一個非主要的手指按下了.

     ACTION_POINTER_UP:一個非主要的手指擡起來了

2.Touch事件元數據

     touch事件的元數據主要包括:touch的位置、手指的個數、touch事件的時間。一個touch手勢被定義爲以ACTION_DOWN開始和以 ACTION_UP結束。

3.事件傳遞流程:

Android 中與 Touch 事件相關的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);

能夠響應這些方法的控件包括:ViewGroup、View、Activity。

方法與控件的對應關係如下:

  Touch 事件相關方法 方法功能 ViewGroup View Activity

  public boolean dispatchTouchEvent(MotionEvent ev) 事件分發 Yes Yes Yes

  public boolean onInterceptTouchEvent(MotionEvent ev) 事件攔截 Yes No No

  public boolean onTouchEvent(MotionEvent ev) 事件響應 Yes Yes Yes

 

區別:

        onTouch方法優先級比onTouchEvent高,會先觸發。假如onTouch方法返回false,會接着觸發onTouchEvent,反之onTouchEvent方法不會被調用。內置諸如click事件的實現等等都基於onTouchEvent,假如onTouch返回true,這些事件將不會被觸發。

使用:

1、onTouch()方法:

  onTouch方式是View的OnTouchListener接口中定義的方法。當一個View綁定了OnTouchListener後,當有Touch事件觸發時,就會調用onTouch方法。

testBt.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN://0 Log.e("TAG", " onTouch按住"); break; case MotionEvent.ACTION_UP://1 Log.e("TAG", " onTouch擡起"); break; case MotionEvent.ACTION_MOVE://2 Log.e("TAG", " onTouch移動"); break; } //事件分發 //1、setOnTouchListener單獨使用的時候返回值需要true,這樣才能保證移動的時候獲取相應的監聽,而非一次監聽(即只有按下事件) //返回false,表示沒有被處理,將向父View傳遞。只能監聽到view的"按下"事件,"移動"和"擡起"都不能監聽到。因爲down事件未結束 //返回true,消耗此事件,表示正確接收並處理,不在分發。"按下""擡起""移動"都能監聽到了 //2、setOnTouchListener和setOnClickListener同時使用時, //返回true,事件被onTouch消耗掉了,因而不會在繼續向下傳遞。只能監聽"按下""擡起""移動",不能監聽到"點擊"; //返回false,"按下""擡起""移動""點擊"都能監聽 //onTouch是優先於onClick的,並且執行了兩次,一次是ACTION_DOWN,一次是ACTION_UP(可能還會有多次ACTION_MOVE)因此事件傳遞的順序是先經過OnTouch,再傳遞給onClick return false; } });

2、onTouchEvent()方法:

  onTouchEvent方法時重載的Activity的方法 重寫了Acitivity的onTouchEvent方法後,當屏幕有Touch事件時,此方法就會被調用。

@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN://0 Log.e("TAG", " onTouchEvent 按住"); break; case MotionEvent.ACTION_UP://1 Log.e("TAG", " onTouchEvent 擡起"); break; case MotionEvent.ACTION_MOVE://2 Log.e("TAG", " onTouchEvent 移動"); break; } return super.onTouchEvent(event); }

8、View刷新機制

在Android的View刷新機制中,父View負責刷新(invalidateChild)、佈局(layoutChild)顯示子View。而當子View需要刷新時,則是通知父View刷新子view來完成。

刷新代碼如下(mParent爲view的父view):

void invalidate(boolean invalidateCache) { final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; //noinspection PointlessBooleanExpression,ConstantConditions if (!HardwareRenderer.RENDER_DIRTY_REGIONS) { if (p != null && ai != null && ai.mHardwareAccelerated) { // fast-track for GL-enabled applications; just invalidate the whole hierarchy // with a null dirty rect, which tells the ViewAncestor to redraw everything p.invalidateChild(this, null); return; } } if (p != null && ai != null) { final Rect r = ai.mTmpInvalRect; r.set(0, 0, mRight - mLeft, mBottom - mTop); // Don't call invalidate -- we don't want to internally scroll // our own bounds p.invalidateChild(this, r); } } }

invalidate()和postInvalidate() 的區別及使用

當Invalidate()被調用的時候,View的OnDraw()就會被調用;Invalidate()是刷新UI,UI更新必須在主線程,所以invalidate必須在UI線程中被調用,如果在子線程中更新視圖的就調用postInvalidate()。

postInvalidate()實際調用的方法,mHandler.sendMessageDelayed,在子線程中用handler發送消息,所以才能在子線程中使用。

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); }

9、View繪製流程

https://www.jianshu.com/p/c151efe22d0d

10、自定義控件原理

https://www.jianshu.com/p/46563c44756e

11、自定義View如何提供獲取View屬性的接口?

自定義屬性的實現流程: 

      1.在values目錄下定義一個attrs.xml :在res/values/attr.xml中定義相關屬性。

      2.在對應的類文件裏生成某些組件 :在對應類的構造函數中通過 obtainStyledAttributes()方法獲得自定義屬性的相關值

      3.在layout佈局文件裏爲這些屬性賦值:在佈局中添加爲該自定義組件設置一個命名空間,並且相關屬性賦值

[xmlns:myandroid=”http://schemas.android.com/apk/res/cn.com.androidtest”]

也可以採用命名空間寫法:xmlns:空間名=”http://schemas.android.com/apk/res/自定義組件所在頂級包名”**

12、Android代碼中實現WAP方式聯網

https://blog.csdn.net/asce1885/article/details/7844159

13、爲什麼不能在子線程更新UI?

爲什麼谷歌要提出:“UI更新一定要在UI線程裏實現”這一規則呢?原因如下:

目的在於提高移動端更新UI的效率和和安全性,以此帶來流暢的體驗。原因是:

Android的UI訪問是沒有加鎖的,多個線程可以同時訪問更新操作同一個UI控件。也就是說訪問UI的時候,android系統當中的控件都不是線程安全的,這將導致在多線程模式下,當多個線程共同訪問更新操作同一個UI控件時容易發生不可控的錯誤,而這是致命的。所以Android中規定只能在UI線程中訪問UI,這相當於從另一個角度給Android的UI訪問加上鎖,一個僞鎖。

14、ANR產生的原因是什麼?

ANR的全稱是application not responding,意思就是程序未響應,類似於我們在windows上見到的程序未響應。ANR發生會使用戶覺得我們的程序不友好,那麼什麼情況會導致ANR的發生呢?

首先ANR的發生是有條件限制的,分爲以下三點:

1.只有主線程纔會產生ANR,主線程就是UI線程;

2.必須發生某些輸入事件或特定操作,比如按鍵或觸屏等輸入事件,在 BroadcastReceiver或Service的各個生命週期調用函數;

3.上述事件響應超時,不同的context規定的上限時間不同

    a.主線程對輸入事件5秒內沒有處理完畢

    b.主線程在執行BroadcastReceiver的onReceive()函數時10秒內沒有處理完畢

    c.主線程在Service的各個生命週期函數時20秒內沒有處理完畢。

那麼導致ANR的根本原因是什麼呢?簡單的總結有以下兩點:

1.主線程執行了耗時操作,比如數據庫操作或網絡編程

2.其他進程(就是其他程序)佔用CPU導致本進程得不到CPU時間片,比如其他進程的頻繁讀寫操作可能會導致這個問題。

細分的話,導致ANR的原因有如下幾點:

1.耗時的網絡訪問

2.大量的數據讀寫

3.數據庫操作

4.硬件操作(比如camera)

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

6.service binder的數量達到上限

7.system server中發生WatchDog ANR

8.service忙導致超時無響應

9.其他線程持有鎖,導致主線程等待超時

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

那麼如何避免ANR的發生呢或者說ANR的解決辦法是什麼呢?

1.避免在主線程執行耗時操作,所有耗時操作應新開一個子線程完成,然後再在主線程更新UI。

2.BroadcastReceiver要執行耗時操作時應啓動一個service,將耗時操作交給service來完成。

3.避免在Intent Receiver裏啓動一個Activity,因爲它會創建一個新的畫面,並從當前用戶正在運行的程序上搶奪焦點。如果你的應用程序在響應Intent廣 播時需要向用戶展示什麼,你應該使用Notification Manager來實現。

15、oom是什麼?

1、什麼是OOM?

程序申請內存過大,虛擬機無法滿足我們,然後自殺了。這個現象通常出現在大圖片的APP開發,或者需要用到很多圖片的時候。通俗來講就是我們的APP需要申請一塊內存來存放圖片的時候,系統認爲我們的程序需要的內存過大,及時系統有充分的內存,比如1G,但是系統也不會分配給我們的APP,故而拋出OOM異常,程序沒有捕捉異常,故而彈窗崩潰了

2、爲什麼會有OOM?

因爲Android系統的APP每個進程或者虛擬機有最大內存限制,一旦超過這個限制系統就會拋出OOM錯誤。跟手機剩餘內存是否充足沒有多少關係。

3、爲什麼Android會有APP的內存限制

(1)要開發者使用內存更加合理。限制每個應用可用內存上限,避免惡意程序或單個程序 使用過多內存導致其他程序的不可運行。有了限制,開發者就必須合理使用資源,優化資源使用

(2)屏幕顯示內容有限,內存足夠即可。即使有萬千圖片千萬數據需要使用到,但在特定時刻需要展示給用戶看的總是有限的,因爲屏幕顯示就那麼大,上面可以放的信息就是很有限的。大部分信息都是處於準備顯示狀態,所以沒必要給予太多heap內存。必須一個ListView顯示圖片,打個比方這個ListView含有500個item,但是屏幕顯示最多有10調item顯示,其餘數據是處於準備顯示狀態。

(3)Android多個虛擬機Davlik的限制需要。android設備上的APP運行,每打開一個應用 就會打開至少一個獨立虛擬機。這樣可以避免系統崩潰,但代價是浪費更多內存。

4、有GC自動回收資源,爲什麼還會有OOM?

Android的GC會按照特定的算法來回收不使用的資源,但是gc一般回收的是無主的對象內存或者軟引用資源。

使用軟引用的圖片資源在一定程度上可以避免OOM。

ps:不用的對象設置爲null,是一個好習慣。不過更好的方法是,不用的圖片直接recycle。因爲有時候通過設置null讓gc來回收還是來不及。

5、怎麼來避免OOM產生呢?

簡單通過SoftReference引用方式管理圖片資源

建一個SoftReference的hashmap,使用圖片時,先檢查這個hashmap是否有softreference,softreference的圖片是否爲空,如果爲空將圖片加載到softreference並加入haspmap。

16、什麼情況導致OOM問題及如何優化

一、前期基礎知識儲備

(1)OOM定義—out of memory,內存溢出,一個程序中,已經不需要使用某個對象,但是因爲仍然有引用指向它垃圾回收器就無法回收它,當該對象佔用的內存無法被回收時,就容易造成內存泄露。多個內存泄漏最終會導致內存溢出,即OOM。

(內存泄漏和內存溢出兩者之間的關係,可參考筆者之前的文章《Android中內存泄漏詳解》)

(2)Java內存概念、內存泄漏相關知識點都可以參考筆者的文章《Android中內存泄漏詳解》

二、出現OOM錯誤的常見原因

(1)資源對象沒關閉造成的內存泄露,try catch finally中將資源回收放到finally語句可以有效避免OOM。資源性對象比如:

①Cursor遊標對象沒有關閉;

②調用registerReceiver後未調用unregisterReceiver();

③未關閉InputStream/OutputStream;

④Bitmap使用後未調用recycle()

(2)作用域不一樣,導致對象不能被垃圾回收器回收,比如:

①非靜態內部類會隱式地持有外部類的引用,

②Context泄露——概括一下,避免Context相關的內存泄露,記住以下事情:

不要保留對Context-Activity長時間的引用(對Activity的引用的時候,必須確保擁有和Activity一樣的生命週期)

嘗試使用Context-Application來替代Context-Activity

如果你不想控制內部類的生命週期,應避免在Activity中使用非靜態的內部類,而應該使用靜態的內部類,並在其中創建一個對Activity的弱引用。

③Thread 引用其他對象也容易出現對象泄露。

④onReceive方法裏執行了太多的操作

(3)內存壓力過大—最直接:

①圖片資源加載過多,超過內存使用空間,例如Bitmap 的使用,bitmap分辨率越高,所佔用的內存就越大,這個是以2爲指數級增長的;

②重複創建view,listview應該使用convertview和viewholder,ListView相關的知識點有兩個:1)ListView的實現;2)ListView的效率優化,其中第二點對於開發者在使用ListView時是需要重點關注的,《第一行代碼》中有相關介紹。

三、避免OOM的常用方法總結

1)資源文件需要選擇合適的文件夾進行存放;

2)優化佈局層次,越扁平化的視圖佈局,佔用的內存就越少,效率越高;

3)減小Bitmap對象的內存佔用;

4)使用更小的圖片,是否存在可以壓縮的空間,是否可以使用一張更小的圖片;

5)複用系統自帶的資源,比如字符串/顏色/圖片/動畫/樣式以及簡單佈局等等,這些資源都可以在應用程序中直接引用;

6)注意在ListView/GridView等出現大量重複子組件的視圖裏面對ConvertView的複用;

7)類似onDraw等頻繁調用的方法,一定需要注意避免在這裏做創建對象的操作,因爲他會迅速增加內存的使用,而且很容易引起頻繁的gc,甚至是內存抖動;

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

9)考慮使用Application Context而不是Activity Context

17、Oom 是否可以try catch?爲什麼?

只有在一種情況下,這樣做是可行的:

在try語句中聲明瞭很大的對象,導致OOM,並且可以確認OOM是由try語句中的對象聲明導致的,那麼在catch語句中,可以釋放掉這些對象,解決OOM的問題,繼續執行剩餘語句。

但是這通常不是合適的做法。

Java中管理內存除了顯式地catch OOM之外還有更多有效的方法:比如SoftReference, WeakReference, 硬盤緩存等。

在JVM用光內存之前,會多次觸發GC,這些GC會降低程序運行的效率。

如果OOM的原因不是try語句中的對象(比如內存泄漏),那麼在catch語句中會繼續拋出OOM

18、內存泄漏是什麼?

  指因爲疏忽或錯誤造成程序未能釋放已經不再使用的內存的情況。內存泄漏並不是指內存在物理上的消失,而是應用程序分配某段內存後,因爲設計錯誤,失去了對該段內存的控制,因而造成了內存的浪費。 

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