android webview 禁用上拉 下拉_从webview的 onPause()和 pauseTimers()说起

f155826fd96ec6bd4a50d708825a05e1.png

背景

onPause()和pauseTimers()在Android平台的浏览器应用中,应该经常用到。与其想对应的还有onResume()和resumeTimers()。这里只讨论前面两个,另外两个只是他们相反的意思。

由于webview的API有很多,自己长期从事的是内核相关,对于这类API只是同事有疑问提出时才会去追溯源码查询其含义。但是这个接口容易引起混淆,同事问了多次,自己也查询了多次也没有去记忆。最近有人问到这两个接口区别,自己凭记忆这样回答:“pauseTimers是js相关的timer,onPause是暂停全部”,就这样寥寥数语,自己也深感回答的不确切和不正确的地方。于是乎还是把这两个接口内核的调用过程及其实现原理记录下来,加强记忆,以免再次出现类似尴尬。

pauseTimers

public void pauseTimers ()

Pauses all layout, parsing, and JavaScript timers for all WebViews.
 This is a global requests, not restricted to just this WebView. This can be useful if the application has been paused.

上面的来自于官方文档,从注释可以看到这个接口会停止所有webview中的layout,parsing,js timer。 这个api接口实质是来停止blink 中的timer来实现。我们来看它的调用关系图:

1fdea5654a0a10dd06d1c00160b51b6f.png
pauseTimer

从上面的图可以看出来,pauseTimer实质是暂停的blink中的renderScheduler对应的timer。我们来看最后的实现:

void RendererSchedulerImpl::SuspendTimerQueue() {
  MainThreadOnly().timer_queue_suspend_count++;
  ForceUpdatePolicy();
#ifndef NDEBUG
  DCHECK(!default_timer_task_runner_->IsQueueEnabled());
  for (const auto& runner : timer_task_runners_) {
    DCHECK(!runner->IsQueueEnabled());
  }
#endif
}

看实现可以知道,更具体的暂停的是主线程的timer。从chromium的多进程模型以及多线程模型可以知道,此时的 MainThreadOnly()为render端的主线程,也就是render thread。

void RendererSchedulerImpl::ForceUpdatePolicy() {
  base::AutoLock lock(any_thread_lock_);
  UpdatePolicyLocked(UpdateType::FORCE_UPDATE);
}
void RendererSchedulerImpl::UpdatePolicyLocked(UpdateType update_type) {
.....
  if (MainThreadOnly().timer_queue_suspend_count != 0 ||
      MainThreadOnly().timer_queue_suspended_when_backgrounded) {
    new_policy.timer_queue_policy.is_enabled = false;
    // TODO(alexclarke): Figure out if we really need to do this.
    new_policy.timer_queue_policy.time_domain_type = TimeDomainType::REAL;
  }
.....
  for (const scoped_refptr<TaskQueue>& timer_queue : timer_task_runners_) {
    ApplyTaskQueuePolicy(timer_queue.get(),
                         MainThreadOnly().current_policy.timer_queue_policy,
                         new_policy.timer_queue_policy);
  }
....
MainThreadOnly().current_policy = new_policy;
}

从上面的调用流程可以知道,当前timer_queue_suspend_count不为0,所以new_policy.timer_queue_policy.is_enabled = false ,timer_queue_policy是一个TaskQueuePolicy类型,在RendererSchedulerImpl中存在多个TaskQueuePolicy:

TaskQueuePolicy compositor_queue_policy;
    TaskQueuePolicy loading_queue_policy;
    TaskQueuePolicy timer_queue_policy;
    TaskQueuePolicy default_queue_policy;

从这里可以看出只是禁用了timer_queue并未禁用其他,这几种类型的policy分别对应着renderer_scheduler中几大task runner。 这里简单描述一下:compositor_queue_policy用来作为render线程中用在compositor中的task runner,对应在cc中的proxyMain。 loading_queue_policy对应用在render线程中,用来loading处理的task runner,它在renderer scheculer中的两个tq:frame_loading_tq和default_loading_tq。default_loading_tq对应着render 线程中的navigation scheduler。frame_loading_tq用于render线程中将load request转发到child_io 线程,以及在blink获取类型为Network时的task runner.

遍历timer_task_runners_中的任务队列,应用最新的策略,先看下会有哪些timer task,从chromium的task创建我们可以知道这个是由RendererSchedulerImpl::NewTimerTaskRunner创建,我们关注它的调用者就会知道pauseTimer的时候会pause哪些部分。从renderer的队列模型可以知道来源于两个timer: frame_timer_tq和 default_timer_tq。

