Release underlying resources when JS instance is GC'ed on Android (#24767)

Summary:
Android followup for #24745. This adds a jsi object that removes blobs when it is gc'ed. We don't have many modules with native code on Android so I've added the native code directly in the blob package as a separate .so. I used a similar structure as the turbomodule package.

## Changelog

[Android] [Fixed] - [Blob] Release underlying resources when JS instance is GC'ed on Android
Pull Request resolved: https://github.com/facebook/react-native/pull/24767

Differential Revision: D15279651

Pulled By: cpojer

fbshipit-source-id: 2bbdc4bbcbeae8945588ac5e3e895c49e6ac9e1a
This commit is contained in:
Janic Duplessis
2019-05-31 03:55:27 -07:00
committed by Facebook Github Bot
parent 1ed352517a
commit a4f7e17a4f
11 changed files with 211 additions and 15 deletions
@@ -16,6 +16,7 @@ rn_android_library(
],
deps = [
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"),
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_dep("third-party/java/okhttp:okhttp3"),
@@ -24,6 +25,7 @@ rn_android_library(
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("java/com/facebook/react/modules/blob/jni:jni"),
react_native_target("java/com/facebook/react/modules/network:network"),
react_native_target("java/com/facebook/react/modules/websocket:websocket"),
],
@@ -0,0 +1,25 @@
package com.facebook.react.modules.blob;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactContext;
import com.facebook.soloader.SoLoader;
/* package */ class BlobCollector {
static {
SoLoader.loadLibrary("reactnativeblob");
}
static void install(final ReactContext reactContext, final BlobModule blobModule) {
reactContext.runOnJSQueueThread(new Runnable() {
@Override
public void run() {
JavaScriptContextHolder jsContext = reactContext.getJavaScriptContextHolder();
synchronized (jsContext) {
nativeInstall(blobModule, jsContext.get());
}
}
});
}
private native static void nativeInstall(Object blobModule, long jsContext);
}
@@ -6,13 +6,10 @@
*/
package com.facebook.react.modules.blob;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import androidx.annotation.Nullable;
import android.webkit.MimeTypeMap;
import com.facebook.react.bridge.Arguments;
@@ -40,6 +37,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import androidx.annotation.Nullable;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
@@ -151,6 +149,11 @@ public class BlobModule extends ReactContextBaseJavaModule {
super(reactContext);
}
@Override
public void initialize() {
BlobCollector.install(getReactApplicationContext(), this);
}
@Override
public String getName() {
return NAME;
@@ -178,11 +181,15 @@ public class BlobModule extends ReactContextBaseJavaModule {
}
public void store(byte[] data, String blobId) {
mBlobs.put(blobId, data);
synchronized (mBlobs) {
mBlobs.put(blobId, data);
}
}
public void remove(String blobId) {
mBlobs.remove(blobId);
synchronized (mBlobs) {
mBlobs.remove(blobId);
}
}
public @Nullable byte[] resolve(Uri uri) {
@@ -201,17 +208,19 @@ public class BlobModule extends ReactContextBaseJavaModule {
}
public @Nullable byte[] resolve(String blobId, int offset, int size) {
byte[] data = mBlobs.get(blobId);
if (data == null) {
return null;
synchronized (mBlobs) {
byte[] data = mBlobs.get(blobId);
if (data == null) {
return null;
}
if (size == -1) {
size = data.length - offset;
}
if (offset > 0 || size != data.length) {
data = Arrays.copyOfRange(data, offset, offset + size);
}
return data;
}
if (size == -1) {
size = data.length - offset;
}
if (offset > 0 || size != data.length) {
data = Arrays.copyOfRange(data, offset, offset + size);
}
return data;
}
public @Nullable byte[] resolve(ReadableMap blob) {
@@ -0,0 +1,21 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := reactnativeblob
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_CFLAGS += -fvisibility=hidden -fexceptions -frtti
LOCAL_STATIC_LIBRARIES := libjsi libjsireact jscruntime
LOCAL_SHARED_LIBRARIES := libfolly_json libfb libreactnativejni
include $(BUILD_SHARED_LIBRARY)
@@ -0,0 +1,22 @@
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "FBJNI_TARGET", "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library")
rn_xplat_cxx_library(
name = "jni",
srcs = glob(["*.cpp"]),
headers = glob(["*.h"]),
header_namespace = "",
compiler_flags = ["-fexceptions"],
fbandroid_allow_jni_merging = True,
platforms = ANDROID,
soname = "libreactnativeblob.$(ext)",
visibility = [
"PUBLIC",
],
deps = [
"fbsource//xplat/folly:molly",
FBJNI_TARGET,
react_native_target("jni/react/jni:jni"),
react_native_xplat_target("jsi:JSCRuntime"),
react_native_xplat_target("jsiexecutor:jsiexecutor"),
],
)
@@ -0,0 +1,61 @@
// Copyright 2004-present Facebook. All Rights Reserved.
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include "BlobCollector.h"
#include <fb/fbjni.h>
#include <memory>
#include <mutex>
using namespace facebook;
namespace facebook {
namespace react {
static constexpr auto kBlobModuleJavaDescriptor =
"com/facebook/react/modules/blob/BlobModule";
BlobCollector::BlobCollector(
jni::global_ref<jobject> blobModule,
const std::string &blobId)
: blobModule_(blobModule), blobId_(blobId) {}
BlobCollector::~BlobCollector() {
auto removeMethod = jni::findClassStatic(kBlobModuleJavaDescriptor)
->getMethod<void(jstring)>("remove");
removeMethod(blobModule_, jni::make_jstring(blobId_).get());
}
void BlobCollector::nativeInstall(
jni::alias_ref<jhybridobject> jThis,
jni::alias_ref<jobject> blobModule,
jlong jsContextNativePointer) {
auto &runtime = *((jsi::Runtime *)jsContextNativePointer);
auto blobModuleRef = jni::make_global(blobModule);
runtime.global().setProperty(
runtime,
"__blobCollectorProvider",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__blobCollectorProvider"),
1,
[blobModuleRef](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
auto blobId = args[0].asString(rt).utf8(rt);
auto blobCollector =
std::make_shared<BlobCollector>(blobModuleRef, blobId);
return jsi::Object::createFromHostObject(rt, blobCollector);
}));
}
void BlobCollector::registerNatives() {
registerHybrid(
{makeNativeMethod("nativeInstall", BlobCollector::nativeInstall)});
}
} // namespace react
} // namespace facebook
@@ -0,0 +1,39 @@
// Copyright 2004-present Facebook. All Rights Reserved.
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#pragma once
#include <fb/fbjni.h>
#include <jsi/jsi.h>
namespace facebook {
namespace react {
class BlobCollector : public jni::HybridClass<BlobCollector>,
public jsi::HostObject {
public:
BlobCollector(
jni::global_ref<jobject> blobManager,
const std::string &blobId);
~BlobCollector();
static constexpr auto kJavaDescriptor =
"Lcom/facebook/react/modules/blob/BlobCollector;";
static void nativeInstall(
jni::alias_ref<jhybridobject> jThis,
jni::alias_ref<jobject> blobModule,
jlong jsContextNativePointer);
static void registerNatives();
private:
friend HybridBase;
jni::global_ref<jobject> blobModule_;
const std::string blobId_;
};
} // namespace react
} // namespace facebook
@@ -0,0 +1,13 @@
// Copyright 2004-present Facebook. All Rights Reserved.
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#include <fb/fbjni.h>
#include "BlobCollector.h"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return facebook::jni::initialize(
vm, [] { facebook::react::BlobCollector::registerNatives(); });
}