Load assets from same folder as JSbundle (Android)

Summary:
https://github.com/facebook/react-native/issues/3679 was only partially fixed as the behaviour only works on iOS. This implements the same behaviour for Android. If the JSBundle was loaded from the assets folder, this will load images from the built-in resources. Else, load the image from the same folder as the JS bundle.

EDIT: For added clarity:

On iOS,
Bundle Location: 'file:///Path/To/Sample.app/main.bundle'
httpServerLocation: '/assets/module/a/'
Name: 'logo'
type: 'png'
**Resolved Asset location: '/Path/To/Sample.app/assets/module/a/logo.png'**

On Android,
Bundle Location: 'file:///sdcard/Path/To/main.bundle'
httpServerLocation: '/assets/module/a/',
name: 'logo'
type: 'png'
**Resolved Asset location: 'file:///sdcard/Path/To/drawable_mdpi/module_a_logo.png'**
Closes https://github.com/facebook/react-native/pull/4527

Reviewed By: svcscm

Differential Revision: D2788005

Pulled By: mkonicek

fb-gh-sync-id: 3f6462a7ee6370a92dd6727ac422c5de346c3ff1
This commit is contained in:
Geoffrey Goh 2016-01-06 08:45:34 -08:00 committed by facebook-github-bot-9
parent ffd4f991f5
commit e730a9fdd0
6 changed files with 115 additions and 53 deletions

View File

