13:TextView#setText、DalvikART虚拟机
1、TextView#setText()繪制流程
setText()方法中調用checkForRelayout()方法
if (mLayout != null) {checkForRelayout();} 復制代碼具體邏輯看checkForRelayout方法: 當寬度不是wrap_content,高度固定或者高度沒有發生變化時,調用invalidate方法后return了;否則執行后面代碼,調用requestLayout、invalidate重新繪制。invalidate在Android 28以上,開啟硬件加速的設備上,不會檢查UI線程(下一點具體講)。
/*** Check whether entirely new text requires a new view layout* or merely a new text layout.*/private void checkForRelayout() {// If we have a fixed width, we can just swap in a new text layout// if the text height stays the same or if the view height is fixed.if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||(mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&(mHint == null || mHintLayout != null) &&(mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {// Static width, so try making a new text layout.int oldht = mLayout.getHeight();int want = mLayout.getWidth();int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();/** No need to bring the text into view, since the size is not* changing (unless we do the requestLayout(), in which case it* will happen at measure).*/makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),false);if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {// In a fixed-height view, so use our new text layout.if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&mLayoutParams.height != LayoutParams.MATCH_PARENT) {invalidate();return;}// Dynamic height, but height has stayed the same,// so use our new text layout.if (mLayout.getHeight() == oldht &&(mHintLayout == null || mHintLayout.getHeight() == oldht)) {invalidate();return;}}// We lose: the height has changed and we have a dynamic height.// Request a new view layout using our new text layout.requestLayout();invalidate();} else {// Dynamic width, so we have no choice but to request a new// view layout with a new text layout.nullLayouts();requestLayout();invalidate();}} 復制代碼TextView#requestLayout:內部調用ViewRootImpl#requestLayout方法,走測量、布局、繪制流程了。
/*** Call this when something has changed which has invalidated the* layout of this view. This will schedule a layout pass of the view* tree. This should not be called while the view hierarchy is currently in a layout* pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the* end of the current layout pass (and then layout will run again) or after the current* frame is drawn and the next layout occurs.** <p>Subclasses which override this method should call the superclass method to* handle possible request-during-layout errors correctly.</p>*/public void requestLayout() {if (mMeasureCache != null) mMeasureCache.clear();if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {// Only trigger request-during-layout logic if this is the view requesting it,// not the views in its parent hierarchyViewRootImpl viewRoot = getViewRootImpl();if (viewRoot != null && viewRoot.isInLayout()) {if (!viewRoot.requestLayoutDuringLayout(this)) {return;}}mAttachInfo.mViewRequestingLayout = this;}mPrivateFlags |= PFLAG_FORCE_LAYOUT;mPrivateFlags |= PFLAG_INVALIDATED;if (mParent != null && !mParent.isLayoutRequested()) {mParent.requestLayout();}if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {mAttachInfo.mViewRequestingLayout = null;}} 復制代碼ViewRootImpl#requestLayout:此處才有checkThread檢測是否在主線程,因此在子線程調用TextView#setText時,如果未導致View重繪,則不會去調用checkThread,也就不會拋出異常了。
@Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;scheduleTraversals();}} 復制代碼而invalidate方法,將會調用到ViewRootImpl中,再次調用scheduleTraversals方法。
因此繪制了2次。
2、invalidate原理
// View public void invalidate(boolean invalidateCache) {invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);}void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {if (mGhostView != null) {mGhostView.invalidate(true);return;}if (skipInvalidate()) {return;}if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {if (fullInvalidate) {mLastIsOpaque = isOpaque();mPrivateFlags &= ~PFLAG_DRAWN;}mPrivateFlags |= PFLAG_DIRTY;if (invalidateCache) {mPrivateFlags |= PFLAG_INVALIDATED;mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;}// Propagate the damage rectangle to the parent view.final AttachInfo ai = mAttachInfo;final ViewParent p = mParent;if (p != null && ai != null && l < r && t < b) {final Rect damage = ai.mTmpInvalRect;damage.set(l, t, r, b);p.invalidateChild(this, damage);}// Damage the entire projection receiver, if necessary.if (mBackground != null && mBackground.isProjected()) {final View receiver = getProjectionReceiver();if (receiver != null) {receiver.damageInParent();}}}}復制代碼到ViewGroup#invalidateChild方法中
@Deprecated@Overridepublic final void invalidateChild(View child, final Rect dirty) {final AttachInfo attachInfo = mAttachInfo;//Android 28if (attachInfo != null && attachInfo.mHardwareAccelerated) {// HW accelerated fast pathonDescendantInvalidated(child, child);return;}......do{...parent = parent.invalidateChildInParent(location, dirty);...return parent;}while(parent != null);return null;} 復制代碼在Android 28以下,走的是parent = parent.invalidateChildInParent(location, dirty),最終在ViewRootImpl的invalidateChildInParent方法第一句檢查UI線程。而當父View不需要重新繪制時,parent將返回null結束循環,只繪制當前View,也不會檢測UI線程。 www.jianshu.com/p/753441fcb…
而在Android 28的invalidateChild方法中,硬件加速開啟的情況下,走的是onDescendantInvalidated方法,不會去檢查UI線程。因此,如果TextView寬度不是wrap_content,高度未變的情況下,是可以在子線程使用setText方法更新UI的。
//ViewGroup 28@Override@CallSuperpublic void onDescendantInvalidated(@NonNull View child, @NonNull View target) {/** HW-only, Rect-ignoring damage codepath** We don't deal with rectangles here, since RenderThread native code computes damage for* everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)*/// if set, combine the animation flag into the parentmPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {// We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential// optimization in provides in a DisplayList world.mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;// simplified invalidateChildInParent behavior: clear cache validity to be safe...mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;}// ... and mark inval if in software layer that needs to repaint (hw handled in native)if (mLayerType == LAYER_TYPE_SOFTWARE) {// Layered parents should be invalidated. Escalate to a full invalidate (and note that// we do this after consuming any relevant flags from the originating descendant)mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;target = this;}if (mParent != null) {mParent.onDescendantInvalidated(this, target);}} 復制代碼 //ViewRootImpl@Overridepublic void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {mIsAnimating = true;}invalidate();}void invalidate() {mDirty.set(0, 0, mWidth, mHeight);if (!mWillDrawSoon) {scheduleTraversals();}} 復制代碼3、Dalvik和ART有什么區別
Dalvik基于寄存器,運行dex文件,使用16位指令集
ART即Android Runtime。 ART 的機制與 Dalvik不同。在Dalvik下,應用每次運行的時候,字節碼都需要通過即時編譯器(just in time ,JIT)轉換為機器碼,這會拖慢應用的運行效率; 而在ART 環境中,應用在第一次安裝的時候,字節碼就會預先編譯成機器碼,使其成為真正的本地應用。這個過程叫做預編譯(AOT,Ahead-Of-Time)。這樣的話,應用的啟動(首次)和執行都會變得更加快速。
總結
以上是生活随笔為你收集整理的13:TextView#setText、DalvikART虚拟机的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 百家中华老字号故宫过大年
- 下一篇: python 內建数据类型