最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

混合棧跳轉(zhuǎn)導(dǎo)致Flutter頁(yè)面事件卡死問題解決

 更新時(shí)間:2022年08月09日 15:39:05   作者:幺風(fēng)  
這篇文章主要為大家介紹了混合棧跳轉(zhuǎn)導(dǎo)致Flutter頁(yè)面事件卡死問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

問題來源

在我們升級(jí)Flutter2.5后,測(cè)試在走整個(gè)業(yè)務(wù)流程中發(fā)現(xiàn)了有頁(yè)面卡死現(xiàn)象,于是給我提了一個(gè)BUG。

在xx頁(yè)面多次操作后,頁(yè)面卡死,頁(yè)面還可以滾動(dòng)但是無法跳轉(zhuǎn),點(diǎn)擊長(zhǎng)按事件都失效了。

在我多次測(cè)試后發(fā)現(xiàn),確實(shí)存在這個(gè)問題,而且老版本也都存在。

問題難點(diǎn)

復(fù)現(xiàn)難

問題定位

最開始,我先確定了失效情況下,事件源頭有沒有正確發(fā)送,所以,先在_dispatchPointerDataPacket方法上添加了斷點(diǎn)。結(jié)果發(fā)現(xiàn)都是正常。其實(shí)也好理解,頁(yè)面可以滾動(dòng),說明引擎層發(fā)送事件肯定是正常的。

在進(jìn)行一系列沒有用的斷點(diǎn)定位后發(fā)現(xiàn),正常事件的hitTestResult(事件中命中測(cè)試階段收集的所有能夠響應(yīng)事件的RenderObject節(jié)點(diǎn))和錯(cuò)誤頁(yè)面的hitTestResult_path數(shù)量不一樣。

正常的hitTestResult

錯(cuò)誤的hitTestResult 

經(jīng)過對(duì)比發(fā)現(xiàn),錯(cuò)誤的列表到RenderPointerListener這個(gè)就停止了,我看這名字還挺熟悉,難道跟IgnorePointer有啥關(guān)系?我通過這個(gè)RenderObject節(jié)點(diǎn)的parent一層一層往上找,發(fā)現(xiàn)是ScrollableState中使用了IgnorePointerScrollableState是列表組件如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),
        ),
      ),
    ),
  ),
);
//...

這里會(huì)通過_ignorePointerKey來把滾動(dòng)區(qū)域及其子節(jié)點(diǎn)的事件都屏蔽了。那么什么時(shí)候_ignorePointerKey會(huì)被置為true呢。

通過了解ScrollableState源碼發(fā)現(xiàn),只要頁(yè)面在滾動(dòng)過程中,_ignorePointerKey就會(huì)被置為true,當(dāng)手指抬起時(shí),才會(huì)將_ignorePointerKey重新置為false

通過多次斷點(diǎn)和日志輸出發(fā)現(xiàn),當(dāng)我從后面的頁(yè)面返回到目標(biāo)頁(yè)面時(shí),第一次滾動(dòng)時(shí),就觸發(fā)了ScrollableStatesetIgnorePointer_ignorePointerKey置為true了,但是后面再無事件將_ignorePointerKey置為false了,此后,再滾動(dòng)頁(yè)面時(shí),也無法觸發(fā)setIgnorePointer方法。

到這里,想繼續(xù)調(diào)試,就需要比較熟悉Flutter的事件原理了,因?yàn)檫@里我只想講一下我解決這個(gè)問題的思路,所以Flutter原理的知識(shí)不多講。后面經(jīng)過一系列調(diào)試發(fā)現(xiàn),問題出在OneSequenceGestureRecognizer這個(gè)抽象類中

abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
  //...
  @protected
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
    // 將當(dāng)前指針和當(dāng)前的handleEvent方法添加到全局指針識(shí)別器中存儲(chǔ)緩存起來
    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)) {
      // 從全局指針中移出當(dāng)前指針
      GestureBinding.instance!.pointerRouter.removeRoute(pointer, handleEvent);
      _trackedPointers.remove(pointer);
      // 如果_trackedPointers是空的
      if (_trackedPointers.isEmpty)
        didStopTrackingLastPointer(pointer);
    }
  }
}

