still_image_camera: Fix incorrect JNI usage

As `jstring`s are also object references, if we would like to use them across native methods/threads, we would have to make a global reference.

We will need to delete this global reference explicitly. Since this string is shared across multiple Interfaces and also in Factory, I used shared_ptr to manage deletion. Added a fancy SharedGlobalRef to id_cache.h.

Also removed global reference creation for java/lang/String classes. Turns out that local references are guaranteed valid for the duration of the method, and I was just being too cautious.
This commit is contained in:
zhupengfei 2020-05-04 10:20:21 +08:00 committed by bunnei
parent e40f7f0946
commit d042855546
5 changed files with 34 additions and 17 deletions

View File

@ -34,8 +34,7 @@ void AndroidMiiSelector::Setup(const Frontend::MiiSelectorConfig& config) {
// List mii names // List mii names
// The 'Standard Mii' is not included here as we need Java side to translate it // The 'Standard Mii' is not included here as we need Java side to translate it
const jclass string_class = const jclass string_class = reinterpret_cast<jclass>(env->FindClass("java/lang/String"));
reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("java/lang/String")));
const jobjectArray array = const jobjectArray array =
env->NewObjectArray(static_cast<jsize>(miis.size()), string_class, nullptr); env->NewObjectArray(static_cast<jsize>(miis.size()), string_class, nullptr);
for (std::size_t i = 0; i < miis.size(); ++i) { for (std::size_t i = 0; i < miis.size(); ++i) {
@ -45,7 +44,6 @@ void AndroidMiiSelector::Setup(const Frontend::MiiSelectorConfig& config) {
env->SetObjectField( env->SetObjectField(
java_config, java_config,
env->GetFieldID(s_mii_selector_config_class, "mii_names", "[Ljava/lang/String;"), array); env->GetFieldID(s_mii_selector_config_class, "mii_names", "[Ljava/lang/String;"), array);
env->DeleteGlobalRef(string_class);
// Invoke backend Execute method // Invoke backend Execute method
jobject data = jobject data =

View File

@ -41,8 +41,7 @@ static jobject ToJavaKeyboardConfig(const Frontend::KeyboardConfig& config) {
env->GetFieldID(s_keyboard_config_class, "hint_text", "Ljava/lang/String;"), env->GetFieldID(s_keyboard_config_class, "hint_text", "Ljava/lang/String;"),
env->NewStringUTF(config.hint_text.c_str())); env->NewStringUTF(config.hint_text.c_str()));
if (config.has_custom_button_text) { if (config.has_custom_button_text) {
const jclass string_class = const jclass string_class = reinterpret_cast<jclass>(env->FindClass("java/lang/String"));
reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("java/lang/String")));
const jobjectArray array = const jobjectArray array =
env->NewObjectArray(static_cast<jsize>(config.button_text.size()), string_class, env->NewObjectArray(static_cast<jsize>(config.button_text.size()), string_class,
env->NewStringUTF(config.button_text[0].c_str())); env->NewStringUTF(config.button_text[0].c_str()));
@ -53,7 +52,6 @@ static jobject ToJavaKeyboardConfig(const Frontend::KeyboardConfig& config) {
env->SetObjectField( env->SetObjectField(
object, env->GetFieldID(s_keyboard_config_class, "button_text", "[Ljava/lang/String;"), object, env->GetFieldID(s_keyboard_config_class, "button_text", "[Ljava/lang/String;"),
array); array);
env->DeleteGlobalRef(string_class);
} }
return object; return object;
} }

View File

