1、code push的更新包逻辑
发布codepush更新的时候使用的命令行一般是这样的:
code-push release YourApp ./your_jsbundle_and_imgs/ 3.0.0 --des "TEST code Push"
这个命令行上传了个3.0.0版本的目录上去,而这个目录会包含你的jsbundle和许多图片,假设当前3.0.0版本的文件如下:
your_jsbundle_and_imgs/
├── drawable-mdpi
│ ├── 1.png
│ ├── 2.png
│ └── 3.png
└── index.android.jsbundle
假设2.0.0版本的文件如下,比3.0.0少了个图片:
your_jsbundle_and_imgs/
├── drawable-mdpi
│ ├── 1.png
│ └── 2.png
└── index.android.jsbundle
如果用户是在2.0.0版本之下且之前都没有过code push升级过,我们在code push获取升级包的时候会怎么样?正常的思维就是把不一样的index.android.jsbundle和新增的3.png两个文件下载下来然后升级即可。然而并非如此。
如果之前都没有过codepush升级,codepush会下载3.0.0版本下所有的jsbundle和其他资源文件,所以第一次更新的时候会消耗更多的流量。
而如果之前用户是1.0.0版本通过codepush升级到2.0.0版本,再升级到3.0.0版本,这个时候codepush就只会下载index.android.jsbundle和新增的3.png两个文件。
造成这个现象的主要原因还是react native的图片加载流程并没有那么智能,rn加载图片资源的主要逻辑代码如下:
defaultAsset(): ResolvedAssetSource {
if (this.isLoadedFromServer()) {
return this.assetServerURL();
}
if (Platform.OS === 'android') {
return this.isLoadedFromFileSystem()
? this.drawableFolderInBundle()
: this.resourceIdentifierWithoutScale();
} else {
return this.scaledAssetURLNearBundle();
}
}
scaledAssetURLNearBundle(): ResolvedAssetSource {
const path = this.jsbundleUrl || 'file://';
return this.fromSource(path + getScaledAssetPath(this.asset));
}
首先看android,如果是从文件系统加载,即从/data/data/com.....之类的目录或者sd卡加载而不是从assets目录加载的情况下,会从相对该目录下的drawable的目录下加载图片。比如我们加载的jsbundle的文件路径是/data/data/com.your.app.package/codepush/index.android.jsbundle,就会从/data/data/com.your.app.package/codepush/目录下找图片; 而如果不是从文件系统加载,即从assets中加载,这个时候就会从apk包中的drawable中加载图片。
再看iOS,iOS是直接从jsbundle当前目录下寻找assets目录。
code push是从文件系统加载jsbundle,所以其图片资源也要放到相应的目录下,因此code push第一次更新的时候会把所有图片全部下载下来,这样做也是为了方便统一管理,第二次更新的情况下codepush就会做一次patch,通过新老版本比对把有用的资源复制留下而没有就不复制。
2、如何减少code push第一次更新的包大小
通过上面的代码分析大家应该有想法了,我们可以只上传需要更新的图片而不是上传所有的图片资源,然后更改RN加载图片的流程,让其可加载文件系统的图片和包中的图片。
if (Platform.OS === 'android') {
if(this.isLoadedFromFileSystem()){//begin assets ios begin drawable android
let resolvedAssetSource = this.drawableFolderInBundle();
let resPath = resolvedAssetSource.uri;
if(drawablePathInfos.includes(resPath)){//已经在bundle目录中有
return resolvedAssetSource;
}
let isFileExist = Smartassets.isFileExist(resPath);
if(isFileExist===true){
return resolvedAssetSource;
}else {
return this.resourceIdentifierWithoutScale();
}
}else {
return this.resourceIdentifierWithoutScale();
}
} else {
let iOSAsset = this.scaledAssetURLNearBundle();
let isFileExist = Smartassets.isFileExist(iOSAsset.uri);
isFileExist = false;
if(isFileExist) {
return iOSAsset;
}else{
let oriJsBundleUrl = 'file://'+defaultMainBundePath+'/'+iOSRelateMainBundlePath;
iOSAsset.uri = iOSAsset.uri.replace(this.jsbundleUrl, oriJsBundleUrl);
return iOSAsset;
}
}
具体的代码写成了一个库放在了github上:
https://github.com/smallnew/react-native-smartassets