OneSequenceGestureRecognizer這個(gè)類的作用是當(dāng)存在多個(gè)手勢(shì)時(shí),只響應(yīng)一個(gè)手勢(shì)。比如我同時(shí)兩個(gè)手指點(diǎn)擊一個(gè)按鈕,按鈕的點(diǎn)擊事件也只會(huì)觸發(fā)一次。像我們常見的TapGestureRecognizerVerticalDragGestureRecognizerHorizontalDragGestureRecognizer等最終都是實(shí)現(xiàn)的這個(gè)類。

在這個(gè)類中startTrackingPointer方法會(huì)在手指按下后,也就是發(fā)生PointerDownEvent時(shí)將當(dāng)前類的handleEvent添加到全局指針識(shí)別器中,并且將這個(gè)pointer(可以看做指針id)添加到_trackedPointers中緩存起來,可以這樣理解,這個(gè)方法就是一次手勢(shì)的開始。

當(dāng)發(fā)生PointerUpEvent等事件時(shí),會(huì)調(diào)用stopTrackingPointer事件,將手勢(shì)移除,這就標(biāo)志著手勢(shì)的結(jié)束。

其中有個(gè)_trackedPointers.isEmpty判斷,會(huì)調(diào)用didStopTrackingLastPointer方法,這個(gè)方法一般是將手勢(shì)識(shí)別器的狀態(tài)置為ready。經(jīng)過我多次對(duì)問題頁(yè)斷點(diǎn)發(fā)現(xiàn),無論如何都調(diào)不到這個(gè)方法,也就是說_trackedPointers里面一直有個(gè)手勢(shì)指針沒有被移除。

這里我要介紹一下VSCode一個(gè)調(diào)試方法。因?yàn)槲疫€不知道問題的根源,所以我復(fù)現(xiàn)問題是通過不斷點(diǎn)擊頁(yè)面同時(shí)觸發(fā)頁(yè)面跳轉(zhuǎn)來達(dá)到的,而且只是有幾率復(fù)現(xiàn)。所以我無法通過斷點(diǎn)來確定這里為何有手勢(shì)事件沒有調(diào)用stopTrackingPointer,所以我使用了VSCode的LogPoint方式來對(duì)整個(gè)過程進(jìn)行日志輸出。

在不斷復(fù)現(xiàn)問題查看日志中發(fā)現(xiàn),在跳轉(zhuǎn)頁(yè)面前,會(huì)有指針事件被添加進(jìn)_trackedPointers,但是卻沒有調(diào)用stopTrackingPointer方法就跳轉(zhuǎn)到新頁(yè)面了。

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

問題確定

由于我們是混合棧項(xiàng)目,我們是自己寫的一套混合棧路由管理,類似FlutterBoost,在進(jìn)行頁(yè)面跳轉(zhuǎn)時(shí),會(huì)將FlutterEngine先detach,然后再跳轉(zhuǎn)。在Flutter的Android發(fā)送事件源碼里面,會(huì)對(duì)FlutterEngine是否attach進(jìn)行判斷,然后觸發(fā)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);
  }

這里由于頁(yè)面跳轉(zhuǎn)時(shí)如果還有事件在處理(比如手指按下并沒有抬起),那么跳轉(zhuǎn)后,F(xiàn)lutter再也接收不到手指抬起的事件了,所以_trackedPointers就一直不被正確移除,導(dǎo)致了事件異常。由于是我們自己寫的混合棧庫(kù),所以修改起來也簡(jiǎn)單。

問題解決

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);
        }
    }
}

我們本身有一個(gè)繼承于FlutterView的類,在其中實(shí)現(xiàn)一下父類的onTouchEvent方法,把isAttachedToFlutterEngine的判斷去掉即可,由于androidTouchProcessor是私有類,所以這里我使用了反射。

iOS解決思路還不太一樣,在新的Flutter版本中,iOS提供了forceTouchesCancelled方法來取消Flutter中的事件,所以iOS是通過在混合棧中detach前,手動(dòng)調(diào)用一下這個(gè)方法來解決這個(gè)問題的。

總結(jié)

由于對(duì)Flutter事件很多細(xì)節(jié)掌握的不夠到位,所以這個(gè)問題從定位問題到最終解決差不多花了一周時(shí)間,解決過程中也加深了我對(duì)Flutter事件的理解。

