編輯:關於Android編程
Android中View繪制的流程包括:measure(測量)->layout(布局)->draw(繪制).
因為Android中每個View都占據了一塊矩形的空間,當我們要在屏幕上顯示這個矩形的View的時候
首先我們需要知道這個矩形的大小(寬和高)這就對應了View的measure流程. 有了View的寬和高,我們還需要知道View左上角的起點在哪裡,右下角的終點在哪裡,這就對應了View的layout流程. 當矩形的區域在屏幕上確定之後,相當於屏幕上有了一塊屬於View的畫布,接下來通過draw方法就可以在這塊畫布上畫畫了.本文重點介紹View的measure流程.一般情況下,我們都是在xml文件裡去定義一個布局文件,針對每個View來說,是一定要聲明layout_width和layout_height的,不然編譯期就會報錯.
那layout_width和layout_height數值包括:
具體的長度單位,例如20dp或者20px. match_parent,代表充滿父控件. wrap_content,代表能包含View中內容即可.從layout_width和layout_height的取值來看,如果Android規定只能使用具體的長度,那可能就不需要measure流程了,因此寬和高已經知道了.因此,從取值上我們知道了measure方法的作用是:將match_parent和wrap_content轉成具體的度量值.
接下來,我們從源碼的角度去分析measure方法.
/**
*
* 這個方法是用來測試View的寬和高的. View的父附件提供了寬和高的限制參數,即View的最值. *
* *
* View的真正測量工作是在 {@link #onMeasure(int, int)} 函數裡進行的. 因此, 只有 * {@link #onMeasure(int, int)} 方法才能被子類重寫. *
* */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) { // 判斷View的layoutMode是否為LAYOUT_MODE_OPTICAL_BOUNDS boolean optical = isLayoutModeOptical(this); // 子View是LAYOUT_MODE_OPTICAL_BOUNDS,父附件不是LAYOUT_MODE_OPTICAL_BOUNDS的情況很少見,不需要去care 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); } // 生成View寬高的緩存key,並且如果緩存Map為null,則構建緩存Map. long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); // 判斷是否為強制布局或者寬、高發生了變化 if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // 清除PFLAG_MEASURED_DIMENSION_SET標記,表示該View還沒有被測量. mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; // 解析從右向左的布局 resolveRtlPropertiesIfNeeded(); // 判斷是否能用cache緩存中view寬和高 int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // View寬和高真正測量的地方 onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { // 獲取緩存中的View寬和高 long value = mMeasureCache.valueAt(cacheIndex); // long占8個字節,前4個字節為寬度,後4個字節為高度. setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } // 無論是調用onMeasure還是使用緩存,都應該設置了PFLAG_MEASURED_DIMENSION_SET標志位. // 沒有設置,則說明測量過程出了問題,因此拋出異常. // 並且,一般出現這種情況一般是子類重寫onMeasure方法,但是最後沒有調用setMeasuredDimension. 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; } // 記錄View的寬和高,並將其存儲到緩存Map裡. mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); }
從measure方法中,我們可以看出,measure方法只要是加了一個緩存機制,真正的測量還是在onMeasure去執行.為了防止緩存的濫用,Android系統直接將measure設置為final類型,即子類不能重寫,意味著子類只需要提供測量的具體實現,不需要care緩存等提速功能的實現.
onMeasure的默認實現非常簡單,注釋源碼如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 將getDefaultSize函數處理後的值設置給寬和高的成員變量
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
// 特殊處理LAYOUT_MODE_OPTICAL_BOUNDS的情況
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;
}
// 真正設置View寬和高的地方
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
// 保存寬度到View的mMeasuredWidth成員變量中
mMeasuredWidth = measuredWidth;
// 保存高度到View的mMeasuredHeight成員變量中
mMeasuredHeight = measuredHeight;
// 設置View的PFLAG_MEASURED_DIMENSION_SET,即代表當前View已經被測量過.
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
通過源碼,我們發現onMeasure只是給mMeasuredWidth和mMeasuredHeight兩個成員變量賦值.那這個值是如何獲取到的呢?和Parent提供的限制又有神馬關系呢?
想回答這個問題,需要先跟蹤一下getDefaultSize的源碼.
public static int getDefaultSize(int size, int measureSpec) {
// size表示View想要的尺寸信息,比如最小寬度或者最小高度
int result = size;
// 解析SpecMode信息
int specMode = MeasureSpec.getMode(measureSpec);
// 解析SpecSize信息
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
// 如果View的度量值設置為WARP_CONTENT的時候
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
// 如果View的度量值設置為具體數值或者MATCH_PARENT的時候
result = specSize;
break;
}
return result;
}
同時,需要看一下getSuggested一系列方法,以getSuggestedMinimumWidth為例:
protected int getSuggestedMinimumWidth() {
// 如果View沒有設置背景,則返回View本身的最小寬度mMinWidth.
// 如果View設置了背景,那麼就取mMinWidth和背景寬度的最大值.
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
這裡有同學會奇怪View的mMinWidth(最小寬度)是什麼時候設置的?我們可以看一下View的構造函數(截取部分代碼):
case R.styleable.View_minWidth:
mMinWidth = a.getDimensionPixelSize(attr, 0);
break;
可以看到,和我們自定義一個View是一樣的,mMinWidth成員變量對應著的自定義屬性是minWidth,如果xml中未定義則默認值是0.
示例設置代碼:
或許還有同學會對上面的MeasureSpec感到陌生,不要著急,這就帶來MeasureSpec的詳解.
MeasureSpec的值由specSize和spceMode共同組成,其中specSize記錄的是大小,specMode記錄的是規格.
我們來看一下MeasureSpec的源碼定義,他是View的內部類,源碼位於: /frameworks/base/core/java/android/view/View.java
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// specMode一共有三種類型
public static final UNSPECIFIED = 0 << MODE_SHIFT; // 任意大小
public static final int EXACTLY = 1 << MODE_SHIFT; // 表示父容器希望子視圖的大小由specSize來決定
public static final int AT_MOST = 2 << MODE_SHIFT; // 表示子視圖最多只能是specSize中指定的大小
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
public static int getMode(int measureSpec) {
return measureSpec & MODE_MASK;
}
public static int getSize(int measureSpec) {
return measureSpec & ~MODE_MASK;
}
}
只看源碼可能大家還比較陌生,這裡結合源碼講解一下.首先,Android只所以提供MeasureSpec類,我認為Android開發人員覺得View尺寸最大不超過2^30,所以將剩余的2位來表示模式,這樣節約了空間.
前面說過,View的寬和高只有三種情況,分別是:具體數值,WARP_CONTENT,MATCH_PARENT.而measure就是將WARP_CONTENT,MATCH_PARENT轉換為具體數值的方法,所以需要有MeasureSpec.getSize()方法獲取具體數值.
那MeasureSpec.getMode的幾種類型和View的賦值也是有對應關系的:
對於非ViewGroup的View,上面介紹的measure->onMeasure方法足以完成View的測量.
但是對於自定義ViewGroup,我們不僅要測量自己的寬和高,同時還需要負責測量child view的寬和高.
測量child view通常會用到measureChild方法.源碼實現如下:
/**
* 測量每個子View的寬高.測量時需要排除padding的影響,如果需要排除margins,則調用measureChildWithMargins方法.
*/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到,measureChild方法只要是獲取子View的widthMeasureSpec和heightMeasureSpec,然後調用measure方法設置子View的實際寬高值.同時,從getChildMeasureSpec的方法傳遞中,我們可以看出child View的值是由父View和child view共同決定的.
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 獲取父容器的MeasureSpecMode
int specMode = MeasureSpec.getMode(spec);
// 獲取父容器的具體大小
int specSize = MeasureSpec.getSize(spec);
// 子view的可能大小=父View的大小-padding
int size = Math.max(0, specSize - padding);
// 設置子View的初始MeasureSpecSize和MeasureSpecMode均為0
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// 當父容器有固定大小(具體數值或MATCH_PARENT)的情況下
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
// view的寬或高賦值為具體數值,例如20dp
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// view的寬或高賦值為MATCH_PARENT
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// view的寬或高賦值為WRAP_CONTENT,這時View的最大值就是父容器大小-padding.所以MeasureSpecSize=父容器大小-padding.
// 同時,View的最大值為MeasureSpecSize,所以MeasureSpecMode設置為AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 當父容器是AT_MOST情況下(即父容器大小賦值為WRAP_CONTENT)
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 由於父容器的mode為AT_MOST,雖然子View設置為MATCH_PARENT,但是父容器大小不是精確的,所以子View也只能是AT_MOST了.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 這個case不需要care,自定義控件不會遇到這種case的.
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
getChildMeasureSpec的總體思路:
通過其父容器提供的MeasureSpec參數得到specMode和specSize,並根據計算出來的specMode以及子視圖的childDimension(layout_width和layout_height中定義的值)來計算子View自身的measureSpec和measureMode.
一、構建思路1、構建一個Request用來封裝 HTTP請求的類型、請求頭參數、請求體、優先級、返回類型、等一些必要的屬性。 這個Request定義為抽象的,使得用戶可以
微信對話列表滑動刪除效果很不錯的,借鑒了github上SwipeListView(項目地址:https://github.com/likebamboo/SwipeList
??通過上一篇文件的分析,我們對Activity的啟動模式有了比較清晰的了解後,本篇我們將繼續對Activity啟動模式的相關參數和任務棧分析,接下來我們就繼續上一篇的問
一、Android中WebView的漏洞分析最近在開發過程中遇到一個問題,就是WebView使用的時候,還是需要解決之前系統(4.2之前)導致的一個漏洞,雖然現在這個系統