Skip to content

Commit 1ee0449

Browse files
[Blob] Release underlying resources when JS instance is GC'ed on Android
1 parent 343f0a1 commit 1ee0449

11 files changed

Lines changed: 211 additions & 15 deletions

File tree

RNTester/android/app/BUCK

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ rn_android_library(
2525
react_native_target("java/com/facebook/react:react"),
2626
react_native_target("java/com/facebook/react/common:build_config"),
2727
react_native_target("java/com/facebook/react/modules/core:core"),
28+
react_native_target("java/com/facebook/react/views/text:text"),
2829
react_native_target("java/com/facebook/react/shell:shell"),
2930
react_native_target("jni/prebuilt:android-jsc"),
3031
# .so files are prebuilt by Gradle with `./gradlew :ReactAndroid:packageReactNdkLibsForBuck`

ReactAndroid/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ def getNdkBuildFullPath() {
222222
task buildReactNdkLib(dependsOn: [prepareJSC, prepareBoost, prepareDoubleConversion, prepareFolly, prepareGlog], type: Exec) {
223223
inputs.dir("$projectDir/../ReactCommon")
224224
inputs.dir("src/main/jni")
225+
inputs.dir("src/main/java/com/facebook/react/modules/blob")
225226
outputs.dir("$buildDir/react-ndk/all")
226227
commandLine(getNdkBuildFullPath(),
227228
"NDK_PROJECT_PATH=null",

ReactAndroid/src/main/java/com/facebook/react/modules/blob/BUCK

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ rn_android_library(
1616
],
1717
deps = [
1818
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"),
19+
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
1920
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
2021
react_native_dep("third-party/java/jsr-305:jsr-305"),
2122
react_native_dep("third-party/java/okhttp:okhttp3"),
@@ -24,6 +25,7 @@ rn_android_library(
2425
react_native_target("java/com/facebook/react/bridge:bridge"),
2526
react_native_target("java/com/facebook/react/common:common"),
2627
react_native_target("java/com/facebook/react/module/annotations:annotations"),
28+
react_native_target("java/com/facebook/react/modules/blob/jni:jni"),
2729
react_native_target("java/com/facebook/react/modules/network:network"),
2830
react_native_target("java/com/facebook/react/modules/websocket:websocket"),
2931
],
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.facebook.react.modules.blob;
2+
3+
import com.facebook.react.bridge.JavaScriptContextHolder;
4+
import com.facebook.react.bridge.ReactContext;
5+
import com.facebook.soloader.SoLoader;
6+
7+
/* package */ class BlobCollector {
8+
static {
9+
SoLoader.loadLibrary("reactnativeblob");
10+
}
11+
12+
static void install(final ReactContext reactContext, final BlobModule blobModule) {
13+
reactContext.runOnJSQueueThread(new Runnable() {
14+
@Override
15+
public void run() {
16+
JavaScriptContextHolder jsContext = reactContext.getJavaScriptContextHolder();
17+
synchronized (jsContext) {
18+
nativeInstall(blobModule, jsContext.get());
19+
}
20+
}
21+
});
22+
}
23+
24+
private native static void nativeInstall(Object blobModule, long jsContext);
25+
}

ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobModule.java

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@
66
*/
77
package com.facebook.react.modules.blob;
88

9-
import android.content.ContentResolver;
10-
import android.content.Context;
119
import android.content.res.Resources;
1210
import android.database.Cursor;
1311
import android.net.Uri;
1412
import android.provider.MediaStore;
15-
import androidx.annotation.Nullable;
1613
import android.webkit.MimeTypeMap;
1714

1815
import com.facebook.react.bridge.Arguments;
@@ -40,6 +37,7 @@
4037
import java.util.Map;
4138
import java.util.UUID;
4239

40+
import androidx.annotation.Nullable;
4341
import okhttp3.MediaType;
4442
import okhttp3.RequestBody;
4543
import okhttp3.ResponseBody;
@@ -151,6 +149,11 @@ public BlobModule(ReactApplicationContext reactContext) {
151149
super(reactContext);
152150
}
153151

152+
@Override
153+
public void initialize() {
154+
BlobCollector.install(getReactApplicationContext(), this);
155+
}
156+
154157
@Override
155158
public String getName() {
156159
return NAME;
@@ -178,11 +181,15 @@ public String store(byte[] data) {
178181
}
179182

180183
public void store(byte[] data, String blobId) {
181-
mBlobs.put(blobId, data);
184+
synchronized (mBlobs) {
185+
mBlobs.put(blobId, data);
186+
}
182187
}
183188

184189
public void remove(String blobId) {
185-
mBlobs.remove(blobId);
190+
synchronized (mBlobs) {
191+
mBlobs.remove(blobId);
192+
}
186193
}
187194

188195
public @Nullable byte[] resolve(Uri uri) {
@@ -201,17 +208,19 @@ public void remove(String blobId) {
201208
}
202209

203210
public @Nullable byte[] resolve(String blobId, int offset, int size) {
204-
byte[] data = mBlobs.get(blobId);
205-
if (data == null) {
206-
return null;
207-
}
208-
if (size == -1) {
209-
size = data.length - offset;
210-
}
211-
if (offset > 0 || size != data.length) {
212-
data = Arrays.copyOfRange(data, offset, offset + size);
211+
synchronized (mBlobs) {
212+
byte[] data = mBlobs.get(blobId);
213+
if (data == null) {
214+
return null;
215+
}
216+
if (size == -1) {
217+
size = data.length - offset;
218+
}
219+
if (offset > 0 || size != data.length) {
220+
data = Arrays.copyOfRange(data, offset, offset + size);
221+
}
222+
return data;
213223
}
214-
return data;
215224
}
216225

217226
public @Nullable byte[] resolve(ReadableMap blob) {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright (c) Facebook, Inc. and its affiliates.
2+
#
3+
# This source code is licensed under the MIT license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
LOCAL_PATH := $(call my-dir)
7+
8+
include $(CLEAR_VARS)
9+
10+
LOCAL_MODULE := reactnativeblob
11+
12+
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
13+
14+
LOCAL_C_INCLUDES := $(LOCAL_PATH)
15+
16+
LOCAL_CFLAGS += -fvisibility=hidden -fexceptions -frtti
17+
18+
LOCAL_STATIC_LIBRARIES := libjsi libjsireact jscruntime
19+
LOCAL_SHARED_LIBRARIES := libfolly_json libfb libreactnativejni
20+
21+
include $(BUILD_SHARED_LIBRARY)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "FBJNI_TARGET", "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library")
2+
3+
rn_xplat_cxx_library(
4+
name = "jni",
5+
srcs = glob(["*.cpp"]),
6+
headers = glob(["*.h"]),
7+
header_namespace = "",
8+
compiler_flags = ["-fexceptions"],
9+
fbandroid_allow_jni_merging = True,
10+
platforms = ANDROID,
11+
soname = "libreactnativeblob.$(ext)",
12+
visibility = [
13+
"PUBLIC",
14+
],
15+
deps = [
16+
"fbsource//xplat/folly:molly",
17+
FBJNI_TARGET,
18+
react_native_target("jni/react/jni:jni"),
19+
react_native_xplat_target("jsi:JSCRuntime"),
20+
react_native_xplat_target("jsiexecutor:jsiexecutor"),
21+
],
22+
)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
// This source code is licensed under the MIT license found in the
3+
// LICENSE file in the root directory of this source tree.
4+
5+
#include "BlobCollector.h"
6+
7+
#include <fb/fbjni.h>
8+
#include <memory>
9+
#include <mutex>
10+
11+
using namespace facebook;
12+
13+
namespace facebook {
14+
namespace react {
15+
16+
static constexpr auto kBlobModuleJavaDescriptor =
17+
"com/facebook/react/modules/blob/BlobModule";
18+
19+
BlobCollector::BlobCollector(
20+
jni::global_ref<jobject> blobModule,
21+
const std::string &blobId)
22+
: blobModule_(blobModule), blobId_(blobId) {}
23+
24+
BlobCollector::~BlobCollector() {
25+
auto removeMethod = jni::findClassStatic(kBlobModuleJavaDescriptor)
26+
->getMethod<void(jstring)>("remove");
27+
removeMethod(blobModule_, jni::make_jstring(blobId_).get());
28+
}
29+
30+
void BlobCollector::nativeInstall(
31+
jni::alias_ref<jhybridobject> jThis,
32+
jni::alias_ref<jobject> blobModule,
33+
jlong jsContextNativePointer) {
34+
auto &runtime = *((jsi::Runtime *)jsContextNativePointer);
35+
auto blobModuleRef = jni::make_global(blobModule);
36+
runtime.global().setProperty(
37+
runtime,
38+
"__blobCollectorProvider",
39+
jsi::Function::createFromHostFunction(
40+
runtime,
41+
jsi::PropNameID::forAscii(runtime, "__blobCollectorProvider"),
42+
1,
43+
[blobModuleRef](
44+
jsi::Runtime &rt,
45+
const jsi::Value &thisVal,
46+
const jsi::Value *args,
47+
size_t count) {
48+
auto blobId = args[0].asString(rt).utf8(rt);
49+
auto blobCollector =
50+
std::make_shared<BlobCollector>(blobModuleRef, blobId);
51+
return jsi::Object::createFromHostObject(rt, blobCollector);
52+
}));
53+
}
54+
55+
void BlobCollector::registerNatives() {
56+
registerHybrid(
57+
{makeNativeMethod("nativeInstall", BlobCollector::nativeInstall)});
58+
}
59+
60+
} // namespace react
61+
} // namespace facebook
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
// This source code is licensed under the MIT license found in the
3+
// LICENSE file in the root directory of this source tree.
4+
5+
#pragma once
6+
7+
#include <fb/fbjni.h>
8+
#include <jsi/jsi.h>
9+
10+
namespace facebook {
11+
namespace react {
12+
13+
class BlobCollector : public jni::HybridClass<BlobCollector>,
14+
public jsi::HostObject {
15+
public:
16+
BlobCollector(
17+
jni::global_ref<jobject> blobManager,
18+
const std::string &blobId);
19+
~BlobCollector();
20+
21+
static constexpr auto kJavaDescriptor =
22+
"Lcom/facebook/react/modules/blob/BlobCollector;";
23+
24+
static void nativeInstall(
25+
jni::alias_ref<jhybridobject> jThis,
26+
jni::alias_ref<jobject> blobModule,
27+
jlong jsContextNativePointer);
28+
29+
static void registerNatives();
30+
31+
private:
32+
friend HybridBase;
33+
34+
jni::global_ref<jobject> blobModule_;
35+
const std::string blobId_;
36+
};
37+
38+
} // namespace react
39+
} // namespace facebook
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
3+
// This source code is licensed under the MIT license found in the
4+
// LICENSE file in the root directory of this source tree.
5+
6+
#include <fb/fbjni.h>
7+
8+
#include "BlobCollector.h"
9+
10+
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
11+
return facebook::jni::initialize(
12+
vm, [] { facebook::react::BlobCollector::registerNatives(); });
13+
}

0 commit comments

Comments
 (0)