React Native 实现图片资源增量更新, Python实现自动化打包

市面上的Rn更新类似于code-push,都是基于包名来标识更新包的,但是公司目前的项目是在一套代码的基础上,包名不作修改,拉了分支给客户部分定制,衍生出很多app,所以要做更新方案,需要自己实现,由于混合部分js基本不动,更新的差异包,主要是图片资源比较大,所以单独针对图片资源进行差异化更新。

这篇文章除了简单讲述下原理,还会分享下千剑提供的python实现的自动化打包r n资源的差异包,因为不实现这个工具的话,手动选出新增或者变化的图片太麻烦。我们要做个smarter worker,毕竟程序员是个技术活。

图片资源增量的原理如下, 部分参考别人的博客,感谢他们的分享:

1.使用一个Image控件如下:
image.png
2.看看RN image源码的实现,image.android.js有一行
image.png
3.resolveAssetSource
image.png
4.AssetSourceResolver
image.png

此处注意,测试图片资源差分的时候,每次要打包试验,从isLoadFromServer()这个方法中看出,在debug下,rn每次是从启动的服务中去加载使用的图片的
resourceIdentifierWithoutScale(),这个方法是从asset目录下加载图片,我们重点关注方法的其中之一是drawableFoldInBundle()他的作用是存在离线bundle包时,加载这个bundle包里的图片资源,所谓离线bundle包就是在本地文件中的bundle包,不是asset中打包进去的bundle。
我们要做的就是修改isLoadFromFileSystem(),使得在运行中,使用增量的图片资源时,返回true,让rn去加载离线bundle中的图片

我们在AssetSourceResolver中新增一个变量


image.png

用特有的格式| xxx1.png | xx2.png |把新增的图片配置进去,放心,这里只是讲原理,后面都会用python全部自动提换生成。

然后修改方法如下:


image.png

到此,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

image.png

那么下次,我正对1.0的版本,做1.0.1的差异包时,python脚本会和上面的1.0的资源做对比,只找出1.0中不存在的图片,连同bundle.js最后打包成一个bundle.zip,这就是差异包

image.png

这个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")
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。