@ -7,7 +7,6 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/frontend/camera/blank_camera.h" #include "core/frontend/camera/blank_camera.h"
#include "jni/camera/still_image_camera.h" #include "jni/camera/still_image_camera.h"
#include "jni/id_cache.h"
static jclass s_still_image_camera_helper_class; static jclass s_still_image_camera_helper_class;
static jmethodID s_open_file_picker; static jmethodID s_open_file_picker;
@ -29,7 +28,8 @@ void CleanupJNI(JNIEnv* env) {
env->DeleteGlobalRef(s_still_image_camera_helper_class); env->DeleteGlobalRef(s_still_image_camera_helper_class);
} }
Interface::Interface(jstring path_, const Service::CAM::Flip& flip) : path(path_) { Interface::Interface(SharedGlobalRef<jstring> path_, const Service::CAM::Flip& flip)
: path(std::move(path_)) {
mirror = base_mirror = mirror = base_mirror =
flip == Service::CAM::Flip::Horizontal || flip == Service::CAM::Flip::Reverse; flip == Service::CAM::Flip::Horizontal || flip == Service::CAM::Flip::Reverse;
invert = base_invert = invert = base_invert =
@ -37,14 +37,14 @@ Interface::Interface(jstring path_, const Service::CAM::Flip& flip) : path(path_
} }
Interface::~Interface() { Interface::~Interface() {
Factory::last_path = nullptr; Factory::last_path.reset();
} }
void Interface::StartCapture() { void Interface::StartCapture() {
JNIEnv* env = IDCache::GetEnvForThread(); JNIEnv* env = IDCache::GetEnvForThread();
jobject bitmap = jobject bitmap =
env->CallStaticObjectMethod(s_still_image_camera_helper_class, s_load_image_from_file, path, env->CallStaticObjectMethod(s_still_image_camera_helper_class, s_load_image_from_file,
resolution.width, resolution.height); path.get(), resolution.width, resolution.height);
if (bitmap == nullptr) { if (bitmap == nullptr) {
LOG_ERROR(Frontend, "Could not load image from file"); LOG_ERROR(Frontend, "Could not load image from file");
opened = false; opened = false;
@ -118,7 +118,7 @@ bool Interface::IsPreviewAvailable() {
return opened; return opened;
} }
jstring Factory::last_path{}; SharedGlobalRef<jstring> Factory::last_path{};
std::unique_ptr<CameraInterface> Factory::Create(const std::string& config, std::unique_ptr<CameraInterface> Factory::Create(const std::string& config,
const Service::CAM::Flip& flip) { const Service::CAM::Flip& flip) {
@ -134,8 +134,9 @@ std::unique_ptr<CameraInterface> Factory::Create(const std::string& config,
if (path == nullptr) { if (path == nullptr) {
return std::make_unique<Camera::BlankCamera>(); return std::make_unique<Camera::BlankCamera>();
} else { } else {
last_path = path; auto shared_path = NewSharedGlobalRef(path);
return std::make_unique<Interface>(path, flip); last_path = shared_path;
return std::make_unique<Interface>(std::move(shared_path), flip);
} }
} }

View File

@ -11,12 +11,13 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "core/frontend/camera/factory.h" #include "core/frontend/camera/factory.h"
#include "core/frontend/camera/interface.h" #include "core/frontend/camera/interface.h"
#include "jni/id_cache.h"
namespace Camera::StillImage { namespace Camera::StillImage {
class Interface final : public CameraInterface { class Interface final : public CameraInterface {
public: public:
Interface(jstring path, const Service::CAM::Flip& flip); Interface(SharedGlobalRef<jstring> path, const Service::CAM::Flip& flip);
~Interface(); ~Interface();
void StartCapture() override; void StartCapture() override;
void StopCapture() override{}; void StopCapture() override{};
@ -29,7 +30,7 @@ public:
bool IsPreviewAvailable() override; bool IsPreviewAvailable() override;
private: private:
jstring path; SharedGlobalRef<jstring> path;
Service::CAM::Resolution resolution; Service::CAM::Resolution resolution;
// Flipping parameters. mirror = horizontal, invert = vertical. // Flipping parameters. mirror = horizontal, invert = vertical.
@ -50,7 +51,7 @@ public:
private: private:
/// Record the path chosen to avoid multiple prompt problem /// Record the path chosen to avoid multiple prompt problem
static jstring last_path; static SharedGlobalRef<jstring> last_path;
friend class Interface; friend class Interface;
}; };

View File

@ -4,6 +4,8 @@
#pragma once #pragma once
#include <memory>
#include <type_traits>
#include <jni.h> #include <jni.h>
namespace IDCache { namespace IDCache {
@ -19,3 +21,20 @@ jmethodID GetExitEmulationActivity();
jmethodID GetRequestCameraPermission(); jmethodID GetRequestCameraPermission();
} // namespace IDCache } // namespace IDCache
template <typename T = jobject>
using SharedGlobalRef = std::shared_ptr<std::remove_pointer_t<T>>;
struct SharedGlobalRefDeleter {
void operator()(jobject ptr) {
JNIEnv* env = IDCache::GetEnvForThread();
env->DeleteGlobalRef(ptr);
}
};
template <typename T = jobject>
SharedGlobalRef<T> NewSharedGlobalRef(T object) {
JNIEnv* env = IDCache::GetEnvForThread();
auto* global_ref = reinterpret_cast<T>(env->NewGlobalRef(object));
return SharedGlobalRef<T>(global_ref, SharedGlobalRefDeleter());
}