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!");}});處理
原因
低版本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版本
如果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 的靜態成員 sRunQueues 和靜態函數getRunQueue
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。
再進一步看一下這個ThreadLocal的get實現(get的樣子往往容易被忽視)
好的,到這里已經看到取當前的線程做了一系列的事情,因此不同線程返回的自然就不一樣。
10.0版本
可以看到這里以不是用ViewRootImpl.getRunQueue(),而是view內部的函數getRunQueue().
好家伙,現在的隊列是屬于view的了,不再是歸屬于線程,變成了共享變量。
因此子線程向隊列里面添加一個runnable之后,ui線程做來取隊列就能取到。執行就是我們期望的結果了。
其中下面這段就是UI線程來執行存入需要處理的任務:
if (mRunQueue != null) {mRunQueue.executeActions(info.mHandler);mRunQueue = null;}總結
子線程在onAttachedToWindow之后調用view.post,是有效的。其次是與系統版本有一定關系,出現問題的場景就是子線程處理的完成數據之后調用view.post時,onAttachedToWindow還沒有回調,一般是activity onCreate函數中初始化完成view之前這段時間可能出現不執行的問題。
總結
以上是生活随笔為你收集整理的view.post不执行的坑点的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 多台Linux服务器之间互相免密登陆
- 下一篇: 我的python 入门 安装 -- h