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

Use soft references to hold sprites #297

Merged

Conversation

akwizgran
Copy link
Contributor

This branch modifies the generator to cut the sprite sheets into strips that can be loaded separately. Each strip is held by a soft reference so it's released when the app is under memory pressure. To avoid reloading a whole strip each time an emoji needs to be displayed, the most recently used bitmaps are kept in an LRU cache.

Please test the memory usage and loading speed under common scenarios, such as scrolling through the emoji chooser or reading a conversation while the app is under memory pressure. I've chosen an arbitrary size for the LRU cache, but a smaller or larger cache may offer a better time/space tradeoff.

Copy link
Owner

@vanniktech vanniktech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good.

I can fix the style changes.

@rubengees can you also have a look at the changes here, please?

sheet = BitmapFactory.decodeResource(context.getResources(), R.drawable.emoji_google_sheet);
strip = (Bitmap) STRIP_REFS[x].get();
if (strip == null) {
Log.i(getClass().getSimpleName(), "Loading strip " + x);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this logging here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed, it's just to get a picture of what's happening while testing the branch.

@rubengees
Copy link
Collaborator

rubengees commented Aug 10, 2018 via email

@akwizgran
Copy link
Contributor Author

Added two commits to fix style errors and remove logging.

@rocboronat
Copy link
Contributor

Testes this PR on production, and it fixes the OOM issue.

Hats off, @akwizgran! 🎩

Copy link
Collaborator

@rubengees rubengees left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! 👏

final Bitmap strip = loadStrip(context);
final Bitmap cut = Bitmap.createBitmap(strip, 1,
y * SPRITE_SIZE_INC_BORDER + 1, SPRITE_SIZE, SPRITE_SIZE);
BITMAP_CACHE.put(key, cut);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tested the performance without this second cache? I could imagine that cutting the image whenever requested would make no big difference (since the stripes are somewhat small) and that would also help with memory pressure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the LRU cache because otherwise it was possible to trigger a lot of loading and unloading of strips when the app was under memory pressure. For example, I'd be looking at a conversation with a few emoji visible, the GC would run and unload the strips backing the emoji, then the next time the conversation was redrawn, the strips would be reloaded. I didn't do any rigorous performance testing, but my guess was that this repeated loading was probably worse for performance overall than keeping a few recently used sprites in memory.

If you'd like to do a proper performance comparison, the version without the LRU cache is here: https://github.com/akwizgran/Emoji/tree/sprite-strips-soft-refs

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see! I'll compare both solutions.

generator/template/Emoji.java Outdated Show resolved Hide resolved
generator/template/Emoji.java Outdated Show resolved Hide resolved
generator/template/Emoji.java Show resolved Hide resolved
if (sheet != null) {
private Bitmap loadStrip(final Context context) {
Bitmap strip = (Bitmap) STRIP_REFS[x].get();
if (strip == null) {
synchronized (LOCK) {
Copy link
Collaborator

@rubengees rubengees Aug 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you synchronize getDrawable, the synchronization here can be removed.

generator/package.json Show resolved Hide resolved
generator/index.js Outdated Show resolved Hide resolved
generator/index.js Outdated Show resolved Hide resolved
@vanniktech
Copy link
Owner

@akwizgran will you address the feedback?

@akwizgran
Copy link
Contributor Author

@vanniktech sorry for the delay, I'll work on this tomorrow.

@akwizgran
Copy link
Contributor Author

I've addressed the review comments but haven't had time to re-test the branch. I'll do that on Monday.

This is a false positive due to the resource ID being generated dynamically.
@codecov
Copy link

codecov bot commented Sep 3, 2018

Codecov Report

Merging #297 into master will decrease coverage by 0.17%.
The diff coverage is 0%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #297      +/-   ##
==========================================
- Coverage   26.01%   25.84%   -0.18%     
==========================================
  Files          24       25       +1     
  Lines         911      917       +6     
  Branches       99       99              
==========================================
  Hits          237      237              
- Misses        655      661       +6     
  Partials       19       19
Impacted Files Coverage Δ
...main/java/com/vanniktech/emoji/emoji/CacheKey.java 0% <0%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a3c52dc...8c2d9d5. Read the comment docs.

@vanniktech
Copy link
Owner

What's unused? I'll take a closer look at this later / tomorrow.

@akwizgran
Copy link
Contributor Author

I've tested the branch and fixed a lint failure caused by dynamically generating the resource IDs.

@akwizgran
Copy link
Contributor Author

What's unused? I'll take a closer look at this later / tomorrow.

Lint considers the sprite strips unused because their resource IDs don't appear in the code. This is a false positive because the resource ID is generated dynamically based on the x coordinate of the sprite.

Copy link
Owner

@vanniktech vanniktech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good from my side. Tested it out quickly and it worked nicely.

I'll ask for another review from @rubengees

Copy link
Collaborator

@rubengees rubengees left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great.

I have compared the performance with the cache and without. For me on my old, bad device, without the cache performs better.
But I am fine with either way, as both work reasonably good.

generator/index.js Show resolved Hide resolved
@vanniktech vanniktech merged commit 2a238cc into vanniktech:master Sep 11, 2018
@vanniktech
Copy link
Owner

Thank you so much for this. I'll leave this in master for a while and hopefully we'll get some feedback.

@daniele-athome
Copy link

I've been using this since a few days ago and it's working very well so far. Thank you all!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants