Skip to content

Commit 13afa2c

Browse files
committed
feature: Use cache for images from network
1 parent 9813b2c commit 13afa2c

File tree

3 files changed

+111
-24
lines changed

3 files changed

+111
-24
lines changed

android/src/main/java/com/reactnativecommunity/imageeditor/ImageEditorModuleImpl.kt

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ import android.text.TextUtils
2222
import android.util.Base64
2323
import androidx.exifinterface.media.ExifInterface
2424
import com.facebook.common.logging.FLog
25+
import com.facebook.common.memory.PooledByteBuffer
26+
import com.facebook.common.memory.PooledByteBufferInputStream
27+
import com.facebook.common.references.CloseableReference
28+
import com.facebook.datasource.DataSource
29+
import com.facebook.datasource.DataSources
30+
import com.facebook.drawee.backends.pipeline.Fresco
31+
import com.facebook.imagepipeline.core.ImagePipeline
32+
import com.facebook.imagepipeline.request.ImageRequest
33+
import com.facebook.imagepipeline.request.ImageRequestBuilder
2534
import com.facebook.infer.annotation.Assertions
2635
import com.facebook.react.bridge.Arguments
2736
import com.facebook.react.bridge.JSApplicationIllegalArgumentException
@@ -31,6 +40,9 @@ import com.facebook.react.bridge.ReadableMap
3140
import com.facebook.react.bridge.ReadableType
3241
import com.facebook.react.bridge.WritableMap
3342
import com.facebook.react.common.ReactConstants
43+
import com.facebook.react.modules.fresco.ReactNetworkImageRequest
44+
import com.facebook.react.views.image.ReactCallerContextFactory
45+
import com.facebook.react.views.imagehelper.ImageSource
3446
import java.io.ByteArrayInputStream
3547
import java.io.File
3648
import java.io.FileInputStream
@@ -51,7 +63,13 @@ object MimeType {
5163
const val WEBP = "image/webp"
5264
}
5365

