react-native icon indicating copy to clipboard operation
react-native copied to clipboard

Replace RCTLocalAssetImageLoader to RCTBundleAssetImageLoader

Open hellohublot opened this issue 1 year ago • 8 comments

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]

  1. Open Perf Monitor (RealDevice_Release can be skipped)
  2. Click Save Image to SandBox
  3. Wait for the loading to end and click Load From SandBox
  4. Verify that the image can be loaded successfully
  5. Verify that the UI thread keeps 60 FPS (RealDevice_Release can be skipped)
  6. Click Clear
  7. Repeat steps [3, 4, 5, 6] several times
  8. 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

hellohublot avatar May 03 '23 16:05 hellohublot

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

analysis-bot avatar May 04 '23 06:05 analysis-bot

@dmytrorykun Hello, could you give me some suggestions for my PR? Because in fact, this bug has existed for a long time

hellohublot avatar Jun 02 '23 04:06 hellohublot

@dmytrorykun has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

facebook-github-bot avatar Jun 05 '23 13:06 facebook-github-bot

@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

hellohublot avatar Jun 13 '23 14:06 hellohublot

This CI error test_ios_rntester_hermes_xcode_integration is because of RCTLocalizedString.h, the FBcoreLocalexxHash48 and RCTLocalizedStringFromKey

hellohublot avatar Jun 13 '23 14:06 hellohublot

Yeah, a colleague has fixed the iOS jobs, but there are other jobs that are broken, unfortunately...

cipolleschi avatar Jun 13 '23 18:06 cipolleschi

/rebase

cipolleschi avatar Jun 13 '23 18:06 cipolleschi

/rebase

cipolleschi avatar Jun 14 '23 14:06 cipolleschi

@cipolleschi Thanks. All the checks have been passed.

hellohublot avatar Jun 16 '23 07:06 hellohublot

@dmytrorykun could you reimport the PR, please?

cipolleschi avatar Jun 16 '23 17:06 cipolleschi

@dmytrorykun Hello. Bump^

hellohublot avatar Jun 27 '23 03:06 hellohublot

@dmytrorykun has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

facebook-github-bot avatar Jun 28 '23 09:06 facebook-github-bot

@cipolleschi Hello. It has been reimported, Is there anything else I can help here? please feel free to ask

hellohublot avatar Jul 03 '23 23:07 hellohublot

@hellohublot the PR is currently being reviewed internally, no action is needed from your part.

dmytrorykun avatar Jul 04 '23 12:07 dmytrorykun

@dmytrorykun merged this pull request in facebook/react-native@b675667a47f651e85d66e12daaeeec00371d1b23.

facebook-github-bot avatar Jul 20 '23 18:07 facebook-github-bot