日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

view.post不执行的坑点

發布時間:2025/3/12 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 view.post不执行的坑点 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

view.post沒執行,runOnUiThread,Handler

目錄

  • 坑點
  • 處理
  • 原因
  • 經歷
  • 復盤
    • 6.0版本
    • 10.0版本
  • 總結

坑點

子線程執行view.post(Runnable) 部分 手機沒有效果。

usernameEditText.post(new Runnable() {@Overridepublic void run() {usernameEditText.setText("text set by runnable!");}});

處理

  • 使用handler.post
  • 使用runOnUiThread
  • 原因

    低版本android基于ThreadLocal實現線程與數據關聯,且線程的數據獨立不共享。遇到多線程使用時,一個線程存儲了數據,另一個線程取不到的數據的原因。
    子線程調用view.post時候,會構造一個隊列存儲到對應的線程數據空間,并將runnable加到此隊列。當view要顯示時候,ui線程會從ui線程的數據空間取出隊列,遍歷執行隊列中的runnable,但是由于ThreadLocal的緣故,ui線程取到的隊列肯定不包含子線程存到隊列的runnable,所以這個runnable是不被執行的。因為剛才是子線程存的runnable,子線程可以取到,而UI線程并沒有存我們期望的runnable,所以取不到。ThreadLocal特點就是線程之間的數據相互隔離,各自使用各自的數據,多線程使用時保證數據的“安全”。
    還沒明白的話,這樣講一下:
    A、B錢包都沒錢了,A從銀行取了1000 人民幣,裝入了自己的錢包,B去商店買1000的商品,此時B從自己錢包里面拿錢時,錢包是空的。所以B是買不了商品的。

    7.0之前的系統存在這個問題,7.0之后已經被修復了。用的時候小心一點。

    經歷

    給同事寫了個程序,當時是一個子線程處理了數據之后調用view.post更新到界面上,自測是沒問題的,結果同事那邊告知不顯示,當時也查看了源代碼,同時也是反復驗證沒有問題,同事那里始終是有問題的,后來同事也沒再提說。一段時間之后見了同事,問及此事,他說 只有他手機有問題,其他的手機沒問題,所以就沒再說這個事情了。讓其掏出手機看了下,果真不顯示,當即開始加日志調試,結果runnable代碼塊沒有被執行,隱隱約約感覺到view.post不靠譜,直接在外層再加一個runOnUiThread之后,他的設備正常。 雖然問題是過去了,但一直沒時間去弄清楚出現這個問題的原因,最近看到項目中有其他小伙伴也寫了同樣的代碼,心里面有點慌。
    同時也查了些資料,總結記錄之。

    復盤

    View.post()方法在android7.0之前,在view沒有attachToWindow的時候調用該方法可能失效,尤其異步線程,如在onCreate,onBindViewHolder時調用view.post方法,可能會不生效,在異步線程view.post方法不執行的情況居多。建議使用Handler post方法代替。
    longlong2015 這里也對次問題進行說明

    于是乎,下載了一份6.0版本的sdk源碼,以及9.0的源碼進行對比,對比情況和引用文章差不多,也進一步對引用文章進行驗證。

    6.0版本

  • View的post函數
  • public boolean post(Runnable action) {final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {return attachInfo.mHandler.post(action);}// Assume that post will succeed laterViewRootImpl.getRunQueue().post(action);return true;}

    如果attachInfo有值,則是用attachInfo中的handler去post這個runnable,如果attachInfo沒有值,則是ViewRootImpl.getRunQueue() 去執行post這個runnable。而attachInfo則是分別dispatchAttachedToWindow (首行)賦值的:

    void dispatchAttachedToWindow(AttachInfo info, int visibility) {//System.out.println("Attached! " + this);mAttachInfo = info;if (mOverlay != null) {mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);}mWindowAttachCount++;// We will need to evaluate the drawable state at least once.mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;if (mFloatingTreeObserver != null) {info.mTreeObserver.merge(mFloatingTreeObserver);mFloatingTreeObserver = null;}if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {mAttachInfo.mScrollContainers.add(this);mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;}performCollectViewAttributes(mAttachInfo, visibility);onAttachedToWindow();ListenerInfo li = mListenerInfo;final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =li != null ? li.mOnAttachStateChangeListeners : null;if (listeners != null && listeners.size() > 0) {// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to// perform the dispatching. The iterator is a safe guard against listeners that// could mutate the list by calling the various add/remove methods. This prevents// the array from being modified while we iterate it.for (OnAttachStateChangeListener listener : listeners) {listener.onViewAttachedToWindow(this);}}int vis = info.mWindowVisibility;if (vis != GONE) {onWindowVisibilityChanged(vis);}// Send onVisibilityChanged directly instead of dispatchVisibilityChanged.// As all views in the subtree will already receive dispatchAttachedToWindow// traversing the subtree again here is not desired.onVisibilityChanged(this, visibility);if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {// If nobody has evaluated the drawable state yet, then do it now.refreshDrawableState();}needGlobalAttributesUpdate(false);}

    dispatchDetachedFromWindow(倒數第三行)中賦空

    void dispatchDetachedFromWindow() {AttachInfo info = mAttachInfo;if (info != null) {int vis = info.mWindowVisibility;if (vis != GONE) {onWindowVisibilityChanged(GONE);}}onDetachedFromWindow();onDetachedFromWindowInternal();InputMethodManager imm = InputMethodManager.peekInstance();if (imm != null) {imm.onViewDetachedFromWindow(this);}ListenerInfo li = mListenerInfo;final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =li != null ? li.mOnAttachStateChangeListeners : null;if (listeners != null && listeners.size() > 0) {// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to// perform the dispatching. The iterator is a safe guard against listeners that// could mutate the list by calling the various add/remove methods. This prevents// the array from being modified while we iterate it.for (OnAttachStateChangeListener listener : listeners) {listener.onViewDetachedFromWindow(this);}}if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {mAttachInfo.mScrollContainers.remove(this);mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;}mAttachInfo = null;if (mOverlay != null) {mOverlay.getOverlayView().dispatchDetachedFromWindow();}}
  • ViewRootImpl.getRunQueue()
    ViewRootImpl 的靜態成員 sRunQueues 和靜態函數getRunQueue
  • static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>(); static RunQueue getRunQueue() {RunQueue rq = sRunQueues.get();if (rq != null) {return rq;}rq = new RunQueue();sRunQueues.set(rq);return rq;}

    ui線程執行“存到隊列中的任務"

    // Execute enqueued actions on every traversal in case a detached view enqueued an actiongetRunQueue().executeActions(mAttachInfo.mHandler);

    根源是sRunQueues.get(),其實也是ThreadLocal的特性。當子線程調用的時候,這里返回的rq 是空的,接著創建一個rt后存入。之后UI線程調用,這里返回的不是子線程創建的rq。

  • ViewRootImpl.RunQueue.executeActions
  • void executeActions(Handler handler) {synchronized (mActions) {final ArrayList<HandlerAction> actions = mActions;final int count = actions.size();for (int i = 0; i < count; i++) {final HandlerAction handlerAction = actions.get(i);handler.postDelayed(handlerAction.action, handlerAction.delay);}actions.clear();}}
  • ThreadLocal.get()
    再進一步看一下這個ThreadLocal的get實現(get的樣子往往容易被忽視)
  • public T get() {// Optimized for the fast path.Thread currentThread = Thread.currentThread();Values values = values(currentThread);if (values != null) {Object[] table = values.table;int index = hash & values.mask;if (this.reference == table[index]) {return (T) table[index + 1];}} else {values = initializeValues(currentThread);}return (T) values.getAfterMiss(this);}

    好的,到這里已經看到取當前的線程做了一系列的事情,因此不同線程返回的自然就不一樣。

    10.0版本

  • View.post
  • public boolean post(Runnable action) {final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {return attachInfo.mHandler.post(action);}// Postpone the runnable until we know on which thread it needs to run.// Assume that the runnable will be successfully placed after attach.getRunQueue().post(action);return true;}

    可以看到這里以不是用ViewRootImpl.getRunQueue(),而是view內部的函數getRunQueue().

  • View.getRunQueue()
  • private HandlerActionQueue getRunQueue() {if (mRunQueue == null) {mRunQueue = new HandlerActionQueue();}return mRunQueue;}

    好家伙,現在的隊列是屬于view的了,不再是歸屬于線程,變成了共享變量。
    因此子線程向隊列里面添加一個runnable之后,ui線程做來取隊列就能取到。執行就是我們期望的結果了。

  • View.dispatchAttachedToWindow
  • void dispatchAttachedToWindow(AttachInfo info, int visibility) {mAttachInfo = info;if (mOverlay != null) {mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);}mWindowAttachCount++;// We will need to evaluate the drawable state at least once.mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;if (mFloatingTreeObserver != null) {info.mTreeObserver.merge(mFloatingTreeObserver);mFloatingTreeObserver = null;}registerPendingFrameMetricsObservers();if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {mAttachInfo.mScrollContainers.add(this);mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;}// Transfer all pending runnables.if (mRunQueue != null) {mRunQueue.executeActions(info.mHandler);mRunQueue = null;}performCollectViewAttributes(mAttachInfo, visibility);onAttachedToWindow();ListenerInfo li = mListenerInfo;final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =li != null ? li.mOnAttachStateChangeListeners : null;if (listeners != null && listeners.size() > 0) {// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to// perform the dispatching. The iterator is a safe guard against listeners that// could mutate the list by calling the various add/remove methods. This prevents// the array from being modified while we iterate it.for (OnAttachStateChangeListener listener : listeners) {listener.onViewAttachedToWindow(this);}}int vis = info.mWindowVisibility;if (vis != GONE) {onWindowVisibilityChanged(vis);if (isShown()) {// Calling onVisibilityAggregated directly here since the subtree will also// receive dispatchAttachedToWindow and this same callonVisibilityAggregated(vis == VISIBLE);}}// Send onVisibilityChanged directly instead of dispatchVisibilityChanged.// As all views in the subtree will already receive dispatchAttachedToWindow// traversing the subtree again here is not desired.onVisibilityChanged(this, visibility);if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {// If nobody has evaluated the drawable state yet, then do it now.refreshDrawableState();}needGlobalAttributesUpdate(false);notifyEnterOrExitForAutoFillIfNeeded(true);notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);}

    其中下面這段就是UI線程來執行存入需要處理的任務:

    if (mRunQueue != null) {mRunQueue.executeActions(info.mHandler);mRunQueue = null;}

    總結

    子線程在onAttachedToWindow之后調用view.post,是有效的。其次是與系統版本有一定關系,出現問題的場景就是子線程處理的完成數據之后調用view.post時,onAttachedToWindow還沒有回調,一般是activity onCreate函數中初始化完成view之前這段時間可能出現不執行的問題。

    總結

    以上是生活随笔為你收集整理的view.post不执行的坑点的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。