以上就是混合棧跳轉(zhuǎn)導(dǎo)致Flutter頁(yè)面事件卡死問題解決的詳細(xì)內(nèi)容,更多關(guān)于混合棧Flutter頁(yè)面卡死的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • iOS中containsString和rangeOfString的區(qū)別小結(jié)

    iOS中containsString和rangeOfString的區(qū)別小結(jié)

    這篇文章主要給大家總結(jié)介紹了關(guān)于iOS中containsString和rangeOfString的一些區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-01-01
  • IOS輕松幾步實(shí)現(xiàn)自定義轉(zhuǎn)場(chǎng)動(dòng)畫

    IOS輕松幾步實(shí)現(xiàn)自定義轉(zhuǎn)場(chǎng)動(dòng)畫

    這篇文章將講述幾個(gè)步驟實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫的自定義方式,并且給出了示例代碼,畢竟代碼才是我們的語(yǔ)言,這樣比較容易上手。下面來一起看看吧。
    2016-09-09
  • iOS如何自定義控制器轉(zhuǎn)場(chǎng)動(dòng)畫push詳解

    iOS如何自定義控制器轉(zhuǎn)場(chǎng)動(dòng)畫push詳解

    在平時(shí)開發(fā)中,有時(shí)候需要一些轉(zhuǎn)場(chǎng)動(dòng)畫給界面調(diào)整增添一些活力,而實(shí)現(xiàn)這些動(dòng)畫相對(duì)比較繁瑣。下面這篇文章主要給大家介紹了關(guān)于iOS如何自定義控制器轉(zhuǎn)場(chǎng)動(dòng)畫push的相關(guān)資料,需要的朋友可以參考下。
    2017-12-12
  • IOS點(diǎn)擊按鈕隱藏狀態(tài)欄詳解及實(shí)例代碼

    IOS點(diǎn)擊按鈕隱藏狀態(tài)欄詳解及實(shí)例代碼

    這篇文章主要介紹了IOS點(diǎn)擊按鈕隱藏狀態(tài)欄詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • iOS 泛型中nullable、null resettable、null kindof 用法詳解

    iOS 泛型中nullable、null resettable、null kindof 用法詳解

    這篇文章主要介紹了iOS 泛型中nullable、null resettable、null kindof 用法詳解的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-09-09
  • 詳解iOS設(shè)置字體的三種方式

    詳解iOS設(shè)置字體的三種方式

    這篇文章主要介紹了iOS設(shè)置字體的三種方式,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-04-04
  • IOS等待時(shí)動(dòng)畫效果的實(shí)現(xiàn)

    IOS等待時(shí)動(dòng)畫效果的實(shí)現(xiàn)

    查詢時(shí)間有長(zhǎng)有短,為了增強(qiáng)用戶體驗(yàn)度,目前用的比較多的手段之一是查詢等待時(shí)添加一個(gè)動(dòng)態(tài)等待效果,這篇文章主要介紹IOS等待時(shí)動(dòng)畫效果的實(shí)現(xiàn),有需要的朋友可以參考下
    2015-08-08
  • 詳解iOS Method Swizzling使用陷阱

    詳解iOS Method Swizzling使用陷阱

    這篇文章主要介紹了詳解iOS Method Swizzling使用陷阱,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • iOS AVCaptureSession實(shí)現(xiàn)視頻錄制功能

    iOS AVCaptureSession實(shí)現(xiàn)視頻錄制功能

    這篇文章主要為大家詳細(xì)介紹了iOS AVCaptureSession實(shí)現(xiàn)視頻錄制功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • iOS仿網(wǎng)易新聞滾動(dòng)導(dǎo)航條效果

    iOS仿網(wǎng)易新聞滾動(dòng)導(dǎo)航條效果

    這篇文章主要為大家詳細(xì)介紹了iOS仿網(wǎng)易新聞滾動(dòng)導(dǎo)航條效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-05-05

最新評(píng)論

周至县| 南澳县| 阿坝县| 偃师市| 辉县市| 抚州市| 抚州市| 永宁县| 临桂县| 泸溪县| 东乌| 西平县| 玛曲县| 海丰县| 灵寿县| 龙南县| 临城县| 克山县| 神农架林区| 濮阳县| 资阳市| 册亨县| 太康县| 鱼台县| 金堂县| 怀远县| 元江| 唐山市| 边坝县| 镇安县| 禹州市| 滦南县| 米易县| 武夷山市| 乌鲁木齐县| 昌邑市| 定兴县| 望谟县| 榆社县| 安化县| 桐庐县|