鸿蒙-性能雪崩 onVisibleAreaChange 可见性回调

15 阅读2分钟

总结:

ArkTs 注册的可见性变化封装到 piplineContext node 中保存,会在 Vsync 每一帧中遍历调用 node 来计算可见性,根据触发阈值条件若触发则 napi 调用 arkTs 可见性变化。随着监听可见组件增多,每一帧间逻辑复杂度也随之增加,遍历耗时增加;

解决方案:

使用 setOnVisibleAreaApproximateChange 代替 (存在问题:复用场景不会回调 api17 release 系统修复)

整体流程:

image.png

1 监听可见变化

代码中常通过这样的方式来设置可见性变化:

2 处理流程

FrameNode 通过 AddVisibleAreaChangeCallback 注册监听,并把自己加入 PipelineContext_NG 的 onVisibleAreaChangeNodeIds_ 。

void PipelineContext::AddVisibleAreaChangeNode(const RefPtr<FrameNode>& node,
    const std::vector<double>& ratios, const VisibleRatioCallback& callback, bool isUserCallback,
    bool isCalculateInnerClip)
{
    CHECK_NULL_VOID(node);
    VisibleCallbackInfo addInfo;
    addInfo.callback = callback;
    addInfo.isCurrentVisible = false;
    onVisibleAreaChangeNodeIds_.emplace(node->GetId());
    if (isUserCallback) {
        node->SetVisibleAreaUserCallback(ratios, addInfo);
    } else {
        node->SetVisibleAreaInnerCallback(ratios, addInfo, isCalculateInnerClip);
    }
}

添加 onVisibleAreaChangeNodeIds_ 代表节点需要关注可见变化,具体会在 FlushWindowStateChangedCallback FlushVsync 等节点进行调用

void PipelineContext::FlushVsync(uint64_t nanoTimestamp, uint32_t frameCount)
{
    ProcessDelayTasks();
    DispatchDisplaySync(nanoTimestamp);
    FlushAnimation(nanoTimestamp);
    FlushFrameCallback(nanoTimestamp);
    auto hasRunningAnimation = FlushModifierAnimation(nanoTimestamp);
    FlushTouchEvents();
    FlushDragEvents();
    FlushFrameCallbackFromCAPI(nanoTimestamp, frameCount);
    FlushBuild();

    taskScheduler_->StartRecordFrameInfo(GetCurrentFrameInfo(recvTime_, nanoTimestamp));
    taskScheduler_->FlushTask();
    UIObserverHandler::GetInstance().HandleLayoutDoneCallBack();
    // flush correct rect again
    taskScheduler_->FlushPersistAfterLayoutTask();
    taskScheduler_->FinishRecordFrameInfo();
    FlushNodeChangeFlag();
    FlushAnimationClosure();
    TryCallNextFrameLayoutCallback();

    HandleOnAreaChangeEvent(nanoTimestamp);
    HandleVisibleAreaChangeEvent(nanoTimestamp);
}
void PipelineContext::FlushWindowStateChangedCallback(bool isShow)
{
      if (!CheckThreadSafe()) {
        LOGW("FlushWindowStateChangedCallback doesn't run on UI thread!");
    }
    std::set<int32_t> onWindowStateChangedCallbacks;
    std::swap(onWindowStateChangedCallbacks, onWindowStateChangedCallbacks_);
    auto iter = onWindowStateChangedCallbacks.begin();
    while (iter != onWindowStateChangedCallbacks.end()) {
        auto node = ElementRegister::GetInstance()->GetUINodeById(*iter);
        if (!node) {
            iter = onWindowStateChangedCallbacks.erase(iter);
        } else {
            if (isShow) {
                node->OnWindowShow();
            } else {
                node->OnWindowHide();
            }
            ++iter;
        }
    }
    std::swap(onWindowStateChangedCallbacks, onWindowStateChangedCallbacks_);
    HandleVisibleAreaChangeEvent(GetTimeFromExternalTimer());
    HandleSubwindow(isShow);
}

PipelineContext_NG::HandleVisibleAreaChangeEvent 遍历 onVisibleAreaChangeNodeIds_ ,对每个 FrameNode 调用 TriggerVisibleAreaChangeCallback

void PipelineContext::HandleVisibleAreaChangeEvent(uint64_t nanoTimestamp)
{
    ACE_FUNCTION_TRACE();
    if (onVisibleAreaChangeNodeIds_.empty()) {
        return;
    }
    auto nodes = FrameNode::GetNodesById(onVisibleAreaChangeNodeIds_);
    for (auto&& frameNode : nodes) {
        frameNode->TriggerVisibleAreaChangeCallback(nanoTimestamp);
    }
}

