Skip to content
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

Closed

Conversation

janicduplessis
Copy link
Contributor

@janicduplessis janicduplessis commented Jun 14, 2016

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 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.

@facebook-github-bot
Copy link
Contributor

By analyzing the blame information on this pull request, we identified @nicklockwood and @rigdern to be potential reviewers.

@facebook-github-bot facebook-github-bot added GH Review: review-needed CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. labels Jun 14, 2016
@janicduplessis
Copy link
Contributor Author

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];
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Member

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:

  1. shouldCacheLoadedImages (i.e. does the loader have its own image cache, defaults to YES)
  2. requiresScheduling (i.e. do we need to manage the invocation of this loader on our URL queue, defaults to YES)

@javache
Copy link
Member

javache commented Jun 14, 2016

Please add to your test plan that network images and photo library images are still properly cached.

@janicduplessis
Copy link
Contributor Author

janicduplessis commented Jun 14, 2016

@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)] ?
Copy link
Member

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.

@janicduplessis
Copy link
Contributor Author

@javache Fixed!

@javache
Copy link
Member

javache commented Jun 14, 2016

@facebook-github-bot import

@ghost
Copy link

ghost commented Jun 14, 2016

Thanks for importing. If you are an FB employee go to Phabricator to review.

@javache
Copy link
Member

javache commented Jun 20, 2016

Update: testing this a bit more thoroughly internally

@janicduplessis
Copy link
Contributor Author

@javache Any updates on this?

@ghost ghost added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 12, 2016
@ghost
Copy link

ghost commented Jul 15, 2016

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?

@ghost ghost added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 15, 2016
@javache
Copy link
Member

javache commented Jul 19, 2016

Should land this week. Sorry for the delay.

@ghost ghost closed this in 5903949 Jul 21, 2016
rozele pushed a commit to microsoft/react-native-windows that referenced this pull request Jul 26, 2016
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
mpretty-cyro pushed a commit to HomePass/react-native that referenced this pull request Aug 25, 2016
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
@AlexDM0
Copy link

AlexDM0 commented Sep 6, 2016

Hi, is this in RN yet? The flickering issue still persists...

@janicduplessis
Copy link
Contributor Author

This is in version 0.32+. Dont forget that it only affects when building in release mode and loading the bundle from disk.

@AlexDM0
Copy link

AlexDM0 commented Sep 6, 2016

Great, thank you!

I was still on 0.31 and didn't see it in the change log. Must have missed it.

Cheers!

On 06 Sep 2016, at 18:56, Janic Duplessis notifications@github.com wrote:

This is in version 0.32+. Dont forget that it only affects when building in release mode and loading the bundle from disk.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.

@waqas19921
Copy link

I experienced this still in 0.32.0

@JetA2
Copy link

JetA2 commented Jan 28, 2021

Is there a similar fix for Android (using RN 0.63.2)?

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants