Skip to content

Commit

Permalink
Load assets from same folder as JSbundle (Android)
Browse files Browse the repository at this point in the history
Summary:
#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 #4527

Reviewed By: svcscm

Differential Revision: D2788005

Pulled By: mkonicek

fb-gh-sync-id: 3f6462a7ee6370a92dd6727ac422c5de346c3ff1
  • Loading branch information
geof90 authored and facebook-github-bot-9 committed Jan 6, 2016
1 parent ffd4f99 commit e730a9f
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 53 deletions.
35 changes: 32 additions & 3 deletions Libraries/Image/__tests__/resolveAssetSource-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

jest
.dontMock('AssetRegistry')
.dontMock('../resolveAssetSource');
.dontMock('../resolveAssetSource')
.dontMock('../../../local-cli/bundle/assetPathUtils');

var AssetRegistry = require('AssetRegistry');
var Platform = require('Platform');
Expand Down Expand Up @@ -132,10 +133,10 @@ describe('resolveAssetSource', () => {
});
});

describe('bundle was loaded from file on Android', () => {
describe('bundle was loaded from assets on Android', () => {
beforeEach(() => {
NativeModules.SourceCode.scriptURL =
'file:///Path/To/Simulator/main.bundle';
'assets://Path/To/Simulator/main.bundle';
Platform.OS = 'android';
});

Expand All @@ -159,6 +160,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,
});
});
});

});

Expand Down
42 changes: 20 additions & 22 deletions Libraries/Image/resolveAssetSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var AssetRegistry = require('AssetRegistry');
var PixelRatio = require('PixelRatio');
var Platform = require('Platform');
var SourceCode = require('NativeModules').SourceCode;
var assetPathUtils = require('../../local-cli/bundle/assetPathUtils');

var _serverURL, _offlinePath;

Expand Down Expand Up @@ -62,18 +63,18 @@ function getOfflinePath() {
* Returns the path at which the asset can be found in the archive
*/
function getPathInArchive(asset) {
var offlinePath = getOfflinePath();
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'
// The Android resource system picks the correct scale.
return (assetDir + '/' + 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 assetPathUtils.getAndroidResourceIdentifier(asset);
} else {
// E.g. 'assets/AwesomeModule/icon@2x.png'
return getOfflinePath() + getScaledAssetPath(asset);
// E.g. '/assets/AwesomeModule/icon@2x.png'
return offlinePath + getScaledAssetPath(asset);
}
}

Expand All @@ -86,29 +87,26 @@ function getPathOnDevserver(devServerUrl, asset) {
'&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'
*/
function getScaledAssetPath(asset) {
var scale = pickScale(asset.scales, PixelRatio.get());
var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
var assetDir = getBasePath(asset);
var assetDir = assetPathUtils.getBasePath(asset);
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 {
// Packager guarantees that `scales` array is sorted
for (var i = 0; i < scales.length; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void loadScript(ReactBridge bridge) {

@Override
public String getSourceUrl() {
return fileName;
return (fileName.startsWith("assets://") ? "" : "file://") + fileName;
}
};
}
Expand Down
4 changes: 3 additions & 1 deletion local-cli/bundle/__tests__/getAssetDestPathAndroid-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
*/
'use strict';

jest.dontMock('../getAssetDestPathAndroid');
jest
.dontMock('../getAssetDestPathAndroid')
.dontMock('../assetPathUtils');

const getAssetDestPathAndroid = require('../getAssetDestPathAndroid');

Expand Down
56 changes: 56 additions & 0 deletions local-cli/bundle/assetPathUtils.js
Original file line number Diff line number Diff line change
@@ -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
};
29 changes: 3 additions & 26 deletions local-cli/bundle/getAssetDestPathAndroid.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,11 @@
'use strict';

const path = require('path');

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';
}
}
const assetPathUtils = require('./assetPathUtils');

function getAssetDestPathAndroid(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;
// 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

const androidFolder = assetPathUtils.getAndroidDrawableFolderName(asset, scale);
const fileName = assetPathUtils.getAndroidResourceIdentifier(asset);
return path.join(androidFolder, fileName + '.' + asset.type);
}

Expand Down

0 comments on commit e730a9f

Please sign in to comment.