FrameNode::TriggerVisibleAreaChangeCallback 计算自身与父可见区域的交集、可见状态和比例。如果变化满足条件,通过 ProcessVisibleAreaChangeEvent 回调 arkts 定义的可见回调函数

void FrameNode::TriggerVisibleAreaChangeCallback(uint64_t timestamp, bool forceDisappear)
{
    auto context = GetContext();
    CHECK_NULL_VOID(context);
    CHECK_NULL_VOID(eventHub_);
    ProcessThrottledVisibleCallback(forceDisappear);
    auto hasInnerCallback = eventHub_->HasVisibleAreaCallback(false);
    auto hasUserCallback = eventHub_->HasVisibleAreaCallback(true);
    if (!hasInnerCallback && !hasUserCallback) {
        return;
    }
    auto& visibleAreaUserRatios = eventHub_->GetVisibleAreaRatios(true);
    auto& visibleAreaUserCallback = eventHub_->GetVisibleAreaCallback(true);
    auto& visibleAreaInnerRatios = eventHub_->GetVisibleAreaRatios(false);
    auto& visibleAreaInnerCallback = eventHub_->GetVisibleAreaCallback(false);
    
    auto visibleResult = GetCacheVisibleRect(timestamp, IsDebugInspectorId());
    SetVisibleAreaChangeTriggerReason(VisibleAreaChangeTriggerReason::VISIBLE_AREA_CHANGE);
    if (hasInnerCallback) {
        if (isCalculateInnerVisibleRectClip_) {
            ProcessVisibleAreaChangeEvent(visibleResult.innerVisibleRect, visibleResult.frameRect,
                visibleAreaInnerRatios, visibleAreaInnerCallback, false);
        } else {
            ProcessVisibleAreaChangeEvent(visibleResult.visibleRect, visibleResult.frameRect, visibleAreaInnerRatios,
                visibleAreaInnerCallback, false);
        }
    }
    if (hasUserCallback) {
        ProcessVisibleAreaChangeEvent(
            visibleResult.visibleRect, visibleResult.frameRect, visibleAreaUserRatios, visibleAreaUserCallback, true);
    }
}

PipelineContext_NG::ProcessVisibleAreaChangeEvent 遍历 visibleAreaChangeNodes_ ,执行注册的回调

void FrameNode::ProcessAllVisibleCallback(const std::vector<double>& visibleAreaUserRatios,
    VisibleCallbackInfo& visibleAreaUserCallback, double currentVisibleRatio, double lastVisibleRatio,
    bool isThrottled, bool isInner)
{
    bool isHandled = false;
    bool isVisible = false;
    double* lastVisibleCallbackRatio = isThrottled ? &lastThrottledVisibleCbRatio_ :
        (isInner ? &lastInnerVisibleCallbackRatio_ : &lastVisibleCallbackRatio_);

    // 遍历比较判断是否满足用户定义的阈值
    for (const auto& callbackRatio : visibleAreaUserRatios) {
        if (GreatNotEqual(currentVisibleRatio, callbackRatio) && LessOrEqual(lastVisibleRatio, callbackRatio)) {
            *lastVisibleCallbackRatio = currentVisibleRatio;
            isVisible = true;
            isHandled = true;
        } else if (LessNotEqual(currentVisibleRatio, callbackRatio) && GreatOrEqual(lastVisibleRatio, callbackRatio)) {
            *lastVisibleCallbackRatio = currentVisibleRatio;
            isVisible = false;
            isHandled = true;
        } else if (NearEqual(callbackRatio, VISIBLE_RATIO_MIN) && NearEqual(currentVisibleRatio, callbackRatio)) {
            *lastVisibleCallbackRatio = VISIBLE_RATIO_MIN;
            currentVisibleRatio = VISIBLE_RATIO_MIN;
            isVisible = false;
            isHandled = true;
        } else if (NearEqual(callbackRatio, VISIBLE_RATIO_MAX) && NearEqual(currentVisibleRatio, callbackRatio)) {
            *lastVisibleCallbackRatio = VISIBLE_RATIO_MAX;
            currentVisibleRatio = VISIBLE_RATIO_MAX;
            isVisible = true;
            isHandled = true;
        }
    }

    auto callback = visibleAreaUserCallback.callback;
    if (isHandled && callback) {
        if (GetTag() == V2::WEB_ETS_TAG) {
            TAG_LOGI(AceLogTag::ACE_UIEVENT, "exp=%{public}d ratio=%{public}s %{public}d-%{public}s reason=%{public}d",
                isVisible, std::to_string(currentVisibleRatio).c_str(), GetId(),
                std::to_string(GetAccessibilityId()).c_str(), static_cast<int32_t>(visibleAreaChangeTriggerReason_));
        }
        // 回调 ArkTS 
        callback(isVisible, currentVisibleRatio);
    }
}