Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 56 additions & 13 deletions fml/platform/android/message_loop_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,31 @@ namespace fml {

static constexpr int kClockType = CLOCK_MONOTONIC;

static fml::jni::ScopedJavaGlobalRef<jclass>* g_looper_class = nullptr;
static jmethodID g_looper_prepare_method_ = nullptr;
static jmethodID g_looper_loop_method_ = nullptr;
static jmethodID g_looper_my_looper_method_ = nullptr;
static jmethodID g_looper_quit_method_ = nullptr;

static void LooperPrepare() {
JNIEnv* env = fml::jni::AttachCurrentThread();
env->CallStaticVoidMethod(g_looper_class->obj(), g_looper_prepare_method_);
}

static void LooperLoop() {
JNIEnv* env = fml::jni::AttachCurrentThread();
env->CallStaticVoidMethod(g_looper_class->obj(), g_looper_loop_method_);
}

static void LooperQuit() {
JNIEnv* env = fml::jni::AttachCurrentThread();
auto my_looper = env->CallStaticObjectMethod(g_looper_class->obj(),
g_looper_my_looper_method_);
if (my_looper != nullptr) {
env->CallVoidMethod(my_looper, g_looper_quit_method_);
}
}

static ALooper* AcquireLooperForThread() {
ALooper* looper = ALooper_forThread();

Expand Down Expand Up @@ -63,23 +88,15 @@ void MessageLoopAndroid::Run() {
FML_DCHECK(looper_.get() == ALooper_forThread());

running_ = true;

while (running_) {
int result = ::ALooper_pollOnce(-1, // infinite timeout
nullptr, // out fd,
nullptr, // out events,
nullptr // out data
);
if (result == ALOOPER_POLL_TIMEOUT || result == ALOOPER_POLL_ERROR) {
// This handles the case where the loop is terminated using ALooper APIs.
running_ = false;
}
}
// Initialize the current thread as a looper.
LooperPrepare();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this means that ALooper_forThread() returns null? or are we calling prepare unnecessarily?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm, this is initializing the state in the JVM, right? could we add comments?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// Run the message queue in this thread.
LooperLoop();
}

void MessageLoopAndroid::Terminate() {
running_ = false;
ALooper_wake(looper_.get());
LooperQuit();
}

void MessageLoopAndroid::WakeUp(fml::TimePoint time_point) {
Expand All @@ -93,4 +110,30 @@ void MessageLoopAndroid::OnEventFired() {
}
}

bool MessageLoopAndroid::Register(JNIEnv* env) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to add a comment to indicate that this could just use AChoreographer once Flutter drops supports for API level < 24?

There's not concrete plan, but worth mentioning for future maintainers that using the NDK simplifies this quite a bit.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a related issue here: flutter/flutter#62166.
I think in the future, if we need to replace the current implementation with NDK, we need to re-evaluate the possible benefits it brings, such as how much performance can be improved, whether the code is simple and maintainable, etc. So I think it's more reasonable to let it exist as an issue instead of writing it in the comments :-)

jclass clazz = env->FindClass("android/os/Looper");
FML_CHECK(clazz != nullptr);

g_looper_class = new fml::jni::ScopedJavaGlobalRef<jclass>(env, clazz);
FML_CHECK(!g_looper_class->is_null());

g_looper_prepare_method_ =
env->GetStaticMethodID(g_looper_class->obj(), "prepare", "()V");
FML_CHECK(g_looper_prepare_method_ != nullptr);

g_looper_loop_method_ =
env->GetStaticMethodID(g_looper_class->obj(), "loop", "()V");
FML_CHECK(g_looper_loop_method_ != nullptr);

g_looper_my_looper_method_ = env->GetStaticMethodID(
g_looper_class->obj(), "myLooper", "()Landroid/os/Looper;");
FML_CHECK(g_looper_my_looper_method_ != nullptr);

g_looper_quit_method_ =
Copy link

@blasten blasten Feb 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no check for g_looper_quit_method_ != nullptr. Is this intended?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's missed before, added now

env->GetMethodID(g_looper_class->obj(), "quit", "()V");
FML_CHECK(g_looper_quit_method_ != nullptr);

return true;
}

} // namespace fml
4 changes: 4 additions & 0 deletions fml/platform/android/message_loop_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "flutter/fml/macros.h"
#include "flutter/fml/message_loop_impl.h"
#include "flutter/fml/platform/android/jni_util.h"
#include "flutter/fml/unique_fd.h"

namespace fml {
Expand All @@ -26,6 +27,9 @@ struct UniqueLooperTraits {
/// This implemenation wraps usage of Android's \p looper.
/// \see https://developer.android.com/ndk/reference/group/looper
class MessageLoopAndroid : public MessageLoopImpl {
public:
static bool Register(JNIEnv* env);

private:
fml::UniqueObject<ALooper*, UniqueLooperTraits> looper_;
fml::UniqueFD timer_fd_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,10 @@ public void setRefreshRateFPS(float refreshRateFPS) {

/**
* The Android vsync waiter implementation in C++ needs to know when a vsync signal arrives, which
* is obtained via Java API. The delegate set here is called on the C++ side when the engine is
* ready to wait for the next vsync signal. The delegate is expected to add a postFrameCallback to
* the {@link android.view.Choreographer}, and call {@link onVsync} to notify the engine.
* is obtained via Java API. The delegate set here is called on the C++ side on the ui thread when
* the engine is ready to wait for the next vsync signal. The delegate is expected to add a
* postFrameCallback to the {@link android.view.Choreographer}, and call {@link onVsync} to notify
* the engine.
*
* @param delegate The delegate that will call the engine back on the next vsync signal.
*/
Expand All @@ -272,7 +273,7 @@ public void setAsyncWaitForVsyncDelegate(@Nullable AsyncWaitForVsyncDelegate del
}

// TODO(mattcarroll): add javadocs
// Called by native.
// Called by native on the ui thread.
private static void asyncWaitForVsync(final long cookie) {
if (asyncWaitForVsyncDelegate != null) {
asyncWaitForVsyncDelegate.asyncWaitForVsync(cookie);
Expand Down
5 changes: 5 additions & 0 deletions shell/platform/android/library_loader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

#include "flutter/fml/platform/android/jni_util.h"
#include "flutter/fml/platform/android/message_loop_android.h"
#include "flutter/shell/platform/android/android_image_generator.h"
#include "flutter/shell/platform/android/flutter_main.h"
#include "flutter/shell/platform/android/platform_view_android.h"
Expand Down Expand Up @@ -32,5 +33,9 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
result = flutter::AndroidImageGenerator::Register(env);
FML_CHECK(result);

// Register MessageLoopAndroid.
result = fml::MessageLoopAndroid::Register(env);
FML_CHECK(result);

return JNI_VERSION_1_4;
}
12 changes: 5 additions & 7 deletions shell/platform/android/vsync_waiter_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,11 @@ void VsyncWaiterAndroid::AwaitVSync() {
auto* weak_this = new std::weak_ptr<VsyncWaiter>(shared_from_this());
jlong java_baton = reinterpret_cast<jlong>(weak_this);

task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() {
JNIEnv* env = fml::jni::AttachCurrentThread();
env->CallStaticVoidMethod(g_vsync_waiter_class->obj(), //
g_async_wait_for_vsync_method_, //
java_baton //
);
});
JNIEnv* env = fml::jni::AttachCurrentThread();
env->CallStaticVoidMethod(g_vsync_waiter_class->obj(), //
g_async_wait_for_vsync_method_, //
java_baton //
);
}

// static
Expand Down