react-native
中使用Image
组件来显示图片,表面上和html
的img
标签大同小异,但是其source
属性包含的逻辑缺复杂的多,同时也和bundle
运行的方式也有关系。
本篇文章将重点讲解下Image
中图片解析逻辑,以及如何自定义图片解析逻辑。
1. 打包结构
react-native bundle --entry-file index.js --bundle-output ./bundle/ios/main.jsbundle --platform ios --assets-dest ./bundle/ios --dev false
react-native bundle --entry-file index.js --bundle-output ./bundle/android/index.bundle --platform android --assets-dest ./bundle/android --dev false
首先看下iOS
和android
打包结果:
iOS
会按照项目结构输出图片资源到我们制定的目录assets
下。
android
中,drawable-mdpi
、drawable-xhdpi
、drawable-xxhdpi
等存放不同分辨率屏幕下的图片,文件名的组成是目录和图片名称通过_
拼接。
2. 图片链接生成逻辑
代码位于react-native/Libraries/Image/resolveAssetSource
resolveAssetSource.js
最终会export
以下内容:
module.exports = resolveAssetSource;
module.exports.pickScale = AssetSourceResolver.pickScale;
module.exports.setCustomSourceTransformer = setCustomSourceTransformer;
- resolveAssetSource: 图片地址拼接工具
- pickScale: 像素比工具
- setCustomSourceTransformer: 自定义图片链接处理方式
这里的重点是resolveAssetSource
,它会处理Image
的source
,并返回图片地址。
创建了AssetSourceResolver
,并传入getDevServerURL()
、getScriptURL()
、asset
。
如果存在自定义处理函数_customSourceTransformer
,就返回它的执行结果。它的设置就是通过setCustomSourceTransformer
来完成的。
否则就调用resolver.defaultAsset
,使用默认的逻辑处理图片。
/**
* `source` is either a number (opaque type returned by require('./foo.png'))
* or an `ImageSource` like { uri: '' }
*/
function resolveAssetSource(source: any): ?ResolvedAssetSource {
if (typeof source === 'object') {
return source;
}
const asset = AssetRegistry.getAssetByID(source);
if (!asset) {
return null;
}
const resolver = new AssetSourceResolver(
getDevServerURL(),
getScriptURL(),
asset,
);
if (_customSourceTransformer) {
return _customSourceTransformer(resolver);
}
return resolver.defaultAsset();
}
接下来看AssetSourceResolver.js
的代码。
我们前文初始化AssetSourceResolver
,设置了三个参数:
- serverUrl: 服务地址,格式为"http://www.xxx.com"
- jsbundleUrl:
bundle
所在位置 - asset
里面包含了最终返回图片的逻辑:defaultAsset
,我们分析之后可以得到:
bundle放在server
通过如下代码拼接图片地址,这里使用serverUrl
要求bundle
文件和图片在同级目录并且在域名下,中间不能有二级目录。
this.fromSource(
this.serverUrl +
getScaledAssetPath(this.asset) +
'?platform=' +
Platform.OS +
'&hash=' +
this.asset.hash,
);
解决方案是通过setCustomSourceTransformer
替换serverUrl
,改为jsbundleUrl
。
bundle内置在app
这里不同平台的处理方式又不一样。
iOS
从资源中加载图片
android
分为两种:资源和文件系统(file://)
class AssetSourceResolver {
serverUrl: ?string;
// where the jsbundle is being run from
jsbundleUrl: ?string;
// the asset to resolve
asset: PackagerAsset;
constructor(serverUrl: ?string, jsbundleUrl: ?string, asset: PackagerAsset) {
this.serverUrl = serverUrl;
this.jsbundleUrl = jsbundleUrl;
this.asset = asset;
}
...
defaultAsset(): ResolvedAssetSource {
if (this.isLoadedFromServer()) {
return this.assetServerURL();
}
if (Platform.OS === 'android') {
return this.isLoadedFromFileSystem()
? this.drawableFolderInBundle()
: this.resourceIdentifierWithoutScale();
} else {
return this.scaledAssetURLNearBundle();
}
}
3. 写在结尾
我们了解Image
组件的图片逻辑之后,就可以按需调整了,通过调用setCustomSourceTransformer
传入自定义函数来控制最终图片的访问地址。
我在项目中的处理是bundle
部署在服务器上,这种方式会有两个问题:
- 图片资源是从域名开始查找,放置在多级目录后就无法访问到图片
- 安卓跳过了
drawable-x
目录
上面的问题都是图片无法显示,不知道看到文章的你是否也想到了解决办法?
本文同步发表于作者博客: React Native 图片资源那些事