react-native
react-native copied to clipboard
Replace RCTLocalAssetImageLoader to RCTBundleAssetImageLoader
Summary:
From the video below, we can see that the UI thread has dropped many frames, and it would become worse if there are multiple images.
If an image is located in the sandbox of the disk, we cannot load it using RCTLocalAssetImageLoader
because RCTLocalAssetImageLoader.requiresScheduling
is set to true, which loads the data on the UI thread and causes main thread stuttering. This will affect libraries such as react-native-code-push
and others that save images to the sandbox of the disk.
Therefore, we should replace RCTLocalAssetImageLoader.canLoadImageURL
from RCTIsLocalAssetURL(url)
to RCTIsBundleAssetURL(url)
. Similarly, we should rename the entire RCTLocalAssetImageLoader
file with RCTBundleAssetImageLoader
, which ignores images in the disk sandbox. And finally these images will be loaded from NSURLRequest
, and our UI thread will run smoothly again.
https://user-images.githubusercontent.com/20135674/236368418-8933a2c6-549c-40d3-a551-81b492fe41d5.mp4
Changelog:
[IOS] [Breaking] - Replace RCTLocalAssetImageLoader
to RCTBundleAssetImageLoader
Test Plan:
Test Code:
constructor(props) {
super(props)
this.state = {
bundle_image: require('./large_image.png'),
sandbox_image: '',
source: null,
isLoading: false,
}
}
render() {
console.log('render', this.state)
return (
<View style={{ flex: 1, padding: 50, backgroundColor: 'white'}}>
<View style={{ flexDirection: 'row', alignItems: 'center', height: 70}}>
{
[{ title: 'Save Image To SandBox', onPress: () => {
let image = Image.resolveAssetSource(this.state.bundle_image)
console.log(image.uri)
this.setState({ isLoading: true })
RNFetchBlob.config({ fileCache: true, appendExt: "png" }).fetch("GET", image.uri).then(response => {
let path = response.path()
path = /^file:\/\//.test(path) ? path : 'file://' + path
console.log(path)
this.state.sandbox_image = path
}).finally(() => this.setState({ isLoading: false }))
}}, { title: 'Load From SandBox', onPress: () => {
this.setState({ source: { uri: this.state.sandbox_image } })
}}, { title: 'Clear', onPress: () => {
this.setState({
source: null,
isLoading: false
})
}}, { title: 'Load From Bundle', onPress: () => {
this.setState({ source: this.state.bundle_image })
}}].map((item, index) => {
return (
<Pressable
key={index}
style={{ height: '100%', justifyContent: 'center', flex: 1, borderWidth: 1, borderColor: 'black', marginLeft: index > 0 ? 15 : 0 }}
onPress={item.onPress}
>
<Text style={{ textAlign: 'center' }}>{item.title}</Text>
</Pressable>
)
})
}
</View>
<ActivityIndicator style={{ marginTop: 10 }} animating={this.state.isLoading} />
<Image
key={`${this.state.source}`}
style={{ marginTop: 20, width: 200, height: 200 }}
source={this.state.source}
onProgress={({ nativeEvent }) => console.log(nativeEvent)}
onLoadStart={() => this.setState({ isLoading: true })}
onLoadEnd={() => this.setState({ isLoading: false })}
/>
</View>
)
}
It needs to be tested in three environments: [Simulator_Debug, RealDevice_Debug, RealDevice_Release]
- Open
Perf Monitor
(RealDevice_Release can be skipped) - Click
Save Image to SandBox
- Wait for the loading to end and click
Load From SandBox
- Verify that the image can be loaded successfully
- Verify that the
UI thread
keeps60 FPS
(RealDevice_Release can be skipped) - Click
Clear
- Repeat steps [3, 4, 5, 6] several times
- Click
Load From Bundle
to verify that the bundle image can be loaded successfully
Simulator_Debug
https://user-images.githubusercontent.com/20135674/236369344-ee1b8ff1-2d49-49f3-a322-d973f4adf3e7.mp4
RealDevice_Debug
https://user-images.githubusercontent.com/20135674/236369356-fe440b2b-f72a-49be-b63c-b4bf709dac8c.mp4
RealDevice_Release
https://user-images.githubusercontent.com/20135674/236369365-8a6a5c2f-09ad-4c90-b6bd-41e8a5e3aa7f.mp4
Platform | Engine | Arch | Size (bytes) | Diff |
---|---|---|---|---|
android | hermes | arm64-v8a | 8,757,654 | +1 |
android | hermes | armeabi-v7a | 8,070,226 | -1 |
android | hermes | x86 | 9,250,252 | +2 |
android | hermes | x86_64 | 9,099,402 | +0 |
android | jsc | arm64-v8a | 9,319,016 | -1 |
android | jsc | armeabi-v7a | 8,508,917 | +3 |
android | jsc | x86 | 9,382,494 | +1 |
android | jsc | x86_64 | 9,635,734 | +1 |
Base commit: e66c906454e40308f68a110f8a9d6258edda2352 Branch: main
@dmytrorykun Hello, could you give me some suggestions for my PR? Because in fact, this bug has existed for a long time
@dmytrorykun has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.
@cipolleschi Thanks, I have modified it according to your suggestion. At the same time, I also modified RCTLocalAssetImageLoader.canLoadImageURL
, because although we replaced RCTAppSetupUtils
, when RCTImageLoader._loadersProvider
is empty, RCTImageLoader._loaders
will still import RCTLocalAssetImageLoader
,
https://github.com/facebook/react-native/blob/3006a33bafc64a1fd699d8895c74fadac84654ad/packages/react-native/Libraries/Image/RCTImageLoader.mm#L189-L194
So for accurate testing, I also modified RCTLocalAssetImageLoader.canLoadImageURL
, since it will be deprecated, so I guess we can modify it
This CI error test_ios_rntester_hermes_xcode_integration
is because of RCTLocalizedString.h
, the FBcoreLocalexxHash48
and RCTLocalizedStringFromKey
Yeah, a colleague has fixed the iOS jobs, but there are other jobs that are broken, unfortunately...
/rebase
/rebase
@cipolleschi Thanks. All the checks have been passed.
@dmytrorykun could you reimport the PR, please?
@dmytrorykun Hello. Bump^
@dmytrorykun has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.
@cipolleschi Hello. It has been reimported, Is there anything else I can help here? please feel free to ask
@hellohublot the PR is currently being reviewed internally, no action is needed from your part.
@dmytrorykun merged this pull request in facebook/react-native@b675667a47f651e85d66e12daaeeec00371d1b23.