一、問:handerThread出現的背景是什麼?
答:我們在安卓項目開發中,經常會遇到一些耗時操作,這時候我們第一直覺,就是開啟一個子線程去執行一個耗時操作,這很友善但是卻又很耗記憶體,因為當你一個Android中的thread執行完耗時操作,線程就會被自動銷毀,如果在短時間内又要執行一個耗時操作,這個時候我們就不得不重新建立線程去執行耗時任務,這樣就存在一個性能問題,多次建立和銷毀線程是很消耗資源的。
二、問:有沒有辦法解決上述問題??
答:為了解決這個問題,我們自己可以建構一個循環線程,例如looper等。當有耗時任務需要投放到改循環線程當中時,線程就會執行耗時任務,執行完成之後,線程又會處于阻塞等待狀态。知道下一個耗時任務被投放進來。雖然這暫時解決了多次建立線程造成記憶體消耗,但是谷歌公司非常的體貼,他給我們已經封裝好了一個更好的架構,就是handlerThread。
三、問:handlerThread是什麼?
答:他就是一個thread線程,但是他跟普通的thread不通的地方就是,它内部他有一個looper,它開啟了運作器,handler+thread+looper。
四、問:子線程為什麼不能開啟handler?
答:handler、sendMeassage()、又或者post(runnable),他都需要一個消息對列來儲存他所發送的消息,而預設子線程當中他是沒有開啟looper輪循器的。而消息對列又是被looper所管理的,是以在子線程你想建立一個handler來發送消息,是沒有關聯到消息對列的來讓你存儲消息,是以會抛出異常。如果你想在子線程當中建立一個handler,你自己必須初始化一個looper,然後調用looper.loop()方法,開啟循環,才能建立handler。
五、問:handlerThread它有哪些特點?
答:
1、handlerThread它本身就是一個線程類,他繼承了thread。
2、它和傳統的thread,是它本身内部有looper對象,可以進行looper循環,是以在handlerThread中,你可以建立handler來發送和處理消息。
3、通過擷取handlerThread的looper對象傳遞給handler,可以再handleMessage方法中執行異步任務。
4、他不會阻塞UI線程,減小對性能的消耗,缺點是不能不是進行多個任務,需要等待進行處理,處理效率降低。
5、與線程池并發不同,handlerThread是串行,背後隻有一個線程。他對于線程池來說更安全。
handlerThread源碼解析:http://blog.csdn.net/lmj623565791/article/details/47079737/
六、問:intentservice是什麼是什麼?
答:intentservice是繼承service并處理異步請求的一個類,在intentservice内有一個工作線程來處理耗時操作,啟動intentservice和啟動傳統的service一樣,同時,當任務執行完,intentservice會自動停止,而不需要我們手動停止或stopSelf()。另外,可以啟動intentservice多次,而每一個耗時操作會以工作隊列的方式在intentservice的onHandleIntent回調方法中執行,并且,每次隻會執行一個工作現場,執行完第一個在執行第二個。
七、問:intentservice使用方法?
答:如下圖,建立intentService時,隻需要實作onHandlerIntent和構造方法即可,onhandlerintent為異步方法,可以執行耗時操作。
八、intentservice源碼分析?
答:推薦部落格:鴻洋:http://blog.csdn.net/lmj623565791/article/details/47143563
我們打開IntentService源碼之後就會發現,它繼承的是service。我們來看它的onCreate()方法。不難發現,它裡面使用了HandlerThread。他就是利用handlerThread來進行異步消息傳遞的。
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
//傳入的是HandlerThread的對象,這裡就讓ServiceHandler變成了一個處理線程的異步類。 他就持有了HandlerThread的looper,是以可移執行異步任務。
mServiceLooper = thread.getLooper();
//ServiceHandler又是繼承handler,進行了内部封裝。
mServiceHandler = new ServiceHandler(mServiceLooper);
}
- 他又是如何啟動異步的呢?
答:intentService啟動後,會調用一個onStartCommand()方法。如下面源碼。onStartCommand中調用了onStart方法,所有操作都是在onStart方法中來執行的。在onStart方法中,mServiceHandler是通過.sendMessage來發送消息的,而消息肯定會發送到hendlerThread中進行處理。因為henlerThread持有looper對象。最後handleMessage中回調onHandleIntent(intent)。
@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
注意還有一個問題,在handleMessage中,有一個stopSelf(msg.arg1);方法。
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
這個方法和不帶參數的stopSelf()不一樣,如果不帶參數他會立即停止任務,如果有參數,他會等待所有全部執行完才會終止任務。
九、問:view樹的繪制流程?
答:通俗可以了解:我們有一個android應用視窗,裡面包含了很多的UI元素。這些元素是以樹形結構來組織的,他們存在着父與子的關系。是以,在繪制的時候,我們需要知道它裡面各個子元素在父元素裡面的大小及位置。而确定子元素在父元素中的大小和位置,可以成為測量過程和布局過程。是以,在Android應用程式視窗對UI的渲染可以分為:測量、布局、繪制三個階段。對應Measure - onlayout - ondraw。
1.measure用來測量View的寬和高。
2.layout用來确定View在父容器中放置的位置。
3.draw用來将view繪制在螢幕上。
推薦部落格:http://blog.csdn.net/guolin_blog/article/details/16330267/
十、問:measure方法詳解?
答:
- 先看看onMeasure函數原型:
onMeasure函數原型:protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)這兩個參數就是包含寬和高的資訊。它還包含了測量模式,也就是說,一個int整數,裡面放了測量模式和尺寸大小。那麼一個數怎麼放兩個資訊呢?我們知道,我們在設定寬高時有3個選擇:wrap_content、match_parent以及指定固定尺寸,而測量模式也有3種:UNSPECIFIED(unspecified),EXACTLY(exactly),AT_MOST,當然,他們并不是一一對應關系哈,這三種模式後面我會詳細介紹,但測量模式無非就是這3種情況,而如果使用二進制,我們隻需要使用2個bit就可以做到,因為2個bit取值範圍是[0,3]裡面可以存放4個數足夠我們用了。那麼Google是怎麼把一個int同時放測量模式和尺寸資訊呢?我們知道int型資料占用32個bit,而google實作的是,将int資料的前面2個bit用于區分不同的布局模式,後面30個bit存放的是尺寸的資料。
- 問:如何擷取到模式和尺寸:
答:
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- 問測量模式有三種,他們有什麼差別?
答:
UNSPECIFIED(unspecified) 父容器沒有對目前View有任何限制,目前View可以任意取尺寸
EXACTLY (exactly) 父容器為子視圖确定一個尺寸的大小,不管你的子視圖希望多大,都必須限定在父容器給給它限定的尺寸之内(相當于match_parent)
AT_MOST 父容器為子視圖指定的一個最大的尺寸,子視圖所有的大小都必須在這個最大的尺寸内,這個模式下,父控件無法擷取子控件的尺寸,隻能用子控件自己根據需求測量自己的尺寸。(相當于wrap_content)
- onMeasure是實作我們測量邏輯的方法,為什麼不是measure?
measure源碼,
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << | (long) heightMeasureSpec & L;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray();
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? - : mMeasureCache.indexOfKey(key);
if (cacheIndex < || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
//注意看這裡,請注意,請注意,請注意
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> ), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << |
(long) mMeasuredHeight & L); // suppress sign extension
}
measure在源碼中還是調用到了onMeasure(widthMeasureSpec, heightMeasureSpec),通過 mOldWidthMeasureSpec = widthMeasureSpec和 mOldHeightMeasureSpec = heightMeasureSpec來進行測量,是以我們在自定義view的時候,複寫onMeasure()方法即可。
- onMeasure源碼
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
我們就可以看到,他又兩個參數,寬和高的測量規格,他調用了setMeasuredDimension()方法。這個方法是測量方案的終極,也是實作測量的方法,看下面setMeasuredDimension()源碼。
他的意思就是說,我們會在onmeasure()方法中擷取到計算的尺寸,然後傳遞給setMeasuredDimension()方法。而setMeasuredDimension()是一個測量結束的方法,這個方法是必須被調用的。否則你會報異常。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
- 總結:measure方法測量
答:它開始于我們的父控件viewgroup,他會通過不斷的周遊子控件的measure方法,然後會根據viewgroup.measureSpec和子控件的layoutparent來決定我們子視圖的measureSpec(測量規格)。通過這個測量規格,進一步擷取到子view的寬高,然後一層一層的向下傳遞,不斷的儲存父控件的測量寬高,整個measure的測量過程就是一個樹型的遞歸過程,為整個view樹計算實際大小,每一個view視圖的控件的實際的寬和高,都是有父視圖和它本身的layoutparent所決定。
十一、問:layout方法詳解?
答:measure過程結束後,視圖的大小就已經測量好了,接下來就是layout的過程了。正如其名字所描述的一樣,這個方法是用于給視圖進行布局的,也就是确定視圖的位置。
- 首先我們看下layout的源碼,别嫌棄多,就大概看下就行。
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != ) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = ; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
如上面源碼,我們會看到,通過
boolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
來判斷視圖的大小是否發生過變化,以确定有沒有必要對目前的視圖進行重繪,同時還會在這裡把傳遞過來的四個參數分别指派給mLeft、mTop、mRight和mBottom這幾個變量。然後會調用 onLayout(changed, l, t, r, b);然後onLayout()方法卻是個空方法,也就是說具體的視圖都要覆寫該函數來實作自己的顯示(比如TextView在這裡實作了繪制文字的過程)。
十二、問:draw方法簡解?
答: draw操作利用前兩部得到的參數,将視圖顯示在螢幕上,到這裡也就完成了整個的視圖繪制工作。子類也不應該修改該方法,因為其内部定義了繪圖的基本操作:
注意:
1、繪制視圖本身,即調用onDraw()函數。在view中onDraw()是個空函數,也就是說具體的視圖都要覆寫該函數來實作自己的顯示(比如TextView在這裡實作了繪制文字的過程)。而對于ViewGroup則不需要實作該函數,因為作為容器是“沒有内容“的,其包含了多個子view,而子View已經實作了自己的繪制方法,是以隻需要告訴子view繪制自己就可以了,也就是下面的dispatchDraw()方法;
2、繪制子視圖,即dispatchDraw()函數。在view中這是個空函數,具體的視圖不需要實作該方法,它是專門為容器類準備的,也就是容器類必須實作該方法;
兩個容易混淆的方法:http://www.cnblogs.com/android-blogs/p/5778851.html
1、invalidate():調用此方法的時候,也就是請求我們的安卓系統,如果視圖大小沒有發生變法,就不會調用layout方法(放置過程)。
2、requestLayout():當布局發生變法的時候,比如說方向或者尺寸發生了改變,我們就會手動調用requestLayout()方法,而調用完此方法之後,他就會觸發measure和layout過程,但是不會觸發draw方法
十二、問:View什麼時候測量/布局/繪制?
答:
Invalidate :請求重繪view樹,假如視圖大小沒有變化就不會調用layout(),隻繪制那些需要重繪的視圖,誰請求就重繪誰(ViewGroup調用就重繪整個ViewGroup)
requestLayout :隻對view樹重新layout,會導緻調用measure和layout過程,不會調用draw()過程
requestFocus :請求view樹的draw過程,但隻繪制需要重繪的視圖
setVisibility() :當View可視狀态在INVISIBLE轉換VISIBLE時,會間接調用invalidate()方法, 當View的可視狀态在INVISIBLE/ VISIBLE 轉換為GONE狀态時,會間接調用requestLayout() 和invalidate方法。同時,由于整個個View樹大小發生了變化,會請求measure()過程以及draw()過程,同樣地,隻繪制需要“重新繪制”的視圖