default_timer_tq作为blink中默认timer,在blink中的著名的Timer类(Timer.cpp)就是使用它。 frame_timer_tq用在blink中获取类型为Timer,Microtask等时使用:

WebTaskRunner* TaskRunnerHelper::get(TaskType type, LocalFrame* frame) {
  // TODO(haraken): Optimize the mapping from TaskTypes to task runners.
  switch (type) {
    case TaskType::DOMManipulation:
    case TaskType::UserInteraction:
    case TaskType::HistoryTraversal:
    case TaskType::Embed:
    case TaskType::MediaElementEvent:
    case TaskType::CanvasBlobSerialization:
    case TaskType::RemoteEvent:
    case TaskType::WebSocket:
    case TaskType::Microtask:
    case TaskType::PostedMessage:
    case TaskType::UnshippedPortMessage:
    case TaskType::Timer:
    case TaskType::Internal:
      return frame ? frame->frameScheduler()->timerTaskRunner()
                   : Platform::current()->currentThread()->getWebTaskRunner();
    case TaskType::Networking:
      return frame ? frame->frameScheduler()->loadingTaskRunner()
                   : Platform::current()->currentThread()->getWebTaskRunner();
    case TaskType::Unthrottled:
      return frame ? frame->frameScheduler()->unthrottledTaskRunner()
                   : Platform::current()->currentThread()->getWebTaskRunner();
    default:
      NOTREACHED();
  }
  return nullptr;
}

由此可见会pause当前的layout和parsing。js timer实质是一个dom timer,其设置的timeout会开启timer基类的startOneShot,也就是属于 default_timer_tq,所以当调用PauseTimer()的时候会pause js timer。

继续看timer_queue:

void RendererSchedulerImpl::ApplyTaskQueuePolicy(
    TaskQueue* task_queue,
    const TaskQueuePolicy& old_task_queue_policy,
    const TaskQueuePolicy& new_task_queue_policy) const {
  if (old_task_queue_policy.is_enabled != new_task_queue_policy.is_enabled) {
    task_queue_throttler_->SetQueueEnabled(task_queue,
                                           new_task_queue_policy.is_enabled);
  }

  ....
}

void TaskQueueThrottler::SetQueueEnabled(TaskQueue* task_queue, bool enabled) {
  TaskQueueMap::iterator find_it = queue_details_.find(task_queue);
....
  // We don't enable the queue here because it's throttled and there might be
  // tasks in it's work queue that would execute immediatly rather than after
  // PumpThrottledTasks runs.
  if (!enabled) {
    task_queue->SetQueueEnabled(false);
    MaybeSchedulePumpQueue(FROM_HERE, tick_clock_->NowTicks(), task_queue,
                           base::nullopt);
  }
}
void TaskQueueImpl::SetQueueEnabled(bool enabled) {
  if (main_thread_only().is_enabled == enabled)
    return;
  main_thread_only().is_enabled = enabled;
  if (!main_thread_only().task_queue_manager)
    return;
  if (enabled) {
    // Note it's the job of the selector to tell the TaskQueueManager if
    // a DoWork needs posting.
    main_thread_only().task_queue_manager->selector_.EnableQueue(this);
  } else {
    main_thread_only().task_queue_manager->selector_.DisableQueue(this);
  }
}

从上面的调用流程可以知道禁用timer task queue的过程。

onPause

public void onPause ()

Does a best-effort attempt to pause any processing that can be paused safely, such as animations and geolocation.
Note that this call does not pause JavaScript. 
To pause JavaScript globally, use pauseTimers(). To resume WebView, call onResume().

上面的来自于官方文档,从注释可以看到这个接口可以尽可能的暂停能够被安全暂停的任意行为,例如动画,定位。但是这个不能暂停JavaScript,如果要暂停javascript使用pauseTimer.

我们来看它的调用关系图:

15e773e075278680c69194bc47662d5a.png
onPause

这个API实质是用来控制显示(visility),我们先来看BrowserViewRenderer::SetIsPaused方法:

void BrowserViewRenderer::SetIsPaused(bool paused) {
  is_paused_ = paused;
}

bool BrowserViewRenderer::IsClientVisible() const {
  return !is_paused_ && (!attached_to_window_ || window_visible_);
}