@ -10,7 +10,8 @@
jest jest
.dontMock('AssetRegistry') .dontMock('AssetRegistry')
.dontMock('../resolveAssetSource'); .dontMock('../resolveAssetSource')
.dontMock('../../../local-cli/bundle/assetPathUtils');
var AssetRegistry = require('AssetRegistry'); var AssetRegistry = require('AssetRegistry');
var Platform = require('Platform'); var Platform = require('Platform');
@ -132,10 +133,10 @@ describe('resolveAssetSource', () => {
}); });
}); });
describe('bundle was loaded from file on Android', () => { describe('bundle was loaded from assets on Android', () => {
beforeEach(() => { beforeEach(() => {
NativeModules.SourceCode.scriptURL = NativeModules.SourceCode.scriptURL =
'file:///Path/To/Simulator/main.bundle'; 'assets://Path/To/Simulator/main.bundle';
Platform.OS = 'android'; Platform.OS = 'android';
}); });
@ -160,6 +161,34 @@ describe('resolveAssetSource', () => {
}); });
}); });
describe('bundle was loaded from file on Android', () => {
beforeEach(() => {
NativeModules.SourceCode.scriptURL =
'file:///sdcard/Path/To/Simulator/main.bundle';
Platform.OS = 'android';
});
it('uses pre-packed image', () => {
expectResolvesAsset({
__packager_asset: true,
fileSystemLocation: '/root/app/module/a',
httpServerLocation: '/assets/AwesomeModule/Subdir',
width: 100,
height: 200,
scales: [1],
hash: '5b6f00f',
name: '!@Logo#1_€',
type: 'png',
}, {
__packager_asset: true,
width: 100,
height: 200,
uri: 'file:///sdcard/Path/To/Simulator/drawable-mdpi/awesomemodule_subdir_logo1_.png',
scale: 1,
});
});
});
}); });
describe('resolveAssetSource.pickScale', () => { describe('resolveAssetSource.pickScale', () => {

View File

@ -25,6 +25,7 @@ var AssetRegistry = require('AssetRegistry');
var PixelRatio = require('PixelRatio'); var PixelRatio = require('PixelRatio');
var Platform = require('Platform'); var Platform = require('Platform');
var SourceCode = require('NativeModules').SourceCode; var SourceCode = require('NativeModules').SourceCode;
var assetPathUtils = require('../../local-cli/bundle/assetPathUtils');
var _serverURL, _offlinePath; var _serverURL, _offlinePath;
@ -62,18 +63,18 @@ function getOfflinePath() {
* Returns the path at which the asset can be found in the archive * Returns the path at which the asset can be found in the archive
*/ */
function getPathInArchive(asset) { function getPathInArchive(asset) {
var offlinePath = getOfflinePath();
if (Platform.OS === 'android') { if (Platform.OS === 'android') {
var assetDir = getBasePath(asset); if (offlinePath) {
// E.g. 'file:///sdcard/AwesomeModule/drawable-mdpi/icon.png'
return 'file://' + offlinePath + getAssetPathInDrawableFolder(asset);
}
// E.g. 'assets_awesomemodule_icon' // E.g. 'assets_awesomemodule_icon'
// The Android resource system picks the correct scale. // The Android resource system picks the correct scale.
return (assetDir + '/' + asset.name) return assetPathUtils.getAndroidResourceIdentifier(asset);
.toLowerCase()
.replace(/\//g, '_') // Encode folder structure in file name
.replace(/([^a-z0-9_])/g, '') // Remove illegal chars
.replace(/^assets_/, ''); // Remove "assets_" prefix
} else { } else {
// E.g. 'assets/AwesomeModule/icon@2x.png' // E.g. '/assets/AwesomeModule/icon@2x.png'
return getOfflinePath() + getScaledAssetPath(asset); return offlinePath + getScaledAssetPath(asset);
} }
} }
@ -86,29 +87,26 @@ function getPathOnDevserver(devServerUrl, asset) {
'&hash=' + asset.hash; '&hash=' + asset.hash;
} }
/**
* Returns a path like 'assets/AwesomeModule'
*/
function getBasePath(asset) {
// TODO(frantic): currently httpServerLocation is used both as
// path in http URL and path within IPA. Should we have zipArchiveLocation?
var path = asset.httpServerLocation;
if (path[0] === '/') {
path = path.substr(1);
}
return path;
}
/** /**
* Returns a path like 'assets/AwesomeModule/icon@2x.png' * Returns a path like 'assets/AwesomeModule/icon@2x.png'
*/ */
function getScaledAssetPath(asset) { function getScaledAssetPath(asset) {
var scale = pickScale(asset.scales, PixelRatio.get()); var scale = pickScale(asset.scales, PixelRatio.get());
var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x'; var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
var assetDir = getBasePath(asset); var assetDir = assetPathUtils.getBasePath(asset);
return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type; return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type;
} }
/**
* Returns a path like 'drawable-mdpi/icon.png'
*/
function getAssetPathInDrawableFolder(asset) {
var scale = pickScale(asset.scales, PixelRatio.get());
var drawbleFolder = assetPathUtils.getAndroidDrawableFolderName(asset, scale);
var fileName = assetPathUtils.getAndroidResourceIdentifier(asset);
return drawbleFolder + '/' + fileName + '.' + asset.type;
}
function pickScale(scales: Array<number>, deviceScale: number): number { function pickScale(scales: Array<number>, deviceScale: number): number {
// Packager guarantees that `scales` array is sorted // Packager guarantees that `scales` array is sorted
for (var i = 0; i < scales.length; i++) { for (var i = 0; i < scales.length; i++) {

View File

@ -37,7 +37,7 @@ public abstract class JSBundleLoader {
@Override @Override
public String getSourceUrl() { public String getSourceUrl() {
return fileName; return (fileName.startsWith("assets://") ? "" : "file://") + fileName;
} }
}; };
} }

View File

@ -8,7 +8,9 @@
*/ */
'use strict'; 'use strict';
jest.dontMock('../getAssetDestPathAndroid'); jest
.dontMock('../getAssetDestPathAndroid')
.dontMock('../assetPathUtils');
const getAssetDestPathAndroid = require('../getAssetDestPathAndroid'); const getAssetDestPathAndroid = require('../getAssetDestPathAndroid');

View File

@ -0,0 +1,56 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
function getAndroidAssetSuffix(scale) {
switch (scale) {
case 0.75: return 'ldpi';
case 1: return 'mdpi';
case 1.5: return 'hdpi';
case 2: return 'xhdpi';
case 3: return 'xxhdpi';
case 4: return 'xxxhdpi';
}
}
function getAndroidDrawableFolderName(asset, scale) {
var suffix = getAndroidAssetSuffix(scale);
if (!suffix) {
throw new Error(
'Don\'t know which android drawable suffix to use for asset: ' +
JSON.stringify(asset)
);
}
const androidFolder = 'drawable-' + suffix;
return androidFolder;
}
function getAndroidResourceIdentifier(asset) {
var folderPath = getBasePath(asset);
return (folderPath + '/' + asset.name)
.toLowerCase()
.replace(/\//g, '_') // Encode folder structure in file name
.replace(/([^a-z0-9_])/g, '') // Remove illegal chars
.replace(/^assets_/, ''); // Remove "assets_" prefix
}
function getBasePath(asset) {
var basePath = asset.httpServerLocation;
if (basePath[0] === '/') {
basePath = basePath.substr(1);
}
return basePath;
}
module.exports = {
getAndroidAssetSuffix: getAndroidAssetSuffix,
getAndroidDrawableFolderName: getAndroidDrawableFolderName,
getAndroidResourceIdentifier: getAndroidResourceIdentifier,
getBasePath: getBasePath
};

View File

@ -9,34 +9,11 @@
'use strict'; 'use strict';
const path = require('path'); const path = require('path');
const assetPathUtils = require('./assetPathUtils');
function getAndroidAssetSuffix(scale) {
switch (scale) {
case 0.75: return 'ldpi';
case 1: return 'mdpi';
case 1.5: return 'hdpi';
case 2: return 'xhdpi';
case 3: return 'xxhdpi';
case 4: return 'xxxhdpi';
}
}
function getAssetDestPathAndroid(asset, scale) { function getAssetDestPathAndroid(asset, scale) {
var suffix = getAndroidAssetSuffix(scale); const androidFolder = assetPathUtils.getAndroidDrawableFolderName(asset, scale);
if (!suffix) { const fileName = assetPathUtils.getAndroidResourceIdentifier(asset);
throw new Error(
'Don\'t know which android drawable suffix to use for asset: ' +
JSON.stringify(asset)
);
}
const androidFolder = 'drawable-' + suffix;
// TODO: reuse this logic from https://fburl.com/151101135
const fileName = (asset.httpServerLocation.substr(1) + '/' + asset.name)
.toLowerCase()
.replace(/\//g, '_') // Encode folder structure in file name
.replace(/([^a-z0-9_])/g, '') // Remove illegal chars
.replace(/^assets_/, ''); // Remove "assets_" prefix
return path.join(androidFolder, fileName + '.' + asset.type); return path.join(androidFolder, fileName + '.' + asset.type);
} }