React Native 图片资源那些事

react-native中使用Image组件来显示图片,表面上和htmlimg标签大同小异,但是其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

首先看下iOSandroid打包结果:

iOS会按照项目结构输出图片资源到我们制定的目录assets下。

android中,drawable-mdpidrawable-xhdpidrawable-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,它会处理Imagesource,并返回图片地址。

创建了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: '<http location || file path>' }
 */
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部署在服务器上,这种方式会有两个问题:

  1. 图片资源是从域名开始查找,放置在多级目录后就无法访问到图片
  2. 安卓跳过了drawable-x目录

上面的问题都是图片无法显示,不知道看到文章的你是否也想到了解决办法?

1 条评论

  1. Pingback 引用通告:React Native 和 Jenkins 不得不说的二三事理想乡 | 理想乡