From 30ab542443bb3dbff3d6f38f19fa7bc3f805c5e2 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 20 Mar 2017 15:18:01 -0700 Subject: [PATCH] The Android FML backend. (#3489) --- fml/platform/android/jni_util.cc | 184 +++++++++++++++++ fml/platform/android/jni_util.h | 48 +++++ fml/platform/android/jni_weak_ref.cc | 67 +++++++ fml/platform/android/jni_weak_ref.h | 50 +++++ fml/platform/android/message_loop_android.cc | 97 +++++++++ fml/platform/android/message_loop_android.h | 50 +++++ fml/platform/android/scoped_java_ref.cc | 89 +++++++++ fml/platform/android/scoped_java_ref.h | 199 +++++++++++++++++++ travis/licenses_golden/licenses_flutter | 8 + 9 files changed, 792 insertions(+) create mode 100644 fml/platform/android/jni_util.cc create mode 100644 fml/platform/android/jni_util.h create mode 100644 fml/platform/android/jni_weak_ref.cc create mode 100644 fml/platform/android/jni_weak_ref.h create mode 100644 fml/platform/android/message_loop_android.cc create mode 100644 fml/platform/android/message_loop_android.h create mode 100644 fml/platform/android/scoped_java_ref.cc create mode 100644 fml/platform/android/scoped_java_ref.h diff --git a/fml/platform/android/jni_util.cc b/fml/platform/android/jni_util.cc new file mode 100644 index 00000000000..792339a68d3 --- /dev/null +++ b/fml/platform/android/jni_util.cc @@ -0,0 +1,184 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/fml/platform/android/jni_util.h" + +#include +#include + +#include "lib/ftl/logging.h" + +namespace fml { +namespace jni { + +static JavaVM* g_jvm = nullptr; +static ScopedJavaGlobalRef* g_android_application_context = nullptr; + +#define ASSERT_NO_EXCEPTION() FTL_CHECK(env->ExceptionCheck() == JNI_FALSE); + +void InitJavaVM(JavaVM* vm) { + FTL_DCHECK(g_jvm == nullptr); + g_jvm = vm; +} + +JNIEnv* AttachCurrentThread() { + FTL_DCHECK(g_jvm != nullptr) + << "Trying to attach to current thread without calling InitJavaVM first."; + JNIEnv* env = nullptr; + jint ret = g_jvm->AttachCurrentThread(&env, nullptr); + FTL_DCHECK(JNI_OK == ret); + return env; +} + +void DetachFromVM() { + if (g_jvm) { + g_jvm->DetachCurrentThread(); + } +} + +void InitAndroidApplicationContext(const JavaRef& context) { + FTL_DCHECK(g_android_application_context == nullptr); + g_android_application_context = new ScopedJavaGlobalRef(context); + FTL_DCHECK(g_android_application_context->obj() != nullptr); +} + +const jobject GetAndroidApplicationContext() { + jobject object = g_android_application_context->obj(); + FTL_DCHECK(object != nullptr) + << "Trying to get Android application context without first calling " + "InitAndroidApplicationContext."; + return object; +} + +static std::string UTF16StringToUTF8String(const char16_t* chars, size_t len) { + std::u16string u16_string(chars, len); + return std::wstring_convert, char16_t>{} + .to_bytes(u16_string); +} + +std::string JavaStringToString(JNIEnv* env, jstring str) { + if (env == nullptr || str == nullptr) { + return ""; + } + const jchar* chars = env->GetStringChars(str, NULL); + if (chars == nullptr) { + return ""; + } + std::string u8_string = UTF16StringToUTF8String( + reinterpret_cast(chars), env->GetStringLength(str)); + env->ReleaseStringChars(str, chars); + ASSERT_NO_EXCEPTION(); + return u8_string; +} + +static std::u16string UTF8StringToUTF16String(const std::string& string) { + return std::wstring_convert, char16_t>{} + .from_bytes(string); +} + +ScopedJavaLocalRef StringToJavaString(JNIEnv* env, + const std::string& u8_string) { + std::u16string u16_string = UTF8StringToUTF16String(u8_string); + auto result = ScopedJavaLocalRef( + env, env->NewString(reinterpret_cast(u16_string.data()), + u16_string.length())); + ASSERT_NO_EXCEPTION(); + return result; +} + +std::vector StringArrayToVector(JNIEnv* env, jobjectArray array) { + std::vector out; + if (env == nullptr || array == nullptr) { + return out; + } + + jsize length = env->GetArrayLength(array); + + if (length == -1) { + return out; + } + + out.resize(length); + for (size_t i = 0; i < length; ++i) { + ScopedJavaLocalRef java_string( + env, static_cast(env->GetObjectArrayElement(array, i))); + out[i] = JavaStringToString(env, java_string.obj()); + } + + return out; +} + +ScopedJavaLocalRef VectorToStringArray( + JNIEnv* env, + const std::vector& vector) { + FTL_DCHECK(env); + ScopedJavaLocalRef string_clazz(env, + env->FindClass("java/lang/String")); + FTL_DCHECK(!string_clazz.is_null()); + jobjectArray joa = + env->NewObjectArray(vector.size(), string_clazz.obj(), NULL); + ASSERT_NO_EXCEPTION(); + for (size_t i = 0; i < vector.size(); ++i) { + ScopedJavaLocalRef item = StringToJavaString(env, vector[i]); + env->SetObjectArrayElement(joa, i, item.obj()); + } + return ScopedJavaLocalRef(env, joa); +} + +bool HasException(JNIEnv* env) { + return env->ExceptionCheck() != JNI_FALSE; +} + +bool ClearException(JNIEnv* env) { + if (!HasException(env)) + return false; + env->ExceptionDescribe(); + env->ExceptionClear(); + return true; +} + +std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { + ScopedJavaLocalRef throwable_clazz( + env, env->FindClass("java/lang/Throwable")); + + jmethodID throwable_printstacktrace = env->GetMethodID( + throwable_clazz.obj(), "printStackTrace", "(Ljava/io/PrintStream;)V"); + + // Create an instance of ByteArrayOutputStream. + ScopedJavaLocalRef bytearray_output_stream_clazz( + env, env->FindClass("java/io/ByteArrayOutputStream")); + + jmethodID bytearray_output_stream_constructor = + env->GetMethodID(bytearray_output_stream_clazz.obj(), "", "()V"); + jmethodID bytearray_output_stream_tostring = env->GetMethodID( + bytearray_output_stream_clazz.obj(), "toString", "()Ljava/lang/String;"); + ScopedJavaLocalRef bytearray_output_stream( + env, env->NewObject(bytearray_output_stream_clazz.obj(), + bytearray_output_stream_constructor)); + + // Create an instance of PrintStream. + ScopedJavaLocalRef printstream_clazz( + env, env->FindClass("java/io/PrintStream")); + + jmethodID printstream_constructor = env->GetMethodID( + printstream_clazz.obj(), "", "(Ljava/io/OutputStream;)V"); + ScopedJavaLocalRef printstream( + env, env->NewObject(printstream_clazz.obj(), printstream_constructor, + bytearray_output_stream.obj())); + + // Call Throwable.printStackTrace(PrintStream) + env->CallVoidMethod(java_throwable, throwable_printstacktrace, + printstream.obj()); + + // Call ByteArrayOutputStream.toString() + ScopedJavaLocalRef exception_string( + env, + static_cast(env->CallObjectMethod( + bytearray_output_stream.obj(), bytearray_output_stream_tostring))); + + return JavaStringToString(env, exception_string.obj()); +} + +} // namespace jni +} // namespace fml diff --git a/fml/platform/android/jni_util.h b/fml/platform/android/jni_util.h new file mode 100644 index 00000000000..5c32d689191 --- /dev/null +++ b/fml/platform/android/jni_util.h @@ -0,0 +1,48 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FML_PLATFORM_ANDROID_JNI_UTIL_H_ +#define FLUTTER_FML_PLATFORM_ANDROID_JNI_UTIL_H_ + +#include + +#include + +#include "flutter/fml/platform/android/scoped_java_ref.h" +#include "lib/ftl/macros.h" + +namespace fml { +namespace jni { + +void InitJavaVM(JavaVM* vm); + +JNIEnv* AttachCurrentThread(); + +void DetachFromVM(); + +void InitAndroidApplicationContext(const JavaRef& context); + +const jobject GetAndroidApplicationContext(); + +std::string JavaStringToString(JNIEnv* env, jstring string); + +ScopedJavaLocalRef StringToJavaString(JNIEnv* env, + const std::string& str); + +std::vector StringArrayToVector(JNIEnv* env, jobjectArray jargs); + +ScopedJavaLocalRef VectorToStringArray( + JNIEnv* env, + const std::vector& vector); + +bool HasException(JNIEnv* env); + +bool ClearException(JNIEnv* env); + +std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable); + +} // namespace jni +} // namespace fml + +#endif // FLUTTER_FML_PLATFORM_ANDROID_JNI_UTIL_H_ diff --git a/fml/platform/android/jni_weak_ref.cc b/fml/platform/android/jni_weak_ref.cc new file mode 100644 index 00000000000..62aea8cee02 --- /dev/null +++ b/fml/platform/android/jni_weak_ref.cc @@ -0,0 +1,67 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/fml/platform/android/jni_weak_ref.h" + +#include "flutter/fml/platform/android/jni_util.h" +#include "lib/ftl/logging.h" + +namespace fml { +namespace jni { + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef() : obj_(NULL) {} + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef( + const JavaObjectWeakGlobalRef& orig) + : obj_(NULL) { + Assign(orig); +} + +JavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj) + : obj_(env->NewWeakGlobalRef(obj)) { + FTL_DCHECK(obj_); +} + +JavaObjectWeakGlobalRef::~JavaObjectWeakGlobalRef() { + reset(); +} + +void JavaObjectWeakGlobalRef::operator=(const JavaObjectWeakGlobalRef& rhs) { + Assign(rhs); +} + +void JavaObjectWeakGlobalRef::reset() { + if (obj_) { + AttachCurrentThread()->DeleteWeakGlobalRef(obj_); + obj_ = NULL; + } +} + +ScopedJavaLocalRef JavaObjectWeakGlobalRef::get(JNIEnv* env) const { + return GetRealObject(env, obj_); +} + +ScopedJavaLocalRef GetRealObject(JNIEnv* env, jweak obj) { + jobject real = NULL; + if (obj) { + real = env->NewLocalRef(obj); + if (!real) + FTL_DLOG(ERROR) << "The real object has been deleted!"; + } + return ScopedJavaLocalRef(env, real); +} + +void JavaObjectWeakGlobalRef::Assign(const JavaObjectWeakGlobalRef& other) { + if (&other == this) + return; + + JNIEnv* env = AttachCurrentThread(); + if (obj_) + env->DeleteWeakGlobalRef(obj_); + + obj_ = other.obj_ ? env->NewWeakGlobalRef(other.obj_) : NULL; +} + +} // namespace jni +} // namespace fml diff --git a/fml/platform/android/jni_weak_ref.h b/fml/platform/android/jni_weak_ref.h new file mode 100644 index 00000000000..cab86b429a7 --- /dev/null +++ b/fml/platform/android/jni_weak_ref.h @@ -0,0 +1,50 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FML_PLATFORM_ANDROID_JNI_WEAK_REF_H_ +#define FLUTTER_FML_PLATFORM_ANDROID_JNI_WEAK_REF_H_ + +#include + +#include "flutter/fml/platform/android/scoped_java_ref.h" + +namespace fml { +namespace jni { + +// Manages WeakGlobalRef lifecycle. +// This class is not thread-safe w.r.t. get() and reset(). Multiple threads may +// safely use get() concurrently, but if the user calls reset() (or of course, +// calls the destructor) they'll need to provide their own synchronization. +class JavaObjectWeakGlobalRef { + public: + JavaObjectWeakGlobalRef(); + + JavaObjectWeakGlobalRef(const JavaObjectWeakGlobalRef& orig); + + JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj); + + virtual ~JavaObjectWeakGlobalRef(); + + void operator=(const JavaObjectWeakGlobalRef& rhs); + + ScopedJavaLocalRef get(JNIEnv* env) const; + + bool is_empty() const { return obj_ == NULL; } + + void reset(); + + private: + void Assign(const JavaObjectWeakGlobalRef& rhs); + + jweak obj_; +}; + +// Get the real object stored in the weak reference returned as a +// ScopedJavaLocalRef. +ScopedJavaLocalRef GetRealObject(JNIEnv* env, jweak obj); + +} // namespace jni +} // namespace fml + +#endif // FLUTTER_FML_PLATFORM_ANDROID_JNI_WEAK_REF_H_ diff --git a/fml/platform/android/message_loop_android.cc b/fml/platform/android/message_loop_android.cc new file mode 100644 index 00000000000..8be30eb1f8c --- /dev/null +++ b/fml/platform/android/message_loop_android.cc @@ -0,0 +1,97 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/fml/platform/android/message_loop_android.h" + +#include +#include + +#include "flutter/fml/platform/linux/timerfd.h" +#include "lib/ftl/files/eintr_wrapper.h" + +namespace fml { + +static constexpr int kClockType = CLOCK_MONOTONIC; + +static ALooper* AcquireLooperForThread() { + ALooper* looper = ALooper_forThread(); + + if (looper == nullptr) { + // No looper has been configured for the current thread. Create one and + // return the same. + looper = ALooper_prepare(0); + } + + // The thread already has a looper. Acquire a reference to the same and return + // it. + ALooper_acquire(looper); + return looper; +} + +MessageLoopAndroid::MessageLoopAndroid() + : looper_(AcquireLooperForThread()), + timer_fd_(::timerfd_create(kClockType, TFD_NONBLOCK | TFD_CLOEXEC)), + running_(false) { + FTL_CHECK(looper_.is_valid()); + FTL_CHECK(timer_fd_.is_valid()); + + static const int kWakeEvents = ALOOPER_EVENT_INPUT; + + ALooper_callbackFunc read_event_fd = [](int, int events, void* data) -> int { + if (events & kWakeEvents) { + reinterpret_cast(data)->OnEventFired(); + } + return 1; // continue receiving callbacks + }; + + int add_result = ::ALooper_addFd(looper_.get(), // looper + timer_fd_.get(), // fd + ALOOPER_POLL_CALLBACK, // ident + kWakeEvents, // events + read_event_fd, // callback + this // baton + ); + FTL_CHECK(add_result == 1); +} + +MessageLoopAndroid::~MessageLoopAndroid() { + int remove_result = ::ALooper_removeFd(looper_.get(), timer_fd_.get()); + FTL_CHECK(remove_result == 1); +} + +void MessageLoopAndroid::Run() { + FTL_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; + } + } +} + +void MessageLoopAndroid::Terminate() { + running_ = false; + ALooper_wake(looper_.get()); +} + +void MessageLoopAndroid::WakeUp(ftl::TimePoint time_point) { + bool result = TimerRearm(timer_fd_.get(), time_point); + FTL_DCHECK(result); +} + +void MessageLoopAndroid::OnEventFired() { + if (TimerDrain(timer_fd_.get())) { + RunExpiredTasksNow(); + } +} + +} // namespace fml diff --git a/fml/platform/android/message_loop_android.h b/fml/platform/android/message_loop_android.h new file mode 100644 index 00000000000..82a11b06031 --- /dev/null +++ b/fml/platform/android/message_loop_android.h @@ -0,0 +1,50 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FML_PLATFORM_ANDROID_MESSAGE_LOOP_ANDROID_H_ +#define FLUTTER_FML_PLATFORM_ANDROID_MESSAGE_LOOP_ANDROID_H_ + +#include + +#include + +#include "flutter/fml/message_loop_impl.h" +#include "lib/ftl/files/unique_fd.h" +#include "lib/ftl/macros.h" +#include "lib/ftl/memory/unique_object.h" + +namespace fml { + +struct UniqueLooperTraits { + static ALooper* InvalidValue() { return nullptr; } + static bool IsValid(ALooper* value) { return value != nullptr; } + static void Free(ALooper* value) { ::ALooper_release(value); } +}; + +class MessageLoopAndroid : public MessageLoopImpl { + private: + ftl::UniqueObject looper_; + ftl::UniqueFD timer_fd_; + bool running_; + + MessageLoopAndroid(); + + ~MessageLoopAndroid() override; + + void Run() override; + + void Terminate() override; + + void WakeUp(ftl::TimePoint time_point) override; + + void OnEventFired(); + + FRIEND_MAKE_REF_COUNTED(MessageLoopAndroid); + FRIEND_REF_COUNTED_THREAD_SAFE(MessageLoopAndroid); + FTL_DISALLOW_COPY_AND_ASSIGN(MessageLoopAndroid); +}; + +} // namespace fml + +#endif // FLUTTER_FML_PLATFORM_ANDROID_MESSAGE_LOOP_ANDROID_H_ diff --git a/fml/platform/android/scoped_java_ref.cc b/fml/platform/android/scoped_java_ref.cc new file mode 100644 index 00000000000..aadc9fbdd09 --- /dev/null +++ b/fml/platform/android/scoped_java_ref.cc @@ -0,0 +1,89 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/fml/platform/android/scoped_java_ref.h" + +#include "flutter/fml/platform/android/jni_util.h" +#include "lib/ftl/logging.h" + +namespace fml { +namespace jni { + +static const int kDefaultLocalFrameCapacity = 16; + +ScopedJavaLocalFrame::ScopedJavaLocalFrame(JNIEnv* env) : env_(env) { + int failed = env_->PushLocalFrame(kDefaultLocalFrameCapacity); + FTL_DCHECK(!failed); +} + +ScopedJavaLocalFrame::ScopedJavaLocalFrame(JNIEnv* env, int capacity) + : env_(env) { + int failed = env_->PushLocalFrame(capacity); + FTL_DCHECK(!failed); +} + +ScopedJavaLocalFrame::~ScopedJavaLocalFrame() { + env_->PopLocalFrame(NULL); +} + +JavaRef::JavaRef() : obj_(NULL) {} + +JavaRef::JavaRef(JNIEnv* env, jobject obj) : obj_(obj) { + if (obj) { + FTL_DCHECK(env && env->GetObjectRefType(obj) == JNILocalRefType); + } +} + +JavaRef::~JavaRef() = default; + +JNIEnv* JavaRef::SetNewLocalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + FTL_DCHECK(env == AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewLocalRef(obj); + if (obj_) + env->DeleteLocalRef(obj_); + obj_ = obj; + return env; +} + +void JavaRef::SetNewGlobalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + FTL_DCHECK(env == AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewGlobalRef(obj); + if (obj_) + env->DeleteGlobalRef(obj_); + obj_ = obj; +} + +void JavaRef::ResetLocalRef(JNIEnv* env) { + if (obj_) { + FTL_DCHECK(env == AttachCurrentThread()); // Is |env| on correct thread. + env->DeleteLocalRef(obj_); + obj_ = NULL; + } +} + +void JavaRef::ResetGlobalRef() { + if (obj_) { + AttachCurrentThread()->DeleteGlobalRef(obj_); + obj_ = NULL; + } +} + +jobject JavaRef::ReleaseInternal() { + jobject obj = obj_; + obj_ = NULL; + return obj; +} + +} // namespace jni +} // namespace fml diff --git a/fml/platform/android/scoped_java_ref.h b/fml/platform/android/scoped_java_ref.h new file mode 100644 index 00000000000..14435665729 --- /dev/null +++ b/fml/platform/android/scoped_java_ref.h @@ -0,0 +1,199 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FML_PLATFORM_ANDROID_SCOPED_JAVA_REF_H_ +#define FLUTTER_FML_PLATFORM_ANDROID_SCOPED_JAVA_REF_H_ + +#include +#include + +#include "lib/ftl/macros.h" + +namespace fml { +namespace jni { + +// Creates a new local reference frame, in which at least a given number of +// local references can be created. Note that local references already created +// in previous local frames are still valid in the current local frame. +class ScopedJavaLocalFrame { + public: + explicit ScopedJavaLocalFrame(JNIEnv* env); + ScopedJavaLocalFrame(JNIEnv* env, int capacity); + ~ScopedJavaLocalFrame(); + + private: + // This class is only good for use on the thread it was created on so + // it's safe to cache the non-threadsafe JNIEnv* inside this object. + JNIEnv* env_; + + FTL_DISALLOW_COPY_AND_ASSIGN(ScopedJavaLocalFrame); +}; + +// Forward declare the generic java reference template class. +template +class JavaRef; + +// Template specialization of JavaRef, which acts as the base class for all +// other JavaRef<> template types. This allows you to e.g. pass +// ScopedJavaLocalRef into a function taking const JavaRef& +template <> +class JavaRef { + public: + jobject obj() const { return obj_; } + + bool is_null() const { return obj_ == NULL; } + + protected: + // Initializes a NULL reference. + JavaRef(); + + // Takes ownership of the |obj| reference passed; requires it to be a local + // reference type. + JavaRef(JNIEnv* env, jobject obj); + + ~JavaRef(); + + // The following are implementation detail convenience methods, for + // use by the sub-classes. + JNIEnv* SetNewLocalRef(JNIEnv* env, jobject obj); + void SetNewGlobalRef(JNIEnv* env, jobject obj); + void ResetLocalRef(JNIEnv* env); + void ResetGlobalRef(); + jobject ReleaseInternal(); + + private: + jobject obj_; + + FTL_DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Generic base class for ScopedJavaLocalRef and ScopedJavaGlobalRef. Useful +// for allowing functions to accept a reference without having to mandate +// whether it is a local or global type. +template +class JavaRef : public JavaRef { + public: + T obj() const { return static_cast(JavaRef::obj()); } + + protected: + JavaRef() {} + ~JavaRef() {} + + JavaRef(JNIEnv* env, T obj) : JavaRef(env, obj) {} + + private: + FTL_DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Holds a local reference to a Java object. The local reference is scoped +// to the lifetime of this object. +// Instances of this class may hold onto any JNIEnv passed into it until +// destroyed. Therefore, since a JNIEnv is only suitable for use on a single +// thread, objects of this class must be created, used, and destroyed, on a +// single thread. +// Therefore, this class should only be used as a stack-based object and from a +// single thread. If you wish to have the reference outlive the current +// callstack (e.g. as a class member) or you wish to pass it across threads, +// use a ScopedJavaGlobalRef instead. +template +class ScopedJavaLocalRef : public JavaRef { + public: + ScopedJavaLocalRef() : env_(NULL) {} + + // Non-explicit copy constructor, to allow ScopedJavaLocalRef to be returned + // by value as this is the normal usage pattern. + ScopedJavaLocalRef(const ScopedJavaLocalRef& other) : env_(other.env_) { + this->SetNewLocalRef(env_, other.obj()); + } + + template + explicit ScopedJavaLocalRef(const U& other) : env_(NULL) { + this->Reset(other); + } + + // Assumes that |obj| is a local reference to a Java object and takes + // ownership of this local reference. + ScopedJavaLocalRef(JNIEnv* env, T obj) : JavaRef(env, obj), env_(env) {} + + ~ScopedJavaLocalRef() { this->Reset(); } + + // Overloaded assignment operator defined for consistency with the implicit + // copy constructor. + void operator=(const ScopedJavaLocalRef& other) { this->Reset(other); } + + void Reset() { this->ResetLocalRef(env_); } + + template + void Reset(const ScopedJavaLocalRef& other) { + // We can copy over env_ here as |other| instance must be from the same + // thread as |this| local ref. (See class comment for multi-threading + // limitations, and alternatives). + this->Reset(other.env_, other.obj()); + } + + template + void Reset(const U& other) { + // If |env_| was not yet set (is still NULL) it will be attached to the + // current thread in SetNewLocalRef(). + this->Reset(env_, other.obj()); + } + + template + void Reset(JNIEnv* env, U obj) { + env_ = this->SetNewLocalRef(env, obj); + } + + // Releases the local reference to the caller. The caller *must* delete the + // local reference when it is done with it. + T Release() { return static_cast(this->ReleaseInternal()); } + + private: + // This class is only good for use on the thread it was created on so + // it's safe to cache the non-threadsafe JNIEnv* inside this object. + JNIEnv* env_; +}; + +// Holds a global reference to a Java object. The global reference is scoped +// to the lifetime of this object. This class does not hold onto any JNIEnv* +// passed to it, hence it is safe to use across threads (within the constraints +// imposed by the underlying Java object that it references). +template +class ScopedJavaGlobalRef : public JavaRef { + public: + ScopedJavaGlobalRef() {} + + explicit ScopedJavaGlobalRef(const ScopedJavaGlobalRef& other) { + this->Reset(other); + } + + ScopedJavaGlobalRef(JNIEnv* env, T obj) { this->Reset(env, obj); } + + template + explicit ScopedJavaGlobalRef(const U& other) { + this->Reset(other); + } + + ~ScopedJavaGlobalRef() { this->Reset(); } + + void Reset() { this->ResetGlobalRef(); } + + template + void Reset(const U& other) { + this->Reset(NULL, other.obj()); + } + + template + void Reset(JNIEnv* env, U obj) { + this->SetNewGlobalRef(env, obj); + } + + // Releases the global reference to the caller. The caller *must* delete the + // global reference when it is done with it. + T Release() { return static_cast(this->ReleaseInternal()); } +}; + +} // namespace jni +} // namespace fml + +#endif // FLUTTER_FML_PLATFORM_ANDROID_SCOPED_JAVA_REF_H_ diff --git a/travis/licenses_golden/licenses_flutter b/travis/licenses_golden/licenses_flutter index 5621737c26c..a37b508d034 100644 --- a/travis/licenses_golden/licenses_flutter +++ b/travis/licenses_golden/licenses_flutter @@ -1915,6 +1915,14 @@ FILE: ../../../flutter/fml/message_loop.h FILE: ../../../flutter/fml/message_loop_impl.cc FILE: ../../../flutter/fml/message_loop_impl.h FILE: ../../../flutter/fml/message_loop_unittests.cc +FILE: ../../../flutter/fml/platform/android/jni_util.cc +FILE: ../../../flutter/fml/platform/android/jni_util.h +FILE: ../../../flutter/fml/platform/android/jni_weak_ref.cc +FILE: ../../../flutter/fml/platform/android/jni_weak_ref.h +FILE: ../../../flutter/fml/platform/android/message_loop_android.cc +FILE: ../../../flutter/fml/platform/android/message_loop_android.h +FILE: ../../../flutter/fml/platform/android/scoped_java_ref.cc +FILE: ../../../flutter/fml/platform/android/scoped_java_ref.h FILE: ../../../flutter/fml/platform/darwin/cf_utils.cc FILE: ../../../flutter/fml/platform/darwin/cf_utils.h FILE: ../../../flutter/fml/platform/linux/message_loop_linux.cc