bool AwContents::IsVisible(JNIEnv* env, const JavaParamRef<jobject>& obj) {
  return browser_view_renderer_.IsClientVisible();
}

private void updateContentViewCoreVisibility() {
  mIsUpdateVisibilityTaskPending = false;
   if (isDestroyed(NO_WARN)) return;
   boolean contentViewCoreVisible = nativeIsVisible(mNativeAwContents);

   if (contentViewCoreVisible && !mIsContentViewCoreVisible) {
        mContentViewCore.onShow();
   } else if (!contentViewCoreVisible && mIsContentViewCoreVisible) {
       mContentViewCore.onHide();
   }
   mIsContentViewCoreVisible = contentViewCoreVisible;
}

从上面的简单的代码流程可以知道,设置到C层的pause状态目的是为了webview获取content的显示状态。从上面的代码可以知道,content的显示状态由暂停状态,windorw的显示状态共同决定。如果当前webview是可见状态,那么window_visible_为TRUE。当调用了onPause这个接口时,contentViewCoreVisible为false。所以这里会调用mContentViewCore.onHide()来隐藏conentview.

我们继续来看WebContentsImpl::WasHidden:

void WebContentsImpl::WasHidden() {
  // If there are entities capturing screenshots or video (e.g., mirroring),
  // don't activate the "disable rendering" optimization.
  if (capturer_count_ == 0) {
    // |GetRenderViewHost()| can be NULL if the user middle clicks a link to
    // open a tab in the background, then closes the tab before selecting it.
    // This is because closing the tab calls WebContentsImpl::Destroy(), which
    // removes the |GetRenderViewHost()|; then when we actually destroy the
    // window, OnWindowPosChanged() notices and calls WasHidden() (which
    // calls us).
    for (RenderWidgetHostView* view : GetRenderWidgetHostViewsInTree()) {
      if (view)
        view->Hide();
    }

    SendPageMessage(new PageMsg_WasHidden(MSG_ROUTING_NONE));
  }

  FOR_EACH_OBSERVER(WebContentsObserver, observers_, WasHidden());

  should_normally_be_visible_ = false;
}

从上面的代码可以看出先将所有的renderwidget隐藏掉,并发送一个PageMsg_WasHidden给page.同时通知所有webcontent的观察者。 先看隐藏renderwiget:

void RenderWidgetHostViewAndroid::Hide() {
...
  HideInternal();
}
void RenderWidgetHostViewAndroid::HideInternal() {
  DCHECK(!is_showing_ || !is_window_activity_started_ || !is_window_visible_)
      << "Hide called when the widget should be shown.";
.....
  // Inform the renderer that we are being hidden so it can reduce its resource
  // utilization.
  host_->WasHidden();
}
void RenderWidgetHostImpl::WasHidden() {
...

  // If we have a renderer, then inform it that we are being hidden so it can
  // reduce its resource utilization.
  Send(new ViewMsg_WasHidden(routing_id_));

  // Tell the RenderProcessHost we were hidden.
  process_->WidgetHidden();
....
}
void RenderWidget::OnWasHidden() {
  TRACE_EVENT0("renderer", "RenderWidget::OnWasHidden");
  // Go into a mode where we stop generating paint and scrolling events.
  SetHidden(true);
  FOR_EACH_OBSERVER(RenderFrameImpl, render_frames_,
                    WasHidden());
}

从上面的调用过程可以知道,这里主要是进行各种判断,然后通知renderer调用renderwidget的OnWasHidden方法,在其中会通过调用SetHidden来禁止生成paint和scroll行为:

void RenderWidget::SetHidden(bool hidden) {
  if (is_hidden_ == hidden)
    return;

  // The status has changed.  Tell the RenderThread about it and ensure
  // throttled acks are released in case frame production ceases.
  is_hidden_ = hidden;
  input_handler_->FlushPendingInputEventAck();

  if (is_hidden_)
    RenderThreadImpl::current()->WidgetHidden();
  else
    RenderThreadImpl::current()->WidgetRestored();

  if (render_widget_scheduling_state_)
    render_widget_scheduling_state_->SetHidden(hidden);
}

RenderThreadImpl::current()->WidgetHidden会在hidden之后kInitialIdleHandlerDelayMs(1s)开启一个空闲任务,主要是回收资源,执行timer(没有调用pauseTimer暂停webkit timer时)等:

void RenderThreadImpl::ScheduleIdleHandler(int64_t initial_delay_ms) {
  idle_notification_delay_in_ms_ = initial_delay_ms;
  idle_timer_.Stop();
  idle_timer_.Start(FROM_HERE,
      base::TimeDelta::FromMilliseconds(initial_delay_ms),
      this, &RenderThreadImpl::IdleHandler);
}
void RenderThreadImpl::IdleHandler() {
  bool run_in_foreground_tab = (widget_count_ > hidden_widget_count_) &&
                               GetContentClient()->renderer()->
                                   RunIdleHandlerWhenWidgetsHidden();
  if (run_in_foreground_tab) {
    if (idle_notifications_to_skip_ > 0) {
      --idle_notifications_to_skip_;
    } else {
      ReleaseFreeMemory();
    }
    ScheduleIdleHandler(kLongIdleHandlerDelayMs);
    return;
  }

  ReleaseFreeMemory();

  // Continue the idle timer if the webkit shared timer is not suspended or
  // something is left to do.
  bool continue_timer = !webkit_shared_timer_suspended_;

  // Schedule next invocation. When the tab is originally hidden, an invocation
  // is scheduled for kInitialIdleHandlerDelayMs in
  // RenderThreadImpl::WidgetHidden in order to race to a minimal heap.
  // After that, idle calls can be much less frequent, so run at a maximum of
  // once every kLongIdleHandlerDelayMs.
  // Dampen the delay using the algorithm (if delay is in seconds):
  //    delay = delay + 1 / (delay + 2)
  // Using floor(delay) has a dampening effect such as:
  //    30s, 30, 30, 31, 31, 31, 31, 32, 32, ...
  // If the delay is in milliseconds, the above formula is equivalent to:
  //    delay_ms / 1000 = delay_ms / 1000 + 1 / (delay_ms / 1000 + 2)
  // which is equivalent to
  //    delay_ms = delay_ms + 1000*1000 / (delay_ms + 2000).
  if (continue_timer) {
    ScheduleIdleHandler(
        std::max(kLongIdleHandlerDelayMs,
                 idle_notification_delay_in_ms_ +
                 1000000 / (idle_notification_delay_in_ms_ + 2000)));

  } else {
    idle_timer_.Stop();
  }

  FOR_EACH_OBSERVER(RenderThreadObserver, observers_, IdleNotification());
}

render_widget_scheduling_state_->SetHidden最后会调用:

void RendererSchedulerImpl::SetAllRenderWidgetsHidden(bool hidden) {
....

  if (hidden) {
    idle_helper_.EnableLongIdlePeriod();

    // Ensure that we stop running idle tasks after a few seconds of being
    // hidden.
    base::TimeDelta end_idle_when_hidden_delay =
        base::TimeDelta::FromMilliseconds(kEndIdleWhenHiddenDelayMillis);
    control_task_runner_->PostDelayedTask(
        FROM_HERE, end_renderer_hidden_idle_period_closure_.callback(),
        end_idle_when_hidden_delay);
    MainThreadOnly().renderer_hidden = true;
  } else {
    MainThreadOnly().renderer_hidden = false;
    EndIdlePeriod();
  }
....
}

从这里可以看出它会在kEndIdleWhenHiddenDelayMillis(10s)之后停止idle tasks任务队列

同时还会调用renderFrameImplWasHidden:

void RenderFrameImpl::WasHidden() {
  FOR_EACH_OBSERVER(RenderFrameObserver, observers_, WasHidden());
...
  if (GetWebFrame()->frameWidget()) {
    GetWebFrame()->frameWidget()->setVisibilityState(visibilityState());
  }
}

framewidget的setVisibilityState这个方法进而设置blink中的visiblity为false。这个false会通知设置compositor的visible状态,当其为false的时候,渲染流水线就无法发起beginframe:

bool SchedulerStateMachine::BeginFrameNeeded() const {
...
  // If we are not visible, we don't need BeginFrame messages.
  if (!visible_) {
    return false;
  }
....

所以无法完成paint操作及其后续。但是这里render线程并未停止,所以input以及js等除了paint之后的部分依然会执行。当resume的时候,就会发起beginframe开始paint及其光栅化上屏等操作完成之前留下的最后行为。

总结

pauseTimer()主要是暂停layout,parsing,以及js timer。主要涉及blink中的timer 任务,与它暂停的timer无关的显示不会受到其影响。 onPause()主要是暂停显示相关,而layout和parse以及js都会继续执行,只是他们的执行结果不能显示出来,须调用onResume才能将其显示出来。

由于水平有限,若有不正确的地方欢迎指正~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值