gecko-dev/widget/android/AndroidUiThread.cpp

371 строка
9.2 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "base/message_loop.h"
#include "GeneratedJNIWrappers.h"
#include "mozilla/Atomics.h"
#include "mozilla/EventQueue.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Monitor.h"
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/ThreadEventQueue.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "nsThread.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"
using namespace mozilla;
namespace {
class AndroidUiThread;
class AndroidUiTask;
StaticAutoPtr<LinkedList<AndroidUiTask> > sTaskQueue;
StaticAutoPtr<mozilla::Mutex> sTaskQueueLock;
StaticRefPtr<AndroidUiThread> sThread;
static bool sThreadDestroyed;
static MessageLoop* sMessageLoop;
static Atomic<Monitor*> sMessageLoopAccessMonitor;
void EnqueueTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs);
/*
* The AndroidUiThread is derived from nsThread so that nsIRunnable objects that get
* dispatched may be intercepted. Only nsIRunnable objects that need to be synchronously
* executed are passed into the nsThread to be queued. All other nsIRunnable object
* are immediately dispatched to the Android UI thread.
* AndroidUiThread is derived from nsThread instead of being an nsIEventTarget
* wrapper that contains an nsThread object because if nsIRunnable objects with a
* delay were dispatch directly to an nsThread object, such as obtained from
* nsThreadManager::GetCurrentThread(), the nsIRunnable could get stuck in the
* nsThread nsIRunnable queue. This is due to the fact that Android controls the
* event loop in the Android UI thread and has no knowledge of when the nsThread
* needs to be drained.
*/
class AndroidUiThread : public nsThread
{
public:
NS_DECL_ISUPPORTS_INHERITED
AndroidUiThread()
: nsThread(MakeNotNull<ThreadEventQueue<mozilla::EventQueue>*>(
MakeUnique<mozilla::EventQueue>()),
nsThread::NOT_MAIN_THREAD,
0)
{}
nsresult Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) override;
nsresult DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs) override;
private:
~AndroidUiThread()
{}
};
NS_IMPL_ISUPPORTS_INHERITED0(AndroidUiThread, nsThread)
NS_IMETHODIMP
AndroidUiThread::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
{
if (aFlags & NS_DISPATCH_SYNC) {
return nsThread::Dispatch(Move(aEvent), aFlags);
} else {
EnqueueTask(Move(aEvent), 0);
return NS_OK;
}
}
NS_IMETHODIMP
AndroidUiThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aDelayMs)
{
EnqueueTask(Move(aEvent), aDelayMs);
return NS_OK;
}
static void
PumpEvents() {
NS_ProcessPendingEvents(sThread.get());
}
class ThreadObserver : public nsIThreadObserver
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSITHREADOBSERVER
ThreadObserver()
{}
private:
virtual ~ThreadObserver()
{}
};
NS_IMPL_ISUPPORTS(ThreadObserver, nsIThreadObserver)
NS_IMETHODIMP
ThreadObserver::OnDispatchedEvent()
{
EnqueueTask(NS_NewRunnableFunction("PumpEvents", &PumpEvents), 0);
return NS_OK;
}
NS_IMETHODIMP
ThreadObserver::OnProcessNextEvent(nsIThreadInternal *thread, bool mayWait)
{
return NS_OK;
}
NS_IMETHODIMP
ThreadObserver::AfterProcessNextEvent(nsIThreadInternal *thread, bool eventWasProcessed)
{
return NS_OK;
}
class AndroidUiTask : public LinkedListElement<AndroidUiTask> {
using TimeStamp = mozilla::TimeStamp;
using TimeDuration = mozilla::TimeDuration;
public:
AndroidUiTask(already_AddRefed<nsIRunnable> aTask)
: mTask(aTask)
, mRunTime() // Null timestamp representing no delay.
{}
AndroidUiTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs)
: mTask(aTask)
, mRunTime(TimeStamp::Now() + TimeDuration::FromMilliseconds(aDelayMs))
{}
bool IsEarlierThan(const AndroidUiTask& aOther) const
{
if (mRunTime) {
return aOther.mRunTime ? mRunTime < aOther.mRunTime : false;
}
// In the case of no delay, we're earlier if aOther has a delay.
// Otherwise, we're not earlier, to maintain task order.
return !!aOther.mRunTime;
}
int64_t MillisecondsToRunTime() const
{
if (mRunTime) {
return int64_t((mRunTime - TimeStamp::Now()).ToMilliseconds());
}
return 0;
}
already_AddRefed<nsIRunnable> TakeTask()
{
return mTask.forget();
}
private:
nsCOMPtr<nsIRunnable> mTask;
const TimeStamp mRunTime;
};
class CreateOnUiThread : public Runnable {
public:
CreateOnUiThread() : Runnable("CreateOnUiThread")
{}
NS_IMETHOD Run() override {
MOZ_ASSERT(!sThreadDestroyed);
MOZ_ASSERT(sMessageLoopAccessMonitor);
MonitorAutoLock lock(*sMessageLoopAccessMonitor);
sThread = new AndroidUiThread();
sThread->InitCurrentThread();
sThread->SetObserver(new ThreadObserver());
sMessageLoop = new MessageLoop(MessageLoop::TYPE_MOZILLA_ANDROID_UI, sThread.get());
lock.NotifyAll();
return NS_OK;
}
};
class DestroyOnUiThread : public Runnable {
public:
DestroyOnUiThread() : Runnable("DestroyOnUiThread"), mDestroyed(false)
{}
NS_IMETHOD Run() override {
MOZ_ASSERT(!sThreadDestroyed);
MOZ_ASSERT(sMessageLoopAccessMonitor);
MOZ_ASSERT(sTaskQueue);
MonitorAutoLock lock(*sMessageLoopAccessMonitor);
sThreadDestroyed = true;
{
// Flush the queue
MutexAutoLock lock (*sTaskQueueLock);
while (AndroidUiTask* task = sTaskQueue->getFirst()) {
delete task;
}
}
delete sMessageLoop;
sMessageLoop = nullptr;
MOZ_ASSERT(sThread);
nsThreadManager::get().UnregisterCurrentThread(*sThread);
sThread = nullptr;
mDestroyed = true;
lock.NotifyAll();
return NS_OK;
}
void WaitForDestruction()
{
MOZ_ASSERT(sMessageLoopAccessMonitor);
MonitorAutoLock lock(*sMessageLoopAccessMonitor);
while (!mDestroyed) {
lock.Wait();
}
}
private:
bool mDestroyed;
};
void
EnqueueTask(already_AddRefed<nsIRunnable> aTask, int aDelayMs)
{
if (sThreadDestroyed) {
return;
}
// add the new task into the sTaskQueue, sorted with
// the earliest task first in the queue
AndroidUiTask* newTask = (aDelayMs ? new AndroidUiTask(mozilla::Move(aTask), aDelayMs)
: new AndroidUiTask(mozilla::Move(aTask)));
bool headOfList = false;
{
MOZ_ASSERT(sTaskQueue);
MOZ_ASSERT(sTaskQueueLock);
MutexAutoLock lock(*sTaskQueueLock);
AndroidUiTask* task = sTaskQueue->getFirst();
while (task) {
if (newTask->IsEarlierThan(*task)) {
task->setPrevious(newTask);
break;
}
task = task->getNext();
}
if (!newTask->isInList()) {
sTaskQueue->insertBack(newTask);
}
headOfList = !newTask->getPrevious();
}
if (headOfList) {
// if we're inserting it at the head of the queue, notify Java because
// we need to get a callback at an earlier time than the last scheduled
// callback
GeckoThread::RequestUiThreadCallback(int64_t(aDelayMs));
}
}
} // namespace
namespace mozilla {
void
CreateAndroidUiThread()
{
MOZ_ASSERT(!sThread);
MOZ_ASSERT(!sMessageLoopAccessMonitor);
sTaskQueue = new LinkedList<AndroidUiTask>();
sTaskQueueLock = new Mutex("AndroidUiThreadTaskQueueLock");
sMessageLoopAccessMonitor = new Monitor("AndroidUiThreadMessageLoopAccessMonitor");
sThreadDestroyed = false;
RefPtr<CreateOnUiThread> runnable = new CreateOnUiThread;
EnqueueTask(do_AddRef(runnable), 0);
}
void
DestroyAndroidUiThread()
{
MOZ_ASSERT(sThread);
RefPtr<DestroyOnUiThread> runnable = new DestroyOnUiThread;
EnqueueTask(do_AddRef(runnable), 0);
runnable->WaitForDestruction();
delete sMessageLoopAccessMonitor;
sMessageLoopAccessMonitor = nullptr;
}
MessageLoop*
GetAndroidUiThreadMessageLoop()
{
if (!sMessageLoopAccessMonitor) {
return nullptr;
}
MonitorAutoLock lock(*sMessageLoopAccessMonitor);
while (!sMessageLoop) {
lock.Wait();
}
return sMessageLoop;
}
RefPtr<nsThread>
GetAndroidUiThread()
{
if (!sMessageLoopAccessMonitor) {
return nullptr;
}
MonitorAutoLock lock(*sMessageLoopAccessMonitor);
while (!sThread) {
lock.Wait();
}
return sThread;
}
int64_t
RunAndroidUiTasks()
{
MutexAutoLock lock(*sTaskQueueLock);
if (sThreadDestroyed) {
return -1;
}
while (!sTaskQueue->isEmpty()) {
AndroidUiTask* task = sTaskQueue->getFirst();
const int64_t timeLeft = task->MillisecondsToRunTime();
if (timeLeft > 0) {
// this task (and therefore all remaining tasks)
// have not yet reached their runtime. return the
// time left until we should be called again
return timeLeft;
}
// Retrieve task before unlocking/running.
nsCOMPtr<nsIRunnable> runnable(task->TakeTask());
// LinkedListElements auto remove from list upon destruction
delete task;
// Unlock to allow posting new tasks reentrantly.
MutexAutoUnlock unlock(*sTaskQueueLock);
runnable->Run();
if (sThreadDestroyed) {
return -1;
}
}
return -1;
}
} // namespace mozilla