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

refactor(android): optimize ImageView image download peformance #13078

Merged
merged 11 commits into from
Sep 23, 2021

Conversation

jquick-axway
Copy link
Contributor

@jquick-axway jquick-axway commented Sep 21, 2021

JIRA:

Summary:

  • TIMOB-28538 Optimized ImageView image url download performance when:
    • HTTP failure response occurs.
    • When image is not cached. Happens when HTTP response contains No-Cache.
  • TIMOB-18786 Fixed rare bug where wrong image is sometimes loaded due to hash code collision.
    • Now uses new TiDrawableReference.Key class which guarantees uniqueness.
    • No longer uses hash code as a key in a hash table.
  • Removed TiImageLruCache and replaced with TiImageCache.
    • Old class kept hard references to bitmaps preventing large photos from being garbage collected.
    • New cache keeps soft references to all bitmaps allowing the Java garbage collector to better handle this when running low on heap memory.
    • New cache stores EXIF orientation. Prevents the need to read image binary more than once.
  • Made singleton creation to be thread safe in several classes.
    • Fixed TiDownloadManager, TiLoadImageManager, TiFileHelper, and TiBlobLruCache.
    • Was wrongly creating multiple instances of TiDownloadManager on startup if there were several ImageViews on screen. Can cause performance initial performance issues since thread pool size would be ignored.
  • Changed ImageView "decodeRetries" property default from 5 to 2.

ListView Complex Template Test:

  1. Build and run ListViewTemplateComplexTest.js attached to TIMOB-28538 on Android.
  2. Scroll up and down in the ListView.
  3. Verify it scrolls quickly. (Might stutter initially but will be smooth from there.)
  4. Note that there will be 1 image missing every several rows and will log HTTP response errors. (This is okay and verifies HTTP error responses are better handled now.)

ListView No-Cache Test:

  1. Build and run ListViewImageRemoteNoCacheTest.js attached to TIMOB-28538 on Android.
  2. Scroll up and down in the ListView and verify it now scrolls quickly.
  3. Verify all images are shown in portrait form. (These images have various EXIF orientations.)
  4. Note that it might take a while to load the images since it involves HTTP redirects, but list should still scroll quickly.

Image Hash Code Collision Test:

  1. Copy FP.jpg and G1.jpg attached to TIMOB-18786 to project's ./assets/images folder.
  2. Use ImageHashTest.js attached to TIMOB-18786 as your "app.js".
  3. Build and run on Android.
  4. Verify the 2 images displayed are different. (Used to show the same image.)

@build
Copy link
Contributor

build commented Sep 21, 2021

Warnings
⚠️ Tests have failed, see below for more information.
Messages
📖 ❌ 6 tests have failed There are 6 tests failing and 1162 skipped out of 20666 total tests.
📖 ✊ The commits in this PR match our conventions! Feel free to Rebase and Merge this PR when ready.
📖

💾 Here's the generated SDK zipfile.

📖 👍 Hey!, You deleted more code than you added. That's awesome!

Tests:

ClassnameNameTimeError
ios.ipad.Titanium.Blobresize very large image (15.0.0)10.908
Error: Timeout of 10000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (app.js)
postlayout@file:///Users/build/Library/Developer/CoreSimulator/Devices/E1EC1393-A4C0-4233-A046-D81785FB996D/data/Containers/Bundle/Application/735BF9C5-6F7B-4E8B-AABA-DFB57BDC4015/mocha.app/ti.blob.test.js:464:13
ios.ipad.Titanium.UI.WebView.userAgent (15.0.0)60.001
Error: Timeout of 60000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (app.js)
ios.iphone.Titanium.Blobresize very large image (15.0.0)10.677
Error: Timeout of 10000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (app.js)
postlayout@file:///Users/build/Library/Developer/CoreSimulator/Devices/EA9D4E3D-F41B-435D-BB03-589B921A1FF7/data/Containers/Bundle/Application/90E5A826-4A93-4EC7-9982-3D78876A0AAC/mocha.app/ti.blob.test.js:464:13
ios.iphone.Titanium.UI.View.borderRadius corners1 value with shadow effect (15.0.0)0.035
Error: expected 'Ti.UI.View' to match image ('snapshots/borderRadiusWithShadow_30px@3x~iphone.png')
    mismatched pixels. allowed: 0, actual: 18
