<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                [TOC] # 背景 近幾個月來收到了多起在Android11手機上,拖拽界面時無法滑動的問題反饋。 表現為在異常的界面上按住屏幕進行**滑動沒有任何響應,但又可以進行點擊**。而除了這個界面,**其他界面一切正常**。 ## 復現場景 在B界面(個人主頁)發送事件(取消關注某個作者),界面A(列表界面)收到事件,進行RemoveData(移除對應作者的作品), 然后調用RecyclerView.Adapter#notifyDataSetChange操作通知刷新。再返回到A界面,此時的A界面變變得無法滑動,但可以點擊。再點擊進入其他界面C,C界面都可正常滑動。 ## 被合并的Move事件 大部分的滑動問題都是因為存在著嵌套滑動沖突。為了驗證是否是嵌套的問題,我們需要在不同層級的View中打印接收到的MotionEvent. 很快,我們就排除了嵌套滑動的因素。因為當我們在Activity#dispatchTouchEvent的時候對MotionEvent進行打印,驚奇的發現MotionEvent在分發到Activity的時候就已經“**不同尋常**”。 1 . 手指在按壓滑動過程中不會收到任何Move事件。Move事件在手指抬起后,跟隨Up事件一并發送,并且有僅只有一個Move事件。 2. 通過查看這個“唯一”的Move事件,發現其MotionEvent#getHistorySize()竟然達到幾十上百,存放著Move過程中的所有軌跡點。 # 排查流程 ## 系統進程 InputReader&InputDispatcher ![](https://img.kancloud.cn/f7/ec/f7ec8e13586f596f7615c5da740cfcb0_1200x716.png) InputReader 和 InputDispatcher 是跑在System Server進程中的里面的兩個 Native 線程,負責**讀取和分發 Input 事件**。要想分析input事件的流向,需要從這里開始入手。 1. InputReader 讀取 Input 事件 2. InputReader 將讀取的 Input 事件放到 InboundQueue 中 3. InputDispatcher 從 InboundQueue 中取出 Input 事件派發到目標 Connection 的 OutBoundQueue(即發送給哪個Window是由InputDispatcher決定的) 4. 同時將事件記錄到各個 Connection 的 WaitQueue 5. App 接收到 Input 事件,同時記錄到 PendingInputEventQueue ,然后對事件進行分發處理 6. App 處理完成后,回調 InputManagerService 將負責監聽的 WaitQueue 中對應的 Input 移除 ### 工具dumpsys ``` adb shell dumpsys input ``` 出現問題的界面進行滑動,同時手指保持再屏幕上,不進行抬起,進行是adb shell dumpsys input 可以看到OutboundQueue中是沒有任何東西的,而WaitQueue中堆積了大量的MotionEvent(action=MOVE),此時也并沒有被合并成一個。 ![](https://img.kancloud.cn/ba/48/ba48e04ccab318f8c15c1c575dae01f5_650x183.png) ## APP進程 ### UP/DOWN 與 MOVE 在每一幀的時候應用只能對一次input事件進行響應反饋。如果在一個VSYNC周期中出現了多個input事件,每次input事件到來的時候都立即分發到應用層是比較浪費資源的。為了避免浪費,就有了Batched Consumption機制,input事件會被進行批處理,然后在每個Frame渲染時發送一個batched input事件給到應用層。 ### 工具 dump ``` 使用AndroidStudio Profile查看Java調用棧 使用AndroidStudio Profile工具,選擇CPU,觸摸界面并進行record,dump文件之后,可以看到java層的代碼調用。(AS也可以進行native調用棧的查看) ``` 出現問題的界面進行滑動,同時手指保持再屏幕上,不進行抬起,進行是adb shell dumpsys input 可以看到OutboundQueue中是沒有任何東西的,而WaitQueue中堆積了大量的MotionEvent(action=MOVE),此時也并沒有被合并成一個。 #### 正常情況Consume Batched MoveEvent ![](https://img.kancloud.cn/c6/9f/c69fe0e5820f5ec1d492992dbfa33f1e_3894x1836.png) #### 異常情況Consume Batched MoveEvent ![](https://img.kancloud.cn/da/cc/dacc4348d88847671273bdfa9e5e57e3_3516x1528.png) ## ViewRootImpl 從前面的java堆棧圖中,我們可以看到java層是主動調用了一個doConsumeBatchedInput來進行input事件消費的。而這個doConsumeBatchedInput與兩個Runnable有關ConsumeBatchedInputRunnable 和 ConsumeBatchedInputImmediatelyRunnable **ConsumeBatchedInputRunnable 和 ConsumeBatchedInputImmediatelyRunnable**ConsumeBatchedInputRunnable和ConsumeBatchedInputImmediatelyRunnable這兩個是ViewRootImpl中定義的Runnable,他們都會調用到native方法nativeConsumeBatchedInputEvents讀取inputChannel中的input event,前者是等到下一個Frame繪制的時候再執行input事件消費。后者如其名稱immediately,是立即進行input事件的消費,**常用于一些異常場景下的事件清零操作**。 與此對應的有mConsumeBatchInputScheduled和mConsumeBatchInputImmediatelyScheduled這兩個變量,來標識是否已經將對應的Runnable添加到MessageQueue里面,避免加入重復的Runnable。在對應Runnable的內部執行中又會把這個變量置為false。 ### Android11的改動 **改動點1: ViewRootImpl#scheduleConsumeBatchedInput** ![](https://img.kancloud.cn/bf/88/bf883a9632acc147efa741506c44a327_4612x424.png) 這里對ConsumeBatchedInputRunnable的添加新增了一個開關變量mConsumeBatchedImmediatelyScheduled,使得“延時消費input”和“立即消費input”變成兩個互斥的操作。 **改動點2: ViewRootImpl#setWindowStopped** ![](https://img.kancloud.cn/a2/0b/a20bf417db9123f506c53c13e9f62613_3002x1260.png) setWindowStopped中新增調用一次scheduleConsumeBatchedInputImmdiately()。目的是在window切換為stopped狀態后為了避免ANR,調用scheduleConsumeBatchedInputImmdiately()**立即**進行一次input事件消費 也就是在這里mConsumeBatchedInputImmediatelyScheduled這個變量被置為true,從結果上來說,這個Runnable并沒有被執行! ### 排查思路 遍歷父布局 針對這兩次的修改,我們大膽猜測mConsumeBatchInputImmediatelyScheduled這個在置為true之后,出現了某種異常,對應的ConsumeBatchedInputImmediatelyRunnable并沒有被執行,該變量并沒有被置為false,導致另外一個ConsumeBatchedInputRunnable不滿足執行條件,進而引發事件消費異常。Move Event沒有被應用消費,導致界面無法滑動。那么我們如何進行驗證呢? ViewRootImpl是框架層的類,代碼層沒法直接引用到,但畢竟是萬view之祖,我們可以拿到DecorView,再拿到DecorView的父View來得到ViewRootImpl,進而探訪這個ViewRootImpl對象。 ![](https://img.kancloud.cn/28/cf/28cfcb18621002e8652e6ddac2c4ab35_640x283.png) ### scheduleConsumeBatchedInputImmediately ![](https://img.kancloud.cn/84/0a/840a6c2bd8e3c966f423061992665790_650x136.png) View中的getHandler()為什么會是ViewRootImpl$ViewRootHandler?先看下源碼中View中是怎么取到handler的。 ## 總結下滑動問題的鏈路流程: 1. 我們業務對一個Stop的界面A進行了列表數據的remove 2. 回到界面A,觸發onStart,在Framework的ViewRootImpl會在此時,觸發一次scheduleTraversals準備下一幀的界面重繪,在Android 11的版本上,還會額外調用一個ConsumeBatchedInputImmediatelyRunnable,因為scheduleTraversals會觸發同步屏障,這個ConsumeBatchedInputImmediatelyRunnable并不會被立即運行,必須等到下一幀開始繪制后才可以運行 3. 繪制開始performTraversal中會調用到onMeasure,onLayout和onDraw等流程,由于我們進行了RecyclerView數據的移除,會觸發到RecyclerView#onLayout,然后觸發部分ItemView的onDetachedFromWindow 4. 在這個onDetachedFromWindow中我們調用了getHandler().removeCallbacksAndMessages(null),將target同為ViewRootImpl$ViewRootHandler的ConsumeBatchedInputImmediatelyRunnable從消息隊列中移除。 5. 渲染結束,但是ConsumeBatchedInputImmediatelyRunnable并沒有被執行,mConsumeBatchInputImmediatelyScheduled卻已經被置為true,沒有被重置為false 6. 觸摸屏幕,底層Down事件分發正常 7. 當底層Input事件中的Move事件到來,觸發了onBatchedInputEventPending,觸發到scheduleConsumeBatchedInput,因為Android 11版本新增了對mConsumeBatchInputImmediatelyScheduled開關變量檢測,沒有往下觸發流程,導致move事件沒有被消費。 8. 底層Up事件正常分發,順帶將前面被阻塞的Batched Move事件上傳 ## 應對方案 這個滑動問題,造成的因素有Android 11框架層的一個冗余調用,也有業務側對View#getHandler().removeCallbacks(null)系列方法的不規范調用。我們業務已經對內部存量的View#getHandler().removeCallbacks(null)調用進行替換和移除。考慮到Android 11框架層這個冗余調用會在短期內一直存在,同時也很難保證所有開發和第三方庫在此系列方法上的規范調用,我們會維持臨時修復方案。 ## 參考資料 [系統級bug解決分享:騰訊開發工程師刨根問底安卓端滑動異常](https://www.163.com/dy/article/GDU0B40U0518R7MO.html) [dumpsys](https://developer.android.google.cn/studio/command-line/dumpsys) ## 系統源碼 1. https://cs.android.com/android/platform/superproject**推薦,優點是可以進行搜索,速度也挺快的** 2. https://android.googlesource.com/**推薦,AOSP開源代碼倉庫,優點是可以查看最新的代碼和提交記錄**
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看