ANR in expo-updates in android on start up with large number of assets
Summary
Receiving a large number of ANRs that look like:
com.pokebattler.raidParty.MainApplication.onCreate
Broadcast of Intent { act=android.intent.action.MY_PACKAGE_REPLACED cmp=com.pokebattler.raidParty/expo.modules.notifications.service.NotificationsService }
Native method - android.os.MessageQueue.nativePollOnce
Broadcast of Intent { act=android.intent.action.MY_PACKAGE_REPLACED cmp=com.pokebattler.raidParty/expo.modules.notifications.service.NotificationsService }
com.pokebattler.raidParty.MainApplication.onCreate
Broadcast of Intent { act=android.intent.action.BOOT_COMPLETED cmp=com.pokebattler.raidParty/expo.modules.notifications.service.NotificationsService }
[libart.so] art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, int, BacktraceMap*, char const*, art::ArtMethod*, void*, bool)
Broadcast of Intent { act=android.intent.action.MY_PACKAGE_REPLACED cmp=com.pokebattler.raidParty/expo.modules.notifications.service.NotificationsService }
This has my app sitting dangerously close to the 0.47% ANR bad behavior cutoff. It spikes to almost 3% when a new release is pushed to the play store.
What platform(s) does this occur on?
Android
Environment
expo-env-info 1.0.5 environment info:
System:
OS: macOS 12.5.1
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 17.4.0 - /usr/local/bin/node
npm: 8.3.1 - /usr/local/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.11.2 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: DriverKit 22.1, iOS 16.1, macOS 13.0, tvOS 16.1, watchOS 9.1
Android SDK:
API Levels: 28, 29, 30, 31, 32
Build Tools: 28.0.3, 29.0.2, 30.0.3, 31.0.0, 32.0.0
IDEs:
Android Studio: 2020.3 AI-203.7717.56.2031.7935034
Xcode: 14.1/14B47b - /usr/bin/xcodebuild
npmPackages:
expo: ^46.0.8 => 46.0.10
react: 18.0.0 => 18.0.0
react-dom: 18.0.0 => 18.0.0
react-native: 0.69.5 => 0.69.5
react-native-web: ~0.18.7 => 0.18.9
npmGlobalPackages:
expo-cli: 6.0.2
Expo Workflow: bare
Minimal reproducible example
This is a bare app that is a fairly heavy user of push notifications
Hi there! It looks like your issue requires a minimal reproducible example, but it is invalid or absent. Please prepare such an example and share it in a new issue.
The best way to get attention to your issue is to provide a clean and easy way for a developer to reproduce the issue on their own machine. Please do not provide your entire project, or a project with more code than is necessary to reproduce the issue.
A side benefit of going through the process of narrowing down the minimal amount of code needed to reproduce the issue is that you may get lucky and discover that the bug is due to a mistake in your application code that you can quickly fix on your own.
Resources
Common concerns
"I've only been able to reproduce it in private, proprietary code"
You may not have spent enough time narrowing down the root cause of the issue. Try out the techniques discussed in this manual debugging guide to learn how to isolate the problem from the rest of your codebase.
"I didn't have time to create one"
That's understandable, it can take some time to prepare. We ask that you hold off on filing an issue until you are able to fully complete the required fields in the issue template.
"You can reproduce it by yourself by creating a project and following these steps"
This is useful knowledge, but it's still valuable to have the resulting project that is produced from running the steps, where you have verified you can reproduce the issue.
hi there! it's hard for us to help with the issue which we cannot reproduce. could you please create a minimal reproducible project and upload it to some git repositories? i'm happy to reopen and help then.
btw, google play console should have an ANR report to show the stacktrace which causing the ANR. you could try to dig and share more information from it.
This is not going to be possible. You are asking me to create an app, get it in the Play Store, get thousands of people using it and to then push an update that triggers when they have the app open.
I do not know how to reproduce it only guesses based on it occurring when a new update is published to the store.
I can get you the ANR report as requested but whatever you are doing on that event is too slow. Is there some way for me to disable that behavior completely?
On Tue, Nov 15, 2022 at 5:00 AM Kudo Chien @.***> wrote:
hi there! it's hard for us to help with the issue which we cannot reproduce. could you please create a minimal reproducible project and upload it to some git repositories? i'm happy to reopen and help then.
btw, google play console should have an ANR report https://support.google.com/googleplay/android-developer/answer/9859174 to show the stacktrace which causing the ANR. you could try to dig and share more information from it.
— Reply to this email directly, view it on GitHub https://github.com/expo/expo/issues/19918#issuecomment-1315276886, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAC7RABKHLHUKTT26YXWUVLWIOCOLANCNFSM6AAAAAARZ45CLQ . You are receiving this because you authored the thread.Message ID: @.***>
i could understand it's hard to create an example or investigate issues that you cannot reproduce, and so do we.
please share the ANR report details with us. the ANR report on google play should indicate the stacktrace of each thread. that's something we could further take a look. let's see if you we find something in the reports together.
Unfortunately there is no ANR report download anymore. The original ANR messages are in the original bug report.
My guess is it is actually not expo notifications but expo updates
"main" tid=1 Native
#00 pc 0x000000000004b38c /apex/com.android.runtime/lib64/bionic/libc.so (syscall+28)
#01 pc 0x00000000001af92c /apex/com.android.art/lib64/libart.so (art::ConditionVariable::WaitHoldingLocks(art::Thread*)+148)
#02 pc 0x000000000066b4b4 /apex/com.android.art/lib64/libart.so (art::GoToRunnable(art::Thread*)+460)
#03 pc 0x000000000066b2a4 /apex/com.android.art/lib64/libart.so (art::JniMethodEnd(unsigned int, art::Thread*)+28)
at android.database.sqlite.SQLiteConnection.nativeBindLong (Native method)
at android.database.sqlite.SQLiteConnection.bindArguments (SQLiteConnection.java:1154)
at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId (SQLiteConnection.java:934)
at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId (SQLiteSession.java:790)
at android.database.sqlite.SQLiteStatement.executeInsert (SQLiteStatement.java:88)
at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert (FrameworkSQLiteStatement.java:51)
at androidx.room.EntityInsertionAdapter.insert (EntityInsertionAdapter.java:64)
at expo.modules.updates.db.dao.AssetDao_Impl._insertUpdateAsset (AssetDao_Impl.java:269)
at expo.modules.updates.db.dao.AssetDao.addExistingAssetToUpdate (AssetDao.kt:128)
at expo.modules.updates.db.dao.AssetDao_Impl.access$101 (AssetDao_Impl.java:33)
at expo.modules.updates.db.dao.AssetDao_Impl.addExistingAssetToUpdate (AssetDao_Impl.java:304)
at expo.modules.updates.loader.Loader.handleAssetDownloadCompleted (Loader.kt:269)
at expo.modules.updates.loader.Loader.downloadAllAssets (Loader.kt:222)
at expo.modules.updates.loader.Loader.processUpdateManifest (Loader.kt:189)
at expo.modules.updates.loader.Loader.access$processUpdateManifest (Loader.kt:18)
at expo.modules.updates.loader.Loader$start$1.onSuccess (Loader.kt:101)
at expo.modules.updates.loader.EmbeddedLoader.loadManifest (EmbeddedLoader.kt:58)
at expo.modules.updates.loader.Loader.start (Loader.kt:91)
at expo.modules.updates.loader.LoaderTask.launchFallbackUpdateFromDisk (LoaderTask.kt:242)
at expo.modules.updates.loader.LoaderTask.start (LoaderTask.kt:99)
at expo.modules.updates.UpdatesController.start (UpdatesController.kt:290)
at expo.modules.updates.UpdatesController$Companion.initialize (UpdatesController.kt:464)
at expo.modules.updates.UpdatesPackage$createReactNativeHostHandlers$handler$1.onWillCreateReactInstanceManager (UpdatesPackage.kt:41)
at expo.modules.ReactNativeHostWrapperBase.createReactInstanceManager (ReactNativeHostWrapperBase.kt:27)
at com.facebook.react.ReactNativeHost.getReactInstanceManager (ReactNativeHost.java:42)
at com.pokebattler.raidParty.MainApplication.onCreate (MainApplication.java:76)
at android.app.Instrumentation.callApplicationOnCreate (Instrumentation.java:1192)
at android.app.ActivityThread.handleBindApplication (ActivityThread.java:6808)
at android.app.ActivityThread.access$1400 (ActivityThread.java:250)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1973)
at android.os.Handler.dispatchMessage (Handler.java:106)
at android.os.Looper.loop (Looper.java:250)
at android.app.ActivityThread.main (ActivityThread.java:7766)
at java.lang.reflect.Method.invoke (Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:604)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:958)
The most likely culprit looking into this is my very large number of icons. My assets folder has 9,844 files in it, the vast majority being pngs with 2x and 3x versions. (64x64 128x128 192x192)
@Kudo Ive looked through the code base in details and have a summary of what the issues are. If there is someone who works on expo-updates who I could speak with on Discord or via email that would be great,
The core issue is the startup time for expo updates has a large number of synchronous calls that must complete before the app is loaded. If these calls take too long, this causes an ANR. I see most of the ANRs are on low end devices and also in background processing which makes sense.
There exists a checkOnLaunch option, it does not avoid enough of the synchronous calls to avoid the ANRs.
The first ANR is the above stack trace with the EmbeddedLoader.loadManifest when there is an update to be downloaded. Part of this is local and part of this can be skipped using checkOnLaunch flag.
The second ANR is the alternative branch that uses DatabaseLoader.initialize lines 86-96 which is used when initializing from the local disk This is a loop of file system calls on application start that causes an ANR with the number of assets I have.
Looking at the diff from sdk-46 to sdk-47 there is an additional continue that skips the expensive check
patch-package expo-updates+0.14.5.patch:
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt
index 9c62461..91756e1 100644
--- a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt
@@ -85,6 +85,11 @@ class DatabaseLauncher(
localAssetFiles = mutableMapOf<AssetEntity, String>().apply {
for (asset in assetEntities) {
+ //patch from sdk-47
+ if (asset.id == launchAsset.id) {
+ // we took care of this one above
+ continue
+ }
val filename = asset.relativePath
if (filename != null) {
val assetFile = ensureAssetExists(asset, database, context)
So my action item for now is
- patch in the continue block
- add in
checkOnLaunch = NEVER - use
Updates.checkForUpdateAsyncandUpdates.fetchUpdateAsyncto do updates.
Improving expo-updates to improve performance when using checkOnLaunch=NEVER or maybe even making this check always async to improve performance for all users is something I might be willing to contribute if there is interest. I realize my 10,000 pngs case is not the norm but its a good test case
hi @celandro thanks for further investigating the diff. i can confirm the problem exist and i would like to reopen this issue and follow up with team.
Thank you for filing this issue! This comment acknowledges we believe this may be a bug and there’s enough information to investigate it. However, we can’t promise any sort of timeline for resolution. We prioritize issues based on severity, breadth of impact, and alignment with our roadmap. If you’d like to help move it more quickly, you can continue to investigate it more deeply and/or you can open a pull request that fixes the cause.
Tracked in ENG-7652
I'm also having a lot of ANR because of expo-updates, specifically this line:
get() {
while (!isStartupFinished) {
try {
(this as java.lang.Object).wait() <--- here
} catch (e: InterruptedException) {
Log.e(TAG, "Interrupted while waiting for launch asset file", e)
}
}
return startupProcedure.launchAssetFile
}
https://github.com/expo/expo/blob/main/packages/expo-updates/android/src/main/java/expo/modules/updates/EnabledUpdatesController.kt#L114
Does anyone know what can I do to improve this? I'm using the most recent versions:
expo: 51.0.28
expo-updates: 0.25.22
Stacktrace:
main (waiting):tid=1 systid=30986
at java.lang.Object.wait(Native method)
at java.lang.Object.wait(Object.java:386)
at java.lang.Object.wait(Object.java:524)
at expo.modules.updates.EnabledUpdatesController.getLaunchAssetFile(EnabledUpdatesController.kt:114)
at expo.modules.updates.UpdatesPackage$createReactNativeHostHandlers$handler$1.getJSBundleFile(UpdatesPackage.kt:31)
at expo.modules.ReactNativeHostWrapperBase$getJSBundleFile$1.invoke(ReactNativeHostWrapperBase.kt:60)
at expo.modules.ReactNativeHostWrapperBase$getJSBundleFile$1.invoke(ReactNativeHostWrapperBase.kt:60)
at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:210)
at kotlin.sequences.FilteringSequence$iterator$1.calcNext(Sequences.kt:170)
at kotlin.sequences.FilteringSequence$iterator$1.hasNext(Sequences.kt:194)
at kotlin.sequences.SequencesKt___SequencesKt.firstOrNull(_Sequences.kt:168)
at expo.modules.ReactNativeHostWrapperBase.getJSBundleFile(ReactNativeHostWrapperBase.kt:61)
at com.facebook.react.ReactNativeHost.createReactInstanceManager(ReactNativeHost.java:98)
at expo.modules.ReactNativeHostWrapperBase.createReactInstanceManager(ReactNativeHostWrapperBase.kt:29)
at com.facebook.react.ReactNativeHost.getReactInstanceManager(ReactNativeHost.java:48)
at com.transistorsoft.rnbackgroundfetch.HeadlessTask.startTask(HeadlessTask.java:70)
at com.transistorsoft.rnbackgroundfetch.HeadlessTask.<init>(HeadlessTask.java:43)
at java.lang.reflect.Constructor.newInstance0(Native method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at com.transistorsoft.tsbackgroundfetch.BGTask.fireHeadlessEvent(BGTask.java:236)
at com.transistorsoft.tsbackgroundfetch.BackgroundFetch.doFetch(BackgroundFetch.java:271)
at com.transistorsoft.tsbackgroundfetch.BackgroundFetch.onFetch(BackgroundFetch.java:233)
at com.transistorsoft.tsbackgroundfetch.FetchJobService.onStartJob(FetchJobService.java:48)
at android.app.job.JobService$1.onStartJob(JobService.java:102)
at android.app.job.JobServiceEngine$JobHandler.handleMessage(JobServiceEngine.java:168)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:230)
at android.os.Looper.loop(Looper.java:319)
at android.app.ActivityThread.main(ActivityThread.java:8919)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:578)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
Hi @Kudo, I was looking your PR (https://github.com/expo/expo/pull/20273) and looks good, but I'm continue seen the ANRs.
I'm using expo-updates: 0.25.22 which includes your code, I'm using the default value of EX_UPDATES_ANDROID_DELAY_LOAD_APP, and I've debugged the code to ensure that the default value (true) is being used and the execution enters to ReactActivityHandler.DelayLoadAppHandler.
So your code is being used, but I’m still experiencing the ANRs.
Do you know what could be happening? In my previous comment you have the stacktrace
Here’s the chart after launching an update to the Play Store:
@RodolfoGS - it looks like this may be related to an interaction between com.transistorsoft.rnbackgroundfetch.HeadlessTask.startTask and expo-updates. do all of your traces include this library?
@brentvatne Yes, all traces includes com.transistorsoft.rnbackgroundfetch (react-native-background-fetch). Also, I notice that all happened in background and Android 14
@RodolfoGS Did you manage to find a workaround for this?
@RodolfoGS any clues on this issue? I had a similar problem right now:
"expo": "~52.0.42",
"expo-updates": "~0.27.4"
"expo-background-fetch": "~13.0.6",
at java.lang.Object.wait
at java.lang.Object.wait(Object.java:386)
at java.lang.Object.wait(Object.java:524)
at expo.modules.updates.EnabledUpdatesController.getLaunchAssetFile(EnabledUpdatesController.kt:108)
at expo.modules.updates.UpdatesPackage$createReactNativeHostHandlers$handler$1.getJSBundleFile(UpdatesPackage.kt:30)
at expo.modules.ReactNativeHostWrapperBase$getJSBundleFile$1.invoke(ReactNativeHostWrapperBase.kt:60)
at expo.modules.ReactNativeHostWrapperBase$getJSBundleFile$1.invoke(ReactNativeHostWrapperBase.kt:60)
at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:210)
at kotlin.sequences.FilteringSequence$iterator$1.calcNext(Sequences.kt:170)
at kotlin.sequences.FilteringSequence$iterator$1.hasNext(Sequences.kt:194)
at kotlin.sequences.SequencesKt___SequencesKt.firstOrNull(_Sequences.kt:172)
at expo.modules.ReactNativeHostWrapperBase.getJSBundleFile(ReactNativeHostWrapperBase.kt:61)
at com.facebook.react.ReactNativeHost.getBaseReactInstanceManagerBuilder(ReactNativeHost.java:116)
at com.facebook.react.ReactNativeHost.createReactInstanceManager(ReactNativeHost.java:85)
at expo.modules.ReactNativeHostWrapperBase.createReactInstanceManager(ReactNativeHostWrapperBase.kt:29)
at com.facebook.react.ReactNativeHost.getReactInstanceManager(ReactNativeHost.java:57)
at expo.modules.adapters.react.apploader.RNHeadlessAppLoader.loadApp(RNHeadlessAppLoader.kt:48)
at expo.modules.taskManager.TaskService.executeTask(TaskService.java:412)
at expo.modules.taskManager.Task.execute(Task.java:58)
at expo.modules.taskManager.TaskManagerUtils.executeTask(TaskManagerUtils.java:75)
at expo.modules.backgroundfetch.BackgroundFetchTaskConsumer.didReceiveBroadcast(BackgroundFetchTaskConsumer.java:83)
at expo.modules.taskManager.TaskService.handleIntent(TaskService.java:315)
at expo.modules.taskManager.TaskBroadcastReceiver.onReceive(TaskBroadcastReceiver.java:15)
at android.app.ActivityThread.handleReceiver(ActivityThread.java:4981)
at android.app.ActivityThread.-$$Nest$mhandleReceiver(unavailable:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2506)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:230)
at android.os.Looper.loop(Looper.java:319)
at android.app.ActivityThread.main(ActivityThread.java:9063)
at java.lang.reflect.Method.invoke
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:588)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
It started in the expo.modules.backgroundfetch, and the update has a lot of files. Android 14 as well.
Can confirm we experience same issue
here is our ANR stacktrace
at java.lang.Object.wait (Native method)
at java.lang.Object.wait (Object.java:405)
at java.lang.Object.wait (Object.java:543)
at expo.modules.updates.EnabledUpdatesController.getLaunchAssetFile (EnabledUpdatesController.kt:108)
at expo.modules.updates.UpdatesPackage$createReactNativeHostHandlers$handler$1.getJSBundleFile (UpdatesPackage.kt:31)
at expo.modules.ReactNativeHostWrapperBase.getJSBundleFile$lambda$4 (ReactNativeHostWrapperBase.kt:60)
at expo.modules.ReactNativeHostWrapperBase.$r8$lambda$7YBxcqii4aYEAFrDQILcDvJjuFY (unavailable)
at expo.modules.ReactNativeHostWrapperBase$$ExternalSyntheticLambda1.invoke (D8$$SyntheticClass)
at kotlin.sequences.TransformingSequence$iterator$1.next (Sequences.kt:210)
at kotlin.sequences.FilteringSequence$iterator$1.calcNext (Sequences.kt:170)
at kotlin.sequences.FilteringSequence$iterator$1.hasNext (Sequences.kt:194)
at kotlin.sequences.SequencesKt___SequencesKt.firstOrNull (_Sequences.kt:172)
at expo.modules.ReactNativeHostWrapperBase.getJSBundleFile (ReactNativeHostWrapperBase.kt:61)
at com.facebook.react.ReactNativeHost.getBaseReactInstanceManagerBuilder (ReactNativeHost.java:116)
at com.facebook.react.ReactNativeHost.createReactInstanceManager (ReactNativeHost.java:85)
at expo.modules.ReactNativeHostWrapperBase.createReactInstanceManager (ReactNativeHostWrapperBase.kt:29)
at com.facebook.react.ReactNativeHost.getReactInstanceManager (ReactNativeHost.java:57)
at com.facebook.react.HeadlessJsTaskService.getReactContext (HeadlessJsTaskService.java:176)
at com.facebook.react.HeadlessJsTaskService.startTask (HeadlessJsTaskService.java:100)
at com.facebook.react.HeadlessJsTaskService.onStartCommand (HeadlessJsTaskService.java:51)
at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:5243)
at android.app.ActivityThread.-$$Nest$mhandleServiceArgs (unavailable)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2447)
at android.os.Handler.dispatchMessage (Handler.java:106)
at android.os.Looper.loopOnce (Looper.java:226)
at android.os.Looper.loop (Looper.java:313)
at android.app.ActivityThread.main (ActivityThread.java:8757)
at java.lang.reflect.Method.invoke (Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)
Our dependencies version (we are on a bare app)
"react-native": "0.77.1",
"expo": "^52.0.0",
"expo-updates": "~0.27.3",
We have ~650 assets, mostly (@2x & @3x)
It seems that issue is mainly on low end devices. On our app I reproduce very often with a Samsung A51 (SM-A515F)
@RodolfoGS do you find a workaround ? @Kudo dou you have some hint where we can look for the culprit and find a fix? I'd love to investigate and provide more info but not really familiar with this repo
I've tried https://docs.expo.dev/eas-update/asset-selection/ (by removing all our asset from updates) but it impact only updates. And our issue is on app startup without any updates
I haven't found any solution to this, I'm still experiencing these ANRs. I'm not sure it's exclusive to low end devices, I'm seeing the issue on Samsung Galaxy S23 and S24 as well.
Are your issues also exclusive to Android 14 and occurring only in the background?
My latest stats:
Thanks for the response @RodolfoGS
For us it's not specific to Android 14 & on app start, foreground.
Maybe it's related to our config
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="NEVER"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
We manually check update on each start (we never update in background)
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
// ...
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
} else {
// ...
}
Issue is relatively new for us, here is our stat
--
We recently update from
"react-native": "0.74.5",
"expo": "^51.0.0",
"expo-updates": "~0.25.27",
to
"react-native": "0.77.1",
"expo": "^52.0.0",
"expo-updates": "~0.27.3",
before we almost never have this issue
I haven't updated yet, so I don't know if the newer versions have issues on foreground too. These are the versions that I'm using:
"react-native": "0.74.5",
"expo": "~51.0.28",
"expo-updates": "^0.25.22",
Some update on my side
Issue seem to be a lock issue between expo-update and another of our lib. Running adb logcat during the ANR result in this log
04-24 17:45:02.252 5631 5631 W JobServiceContext: No response from client for onStartJob 69d87e1 #u0a614/3 XX.XXX.XXX/androidx.work.impl.background.systemjob.SystemJobService
04-24 17:45:06.399 5631 31627 E ActivityManager: ANR in XX.XXX.XX
04-24 17:45:06.399 5631 31627 E ActivityManager: Reason: executing service XX.XXX.XX/com.urbanairship.reactnative.AirshipHeadlessEventService
04-24 17:45:06.399 5631 31627 E ActivityManager: 2.9% 31416/XX.XXX.XX: 1.2% user + 1.6% kernel / faults: 2833 minor 210 major
And in the PlayStore report, this Airship is also reported as waiting in another thread
at java.lang.Object.wait (Native method)
at java.lang.Object.wait (Object.java:405)
at java.lang.Object.wait (Object.java:543)
at com.urbanairship.PendingResult.get (PendingResult.java:133)
at com.urbanairship.channel.AirshipChannel.extendPayload (AirshipChannel.kt:557)
at com.urbanairship.channel.AirshipChannel._init_$lambda$4 (AirshipChannel.kt:153)
at com.urbanairship.channel.AirshipChannel.$r8$lambda$213WeUaDlFMMZ5nFqD9WPONBdxk (unavailable)
at com.urbanairship.channel.AirshipChannel$$ExternalSyntheticLambda1.extend (D8$$SyntheticClass)
at com.urbanairship.channel.ChannelRegistrar.buildCraPayload (ChannelRegistrar.kt:104)
at com.urbanairship.channel.ChannelRegistrar.isUpToDate (ChannelRegistrar.kt:160)
at com.urbanairship.channel.ChannelRegistrar.onNewChannelIdCreated (ChannelRegistrar.kt:243)
at com.urbanairship.channel.ChannelRegistrar.onNewChannelIdCreated$default (ChannelRegistrar.kt:225)
at com.urbanairship.channel.ChannelRegistrar.regularCreateChannel (ChannelRegistrar.kt:222)
at com.urbanairship.channel.ChannelRegistrar.access$regularCreateChannel (ChannelRegistrar.kt:60)
at com.urbanairship.channel.ChannelRegistrar$regularCreateChannel$1.invokeSuspend (unavailable:15)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:101)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent (EventLoop.common.kt:263)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking (Builders.kt:95)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking (Builders.kt:69)
at kotlinx.coroutines.BuildersKt.runBlocking (unavailable:1)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default (Builders.kt:47)
at kotlinx.coroutines.BuildersKt.runBlocking$default (unavailable:1)
at com.urbanairship.channel.AirshipChannel.onPerformJob (AirshipChannel.kt:248)
at com.urbanairship.job.JobRunner$DefaultRunner.lambda$run$0 (JobRunner.java:41)
at com.urbanairship.job.JobRunner$DefaultRunner$$ExternalSyntheticLambda1.run (D8$$SyntheticClass)
at com.urbanairship.util.SerialExecutor$1.run (SerialExecutor.java:41)
at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:644)
at com.urbanairship.util.AirshipThreadFactory$1.run (AirshipThreadFactory.java:50)
at java.lang.Thread.run (Thread.java:1012)
I suspect some lock issues between these two libs. If I remove airship, ANR seem gone (I reproduct issue locally in release mode).
Not sure for now how to fix issue without removing lib, but keep updated if I find a solution
At the same time, if some expo/android folks can better explain what really happen and how we can solve this lock issue I'll take it
hi there! i would highly recommend people to try out sdk 53. from #36059, we've reduced the step to copy assets from startup and that would reduce the chance of ANR issues.
thanks for the info @Kudo
On our side we have delayed the run of Aiship module to avoid any lock between the two and our ANR are gone. Will test sdk 53 when possible and update this issue with our result
@RodolfoGS any clues on this issue? I had a similar problem right now:
"expo": "~52.0.42", "expo-updates": "~0.27.4" "expo-background-fetch": "~13.0.6",at java.lang.Object.wait at java.lang.Object.wait(Object.java:386) at java.lang.Object.wait(Object.java:524) at expo.modules.updates.EnabledUpdatesController.getLaunchAssetFile(EnabledUpdatesController.kt:108) at expo.modules.updates.UpdatesPackage$createReactNativeHostHandlers$handler$1.getJSBundleFile(UpdatesPackage.kt:30) at expo.modules.ReactNativeHostWrapperBase$getJSBundleFile$1.invoke(ReactNativeHostWrapperBase.kt:60) at expo.modules.ReactNativeHostWrapperBase$getJSBundleFile$1.invoke(ReactNativeHostWrapperBase.kt:60) at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:210) at kotlin.sequences.FilteringSequence$iterator$1.calcNext(Sequences.kt:170) at kotlin.sequences.FilteringSequence$iterator$1.hasNext(Sequences.kt:194) at kotlin.sequences.SequencesKt___SequencesKt.firstOrNull(_Sequences.kt:172) at expo.modules.ReactNativeHostWrapperBase.getJSBundleFile(ReactNativeHostWrapperBase.kt:61) at com.facebook.react.ReactNativeHost.getBaseReactInstanceManagerBuilder(ReactNativeHost.java:116) at com.facebook.react.ReactNativeHost.createReactInstanceManager(ReactNativeHost.java:85) at expo.modules.ReactNativeHostWrapperBase.createReactInstanceManager(ReactNativeHostWrapperBase.kt:29) at com.facebook.react.ReactNativeHost.getReactInstanceManager(ReactNativeHost.java:57) at expo.modules.adapters.react.apploader.RNHeadlessAppLoader.loadApp(RNHeadlessAppLoader.kt:48) at expo.modules.taskManager.TaskService.executeTask(TaskService.java:412) at expo.modules.taskManager.Task.execute(Task.java:58) at expo.modules.taskManager.TaskManagerUtils.executeTask(TaskManagerUtils.java:75) at expo.modules.backgroundfetch.BackgroundFetchTaskConsumer.didReceiveBroadcast(BackgroundFetchTaskConsumer.java:83) at expo.modules.taskManager.TaskService.handleIntent(TaskService.java:315) at expo.modules.taskManager.TaskBroadcastReceiver.onReceive(TaskBroadcastReceiver.java:15) at android.app.ActivityThread.handleReceiver(ActivityThread.java:4981) at android.app.ActivityThread.-$$Nest$mhandleReceiver(unavailable:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2506) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:230) at android.os.Looper.loop(Looper.java:319) at android.app.ActivityThread.main(ActivityThread.java:9063) at java.lang.reflect.Method.invoke at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:588) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)It started in the
expo.modules.backgroundfetch, and the update has a lot of files. Android 14 as well.
@lmonteiro90 did you find a solution? We experience the same issue
@Daavidaviid, not really, but I removed the check for updates in the background, and it seems it helped. I managed to make the updates smaller as well, and in the end, I didn't experience this issue anymore.
Btw I don't know if any of you already tried Expo 53: [https://expo.dev/changelog/sdk-53]
There is a new module that can trigger the updates, called expo-background-task, and there is an important thing in the changelog:
expo-updates for Android no longer copies embedded assets on launch. Prior to SDK 53, expo-updates would copy assets to its own cache directory to ensure consistency in asset paths and sidestep issues that could arise from differences in accessing files from app resources and storage. We have learned that this has some undesirable side effects, such as introducing the possibility of ANRs on launch in some circumstances.
Maybe this could be it, I'll try this version later this week, if I have any news I'll come back here.
@Kudo do you think disabling expo-updates with updates.enabled: false in app.json disable the copying behavior? I will test and report back, but will take a while to get definite results.
I won't be able to upgrade to SDK53 right away, and the ANRs are skyrocketing. Maybe the fix could be backported to SDK52?
@rikur - we aren't going to backport to sdk 52 because it is a very significant change. you can disable expo-updates by removing the package from your app, or using that field in your app.json
@gkueny we also have Eas Updates + Airship in our app and seeing a lot of ANR issues.
we have delayed the run of Aiship module to avoid any lock between the two and our ANR are gone.
by delaying the run you mean not calling Airship.takeOff immediately on app startup?
@DaniyarJakupov Our fix was to disable privacy features (It was this part that was causing the problem) by default and enable them after app start
Disable by default in android:
--- a/android/app/src/main/assets/airshipconfig.properties
+++ b/android/app/src/main/assets/airshipconfig.properties
+enabledFeatures=none
Enable after start:
+ await Airship.privacyManager.enableFeatures([
+ Feature.Push,
+ Feature.Analytics,
+ Feature.InAppAutomation,
+ Feature.InAppAutomation,
+ Feature.TagsAndAttributes,
+ Feature.Contacts,
+ Feature.FeatureFlags,
+ ]);
@gkueny thank you a lot for the reply! We will check if it helps in our case
On our side, even with the fix on airship, we continue to have some ANR (but less).
On our next version we will try @Kudo fix to avoid copying asset on launch. We can not update to expo 53 & latest expo-updates version now, so here is our patch for expo 52:
dependencies versions:
"react-native": "0.77.1",
"expo": "^52.0.0",
"expo-updates": "~0.27.3",
"expo-asset": "~11.0.4"
expo-update patch:
diff --git a/node_modules/expo-updates/android/build.gradle b/node_modules/expo-updates/android/build.gradle
index 24d3f2a..7916ada 100644
--- a/node_modules/expo-updates/android/build.gradle
+++ b/node_modules/expo-updates/android/build.gradle
@@ -53,6 +53,9 @@ def exUpdatesNativeDebug = getBoolStringFromPropOrEnv("EX_UPDATES_NATIVE_DEBUG",
// (default true)
def exUpdatesAndroidDelayLoadApp = getBoolStringFromPropOrEnv("EX_UPDATES_ANDROID_DELAY_LOAD_APP", true)
+// If true, updates will copy embedded assets to file system when startup. (default false)
+def exUpdatesCopyEmbeddedAssets = getBoolStringFromPropOrEnv("EX_UPDATES_COPY_EMBEDDED_ASSETS", false)
+
def useDevClient = findProject(":expo-dev-client") != null
android {
@@ -69,6 +72,7 @@ android {
buildConfigField("boolean", "EX_UPDATES_NATIVE_DEBUG", exUpdatesNativeDebug)
buildConfigField("boolean", "EX_UPDATES_ANDROID_DELAY_LOAD_APP", exUpdatesAndroidDelayLoadApp)
+ buildConfigField("boolean", "EX_UPDATES_COPY_EMBEDDED_ASSETS", exUpdatesCopyEmbeddedAssets)
buildConfigField("boolean", "USE_DEV_CLIENT", useDevClient.toString())
}
testOptions {
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/UpdatesUtils.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/UpdatesUtils.kt
index 4ddee84..a2c2343 100644
--- a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/UpdatesUtils.kt
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/UpdatesUtils.kt
@@ -19,7 +19,6 @@ import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
import java.util.regex.Pattern
-import kotlin.experimental.and
/**
* Miscellaneous helper functions that are used by multiple classes in the library.
@@ -145,11 +144,11 @@ object UpdatesUtils {
}
}
+ /**
+ * Create an asset filename in file system (files are saved in the `.expo-internal` directory)
+ */
fun createFilenameForAsset(asset: AssetEntity): String {
- var fileExtension: String? = ""
- if (asset.type != null) {
- fileExtension = if (asset.type!!.startsWith(".")) asset.type else "." + asset.type
- }
+ val fileExtension = asset.getFileExtension()
return if (asset.key == null) {
// create a filename that's unlikely to collide with any other asset
"asset-" + Date().time + "-" + Random().nextInt() + fileExtension
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/db/dao/AssetDao.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/db/dao/AssetDao.kt
index 6db800f..ce8625b 100644
--- a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/db/dao/AssetDao.kt
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/db/dao/AssetDao.kt
@@ -5,6 +5,7 @@ import androidx.room.*
import expo.modules.updates.db.entity.AssetEntity
import expo.modules.updates.db.entity.UpdateAssetEntity
import expo.modules.updates.db.entity.UpdateEntity
+import expo.modules.updates.utils.AndroidResourceAssetUtils
import java.util.*
/**
@@ -88,12 +89,17 @@ abstract class AssetDao {
}
fun loadAssetWithKey(key: String?): AssetEntity? {
- val assets = loadAssetWithKeyInternal(key)
- return if (assets.isNotEmpty()) {
- assets[0]
- } else {
- null
+ val asset = loadAssetWithKeyInternal(key).firstOrNull() ?: return null
+
+ // Load some properties not stored in database but can be computed from other fields
+ asset.relativePath?.let {
+ val (embeddedAssetFilename, resourceFolder, resourceFilename) =
+ AndroidResourceAssetUtils.parseAndroidResponseAssetFromPath(it)
+ asset.embeddedAssetFilename = embeddedAssetFilename
+ asset.resourcesFolder = resourceFolder
+ asset.resourcesFilename = resourceFilename
}
+ return asset
}
fun mergeAndUpdateAsset(existingEntity: AssetEntity, newEntity: AssetEntity) {
@@ -119,11 +125,11 @@ abstract class AssetDao {
// we need to keep track of whether the calling class expects this asset to be the launch asset
existingEntity.isLaunchAsset = newEntity.isLaunchAsset
// some fields on the asset entity are not stored in the database but might still be used by application code
- existingEntity.embeddedAssetFilename = newEntity.embeddedAssetFilename
- existingEntity.resourcesFilename = newEntity.resourcesFilename
- existingEntity.resourcesFolder = newEntity.resourcesFolder
- existingEntity.scale = newEntity.scale
- existingEntity.scales = newEntity.scales
+ newEntity.embeddedAssetFilename?.let { existingEntity.embeddedAssetFilename = it }
+ newEntity.resourcesFilename?.let { existingEntity.resourcesFilename = it }
+ newEntity.resourcesFolder?.let { existingEntity.resourcesFolder = it }
+ newEntity.scale?.let { existingEntity.scale = it }
+ newEntity.scales?.let { existingEntity.scales = it }
}
@Transaction
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/db/entity/AssetEntity.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/db/entity/AssetEntity.kt
index 5eb7a99..089cbba 100644
--- a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/db/entity/AssetEntity.kt
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/db/entity/AssetEntity.kt
@@ -68,4 +68,13 @@ class AssetEntity(@field:ColumnInfo(name = "key") var key: String?, var type: St
@Ignore
var scales: Array<Float>? = null
+
+ internal fun getFileExtension(): String {
+ val type = this.type ?: return ""
+ return if (type.startsWith(".")) {
+ type
+ } else {
+ ".$type"
+ }
+ }
}
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt
index 3ac8773..44a3ce4 100644
--- a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt
@@ -2,6 +2,8 @@ package expo.modules.updates.launcher
import android.content.Context
import android.net.Uri
+import androidx.annotation.VisibleForTesting
+import expo.modules.updates.BuildConfig
import expo.modules.updates.UpdatesConfiguration
import expo.modules.updates.UpdatesUtils
import expo.modules.updates.db.UpdatesDatabase
@@ -18,6 +20,7 @@ import expo.modules.updates.manifest.EmbeddedManifestUtils
import expo.modules.updates.manifest.EmbeddedUpdate
import expo.modules.updates.manifest.ManifestMetadata
import expo.modules.updates.selectionpolicy.SelectionPolicy
+import expo.modules.updates.utils.AndroidResourceAssetUtils
import org.json.JSONObject
import java.io.File
@@ -44,7 +47,8 @@ class DatabaseLauncher(
private val updatesDirectory: File?,
private val fileDownloader: FileDownloader,
private val selectionPolicy: SelectionPolicy,
- private val logger: UpdatesLogger
+ private val logger: UpdatesLogger,
+ private val shouldCopyEmbeddedAssets: Boolean = BuildConfig.EX_UPDATES_COPY_EMBEDDED_ASSETS
) : Launcher {
private val loaderFiles: LoaderFiles = LoaderFiles()
override var launchedUpdate: UpdateEntity? = null
@@ -97,7 +101,18 @@ class DatabaseLauncher(
val embeddedUpdate = EmbeddedManifestUtils.getEmbeddedUpdate(context, configuration)
val extraHeaders = FileDownloader.getExtraHeadersForRemoteAssetRequest(launchedUpdate, embeddedUpdate?.updateEntity, launchedUpdate)
- val launchAssetFile = ensureAssetExists(launchAsset, database, embeddedUpdate, extraHeaders)
+ val embeddedLaunchAsset = if (!shouldCopyEmbeddedAssets) {
+ embeddedUpdate?.assetEntityList
+ ?.find { it.key == launchAsset.key }
+ ?.embeddedAssetFilename
+ ?.let {
+ // react-native uses `assets://` to indicate loading a bundle from assets
+ "assets://$it"
+ }
+ } else {
+ null
+ }
+ val launchAssetFile = embeddedLaunchAsset ?: ensureAssetExists(launchAsset, database, embeddedUpdate, extraHeaders)
if (launchAssetFile != null) {
this.launchAssetFile = launchAssetFile.toString()
}
@@ -110,12 +125,14 @@ class DatabaseLauncher(
// we took care of this one above
continue
}
- val filename = asset.relativePath
- if (filename != null) {
+ val filename = asset.relativePath ?: continue
+ if (!AndroidResourceAssetUtils.isAndroidResourceAsset(filename)) {
val assetFile = ensureAssetExists(asset, database, embeddedUpdate, extraHeaders)
if (assetFile != null) {
this[asset] = Uri.fromFile(assetFile).toString()
}
+ } else {
+ this[asset] = filename
}
}
}
@@ -158,6 +175,20 @@ class DatabaseLauncher(
if (asset.isLaunchAsset) {
continue
}
+
+ if (!shouldCopyEmbeddedAssets) {
+ val filename = AndroidResourceAssetUtils.createEmbeddedFilenameForAsset(asset)
+ if (filename != null) {
+ asset.relativePath = filename
+ this[asset] = filename
+ logger.info("embeddedAssetFileMap: ${asset.key},${asset.type} => ${this[asset]}")
+ } else {
+ val cause = Exception("Missing embedded asset")
+ logger.error("embeddedAssetFileMap: no file for ${asset.key},${asset.type}", cause, UpdatesErrorCode.AssetsFailedToLoad)
+ }
+ continue
+ }
+
val filename = UpdatesUtils.createFilenameForAsset(asset)
asset.relativePath = filename
val assetFile = File(updatesDirectory, filename)
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/EmbeddedLoader.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/EmbeddedLoader.kt
index 8e7226a..9a9cc42 100644
--- a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/EmbeddedLoader.kt
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/EmbeddedLoader.kt
@@ -1,6 +1,7 @@
package expo.modules.updates.loader
import android.content.Context
+import expo.modules.updates.BuildConfig
import expo.modules.updates.UpdatesConfiguration
import expo.modules.updates.db.entity.AssetEntity
import expo.modules.updates.db.UpdatesDatabase
@@ -9,6 +10,7 @@ import expo.modules.updates.loader.FileDownloader.RemoteUpdateDownloadCallback
import expo.modules.updates.UpdatesUtils
import expo.modules.updates.db.entity.UpdateEntity
import expo.modules.updates.logging.UpdatesLogger
+import expo.modules.updates.utils.AndroidResourceAssetUtils
import java.io.File
import java.io.FileNotFoundException
import java.lang.AssertionError
@@ -16,14 +18,14 @@ import java.lang.Exception
import java.util.*
/**
- * Subclass of [Loader] which handles copying the embedded update's assets into the
- * expo-updates cache location.
+ * Subclass of [Loader] which handles embedded update assets
*
- * Rather than launching the embedded update directly from its location in the app bundle/apk, we
- * first try to read it into the expo-updates cache and database and launch it like any other
- * update. The benefits of this include (a) a single code path for launching most updates and (b)
- * assets included in embedded updates and copied into the cache in this way do not need to be
- * re-downloaded if included in future updates.
+ * @param shouldCopyEmbeddedAssets if true, copying the embedded update's assets into the expo-updates cache location.
+ * Rather than launching the embedded update directly from its location in the app bundle/apk, we
+ * first try to read it into the expo-updates cache and database and launch it like any other
+ * update. The benefits of this include (a) a single code path for launching most updates and (b)
+ * assets included in embedded updates and copied into the cache in this way do not need to be
+ * re-downloaded if included in future updates.
*/
class EmbeddedLoader internal constructor(
context: Context,
@@ -31,7 +33,8 @@ class EmbeddedLoader internal constructor(
logger: UpdatesLogger,
database: UpdatesDatabase,
updatesDirectory: File,
- private val loaderFiles: LoaderFiles
+ private val loaderFiles: LoaderFiles,
+ private val shouldCopyEmbeddedAssets: Boolean = BuildConfig.EX_UPDATES_COPY_EMBEDDED_ASSETS
) : Loader(
context,
configuration,
@@ -76,10 +79,19 @@ class EmbeddedLoader internal constructor(
embeddedUpdate: UpdateEntity?,
callback: AssetDownloadCallback
) {
+ if (!shouldCopyEmbeddedAssets) {
+ assetEntity.downloadTime = Date()
+ assetEntity.relativePath = AndroidResourceAssetUtils.createEmbeddedFilenameForAsset(assetEntity)
+ // Passing `isNew=true` aka `AssetLoadResult.FINISHED` to the callback,
+ // because we assume embedded asset is always existed without filesystem out of sync.
+ callback.onSuccess(assetEntity, true)
+ return
+ }
+
val filename = UpdatesUtils.createFilenameForAsset(assetEntity)
val destination = File(updatesDirectory, filename)
- if (loaderFiles.fileExists(destination)) {
+ if (loaderFiles.fileExists(context, updatesDirectory, filename)) {
assetEntity.relativePath = filename
callback.onSuccess(assetEntity, false)
} else {
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/Loader.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/Loader.kt
index 05bce68..cb3a4b9 100644
--- a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/Loader.kt
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/Loader.kt
@@ -233,12 +233,8 @@ abstract class Loader protected constructor(
}
// if we already have a local copy of this asset, don't try to download it again!
- if (assetEntity.relativePath != null && loaderFiles.fileExists(
- File(
- updatesDirectory,
- assetEntity.relativePath
- )
- )
+ if (assetEntity.relativePath != null &&
+ loaderFiles.fileExists(context, updatesDirectory, assetEntity.relativePath)
) {
handleAssetDownloadCompleted(assetEntity, AssetLoadResult.ALREADY_EXISTS)
continue
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/LoaderFiles.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/LoaderFiles.kt
index ce7a1b8..68f66f3 100644
--- a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/LoaderFiles.kt
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/LoaderFiles.kt
@@ -8,6 +8,7 @@ import expo.modules.updates.UpdatesUtils
import expo.modules.updates.db.entity.AssetEntity
import expo.modules.updates.manifest.EmbeddedManifestUtils
import expo.modules.updates.manifest.Update
+import expo.modules.updates.utils.AndroidResourceAssetUtils
import java.io.File
import java.io.IOException
import java.security.NoSuchAlgorithmException
@@ -16,8 +17,12 @@ import java.security.NoSuchAlgorithmException
* Utility class for Loader and its subclasses, to allow for easy mocking
*/
open class LoaderFiles {
- fun fileExists(destination: File): Boolean {
- return destination.exists()
+ fun fileExists(context: Context, updateDirectory: File?, relativePath: String?): Boolean {
+ val filePath = relativePath ?: return false
+ if (AndroidResourceAssetUtils.isAndroidAssetOrResourceExisted(context, filePath)) {
+ return true
+ }
+ return File(updateDirectory, filePath).exists()
}
fun readEmbeddedUpdate(
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/utils/AndroidResourceAssetUtils.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/utils/AndroidResourceAssetUtils.kt
new file mode 100644
index 0000000..0b7b87b
--- /dev/null
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/utils/AndroidResourceAssetUtils.kt
@@ -0,0 +1,120 @@
+// Copyright 2015-present 650 Industries. All rights reserved.
+
+package expo.modules.updates.utils
+
+import android.annotation.SuppressLint
+import android.content.Context
+import androidx.core.net.toUri
+import expo.modules.core.errors.InvalidArgumentException
+import expo.modules.updates.db.entity.AssetEntity
+import java.io.IOException
+
+/**
+ * Helpers for Android embedded assets and resources
+ */
+internal object AndroidResourceAssetUtils {
+ private const val ANDROID_EMBEDDED_URL_BASE_ASSET = "file:///android_asset/"
+ private const val ANDROID_EMBEDDED_URL_BASE_RESOURCE = "file:///android_res/"
+
+ /**
+ * Create an embedded asset filename in `file:///android_res/` or `file:///android_asset/` format
+ */
+ fun createEmbeddedFilenameForAsset(asset: AssetEntity): String? {
+ val fileExtension = asset.getFileExtension()
+ if (asset.embeddedAssetFilename != null) {
+ return "${ANDROID_EMBEDDED_URL_BASE_ASSET}${asset.embeddedAssetFilename}$fileExtension"
+ }
+ if (asset.resourcesFolder != null && asset.resourcesFilename != null) {
+ return "${ANDROID_EMBEDDED_URL_BASE_RESOURCE}${asset.resourcesFolder}${getDrawableSuffix(asset.scale)}/${asset.resourcesFilename}$fileExtension"
+ }
+ return null
+ }
+
+ /**
+ * Return whether the filePath is an Android asset or resource
+ */
+ fun isAndroidResourceAsset(filePath: String): Boolean {
+ return filePath.startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE) ||
+ filePath.startsWith(ANDROID_EMBEDDED_URL_BASE_ASSET)
+ }
+
+ /**
+ * Check a given file name is existed in Android embedded assets
+ */
+ fun isAndroidAssetExisted(context: Context, name: String) = try {
+ context.assets.open(name).close()
+ true
+ } catch (e: IOException) {
+ false
+ }
+
+ /**
+ * Check a given resource folder and filename is existed in Android embedded resources
+ */
+ @SuppressLint("DiscouragedApi")
+ fun isAndroidResourceExisted(context: Context, resourceFolder: String, resourceFilename: String): Boolean {
+ return context.resources.getIdentifier(
+ resourceFilename,
+ resourceFolder,
+ context.packageName
+ ) != 0
+ }
+
+ /**
+ * Check if given filePath matches and exists in the Android embedded assets or resources
+ */
+ fun isAndroidAssetOrResourceExisted(context: Context, filePath: String): Boolean {
+ val (embeddedAssetFilename, resourceFolder, resourceFilename) = parseAndroidResponseAssetFromPath(filePath)
+ return when {
+ embeddedAssetFilename != null -> isAndroidAssetExisted(context, embeddedAssetFilename)
+ resourceFolder != null && resourceFilename != null -> isAndroidResourceExisted(context, resourceFolder, resourceFilename)
+ else -> {
+ false
+ }
+ }
+ }
+
+ /**
+ * Data structure for Android embedded asset and resource
+ */
+ data class AndroidResourceAsset(
+ val embeddedAssetFilename: String?,
+ val resourcesFolder: String?,
+ val resourceFilename: String?
+ )
+
+ /**
+ * Parse a file path and return as `AndroidResourceAsset`
+ */
+ fun parseAndroidResponseAssetFromPath(filePath: String): AndroidResourceAsset {
+ if (filePath.startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) {
+ val uri = filePath.toUri()
+ val pathSegments = uri.pathSegments
+ if (pathSegments.size < 3) {
+ throw InvalidArgumentException("Invalid resource file path: $filePath")
+ }
+ // Strip any qualifiers after a dash, for example "drawable-xhdpi" becomes "drawable"
+ val resourcesFolder = pathSegments[1].substringBefore('-')
+ // Strip file extension for resource name
+ val resourceFilename = pathSegments[2].substringBeforeLast('.', pathSegments[2])
+ return AndroidResourceAsset(null, resourcesFolder, resourceFilename)
+ }
+ if (filePath.startsWith(ANDROID_EMBEDDED_URL_BASE_ASSET)) {
+ val embeddedAssetFilename = filePath.substringAfterLast('/')
+ return AndroidResourceAsset(embeddedAssetFilename, null, null)
+ }
+ return AndroidResourceAsset(null, null, null)
+ }
+
+ private fun getDrawableSuffix(scale: Float?): String {
+ return when (scale) {
+ 0.75f -> "-ldpi"
+ 1f -> "-mdpi"
+ 1.5f -> "-hdpi"
+ 2f -> "-xhdpi"
+ 3f -> "-xxhdpi"
+ 4f -> "-xxxhdpi"
+ else -> ""
+ }
+ }
+}
expo-asset patch:
diff --git a/node_modules/expo-asset/android/src/main/java/expo/modules/asset/AssetModule.kt b/node_modules/expo-asset/android/src/main/java/expo/modules/asset/AssetModule.kt
index 22609cd..c99d154 100644
--- a/node_modules/expo-asset/android/src/main/java/expo/modules/asset/AssetModule.kt
+++ b/node_modules/expo-asset/android/src/main/java/expo/modules/asset/AssetModule.kt
@@ -57,6 +57,7 @@ class AssetModule : Module() {
try {
val inputStream = when {
uri.toString().contains(":").not() -> openAssetResourceStream(context, uri.toString())
+ uri.toString().startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE) -> openAndroidResStream(context, uri.toString())
else -> uri.toURL().openStream()
}
inputStream.use { input ->
@@ -75,7 +76,7 @@ class AssetModule : Module() {
Name("ExpoAsset")
AsyncFunction("downloadAsync") Coroutine { uri: URI, md5Hash: String?, type: String ->
- if (uri.scheme === "file") {
+ if (uri.scheme === "file" && !uri.toString().startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) {
return@Coroutine uri
}
diff --git a/node_modules/expo-asset/android/src/main/java/expo/modules/asset/ResourceAsset.kt b/node_modules/expo-asset/android/src/main/java/expo/modules/asset/ResourceAsset.kt
index 83c1034..8b12b1e 100644
--- a/node_modules/expo-asset/android/src/main/java/expo/modules/asset/ResourceAsset.kt
+++ b/node_modules/expo-asset/android/src/main/java/expo/modules/asset/ResourceAsset.kt
@@ -3,15 +3,27 @@ package expo.modules.asset
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
+import androidx.core.net.toUri
+import expo.modules.core.errors.InvalidArgumentException
import java.io.InputStream
+internal const val ANDROID_EMBEDDED_URL_BASE_RESOURCE = "file:///android_res/"
+
/**
* Opens an Android resource as stream.
*/
internal fun openAssetResourceStream(context: Context, assetName: String): InputStream {
- val resources = context.resources
val resId = findResourceId(context, assetName) ?: throw Resources.NotFoundException(assetName)
- return resources.openRawResource(resId)
+ return context.resources.openRawResource(resId)
+}
+
+/**
+ * Opens an Android resource as stream for `file:///android_res/` format
+ */
+internal fun openAndroidResStream(context: Context, resourceFilePath: String): InputStream {
+ val resId = findResourceIdForAndroidResPath(context, resourceFilePath)
+ ?: throw Resources.NotFoundException(resourceFilePath)
+ return context.resources.openRawResource(resId)
}
@SuppressLint("DiscouragedApi")
@@ -22,3 +34,28 @@ private fun findResourceId(context: Context, assetName: String): Int? {
return resources.getIdentifier(assetName, "raw", packageName).takeIf { it != 0 }
?: resources.getIdentifier(assetName, "drawable", packageName).takeIf { it != 0 }
}
+
+@SuppressLint("DiscouragedApi")
+private fun findResourceIdForAndroidResPath(context: Context, resourceFilePath: String): Int? {
+ if (!resourceFilePath.startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) {
+ throw InvalidArgumentException("Invalid resource file path: $resourceFilePath")
+ }
+ val uri = resourceFilePath.toUri()
+ val pathSegments = uri.pathSegments
+ if (pathSegments.size < 3) {
+ throw InvalidArgumentException("Invalid resource file path: $resourceFilePath")
+ }
+
+ // Strip any qualifiers after a dash, for example "drawable-xhdpi" becomes "drawable"
+ val resourceDirectory = pathSegments[1].substringBefore('-')
+
+ // Strip file extension for resource name
+ val resourceFilename = pathSegments[2]
+ val resourceName = resourceFilename.substringBeforeLast('.', resourceFilename)
+
+ return context.resources.getIdentifier(
+ resourceName,
+ resourceDirectory,
+ context.packageName
+ ).takeIf { it != 0 }
+}
diff --git a/node_modules/expo-asset/build/Asset.d.ts b/node_modules/expo-asset/build/Asset.d.ts
index ff00a21..e8a49aa 100644
--- a/node_modules/expo-asset/build/Asset.d.ts
+++ b/node_modules/expo-asset/build/Asset.d.ts
@@ -8,6 +8,11 @@ export type AssetDescriptor = {
height?: number | null;
};
export { AssetMetadata };
+/**
+ * Android resource URL prefix.
+ * @hidden
+ */
+export declare const ANDROID_EMBEDDED_URL_BASE_RESOURCE = "file:///android_res/";
/**
* The `Asset` class represents an asset in your app. It gives metadata about the asset (such as its
* name and type) and provides facilities to load the asset data.
diff --git a/node_modules/expo-asset/build/Asset.d.ts.map b/node_modules/expo-asset/build/Asset.d.ts.map
index 6facc28..2204bd7 100644
--- a/node_modules/expo-asset/build/Asset.d.ts.map
+++ b/node_modules/expo-asset/build/Asset.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"Asset.d.ts","sourceRoot":"","sources":["../src/Asset.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAqB,MAAM,gBAAgB,CAAC;AASlE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAOF,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB;;;GAGG;AACH,qBAAa,KAAK;IAChB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAM;IAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAM;IAE1B;;;OAGG;IACI,IAAI,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B;;OAEG;IACH,SAAgB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC3C;;;;;;;OAOG;IACH,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACI,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IACtC;;;OAGG;IACI,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IACnC;;OAEG;IACI,MAAM,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEpC,OAAO,CAAC,WAAW,CAAkB;IAErC;;OAEG;IACI,UAAU,EAAE,OAAO,CAAS;IAEnC,OAAO,CAAC,kBAAkB,CAAkC;gBAEhD,EAAE,IAAI,EAAE,IAAI,EAAE,IAAW,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,eAAe;IA+B5E;;;;;;;;;;OAUG;IACH,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAMnF;;;;;OAKG;IACH,MAAM,CAAC,UAAU,CACf,kBAAkB,EAAE,MAAM,GAAG,MAAM,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GACnF,KAAK;IAwDR,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,KAAK;IAsB/C,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK;IA2BlC;;;;;;;OAOG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAoCrC"}
\ No newline at end of file
+{"version":3,"file":"Asset.d.ts","sourceRoot":"","sources":["../src/Asset.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAqB,MAAM,gBAAgB,CAAC;AASlE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAOF,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB;;;GAGG;AACH,eAAO,MAAM,kCAAkC,yBAAyB,CAAC;AAEzE;;;GAGG;AACH,qBAAa,KAAK;IAChB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAM;IAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAM;IAE1B;;;OAGG;IACI,IAAI,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B;;OAEG;IACH,SAAgB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC3C;;;;;;;OAOG;IACH,SAAgB,GAAG,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACI,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IACtC;;;OAGG;IACI,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IACnC;;OAEG;IACI,MAAM,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEpC,OAAO,CAAC,WAAW,CAAkB;IAErC;;OAEG;IACI,UAAU,EAAE,OAAO,CAAS;IAEnC,OAAO,CAAC,kBAAkB,CAAkC;gBAEhD,EAAE,IAAI,EAAE,IAAI,EAAE,IAAW,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,eAAe;IAmC5E;;;;;;;;;;OAUG;IACH,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAMnF;;;;;OAKG;IACH,MAAM,CAAC,UAAU,CACf,kBAAkB,EAAE,MAAM,GAAG,MAAM,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GACnF,KAAK;IAwDR,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,KAAK;IAsB/C,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK;IA2BlC;;;;;;;OAOG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAoCrC"}
\ No newline at end of file
diff --git a/node_modules/expo-asset/build/Asset.fx.js b/node_modules/expo-asset/build/Asset.fx.js
index 6ba2523..f4e1d68 100644
--- a/node_modules/expo-asset/build/Asset.fx.js
+++ b/node_modules/expo-asset/build/Asset.fx.js
@@ -1,4 +1,4 @@
-import { Asset } from './Asset';
+import { Asset, ANDROID_EMBEDDED_URL_BASE_RESOURCE } from './Asset';
import { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';
import { setCustomSourceTransformer } from './resolveAssetSource';
// Override React Native's asset resolution for `Image` components in contexts where it matters
@@ -8,6 +8,9 @@ if (IS_ENV_WITH_LOCAL_ASSETS) {
// Bundler is using the hashAssetFiles plugin if and only if the fileHashes property exists
if (resolver.asset.fileHashes) {
const asset = Asset.fromMetadata(resolver.asset);
+ if (asset.uri.startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) {
+ return resolver.resourceIdentifierWithoutScale();
+ }
return resolver.fromSource(asset.downloaded ? asset.localUri : asset.uri);
}
else {
diff --git a/node_modules/expo-asset/build/Asset.fx.js.map b/node_modules/expo-asset/build/Asset.fx.js.map
index 5544be5..2f776d8 100644
--- a/node_modules/expo-asset/build/Asset.fx.js.map
+++ b/node_modules/expo-asset/build/Asset.fx.js.map
@@ -1 +1 @@
-{"version":3,"file":"Asset.fx.js","sourceRoot":"","sources":["../src/Asset.fx.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAElE,+FAA+F;AAC/F,IAAI,wBAAwB,EAAE;IAC5B,0BAA0B,CAAC,CAAC,QAAQ,EAAE,EAAE;QACtC,IAAI;YACF,2FAA2F;YAC3F,IAAI,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE;gBAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBACjD,OAAO,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,QAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;aAC5E;iBAAM;gBACL,OAAO,QAAQ,CAAC,YAAY,EAAE,CAAC;aAChC;SACF;QAAC,MAAM;YACN,OAAO,QAAQ,CAAC,YAAY,EAAE,CAAC;SAChC;IACH,CAAC,CAAC,CAAC;CACJ","sourcesContent":["import { Asset } from './Asset';\nimport { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';\nimport { setCustomSourceTransformer } from './resolveAssetSource';\n\n// Override React Native's asset resolution for `Image` components in contexts where it matters\nif (IS_ENV_WITH_LOCAL_ASSETS) {\n setCustomSourceTransformer((resolver) => {\n try {\n // Bundler is using the hashAssetFiles plugin if and only if the fileHashes property exists\n if (resolver.asset.fileHashes) {\n const asset = Asset.fromMetadata(resolver.asset);\n return resolver.fromSource(asset.downloaded ? asset.localUri! : asset.uri);\n } else {\n return resolver.defaultAsset();\n }\n } catch {\n return resolver.defaultAsset();\n }\n });\n}\n"]}
\ No newline at end of file
+{"version":3,"file":"Asset.fx.js","sourceRoot":"","sources":["../src/Asset.fx.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,kCAAkC,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,kBAAkB,EAAE,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEtF,+FAA+F;AAC/F,IAAI,wBAAwB,EAAE,CAAC;IAC7B,MAAM,cAAc;IAClB,aAAa;IACb,kBAAkB,CAAC,0BAA0B,IAAI,0BAA0B,CAAC;IAC9E,cAAc,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC1B,IAAI,CAAC;YACH,2FAA2F;YAC3F,IAAI,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;gBAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBACjD,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,kCAAkC,CAAC,EAAE,CAAC;oBAC7D,OAAO,QAAQ,CAAC,8BAA8B,EAAE,CAAC;gBACnD,CAAC;gBACD,OAAO,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,QAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC7E,CAAC;iBAAM,CAAC;gBACN,OAAO,QAAQ,CAAC,YAAY,EAAE,CAAC;YACjC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,QAAQ,CAAC,YAAY,EAAE,CAAC;QACjC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { Asset, ANDROID_EMBEDDED_URL_BASE_RESOURCE } from './Asset';\nimport { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';\nimport resolveAssetSource, { setCustomSourceTransformer } from './resolveAssetSource';\n\n// Override React Native's asset resolution for `Image` components in contexts where it matters\nif (IS_ENV_WITH_LOCAL_ASSETS) {\n const setTransformer =\n // @ts-ignore\n resolveAssetSource.setCustomSourceTransformer || setCustomSourceTransformer;\n setTransformer((resolver) => {\n try {\n // Bundler is using the hashAssetFiles plugin if and only if the fileHashes property exists\n if (resolver.asset.fileHashes) {\n const asset = Asset.fromMetadata(resolver.asset);\n if (asset.uri.startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) {\n return resolver.resourceIdentifierWithoutScale();\n }\n return resolver.fromSource(asset.downloaded ? asset.localUri! : asset.uri);\n } else {\n return resolver.defaultAsset();\n }\n } catch {\n return resolver.defaultAsset();\n }\n });\n}\n"]}
\ No newline at end of file
diff --git a/node_modules/expo-asset/build/Asset.js b/node_modules/expo-asset/build/Asset.js
index ff46fea..4aa492d 100644
--- a/node_modules/expo-asset/build/Asset.js
+++ b/node_modules/expo-asset/build/Asset.js
@@ -7,6 +7,11 @@ import * as ImageAssets from './ImageAssets';
import { getLocalAssetUri } from './LocalAssets';
import { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';
import resolveAssetSource from './resolveAssetSource';
+/**
+ * Android resource URL prefix.
+ * @hidden
+ */
+export const ANDROID_EMBEDDED_URL_BASE_RESOURCE = 'file:///android_res/';
/**
* The `Asset` class represents an asset in your app. It gives metadata about the asset (such as its
* name and type) and provides facilities to load the asset data.
@@ -69,7 +74,12 @@ export class Asset {
}
if (hash) {
this.localUri = getLocalAssetUri(hash, type);
- if (this.localUri) {
+ if (this.localUri?.startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) {
+ // Treat Android embedded resources as not downloaded state, because the uri is not direct accessible.
+ this.uri = this.localUri;
+ this.localUri = null;
+ }
+ else if (this.localUri) {
this.downloaded = true;
}
}
diff --git a/node_modules/expo-asset/build/Asset.js.map b/node_modules/expo-asset/build/Asset.js.map
index fef5766..74ac0fc 100644
--- a/node_modules/expo-asset/build/Asset.js.map
+++ b/node_modules/expo-asset/build/Asset.js.map
@@ -1 +1 @@
-{"version":3,"file":"Asset.js","sourceRoot":"","sources":["../src/Asset.ts"],"names":[],"mappings":"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","sourcesContent":["import { getAssetByID } from '@react-native/assets-registry/registry';\nimport { Platform } from 'expo-modules-core';\n\nimport { AssetMetadata, selectAssetSource } from './AssetSources';\nimport * as AssetUris from './AssetUris';\nimport { downloadAsync } from './ExpoAsset';\nimport * as ImageAssets from './ImageAssets';\nimport { getLocalAssetUri } from './LocalAssets';\nimport { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';\nimport resolveAssetSource from './resolveAssetSource';\n\n// @docsMissing\nexport type AssetDescriptor = {\n name: string;\n type: string;\n hash?: string | null;\n uri: string;\n width?: number | null;\n height?: number | null;\n};\n\ntype DownloadPromiseCallbacks = {\n resolve: () => void;\n reject: (error: Error) => void;\n};\n\nexport { AssetMetadata };\n\n/**\n * The `Asset` class represents an asset in your app. It gives metadata about the asset (such as its\n * name and type) and provides facilities to load the asset data.\n */\nexport class Asset {\n private static byHash = {};\n private static byUri = {};\n\n /**\n * The name of the asset file without the extension. Also without the part from `@` onward in the\n * filename (used to specify scale factor for images).\n */\n public name: string;\n /**\n * The extension of the asset filename.\n */\n public readonly type: string;\n /**\n * The MD5 hash of the asset's data.\n */\n public readonly hash: string | null = null;\n /**\n * A URI that points to the asset's data on the remote server. When running the published version\n * of your app, this refers to the location on Expo's asset server where Expo has stored your\n * asset. When running the app from Expo CLI during development, this URI points to Expo CLI's\n * server running on your computer and the asset is served directly from your computer. If you\n * are not using Classic Updates (legacy), this field should be ignored as we ensure your assets\n * are on device before running your application logic.\n */\n public readonly uri: string;\n /**\n * If the asset has been downloaded (by calling [`downloadAsync()`](#downloadasync)), the\n * `file://` URI pointing to the local file on the device that contains the asset data.\n */\n public localUri: string | null = null;\n /**\n * If the asset is an image, the width of the image data divided by the scale factor. The scale\n * factor is the number after `@` in the filename, or `1` if not present.\n */\n public width: number | null = null;\n /**\n * If the asset is an image, the height of the image data divided by the scale factor. The scale factor is the number after `@` in the filename, or `1` if not present.\n */\n public height: number | null = null;\n\n private downloading: boolean = false;\n\n /**\n * Whether the asset has finished downloading from a call to [`downloadAsync()`](#downloadasync).\n */\n public downloaded: boolean = false;\n\n private _downloadCallbacks: DownloadPromiseCallbacks[] = [];\n\n constructor({ name, type, hash = null, uri, width, height }: AssetDescriptor) {\n this.name = name;\n this.type = type;\n this.hash = hash;\n this.uri = uri;\n\n if (typeof width === 'number') {\n this.width = width;\n }\n if (typeof height === 'number') {\n this.height = height;\n }\n\n if (hash) {\n this.localUri = getLocalAssetUri(hash, type);\n if (this.localUri) {\n this.downloaded = true;\n }\n }\n\n if (Platform.OS === 'web') {\n if (!name) {\n this.name = AssetUris.getFilename(uri);\n }\n if (!type) {\n this.type = AssetUris.getFileExtension(uri);\n }\n }\n }\n\n // @needsAudit\n /**\n * A helper that wraps `Asset.fromModule(module).downloadAsync` for convenience.\n * @param moduleId An array of `require('path/to/file')` or external network URLs. Can also be\n * just one module or URL without an Array.\n * @return Returns a Promise that fulfills with an array of `Asset`s when the asset(s) has been\n * saved to disk.\n * @example\n * ```ts\n * const [{ localUri }] = await Asset.loadAsync(require('./assets/snack-icon.png'));\n * ```\n */\n static loadAsync(moduleId: number | number[] | string | string[]): Promise<Asset[]> {\n const moduleIds = Array.isArray(moduleId) ? moduleId : [moduleId];\n return Promise.all(moduleIds.map((moduleId) => Asset.fromModule(moduleId).downloadAsync()));\n }\n\n // @needsAudit\n /**\n * Returns the [`Asset`](#asset) instance representing an asset given its module or URL.\n * @param virtualAssetModule The value of `require('path/to/file')` for the asset or external\n * network URL\n * @return The [`Asset`](#asset) instance for the asset.\n */\n static fromModule(\n virtualAssetModule: number | string | { uri: string; width: number; height: number }\n ): Asset {\n if (typeof virtualAssetModule === 'string') {\n return Asset.fromURI(virtualAssetModule);\n }\n if (\n typeof virtualAssetModule === 'object' &&\n 'uri' in virtualAssetModule &&\n typeof virtualAssetModule.uri === 'string'\n ) {\n const extension = AssetUris.getFileExtension(virtualAssetModule.uri);\n return new Asset({\n name: '',\n type: extension.startsWith('.') ? extension.substring(1) : extension,\n hash: null,\n uri: virtualAssetModule.uri,\n width: virtualAssetModule.width,\n height: virtualAssetModule.height,\n });\n }\n\n const meta = getAssetByID(virtualAssetModule);\n if (!meta) {\n throw new Error(`Module \"${virtualAssetModule}\" is missing from the asset registry`);\n }\n\n // Outside of the managed env we need the moduleId to initialize the asset\n // because resolveAssetSource depends on it\n if (!IS_ENV_WITH_LOCAL_ASSETS) {\n // null-check is performed above with `getAssetByID`.\n const { uri } = resolveAssetSource(virtualAssetModule)!;\n\n const asset = new Asset({\n name: meta.name,\n type: meta.type,\n hash: meta.hash,\n uri,\n width: meta.width,\n height: meta.height,\n });\n\n // For images backward compatibility,\n // keeps localUri the same as uri for React Native's Image that\n // works fine with drawable resource names.\n if (Platform.OS === 'android' && !uri.includes(':') && (meta.width || meta.height)) {\n asset.localUri = asset.uri;\n asset.downloaded = true;\n }\n\n Asset.byHash[meta.hash] = asset;\n return asset;\n }\n\n return Asset.fromMetadata(meta);\n }\n\n // @docsMissing\n static fromMetadata(meta: AssetMetadata): Asset {\n // The hash of the whole asset, not to be confused with the hash of a specific file returned\n // from `selectAssetSource`\n const metaHash = meta.hash;\n if (Asset.byHash[metaHash]) {\n return Asset.byHash[metaHash];\n }\n\n const { uri, hash } = selectAssetSource(meta);\n const asset = new Asset({\n name: meta.name,\n type: meta.type,\n hash,\n uri,\n width: meta.width,\n height: meta.height,\n });\n Asset.byHash[metaHash] = asset;\n return asset;\n }\n\n // @docsMissing\n static fromURI(uri: string): Asset {\n if (Asset.byUri[uri]) {\n return Asset.byUri[uri];\n }\n\n // Possibly a Base64-encoded URI\n let type = '';\n if (uri.indexOf(';base64') > -1) {\n type = uri.split(';')[0].split('/')[1];\n } else {\n const extension = AssetUris.getFileExtension(uri);\n type = extension.startsWith('.') ? extension.substring(1) : extension;\n }\n\n const asset = new Asset({\n name: '',\n type,\n hash: null,\n uri,\n });\n\n Asset.byUri[uri] = asset;\n\n return asset;\n }\n\n // @needsAudit\n /**\n * Downloads the asset data to a local file in the device's cache directory. Once the returned\n * promise is fulfilled without error, the [`localUri`](#localuri) field of this asset points\n * to a local file containing the asset data. The asset is only downloaded if an up-to-date local\n * file for the asset isn't already present due to an earlier download. The downloaded `Asset`\n * will be returned when the promise is resolved.\n * @return Returns a Promise which fulfills with an `Asset` instance.\n */\n async downloadAsync(): Promise<this> {\n if (this.downloaded) {\n return this;\n }\n if (this.downloading) {\n await new Promise<void>((resolve, reject) => {\n this._downloadCallbacks.push({ resolve, reject });\n });\n return this;\n }\n this.downloading = true;\n\n try {\n if (Platform.OS === 'web') {\n if (ImageAssets.isImageType(this.type)) {\n const { width, height, name } = await ImageAssets.getImageInfoAsync(this.uri);\n this.width = width;\n this.height = height;\n this.name = name;\n } else {\n this.name = AssetUris.getFilename(this.uri);\n }\n }\n this.localUri = await downloadAsync(this.uri, this.hash, this.type);\n\n this.downloaded = true;\n this._downloadCallbacks.forEach(({ resolve }) => resolve());\n } catch (e) {\n this._downloadCallbacks.forEach(({ reject }) => reject(e));\n throw e;\n } finally {\n this.downloading = false;\n this._downloadCallbacks = [];\n }\n return this;\n }\n}\n"]}
\ No newline at end of file
+{"version":3,"file":"Asset.js","sourceRoot":"","sources":["../src/Asset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAiB,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,KAAK,SAAS,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,WAAW,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,kBAAkB,MAAM,sBAAsB,CAAC;AAmBtD;;;GAGG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAG,sBAAsB,CAAC;AAEzE;;;GAGG;AACH,MAAM,OAAO,KAAK;IACR,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;IACnB,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;IAE1B;;;OAGG;IACI,IAAI,CAAS;IACpB;;OAEG;IACa,IAAI,CAAS;IAC7B;;OAEG;IACa,IAAI,GAAkB,IAAI,CAAC;IAC3C;;;;;;;OAOG;IACa,GAAG,CAAS;IAC5B;;;OAGG;IACI,QAAQ,GAAkB,IAAI,CAAC;IACtC;;;OAGG;IACI,KAAK,GAAkB,IAAI,CAAC;IACnC;;OAEG;IACI,MAAM,GAAkB,IAAI,CAAC;IAE5B,WAAW,GAAY,KAAK,CAAC;IAErC;;OAEG;IACI,UAAU,GAAY,KAAK,CAAC;IAE3B,kBAAkB,GAA+B,EAAE,CAAC;IAE5D,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAmB;QAC1E,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QAEf,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,CAAC;QACD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,CAAC;QAED,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,QAAQ,GAAG,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC7C,IAAI,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,kCAAkC,CAAC,EAAE,CAAC;gBAClE,sGAAsG;gBACtG,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC;gBACzB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACvB,CAAC;iBAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACzB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACzB,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACzC,CAAC;YACD,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;IACH,CAAC;IAED,cAAc;IACd;;;;;;;;;;OAUG;IACH,MAAM,CAAC,SAAS,CAAC,QAA+C;QAC9D,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAClE,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IAC9F,CAAC;IAED,cAAc;IACd;;;;;OAKG;IACH,MAAM,CAAC,UAAU,CACf,kBAAoF;QAEpF,IAAI,OAAO,kBAAkB,KAAK,QAAQ,EAAE,CAAC;YAC3C,OAAO,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAC3C,CAAC;QACD,IACE,OAAO,kBAAkB,KAAK,QAAQ;YACtC,KAAK,IAAI,kBAAkB;YAC3B,OAAO,kBAAkB,CAAC,GAAG,KAAK,QAAQ,EAC1C,CAAC;YACD,MAAM,SAAS,GAAG,SAAS,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;YACrE,OAAO,IAAI,KAAK,CAAC;gBACf,IAAI,EAAE,EAAE;gBACR,IAAI,EAAE,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;gBACpE,IAAI,EAAE,IAAI;gBACV,GAAG,EAAE,kBAAkB,CAAC,GAAG;gBAC3B,KAAK,EAAE,kBAAkB,CAAC,KAAK;gBAC/B,MAAM,EAAE,kBAAkB,CAAC,MAAM;aAClC,CAAC,CAAC;QACL,CAAC;QAED,MAAM,IAAI,GAAG,YAAY,CAAC,kBAAkB,CAAC,CAAC;QAC9C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,WAAW,kBAAkB,sCAAsC,CAAC,CAAC;QACvF,CAAC;QAED,0EAA0E;QAC1E,2CAA2C;QAC3C,IAAI,CAAC,wBAAwB,EAAE,CAAC;YAC9B,qDAAqD;YACrD,MAAM,EAAE,GAAG,EAAE,GAAG,kBAAkB,CAAC,kBAAkB,CAAE,CAAC;YAExD,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;gBACtB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,GAAG;gBACH,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,CAAC,CAAC;YAEH,qCAAqC;YACrC,+DAA+D;YAC/D,2CAA2C;YAC3C,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnF,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC;gBAC3B,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC;YAC1B,CAAC;YAED,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;YAChC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,eAAe;IACf,MAAM,CAAC,YAAY,CAAC,IAAmB;QACrC,4FAA4F;QAC5F,2BAA2B;QAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;QAC3B,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;QAED,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;YACtB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI;YACJ,GAAG;YACH,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;QAC/B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,eAAe;IACf,MAAM,CAAC,OAAO,CAAC,GAAW;QACxB,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;QAED,gCAAgC;QAChC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAChC,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,MAAM,SAAS,GAAG,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAClD,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;YACtB,IAAI,EAAE,EAAE;YACR,IAAI;YACJ,IAAI,EAAE,IAAI;YACV,GAAG;SACJ,CAAC,CAAC;QAEH,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAEzB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,cAAc;IACd;;;;;;;OAOG;IACH,KAAK,CAAC,aAAa;QACjB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACpD,CAAC,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QAExB,IAAI,CAAC;YACH,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;gBAC1B,IAAI,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACvC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,WAAW,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;oBAC9E,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;oBACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;oBACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;gBACnB,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;YACD,IAAI,CAAC,QAAQ,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAEpE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3D,MAAM,CAAC,CAAC;QACV,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;YACzB,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC;QAC/B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC","sourcesContent":["import { getAssetByID } from '@react-native/assets-registry/registry';\nimport { Platform } from 'expo-modules-core';\n\nimport { AssetMetadata, selectAssetSource } from './AssetSources';\nimport * as AssetUris from './AssetUris';\nimport { downloadAsync } from './ExpoAsset';\nimport * as ImageAssets from './ImageAssets';\nimport { getLocalAssetUri } from './LocalAssets';\nimport { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';\nimport resolveAssetSource from './resolveAssetSource';\n\n// @docsMissing\nexport type AssetDescriptor = {\n name: string;\n type: string;\n hash?: string | null;\n uri: string;\n width?: number | null;\n height?: number | null;\n};\n\ntype DownloadPromiseCallbacks = {\n resolve: () => void;\n reject: (error: Error) => void;\n};\n\nexport { AssetMetadata };\n\n/**\n * Android resource URL prefix.\n * @hidden\n */\nexport const ANDROID_EMBEDDED_URL_BASE_RESOURCE = 'file:///android_res/';\n\n/**\n * The `Asset` class represents an asset in your app. It gives metadata about the asset (such as its\n * name and type) and provides facilities to load the asset data.\n */\nexport class Asset {\n private static byHash = {};\n private static byUri = {};\n\n /**\n * The name of the asset file without the extension. Also without the part from `@` onward in the\n * filename (used to specify scale factor for images).\n */\n public name: string;\n /**\n * The extension of the asset filename.\n */\n public readonly type: string;\n /**\n * The MD5 hash of the asset's data.\n */\n public readonly hash: string | null = null;\n /**\n * A URI that points to the asset's data on the remote server. When running the published version\n * of your app, this refers to the location on Expo's asset server where Expo has stored your\n * asset. When running the app from Expo CLI during development, this URI points to Expo CLI's\n * server running on your computer and the asset is served directly from your computer. If you\n * are not using Classic Updates (legacy), this field should be ignored as we ensure your assets\n * are on device before running your application logic.\n */\n public readonly uri: string;\n /**\n * If the asset has been downloaded (by calling [`downloadAsync()`](#downloadasync)), the\n * `file://` URI pointing to the local file on the device that contains the asset data.\n */\n public localUri: string | null = null;\n /**\n * If the asset is an image, the width of the image data divided by the scale factor. The scale\n * factor is the number after `@` in the filename, or `1` if not present.\n */\n public width: number | null = null;\n /**\n * If the asset is an image, the height of the image data divided by the scale factor. The scale factor is the number after `@` in the filename, or `1` if not present.\n */\n public height: number | null = null;\n\n private downloading: boolean = false;\n\n /**\n * Whether the asset has finished downloading from a call to [`downloadAsync()`](#downloadasync).\n */\n public downloaded: boolean = false;\n\n private _downloadCallbacks: DownloadPromiseCallbacks[] = [];\n\n constructor({ name, type, hash = null, uri, width, height }: AssetDescriptor) {\n this.name = name;\n this.type = type;\n this.hash = hash;\n this.uri = uri;\n\n if (typeof width === 'number') {\n this.width = width;\n }\n if (typeof height === 'number') {\n this.height = height;\n }\n\n if (hash) {\n this.localUri = getLocalAssetUri(hash, type);\n if (this.localUri?.startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) {\n // Treat Android embedded resources as not downloaded state, because the uri is not direct accessible.\n this.uri = this.localUri;\n this.localUri = null;\n } else if (this.localUri) {\n this.downloaded = true;\n }\n }\n\n if (Platform.OS === 'web') {\n if (!name) {\n this.name = AssetUris.getFilename(uri);\n }\n if (!type) {\n this.type = AssetUris.getFileExtension(uri);\n }\n }\n }\n\n // @needsAudit\n /**\n * A helper that wraps `Asset.fromModule(module).downloadAsync` for convenience.\n * @param moduleId An array of `require('path/to/file')` or external network URLs. Can also be\n * just one module or URL without an Array.\n * @return Returns a Promise that fulfills with an array of `Asset`s when the asset(s) has been\n * saved to disk.\n * @example\n * ```ts\n * const [{ localUri }] = await Asset.loadAsync(require('./assets/snack-icon.png'));\n * ```\n */\n static loadAsync(moduleId: number | number[] | string | string[]): Promise<Asset[]> {\n const moduleIds = Array.isArray(moduleId) ? moduleId : [moduleId];\n return Promise.all(moduleIds.map((moduleId) => Asset.fromModule(moduleId).downloadAsync()));\n }\n\n // @needsAudit\n /**\n * Returns the [`Asset`](#asset) instance representing an asset given its module or URL.\n * @param virtualAssetModule The value of `require('path/to/file')` for the asset or external\n * network URL\n * @return The [`Asset`](#asset) instance for the asset.\n */\n static fromModule(\n virtualAssetModule: number | string | { uri: string; width: number; height: number }\n ): Asset {\n if (typeof virtualAssetModule === 'string') {\n return Asset.fromURI(virtualAssetModule);\n }\n if (\n typeof virtualAssetModule === 'object' &&\n 'uri' in virtualAssetModule &&\n typeof virtualAssetModule.uri === 'string'\n ) {\n const extension = AssetUris.getFileExtension(virtualAssetModule.uri);\n return new Asset({\n name: '',\n type: extension.startsWith('.') ? extension.substring(1) : extension,\n hash: null,\n uri: virtualAssetModule.uri,\n width: virtualAssetModule.width,\n height: virtualAssetModule.height,\n });\n }\n\n const meta = getAssetByID(virtualAssetModule);\n if (!meta) {\n throw new Error(`Module \"${virtualAssetModule}\" is missing from the asset registry`);\n }\n\n // Outside of the managed env we need the moduleId to initialize the asset\n // because resolveAssetSource depends on it\n if (!IS_ENV_WITH_LOCAL_ASSETS) {\n // null-check is performed above with `getAssetByID`.\n const { uri } = resolveAssetSource(virtualAssetModule)!;\n\n const asset = new Asset({\n name: meta.name,\n type: meta.type,\n hash: meta.hash,\n uri,\n width: meta.width,\n height: meta.height,\n });\n\n // For images backward compatibility,\n // keeps localUri the same as uri for React Native's Image that\n // works fine with drawable resource names.\n if (Platform.OS === 'android' && !uri.includes(':') && (meta.width || meta.height)) {\n asset.localUri = asset.uri;\n asset.downloaded = true;\n }\n\n Asset.byHash[meta.hash] = asset;\n return asset;\n }\n\n return Asset.fromMetadata(meta);\n }\n\n // @docsMissing\n static fromMetadata(meta: AssetMetadata): Asset {\n // The hash of the whole asset, not to be confused with the hash of a specific file returned\n // from `selectAssetSource`\n const metaHash = meta.hash;\n if (Asset.byHash[metaHash]) {\n return Asset.byHash[metaHash];\n }\n\n const { uri, hash } = selectAssetSource(meta);\n const asset = new Asset({\n name: meta.name,\n type: meta.type,\n hash,\n uri,\n width: meta.width,\n height: meta.height,\n });\n Asset.byHash[metaHash] = asset;\n return asset;\n }\n\n // @docsMissing\n static fromURI(uri: string): Asset {\n if (Asset.byUri[uri]) {\n return Asset.byUri[uri];\n }\n\n // Possibly a Base64-encoded URI\n let type = '';\n if (uri.indexOf(';base64') > -1) {\n type = uri.split(';')[0].split('/')[1];\n } else {\n const extension = AssetUris.getFileExtension(uri);\n type = extension.startsWith('.') ? extension.substring(1) : extension;\n }\n\n const asset = new Asset({\n name: '',\n type,\n hash: null,\n uri,\n });\n\n Asset.byUri[uri] = asset;\n\n return asset;\n }\n\n // @needsAudit\n /**\n * Downloads the asset data to a local file in the device's cache directory. Once the returned\n * promise is fulfilled without error, the [`localUri`](#localuri) field of this asset points\n * to a local file containing the asset data. The asset is only downloaded if an up-to-date local\n * file for the asset isn't already present due to an earlier download. The downloaded `Asset`\n * will be returned when the promise is resolved.\n * @return Returns a Promise which fulfills with an `Asset` instance.\n */\n async downloadAsync(): Promise<this> {\n if (this.downloaded) {\n return this;\n }\n if (this.downloading) {\n await new Promise<void>((resolve, reject) => {\n this._downloadCallbacks.push({ resolve, reject });\n });\n return this;\n }\n this.downloading = true;\n\n try {\n if (Platform.OS === 'web') {\n if (ImageAssets.isImageType(this.type)) {\n const { width, height, name } = await ImageAssets.getImageInfoAsync(this.uri);\n this.width = width;\n this.height = height;\n this.name = name;\n } else {\n this.name = AssetUris.getFilename(this.uri);\n }\n }\n this.localUri = await downloadAsync(this.uri, this.hash, this.type);\n\n this.downloaded = true;\n this._downloadCallbacks.forEach(({ resolve }) => resolve());\n } catch (e) {\n this._downloadCallbacks.forEach(({ reject }) => reject(e));\n throw e;\n } finally {\n this.downloading = false;\n this._downloadCallbacks = [];\n }\n return this;\n }\n}\n"]}
\ No newline at end of file
diff --git a/node_modules/expo-asset/src/Asset.fx.ts b/node_modules/expo-asset/src/Asset.fx.ts
index 2288e2d..9a71d9a 100644
--- a/node_modules/expo-asset/src/Asset.fx.ts
+++ b/node_modules/expo-asset/src/Asset.fx.ts
@@ -1,4 +1,4 @@
-import { Asset } from './Asset';
+import { Asset, ANDROID_EMBEDDED_URL_BASE_RESOURCE } from './Asset';
import { IS_ENV_WITH_LOCAL_ASSETS } from './PlatformUtils';
import { setCustomSourceTransformer } from './resolveAssetSource';
@@ -9,6 +9,9 @@ if (IS_ENV_WITH_LOCAL_ASSETS) {
// Bundler is using the hashAssetFiles plugin if and only if the fileHashes property exists
if (resolver.asset.fileHashes) {
const asset = Asset.fromMetadata(resolver.asset);
+ if (asset.uri.startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) {
+ return resolver.resourceIdentifierWithoutScale();
+ }
return resolver.fromSource(asset.downloaded ? asset.localUri! : asset.uri);
} else {
return resolver.defaultAsset();
diff --git a/node_modules/expo-asset/src/Asset.ts b/node_modules/expo-asset/src/Asset.ts
index 3193760..84a51df 100644
--- a/node_modules/expo-asset/src/Asset.ts
+++ b/node_modules/expo-asset/src/Asset.ts
@@ -26,6 +26,12 @@ type DownloadPromiseCallbacks = {
export { AssetMetadata };
+/**
+ * Android resource URL prefix.
+ * @hidden
+ */
+export const ANDROID_EMBEDDED_URL_BASE_RESOURCE = 'file:///android_res/';
+
/**
* The `Asset` class represents an asset in your app. It gives metadata about the asset (such as its
* name and type) and provides facilities to load the asset data.
@@ -95,7 +101,11 @@ export class Asset {
if (hash) {
this.localUri = getLocalAssetUri(hash, type);
- if (this.localUri) {
+ if (this.localUri?.startsWith(ANDROID_EMBEDDED_URL_BASE_RESOURCE)) {
+ // Treat Android embedded resources as not downloaded state, because the uri is not direct accessible.
+ this.uri = this.localUri;
+ this.localUri = null;
+ } else if (this.localUri) {
this.downloaded = true;
}
}