市面上的Rn更新类似于code-push,都是基于包名来标识更新包的,但是公司目前的项目是在一套代码的基础上,包名不作修改,拉了分支给客户部分定制,衍生出很多app,所以要做更新方案,需要自己实现,由于混合部分js基本不动,更新的差异包,主要是图片资源比较大,所以单独针对图片资源进行差异化更新。
这篇文章除了简单讲述下原理,还会分享下千剑提供的python实现的自动化打包r n资源的差异包,因为不实现这个工具的话,手动选出新增或者变化的图片太麻烦。我们要做个smarter worker,毕竟程序员是个技术活。
图片资源增量的原理如下, 部分参考别人的博客,感谢他们的分享:
1.使用一个Image控件如下:
2.看看RN image源码的实现,image.android.js有一行
3.resolveAssetSource
4.AssetSourceResolver
此处注意,测试图片资源差分的时候,每次要打包试验,从isLoadFromServer()这个方法中看出,在debug下,rn每次是从启动的服务中去加载使用的图片的
resourceIdentifierWithoutScale(),这个方法是从asset目录下加载图片,我们重点关注方法的其中之一是drawableFoldInBundle()他的作用是存在离线bundle包时,加载这个bundle包里的图片资源,所谓离线bundle包就是在本地文件中的bundle包,不是asset中打包进去的bundle。
我们要做的就是修改isLoadFromFileSystem(),使得在运行中,使用增量的图片资源时,返回true,让rn去加载离线bundle中的图片
我们在AssetSourceResolver中新增一个变量
用特有的格式| xxx1.png | xx2.png |把新增的图片配置进去,放心,这里只是讲原理,后面都会用python全部自动提换生成。
然后修改方法如下:
到此,rn源码处的修改完毕
那么要怎么做图片资源的增量更新包呢?
首先,Rn打包时,你运行如下命令:
react-native bundle --entry-file index.js --platform android --dev false --bundle-output ./bundle/index.android.bundle --assets-dest ./bundle
RN除了会生成一个bundle.js,还有遍历工程中的图片资源,把放在RN工程中的图片,重新命名后,复制到android原生的drawable-hdpi drawable-mdpi这些目录。
pyhon实现自动化打包增量资源
那么千剑用python实现的自动化打包的思路大概是这样的:
如果每进行一次原生apk的打包,那么生成一份此次原生打包的资源目录,用作以后RN图片资源增量的对比使用,举例,我这次原生整体打包的版本是1.0
那么下次,我正对1.0的版本,做1.0.1的差异包时,python脚本会和上面的1.0的资源做对比,只找出1.0中不存在的图片,连同bundle.js最后打包成一个bundle.zip,这就是差异包
这个python自动打包主要两个脚本:
create_apk.py是打包原生的时候使用,用来备份要对比的资源
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#当apk要更新的时候使用这个脚步打包,主要功能做一下几件事
#1.打包生成bundle
#2.打包生成apk
import os;
import shutil
# 当apk版本,apk打包时此处修改成相同
apkversion = "1.0"
# 每更新一版apk,起始的bundle都是1.0,所以使用这个脚本打包时,只修改上面的的apkversion就行
bundleVersion = "1.0"
# 获取当前脚本目录
pyAndroidUpgrade = os.getcwd()
oemPjDir=os.path.abspath(os.path.dirname(os.getcwd()))
#跳转到oem主目录
os.chdir(oemPjDir)
#1.执行bundle打包命令
# 返回0表示成功
rnStatus = os.system("react-native bundle --entry-file index.js --platform android --dev false --bundle-output ./android/app/src/main/assets/index.android.bundle --assets-dest ./android/app/src/main/res/")
#2.进入android目录,打包apk
androidDir = os.getcwd() + "/android"
os.chdir(androidDir)
#先删掉android目录下的带components_echarts_tpl.html的文件,要不打包不成功
androidResDir=androidDir+"/app/src/main/res"
rawDir=androidResDir+"/raw"
#遍历删除
for item in os.listdir(rawDir):
if (os.path.basename(item).endswith("echarts_tpl.html")):
remoPath = os.path.join(rawDir, item)
os.remove(remoPath)
#执行打包
apkStatus = os.system("./gradlew assembleRelease")
if apkStatus==0:
print("apk打包成功")
else:
print("apk打包失败")
#3.把res目录下的drawable目录拷一份到
#在当前rn目录下,比如打1.0apk的包,要创建一个apk/1.0/bundle的包用来跟后面的rn差分包做对比
createVersionPath=pyAndroidUpgrade+"/apk/"+apkversion
createPath=createVersionPath+"/bundle"
#存在先清空下面的资源
if os.path.exists(createPath):
print("")
#创建目录
else:
os.makedirs(createPath)
#拷贝文件函数
def copyFiles(source_path,target_path):
for root, dirs, files in os.walk(source_path):
for file in files:
src_file = os.path.join(root, file)
shutil.copy(src_file, target_path)
#print(src_file)
#拷贝android下的资源到当前版本目录下的bundle文件夹
drawableList=["drawable-hdpi","drawable-mdpi","drawable-xhdpi","drawable-xxhdpi","drawable-xxxhdpi","raw"]
for drawableItem in drawableList:
sourcePath=androidResDir+"/"+drawableItem
targetPath=createPath+"/"+drawableItem
if(os.path.exists(targetPath)==False):
os.makedirs(targetPath)
#os.remove(targetPath)
copyFiles(sourcePath,targetPath)
另一个是create_bundle_zip.py 用来打rn差异包的:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import os;
import shutil
import zipfile
# 当apk版本号,不是bundle版本号
apkversion = "1.0"
# 要对比的bundle版本号
bundleVersion = "1.2"
# 获取当前脚本目录
pyAndroidUpgrade = os.getcwd()
oemPjDir=os.path.abspath(os.path.dirname(os.getcwd()))
#跳转到oem主目录
os.chdir(oemPjDir)
#1.运行bundle打包命令,把资源文件打包到指定目录,比如打1.1的bundle,那么图片打到rn/1.1/bundle
#图片保存路径
drawableSavePath=pyAndroidUpgrade+"/rn/"+apkversion+"/"+bundleVersion+"/bundle"
#第一次生成的bundle.js先保存在临时目录,因为这个没用,后面要替换的
bundleSavePath=pyAndroidUpgrade+"/temp"
if(os.path.exists(drawableSavePath)==False):
os.makedirs(drawableSavePath)
#bundle打包命令
bundleDrawableCmd="react-native bundle --entry-file index.js --platform android --dev true --bundle-output "+bundleSavePath+"/index.android.bundle --assets-dest "+drawableSavePath+"/"
# 返回0表示成功
rnStatus = os.system(bundleDrawableCmd)
if rnStatus==0:
#删除temp下的文件
os.remove(bundleSavePath+"/index.android.bundle")
else:
print("第一次打包失败")
#跳转到py目录
os.chdir(pyAndroidUpgrade)
# old目录
oldBundlePath = pyAndroidUpgrade + "/apk/" + apkversion + "/bundle"
# 要对比的bundle目录
checkBundlePath = pyAndroidUpgrade + "/rn/" + apkversion + "/" + bundleVersion
# 获取oldBundlePath下所有资源的文件名
pngFiles = []
#要单独删除的文件名
deleteFiles=[]
deleteFiles.append("node_modules_nativeecharts_src_components_echarts_tpl.html")
def search(root):
items = os.listdir(root)
for item in items:
path = os.path.join(root, item)
if os.path.isdir(path):
search(path)
elif path.endswith(".png"):
# print(os.path.basename(path))
pngFiles.append(os.path.basename(path))
else:
os.path.basename(path)
search(oldBundlePath)
# 遍历当前要对比包的资源文件,要是在oldBundlePath下存在的资源就删除
zipFiles = [] # 不删除的文件要保存,后面要压缩
savePngs = [] # 保存要保留下来的图片
def iteraNew(root):
#print("iteraNew")
items = os.listdir(root)
for item in items:
path = os.path.join(root, item)
if os.path.isdir(path):
iteraNew(path)
elif path.endswith(".png"):
# print("bundle==" + os.path.basename(path))
if checkInOld(os.path.basename(path)):
os.remove(path)
else:
if (checkSavePngExists(os.path.basename(path)) != True):
savePngs.append(os.path.basename(path))
zipFiles.append(path)
# print ("不存在==" + os.path.basename(path))
else:
deleNouseFile(path)
zipFiles.append(path)
#删除没用的文件node_modules_nativeecharts_src_components_echarts_tpl.html
def deleNouseFile(filePath):
checkName=os.path.basename(filePath)
for item in deleteFiles:
if (item==checkName):
os.remove(filePath)
def checkInOld(pngName):
tag = False # 默认不存在
for item in pngFiles:
if (item == pngName):
tag = True
return tag
def checkSavePngExists(pngName):
tag = False # 默认不存在
for item in savePngs:
if (item == pngName):
tag = True
return tag
iteraNew(checkBundlePath)
# 生成差异列表图片名称,以便给rn配置
# 拼成|xxx_ss.png|xxx_ff.png|样子
result = ""
for saveItem in savePngs:
# print("save=="+saveItem)
result = result + "|" + saveItem
result = result + "|"
print ("要配置的增量图片如下")
print (result)
#读取mergeJs1和mergeJs2的内容合并成要替换的内容
def readJsFile(filePath):
result = ""
f = open(filePath)
line = f.readline()
while line:
result = result + line
line = f.readline()
f.close()
return result
#最后要写入的结果
resultContent=""
js1Content=readJsFile(pyAndroidUpgrade+"/mergeJs1.txt")
js2Content=readJsFile(pyAndroidUpgrade+"/mergeJs2.txt")
insertContent="var patchImgNames = \'"+result+"\'";
resultContent=js1Content+"\r\n"+insertContent+"\r\n"+js2Content
#print(resultContent)
#生成的内容覆盖node_modules/react-native/Libraries/Image/AssetSourceResolver.js
AssetSourceResolvePath=oemPjDir+"/node_modules/react-native/Libraries/Image/AssetSourceResolver.js"
filename = AssetSourceResolvePath
with open(filename, 'w') as file_object:
file_object.write(resultContent)
#重新打包budle生成正确的index.android.bundle
#跳转到oem主目录
def del_file(path):
for i in os.listdir(path):
path_file = os.path.join(path,i)
if os.path.isfile(path_file):
os.remove(path_file)
else:
del_file(path_file)
os.chdir(oemPjDir)
#图片保存路径
drawableSavePath=pyAndroidUpgrade+"/temp"
#第一次生成的bundle.js先保存在临时目录,因为这个没用,后面要替换的
bundleSavePath=pyAndroidUpgrade+"/rn/"+apkversion+"/"+bundleVersion+"/bundle"
#bundle打包命令
bundleDrawableCmd="react-native bundle --entry-file index.js --platform android --dev true --bundle-output "+bundleSavePath+"/index.android.bundle --assets-dest "+drawableSavePath+"/"
# 返回0表示成功
rnStatus = os.system(bundleDrawableCmd)
if rnStatus==0:
#删除temp下的文件
del_file(drawableSavePath)
print("最终bundle包打包成功")
else:
print("最终bundle包打包失败")
# 生成zip文件
os.chdir(checkBundlePath)
shutil.make_archive(apkversion+"_"+bundleVersion+"_bundle", "zip", os.getcwd() + "/bundle")
print("生成的差异包是:"+checkBundlePath+"/bundle/"+apkversion+"_"+bundleVersion+"_bundle.zip")