value@file:///Users/build/Library/Developer/CoreSimulator/Devices/EA9D4E3D-F41B-435D-BB03-589B921A1FF7/data/Containers/Bundle/Application/90E5A826-4A93-4EC7-9982-3D78876A0AAC/mocha.app/node_modules/should/cjs/should.js:356:23
postlayout@file:///Users/build/Library/Developer/CoreSimulator/Devices/EA9D4E3D-F41B-435D-BB03-589B921A1FF7/data/Containers/Bundle/Application/90E5A826-4A93-4EC7-9982-3D78876A0AAC/mocha.app/ti.ui.view.test.js:1264:39
ios.iphone.Titanium.UI.WebView.userAgent (15.0.0)60.002
Error: Timeout of 60000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (app.js)
ios.macos.Titanium.UI.WebView.userAgent (11.3.1)75.16
Error: Timeout of 60000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (app.js)

Generated by 🚫 dangerJS against f26bfbb

Copy link
Contributor

@garymathews garymathews left a comment

Choose a reason for hiding this comment

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

Although unrelated to your changes, I'm seeing that TiDrawableReference is retrying a bad image URL up to seven times. There's old code that does this due to a bug from Android 2.3 😱

We should change DEFAULT_DECODE_RETRIES to 2, amend this for to:

for (int i = 1; i < decodeRetries; i++) {

and increase this timeout to 1000ms or 3000ms

@build build added the docs label Sep 22, 2021
Copy link
Contributor

@garymathews garymathews left a comment

Choose a reason for hiding this comment

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

CR: PASS
FT: PASS

Here's a modified ListViewImageRemoteNoCacheTest.js test so you can verify the images that are loaded are not the same.

const imageUrls = [
	'https://github.com/appcelerator/titanium_mobile/raw/master/tests/Resources/ExifFlipHorizontal.jpg',
	'https://github.com/appcelerator/titanium_mobile/raw/master/tests/Resources/ExifFlipVertical.jpg',
	'https://github.com/appcelerator/titanium_mobile/raw/master/tests/Resources/ExifRotate180.jpg',
	'https://github.com/appcelerator/titanium_mobile/raw/master/tests/Resources/ExifRotate270.jpg',
	'https://github.com/appcelerator/titanium_mobile/raw/master/tests/Resources/ExifRotate90.jpg',
	'https://github.com/appcelerator/titanium_mobile/raw/master/tests/Resources/ExifTranspose.jpg',
	'https://github.com/appcelerator/titanium_mobile/raw/master/tests/Resources/ExifTransverse.jpg',
];
function createListViewSectionItems() {
	const items = [];

	for (let index = 1; index <= 1000; index++) {
		const url = imageUrls[index % imageUrls.length];

		items.push({ properties: {
			title: `Row ${index}: ${url.split('/')[9]}`,
			image: url
		}});
	}
	return items;
}
const window = Ti.UI.createWindow();
window.add(
	Ti.UI.createListView({
		templates: {
			template: {
				properties: { layout: 'vertical' },
				childTemplates: [
					{
						type: 'Ti.UI.ImageView',
						bindId: 'image',
						properties: { width: 160 }
					},
					{
						type: 'Ti.UI.Label',
						bindId: 'title',
						properties: {
							color: 'black',
							font: { fontSize: '20dp', fontWeight:'bold' }
						}
					}
				]
			}
		},
		defaultItemTemplate: 'template',
		sections: [
			Ti.UI.createListSection({
				headerTitle: 'ListView',
				items: createListViewSectionItems(),
			})
		],
	})
);
window.open();

Also verified error event still triggers, as this test was failing on Jenkins.

const win = Ti.UI.createWindow();
const img = Ti.UI.createImageView();

img.addEventListener('error', e => {
	alert(`Error loading image:\n\n${e.image}`);
});
img.image = 'https://invalid.host.com/image.jpg';

win.add(img);
win.open();

@lokeshchdhry
Copy link
Contributor

FR Passed.

@lokeshchdhry lokeshchdhry merged commit bdf7e68 into tidev:master Sep 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants