Skip to content

Commit cd34420

Browse files
authored
fix: Fix loading TFLite models from Resources in release on Android (#30)
* fix: Fix loading TFLite models from Resources in release on Android * Update TfliteModule.java * Try parsing URI * Update TfliteModule.java * Update Podfile.lock * fix: Fix metro and scale * Update AndroidManifest.xml * Update worklets
1 parent 16d9fcb commit cd34420

File tree

8 files changed

+86
-34
lines changed

8 files changed

+86
-34
lines changed

android/src/main/cpp/Tflite.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,6 @@ struct TfliteModule : public jni::JavaClass<TfliteModule> {
4646

4747
auto byteData = method(cls, url);
4848

49-
// TODO: to review by someone experienced much more in C++
50-
// Detaching current thread causes app crash with exception:
51-
// "Unable to retrieve jni environment. is the thread attached?"
52-
// anyway, there is still a risk of memory leakage without calling the function below:
53-
54-
// java_machine->DetachCurrentThread();
55-
5649
auto size = byteData->size();
5750
auto bytes = byteData->getRegion(0, size);
5851
void* data = malloc(size);

android/src/main/java/com/tflite/TfliteModule.java

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
package com.tflite;
22

3+
import android.annotation.SuppressLint;
4+
import android.content.Context;
5+
import android.net.Uri;
36
import android.util.Log;
47

58
import androidx.annotation.NonNull;
69

710
import com.facebook.proguard.annotations.DoNotStrip;
811
import com.facebook.react.bridge.JavaScriptContextHolder;
9-
import com.facebook.react.bridge.Promise;
1012
import com.facebook.react.bridge.ReactApplicationContext;
1113
import com.facebook.react.bridge.ReactContextBaseJavaModule;
1214
import com.facebook.react.bridge.ReactMethod;
1315
import com.facebook.react.module.annotations.ReactModule;
1416
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl;
1517

18+
import java.io.ByteArrayOutputStream;
19+
import java.io.InputStream;
20+
import java.lang.ref.WeakReference;
21+
1622
import okhttp3.OkHttpClient;
1723
import okhttp3.Request;
1824
import okhttp3.Response;
@@ -21,9 +27,12 @@
2127
@ReactModule(name = TfliteModule.NAME)
2228
public class TfliteModule extends ReactContextBaseJavaModule {
2329
public static final String NAME = "Tflite";
30+
private static WeakReference<ReactApplicationContext> weakContext;
31+
private static final OkHttpClient client = new OkHttpClient();
2432

2533
public TfliteModule(ReactApplicationContext reactContext) {
2634
super(reactContext);
35+
weakContext = new WeakReference<>(reactContext);
2736
}
2837

2938
@Override
@@ -32,21 +41,64 @@ public String getName() {
3241
return NAME;
3342
}
3443

44+
@SuppressLint("DiscouragedApi")
45+
private static int getResourceId(Context context, String name) {
46+
return context.getResources().getIdentifier(
47+
name,
48+
"raw",
49+
context.getPackageName()
50+
);
51+
}
52+
53+
/** @noinspection unused*/
3554
@DoNotStrip
36-
public static byte[] fetchByteDataFromUrl(String url) {
37-
OkHttpClient client = new OkHttpClient();
55+
public static byte[] fetchByteDataFromUrl(String url) throws Exception {
56+
Log.i(NAME, "Loading byte data from URL: " + url + "...");
3857

39-
Request request = new Request.Builder().url(url).build();
58+
Uri uri = null;
59+
Integer resourceId = null;
60+
if (url.contains("://")) {
61+
Log.i(NAME, "Parsing URL...");
62+
uri = Uri.parse(url);
63+
Log.i(NAME, "Parsed URL: " + uri.toString());
64+
} else {
65+
Log.i(NAME, "Parsing resourceId...");
66+
resourceId = getResourceId(weakContext.get(), url);
67+
Log.i(NAME, "Parsed resourceId: " + resourceId);
68+
}
4069

41-
try (Response response = client.newCall(request).execute()) {
42-
if (response.isSuccessful() && response.body() != null) {
43-
return response.body().bytes();
44-
} else {
45-
throw new RuntimeException("Response was not successful!");
70+
if (uri != null) {
71+
// It's a URL/http resource
72+
Request request = new Request.Builder().url(uri.toString()).build();
73+
try (Response response = client.newCall(request).execute()) {
74+
if (response.isSuccessful() && response.body() != null) {
75+
return response.body().bytes();
76+
} else {
77+
throw new RuntimeException("Response was not successful!");
78+
}
79+
} catch (Exception ex) {
80+
Log.e(NAME, "Failed to fetch URL " + url + "!", ex);
81+
throw ex;
82+
}
83+
} else if (resourceId != null) {
84+
// It's bundled into the Android resources/assets
85+
Context context = weakContext.get();
86+
if (context == null) {
87+
throw new Exception("React Context has already been destroyed!");
88+
}
89+
try (InputStream stream = context.getResources().openRawResource(resourceId)) {
90+
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
91+
byte[] buffer = new byte[2048];
92+
int length;
93+
while ((length = stream.read(buffer)) != -1) {
94+
byteStream.write(buffer, 0, length);
95+
}
96+
return byteStream.toByteArray();
4697
}
47-
} catch (Exception e) {
48-
Log.e(NAME, "Failed to fetch URL " + url + "!", e);
49-
return null;
98+
} else {
99+
// It's a bird? it's a plane? not it's an error
100+
throw new Exception("Input is neither a valid URL, nor a resourceId - " +
101+
"cannot load TFLite model! (Input: " + url + ")");
50102
}
51103
}
52104

example/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
22

33
<uses-permission android:name="android.permission.INTERNET" />
4+
<uses-permission android:name="android.permission.CAMERA" />
45

56
<application
67
android:name=".MainApplication"

example/ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ PODS:
890890
- glog
891891
- RCT-Folly (= 2022.05.16.00)
892892
- React-Core
893-
- react-native-worklets-core (0.2.4):
893+
- react-native-worklets-core (0.3.0):
894894
- React
895895
- React-callinvoker
896896
- React-Core
@@ -1275,7 +1275,7 @@ SPEC CHECKSUMS:
12751275
React-logger: 66b168e2b2bee57bd8ce9e69f739d805732a5570
12761276
React-Mapbuffer: 9ee041e1d7be96da6d76a251f92e72b711c651d6
12771277
react-native-fast-tflite: 369d7ded6d82a878fb481ab772c26804a2a797f0
1278-
react-native-worklets-core: b4094f51cb2bc649e297206425cb8956f4945e3e
1278+
react-native-worklets-core: a894d572639fcf37c6d284cc799882d25d00c93d
12791279
React-nativeconfig: d753fbbc8cecc8ae413d615599ac378bbf6999bb
12801280
React-NativeModulesApple: 964f4eeab1b4325e8b6a799cf4444c3fd4eb0a9c
12811281
React-perflogger: 29efe63b7ef5fbaaa50ef6eaa92482f98a24b97e
@@ -1299,7 +1299,7 @@ SPEC CHECKSUMS:
12991299
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
13001300
vision-camera-resize-plugin: 536345c29f42e04438d34d0cada7b4992a8fc104
13011301
VisionCamera: edbcd00e27a438b2228f67823e2b8d15a189065f
1302-
Yoga: e64aa65de36c0832d04e8c7bd614396c77a80047
1302+
Yoga: 13c8ef87792450193e117976337b8527b49e8c03
13031303

13041304
PODFILE CHECKSUM: 33c2f84b68c18fe6787cbe3e723675d15fcc7e66
13051305

example/metro.config.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1+
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
12
const path = require('path');
23
const escape = require('escape-string-regexp');
34
const exclusionList = require('metro-config/src/defaults/exclusionList');
45
const pak = require('../package.json');
56

67
const root = path.resolve(__dirname, '..');
8+
const modules = Object.keys({ ...pak.peerDependencies });
79

8-
const modules = Object.keys({
9-
...pak.peerDependencies,
10-
});
11-
12-
module.exports = {
13-
projectRoot: __dirname,
10+
/**
11+
* Metro configuration
12+
* https://facebook.github.io/metro/docs/configuration
13+
*
14+
* @type {import('metro-config').MetroConfig}
15+
*/
16+
const config = {
1417
watchFolders: [root],
1518

1619
// We need to make sure that only one version is loaded for peerDependencies
@@ -39,3 +42,5 @@ module.exports = {
3942
}),
4043
},
4144
};
45+
46+
module.exports = mergeConfig(getDefaultConfig(__dirname), config);

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"react": "18.2.0",
1414
"react-native": "0.73.2",
1515
"react-native-vision-camera": "^3.8.2",
16-
"react-native-worklets-core": "^0.2.4",
16+
"react-native-worklets-core": "^0.3.0",
1717
"vision-camera-resize-plugin": "^2.0.1"
1818
},
1919
"devDependencies": {

example/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export default function App(): React.ReactNode {
5151

5252
console.log(`Running inference on ${frame}`)
5353
const resized = resize(frame, {
54-
size: {
54+
scale: {
5555
width: 320,
5656
height: 320,
5757
},
@@ -79,6 +79,7 @@ export default function App(): React.ReactNode {
7979
style={StyleSheet.absoluteFill}
8080
isActive={true}
8181
frameProcessor={frameProcessor}
82+
pixelFormat="yuv"
8283
/>
8384
) : (
8485
<Text>No Camera available.</Text>

example/yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5779,10 +5779,10 @@ react-native-vision-camera@^3.8.2:
57795779
resolved "https://registry.yarnpkg.com/react-native-vision-camera/-/react-native-vision-camera-3.8.2.tgz#f4f75f84c6a19e1c3474ddc0f7f785b5a526739b"
57805780
integrity sha512-MY39l2e3hNRPUefn2JPShOFExcw0PblbAcUGvJrIfS9pMzdIyceo0umRAx8lOGXzDUAdb+xy/tFWb8zGxKimCQ==
57815781

5782-
react-native-worklets-core@^0.2.4:
5783-
version "0.2.4"
5784-
resolved "https://registry.yarnpkg.com/react-native-worklets-core/-/react-native-worklets-core-0.2.4.tgz#a4a79c04f2c6769ff03e96b7529c8638c88fa560"
5785-
integrity sha512-NKqxLRDOYfO7RJRZHHDA/1yztdiSadSTTILgHw2VwSrcQcmnWAZGXWr9EYpzKblpbk/vXfJa6QpWIKHjNrbe4w==
5782+
react-native-worklets-core@^0.3.0:
5783+
version "0.3.0"
5784+
resolved "https://registry.yarnpkg.com/react-native-worklets-core/-/react-native-worklets-core-0.3.0.tgz#4d6c4c4999d3bf2f8c67fa46fdd70d8b5bc7ba0e"
5785+
integrity sha512-bOoo6xbl+f+mz0Q65bLkUP3FxgYt7f/Bzi0c5Yj8Ix4vQCKMtgHwafrqCNRhW32N7TpTv3+zOoyQMJwQVr/frQ==
57865786
dependencies:
57875787
string-hash-64 "^1.0.3"
57885788

0 commit comments

Comments
 (0)