-
Notifications
You must be signed in to change notification settings - Fork 24k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Load local assets synchronously to prevent image flicker #8102
Conversation
By analyzing the blame information on this pull request, we identified @nicklockwood and @rigdern to be potential reviewers. |
Related issue #2570 |
@@ -480,6 +470,18 @@ - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)image | |||
__block void(^cancelLoad)(void) = nil; | |||
__weak RCTImageLoader *weakSelf = self; | |||
|
|||
// Load handlers are executed before the generic caching logic so they are responsible | |||
// for caching the images if necessary. | |||
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:imageURLRequest.URL]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving this here means that all photo loaders implementing this API will now skip caching. This includes for example the RCTPhotoLibraryImageLoader
and RCTImageLoaderHelpers
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, I did a search for the protocol in xcode but it probably only searched in the current project. Does this still seems like the right approach if there are other loaders that depend on the decoded image cache?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe passing an additional parameter to the completion block of RCTImageURLLoader so loaders can decide if the result will be cached or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably need another approach. To clarify: your issue is that we first have to schedule this on the _URLCacheQueue
and then we go to the main queue right? Keep in mind that we also do this for memory reasons, if you start loading a ton of images on the main thread, skipping our queueing, you'll run into OOMs as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather expose an (optional) API on the loadHandler asking for things like shouldCacheImageForURL
? Then if you want to, you can skip that part. That doesn't solve the threading approach though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we have to skip the URLCache queue and also avoid caching the resulting image as this is already handled by UIImage imageNamed, we definetly don't want to cache then twice. Afaik memory should not be an issue since it only applies to local images and they are usually small icons or a few large images.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could always add a prop like async to the image component to control this behaviour but i think it should default to loading synchronously.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually isn't this approach less memory intensive but potentially slower? We are loading images one by one on the main thread so there should only ever be one in memory at a given time. The url queue is useful for loading images asynchronously since there may be many image decoding happening at the same time so huge peaks of memory usage. The only thing that could happen is that many images are loaded on the url queue at the same time while images are loading on the main thread but since the main thread is loading only one image it should not be an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add 2 optional methods to the URLLoader protocol:
- shouldCacheLoadedImages (i.e. does the loader have its own image cache, defaults to YES)
- requiresScheduling (i.e. do we need to manage the invocation of this loader on our URL queue, defaults to YES)
Please add to your test plan that network images and photo library images are still properly cached. |
40dbc26
to
e837958
Compare
@javache Update as per our discussion, also tested the photo library images behave the same as before so the loader is executed on the url cache queue and the result image is cached. Also tested that network images are still cached properly. |
{ | ||
__block volatile uint32_t cancelled = 0; | ||
__block void(^cancelLoad)(void) = nil; | ||
__weak RCTImageLoader *weakSelf = self; | ||
// Find suitable image URL loader | ||
__block id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:imageURLRequest.URL]; | ||
__block BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You only need to mark variables as __block if you plan to re-assign them from inside a block. That doesn't seem to be the case here.
@javache Fixed! |
@facebook-github-bot import |
Thanks for importing. If you are an FB employee go to Phabricator to review. |
Update: testing this a bit more thoroughly internally |
@javache Any updates on this? |
It's been a while since the last commit was reviewed and the labels show this pull request needs review. Based on the blame information for the files in this pull request we identified @nicklockwood as a potential reviewer. Could you take a look please or cc someone with more context? |
Should land this week. Sorry for the delay. |
Summary: This uses `[UIImage imageNamed:]` to load local assets that are bundled using `require('../image/path.png')` and makes sure it is done synchronously on the main queue to prevent images from flickering. This improves user experience a lot when using large local images and prevents icon flickers to match the behaviour of most native apps. This adds to methods to the ImageLoader protocol, one to tell if the image loader must be executed on the url cache queue and one to tell if the result of the image loader should be cached. I then use these to make the LocalImageLoader bypass the url cache queue and avoid caching images twice. Note that this doesn't affect debug builds since images are loaded from the packager. I'm not sure if we want to still support async loading of local images as I'm not sure how much of a perf difference this will make. Maybe someone at fb can benchmark this see how it affects your apps but there wasn't a noticeable one in mine. Also I only enabled this for loading png and jpg im Closes facebook/react-native#8102 Reviewed By: bnham Differential Revision: D3433647 Pulled By: javache fbshipit-source-id: 37bd6aff20c0465c163db3cdbcaeaedff55f7b1f
Summary: This uses `[UIImage imageNamed:]` to load local assets that are bundled using `require('../image/path.png')` and makes sure it is done synchronously on the main queue to prevent images from flickering. This improves user experience a lot when using large local images and prevents icon flickers to match the behaviour of most native apps. This adds to methods to the ImageLoader protocol, one to tell if the image loader must be executed on the url cache queue and one to tell if the result of the image loader should be cached. I then use these to make the LocalImageLoader bypass the url cache queue and avoid caching images twice. Note that this doesn't affect debug builds since images are loaded from the packager. I'm not sure if we want to still support async loading of local images as I'm not sure how much of a perf difference this will make. Maybe someone at fb can benchmark this see how it affects your apps but there wasn't a noticeable one in mine. Also I only enabled this for loading png and jpg im Closes facebook#8102 Reviewed By: bnham Differential Revision: D3433647 Pulled By: javache fbshipit-source-id: 37bd6aff20c0465c163db3cdbcaeaedff55f7b1f
Hi, is this in RN yet? The flickering issue still persists... |
This is in version 0.32+. Dont forget that it only affects when building in release mode and loading the bundle from disk. |
Great, thank you! I was still on 0.31 and didn't see it in the change log. Must have missed it. Cheers!
|
I experienced this still in 0.32.0 |
Is there a similar fix for Android (using RN 0.63.2)? |
This uses
[UIImage imageNamed:]
to load local assets that are bundled usingrequire('../image/path.png')
and makes sure it is done synchronously on the main queue to prevent images from flickering. This improves user experience a lot when using large local images and prevents icon flickers to match the behaviour of most native apps.This adds to methods to the ImageLoader protocol, one to tell if the image loader must be executed on the url cache queue and one to tell if the result of the image loader should be cached. I then use these to make the LocalImageLoader bypass the url cache queue and avoid caching images twice.
Note that this doesn't affect debug builds since images are loaded from the packager.
I'm not sure if we want to still support async loading of local images as I'm not sure how much of a perf difference this will make. Maybe someone at fb can benchmark this see how it affects your apps but there wasn't a noticeable one in mine. Also I only enabled this for loading png and jpg images, not sure if we want to add more there.
Test plan
Tested my branch on an app that makes heavy use of local images and made sure there is no flicker and that the code is executed synchronously. Also did some testing in UIExplorer to make sure everything still looked fine.