54-
class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
66+
class ImageEditorModuleImpl(
67+
private val reactContext: ReactApplicationContext,
68+
private val callerContext: Any?,
69+
private val callerContextFactory: ReactCallerContextFactory?,
70+
private val imagePipeline: ImagePipeline?
71+
) {
72+
5573
private val moduleCoroutineScope = CoroutineScope(Dispatchers.Default)
5674

5775
init {
@@ -65,6 +83,56 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
6583
cleanTask()
6684
}
6785

86+
private fun getCallerContext(): Any? {
87+
return callerContextFactory?.getOrCreateCallerContext("", "") ?: callerContext
88+
}
89+
90+
private fun getImagePipeline(): ImagePipeline {
91+
return imagePipeline ?: Fresco.getImagePipeline()
92+
}
93+
94+
private fun fetchAndCacheImage(
95+
uri: String,
96+
headers: ReadableMap?,
97+
): InputStream? {
98+
try {
99+
val source = ImageSource(reactContext, uri)
100+
val imageRequest: ImageRequest =
101+
if (headers != null) {
102+
val imageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(source.uri)
103+
ReactNetworkImageRequest.fromBuilderWithHeaders(imageRequestBuilder, headers)
104+
} else ImageRequestBuilder.newBuilderWithSource(source.uri).build()
105+
106+
val dataSource: DataSource<CloseableReference<PooledByteBuffer>> =
107+
getImagePipeline().fetchEncodedImage(imageRequest, getCallerContext())
108+
109+
try {
110+
val ref: CloseableReference<PooledByteBuffer>? =
111+
DataSources.waitForFinalResult(dataSource)
112+
if (ref != null) {
113+
try {
114+
val result = ref.get()
115+
return PooledByteBufferInputStream(result)
116+
} finally {
117+
CloseableReference.closeSafely(ref)
118+
}
119+
}
120+
return null
121+
} finally {
122+
dataSource.close()
123+
}
124+
} catch (e: Exception) {
125+
// Fallback to default network requests
126+
val connection = URL(uri).openConnection()
127+
headers?.toHashMap()?.forEach { (key, value) ->
128+
if (value is kotlin.String) {
129+
connection.setRequestProperty(key, value)
130+
}
131+
}
132+
return connection.getInputStream()
133+
}
134+
}
135+
68136
/**
69137
* Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
70138
* image files. This is run when the module is invalidated (i.e. app is shutting down) and when
@@ -102,7 +170,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
102170
fun cropImage(uri: String?, options: ReadableMap, promise: Promise) {
103171
val headers =
104172
if (options.hasKey("headers") && options.getType("headers") == ReadableType.Map)
105-
options.getMap("headers")?.toHashMap()
173+
options.getMap("headers")
106174
else null
107175
val format = if (options.hasKey("format")) options.getString("format") else null
108176
val offset = if (options.hasKey("offset")) options.getMap("offset") else null
@@ -148,7 +216,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
148216
// memory
149217
val hasTargetSize = targetWidth > 0 && targetHeight > 0
150218
val cropped: Bitmap? =
151-
if (hasTargetSize) {
219+
if (hasTargetSize)
152220
cropAndResizeTask(
153221
outOptions,
154222
uri,
@@ -160,9 +228,8 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
160228
targetHeight,
161229
headers
162230
)
163-
} else {
164-
cropTask(outOptions, uri, x, y, width, height, headers)
165-
}
231+
else cropTask(outOptions, uri, x, y, width, height, headers)
232+
166233
if (cropped == null) {
167234
throw IOException("Cannot decode bitmap: $uri")
168235
}
@@ -196,7 +263,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
196263
y: Int,
197264
width: Int,
198265
height: Int,
199-
headers: HashMap<String, Any?>?
266+
headers: ReadableMap?
200267
): Bitmap? {
201268
return openBitmapInputStream(uri, headers)?.use {
202269
// Efficiently crops image without loading full resolution into memory
@@ -258,7 +325,7 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
258325
rectHeight: Int,
259326
outputWidth: Int,
260327
outputHeight: Int,
261-
headers: HashMap<String, Any?>?
328+
headers: ReadableMap?
262329
): Bitmap? {
263330
Assertions.assertNotNull(outOptions)
264331

@@ -337,20 +404,14 @@ class ImageEditorModuleImpl(private val reactContext: ReactApplicationContext) {
337404
}
338405
}
339406

340-
private fun openBitmapInputStream(uri: String, headers: HashMap<String, Any?>?): InputStream? {
407+
private fun openBitmapInputStream(uri: String, headers: ReadableMap?): InputStream? {
341408
return if (uri.startsWith("data:")) {
342409
val src = uri.substring(uri.indexOf(",") + 1)
343410
ByteArrayInputStream(Base64.decode(src, Base64.DEFAULT))
344411
} else if (isLocalUri(uri)) {
345412
reactContext.contentResolver.openInputStream(Uri.parse(uri))
346413
} else {
347-
val connection = URL(uri).openConnection()
348-
headers?.forEach { (key, value) ->
349-
if (value is String) {
350-
connection.setRequestProperty(key, value)
351-
}
352-
}
353-
connection.getInputStream()
414+
fetchAndCacheImage(uri, headers)
354415
}
355416
}
356417

android/src/newarch/com/reactnativecommunity/imageeditor/ImageEditorModule.kt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
package com.reactnativecommunity.imageeditor
22

3+
import com.facebook.imagepipeline.core.ImagePipeline
34
import com.facebook.react.bridge.Promise
45
import com.facebook.react.bridge.ReactApplicationContext
56
import com.facebook.react.bridge.ReadableMap
67
import com.facebook.react.module.annotations.ReactModule
8+
import com.facebook.react.views.image.ReactCallerContextFactory
79

810
@ReactModule(name = ImageEditorModule.NAME)
9-
class ImageEditorModule(reactContext: ReactApplicationContext) :
10-
NativeRNCImageEditorSpec(reactContext) {
11+
class ImageEditorModule : NativeRNCImageEditorSpec {
1112
private val moduleImpl: ImageEditorModuleImpl
1213

13-
init {
14-
moduleImpl = ImageEditorModuleImpl(reactContext)
14+
constructor(reactContext: ReactApplicationContext) : super(reactContext) {
15+
moduleImpl = ImageEditorModuleImpl(reactContext, this, null, null)
16+
}
17+
18+
constructor(reactContext: ReactApplicationContext, callerContext: Any?) : super(reactContext) {
19+
moduleImpl = ImageEditorModuleImpl(reactContext, callerContext, null, null)
20+
}
21+
22+
constructor(
23+
reactContext: ReactApplicationContext,
24+
imagePipeline: ImagePipeline?,
25+
callerContextFactory: ReactCallerContextFactory?
26+
) : super(reactContext) {
27+
moduleImpl = ImageEditorModuleImpl(reactContext, null, callerContextFactory, imagePipeline)
1528
}
1629

1730
override fun getName(): String {

android/src/oldarch/com/reactnativecommunity/imageeditor/ImageEditorModule.kt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
package com.reactnativecommunity.imageeditor
22

3+
import com.facebook.imagepipeline.core.ImagePipeline
34
import com.facebook.react.bridge.Promise
45
import com.facebook.react.bridge.ReactApplicationContext
56
import com.facebook.react.bridge.ReactContextBaseJavaModule
67
import com.facebook.react.bridge.ReactMethod
78
import com.facebook.react.bridge.ReadableMap
89
import com.facebook.react.module.annotations.ReactModule
10+
import com.facebook.react.views.image.ReactCallerContextFactory
911

1012
@ReactModule(name = ImageEditorModule.NAME)
11-
class ImageEditorModule(reactContext: ReactApplicationContext) :
12-
ReactContextBaseJavaModule(reactContext) {
13+
class ImageEditorModule : ReactContextBaseJavaModule {
1314
private val moduleImpl: ImageEditorModuleImpl
1415

15-
init {
16-
moduleImpl = ImageEditorModuleImpl(reactContext)
16+
constructor(reactContext: ReactApplicationContext) : super(reactContext) {
17+
moduleImpl = ImageEditorModuleImpl(reactContext, this, null, null)
18+
}
19+
20+
constructor(reactContext: ReactApplicationContext, callerContext: Any?) : super(reactContext) {
21+
moduleImpl = ImageEditorModuleImpl(reactContext, callerContext, null, null)
22+
}
23+
24+
constructor(
25+
reactContext: ReactApplicationContext,
26+
imagePipeline: ImagePipeline?,
27+
callerContextFactory: ReactCallerContextFactory?
28+
) : super(reactContext) {
29+
moduleImpl = ImageEditorModuleImpl(reactContext, null, callerContextFactory, imagePipeline)
1730
}
1831

1932
override fun getName(): String {

0 commit comments

Comments
 (0)