<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在我們升級Flutter2.5後,測試在走整個業務流程中發現了有頁面卡死現象,於是給我提了一個BUG。
在xx頁面多次操作後,頁面卡死,頁面還可以捲動但是無法跳轉,點選長按事件都失效了。
在我多次測試後發現,確實存在這個問題,而且老版本也都存在。
復現難
最開始,我先確定了失效情況下,事件源頭有沒有正確傳送,所以,先在_dispatchPointerDataPacket
方法上新增了斷點。結果發現都是正常。其實也好理解,頁面可以捲動,說明引擎層傳送事件肯定是正常的。
在進行一系列沒有用的斷點定位後發現,正常事件的hitTestResult
(事件中命中測試階段收集的所有能夠響應事件的RenderObject
節點)和錯誤頁面的hitTestResult
的_path
數量不一樣。
正常的hitTestResult
錯誤的hitTestResult
經過對比發現,錯誤的列表到RenderPointerListener
這個就停止了,我看這名字還挺熟悉,難道跟IgnorePointer
有啥關係?我通過這個RenderObject
節點的parent
一層一層往上找,發現是ScrollableState
中使用了IgnorePointer
(ScrollableState
是列表元件如ListView
、SingleChildScrollView
等底層使用的Widget State)
//... Widget result = _ScrollableScope( scrollable: this, position: position, child: Listener( onPointerSignal: _receivedPointerSignal, child: RawGestureDetector( key: _gestureDetectorKey, gestures: _gestureRecognizers, behavior: HitTestBehavior.opaque, excludeFromSemantics: widget.excludeFromSemantics, child: Semantics( explicitChildNodes: !widget.excludeFromSemantics, child: IgnorePointer( key: _ignorePointerKey, ignoring: _shouldIgnorePointer, ignoringSemantics: false, child: widget.viewportBuilder(context, position), ), ), ), ), ); //...
這裡會通過_ignorePointerKey
來把卷動區域及其子節點的事件都遮蔽了。那麼什麼時候_ignorePointerKey
會被置為true
呢。
通過了解ScrollableState
原始碼發現,只要頁面在捲動過程中,_ignorePointerKey
就會被置為true
,當手指擡起時,才會將_ignorePointerKey
重新置為false
。
通過多次斷點和紀錄檔輸出發現,當我從後面的頁面返回到目標頁面時,第一次捲動時,就觸發了ScrollableState
的setIgnorePointer
將_ignorePointerKey
置為true
了,但是後面再無事件將_ignorePointerKey
置為false
了,此後,再捲動頁面時,也無法觸發setIgnorePointer
方法。
到這裡,想繼續偵錯,就需要比較熟悉Flutter的事件原理了,因為這裡我只想講一下我解決這個問題的思路,所以Flutter原理的知識不多講。後面經過一系列偵錯發現,問題出在OneSequenceGestureRecognizer
這個抽象類中
abstract class OneSequenceGestureRecognizer extends GestureRecognizer { //... @protected void startTrackingPointer(int pointer, [Matrix4? transform]) { // 將當前指標和當前的handleEvent方法新增到全域性指標識別器中儲存快取起來 GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform); _trackedPointers.add(pointer); assert(!_entries.containsValue(pointer)); _entries[pointer] = _addPointerToArena(pointer); } @protected void stopTrackingPointer(int pointer) { if (_trackedPointers.contains(pointer)) { // 從全域性指標中移出當前指標 GestureBinding.instance!.pointerRouter.removeRoute(pointer, handleEvent); _trackedPointers.remove(pointer); // 如果_trackedPointers是空的 if (_trackedPointers.isEmpty) didStopTrackingLastPointer(pointer); } } }
OneSequenceGestureRecognizer
這個類的作用是當存在多個手勢時,只響應一個手勢。比如我同時兩個手指點選一個按鈕,按鈕的點選事件也只會觸發一次。像我們常見的TapGestureRecognizer
、VerticalDragGestureRecognizer
、HorizontalDragGestureRecognizer
等最終都是實現的這個類。
在這個類中startTrackingPointer
方法會在手指按下後,也就是發生PointerDownEvent
時將當前類的handleEvent
新增到全域性指標識別器中,並且將這個pointer
(可以看做指標id)新增到_trackedPointers
中快取起來,可以這樣理解,這個方法就是一次手勢的開始。
當發生PointerUpEvent
等事件時,會呼叫stopTrackingPointer
事件,將手勢移除,這就標誌著手勢的結束。
其中有個_trackedPointers.isEmpty
判斷,會呼叫didStopTrackingLastPointer
方法,這個方法一般是將手勢識別器的狀態置為ready
。經過我多次對問題頁斷點發現,無論如何都調不到這個方法,也就是說_trackedPointers
裡面一直有個手勢指標沒有被移除。
這裡我要介紹一下VSCode一個偵錯方法。因為我還不知道問題的根源,所以我復現問題是通過不斷點選頁面同時觸發頁面跳轉來達到的,而且只是有機率復現。所以我無法通過斷點來確定這裡為何有手勢事件沒有呼叫stopTrackingPointer
,所以我使用了VSCode的LogPoint
方式來對整個過程進行紀錄檔輸出。
在不斷復現問題檢視紀錄檔中發現,在跳轉頁面前,會有指標事件被新增進_trackedPointers
,但是卻沒有呼叫stopTrackingPointer
方法就跳轉到新頁面了。
tap 4. addAllowedPointer (tap.dart) _down != null = true 637436658 tap 5. _trackedPointers add 195 502831342 handleEvent: 931478062 tap 5. _trackedPointers add 195 21393736 handleEvent: 790157058 tap 5. _trackedPointers add 195 126324365 handleEvent: 160402385 onNativeRouteEvent: (9): NativeRouteEvent.onCreate onNativeRouteEvent: (8): NativeRouteEvent.onPause onFlutterRouteEvent: (9): FlutterRouteEvent.onPush
由於我們是混合棧專案,我們是自己寫的一套混合棧路由管理,類似FlutterBoost,在進行頁面跳轉時,會將FlutterEngine
先detach,然後再跳轉。在Flutter的Android傳送事件原始碼裡面,會對FlutterEngine
是否attach
進行判斷,然後觸發Flutter Framework一系列處理。
@Override public boolean onTouchEvent(@NonNull MotionEvent event) { // 這裡判斷是否attach if (!isAttachedToFlutterEngine()) { return super.onTouchEvent(event); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { requestUnbufferedDispatch(event); } return androidTouchProcessor.onTouchEvent(event); }
這裡由於頁面跳轉時如果還有事件在處理(比如手指按下並沒有擡起),那麼跳轉後,Flutter再也接收不到手指擡起的事件了,所以_trackedPointers
就一直不被正確移除,導致了事件異常。由於是我們自己寫的混合棧庫,所以修改起來也簡單。
Android
public class XXXFlutterView extends FlutterView { // ... @Override public boolean onTouchEvent(@NonNull MotionEvent event) { try { AndroidTouchProcessor androidTouchProcessor; Field field = this.getClass().getSuperclass().getDeclaredField("androidTouchProcessor"); field.setAccessible(true); androidTouchProcessor = (AndroidTouchProcessor)field.get(this); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { requestUnbufferedDispatch(event); } return androidTouchProcessor.onTouchEvent(event); } catch (Exception e) { e.printStackTrace(); return super.onTouchEvent(event); } } }
我們本身有一個繼承於FlutterView
的類,在其中實現一下父類別的onTouchEvent
方法,把isAttachedToFlutterEngine
的判斷去掉即可,由於androidTouchProcessor
是私有類,所以這裡我使用了反射。
iOS解決思路還不太一樣,在新的Flutter版本中,iOS提供了forceTouchesCancelled
方法來取消Flutter中的事件,所以iOS是通過在混合棧中detach前,手動呼叫一下這個方法來解決這個問題的。
由於對Flutter事件很多細節掌握的不夠到位,所以這個問題從定位問題到最終解決差不多花了一週時間,解決過程中也加深了我對Flutter事件的理解。
以上就是混合棧跳轉導致Flutter頁面事件卡死問題解決的詳細內容,更多關於混合棧Flutter頁面卡死的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45