手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)

hi,各位看官,前一篇大概介绍了如何生成打包需要的原料,下面这篇来分析下打包工具是如何做的。欢迎来到打包系统核心部分:自动化打包

PS:该自动化打包工具是Python代码编写,需要大伙有点python的功底。(不过没事,我也是边学边撸出来的。)。打包系统的的交互会涉及到网页和桌面两种方式,因网页打包涉及到与前端交互就不细讲,该系列只会讲桌面部分供大家参考。

自动化打包过程分析及实现

自动化打包思路

前面已经说过,搞事情就得反编译apk,整体的自动化打包也是基于包体的反编译和回编译设计的。利用apktool工具先反编译游戏母包,之后合并游戏母包和渠道资源,利用dx.jar和baksmail.jar工具将渠道资源包内封装好的源码合并到游戏反编译后的smali代码中,再回编译生成新的包体,最后给包体签名优化输出最终的游戏_渠道包。可以参考下图

自动化打包过程图解
手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第1张图片
image.png
自动化打包过程示例:

以 手游SDK框架Demo的工程及乐享渠道SDK为例子,下面讲解下打包的实现过程:

原料准备
  • 1、游戏母包:手游SDK框架Demo接入模拟测试渠道生成GameSDKFrame.apk。(模拟游戏母包)
  • 2、渠道资源包:手游SDK框架Demo接入乐享渠道并生成对应的混淆jar:sdk_lexiang_v1.0.0.jar,整合资源目录:


    手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第2张图片
    image.png

相关说明可以在打包示例资源下载。

命令打包过程
  • 1、将GameSDKFrame.apk反编译成资源文件(GameSDKFrame.apk模拟游戏母包),进入cmd,执行命令:

apktool d -f [待编译apk路径] -o [输出资源路径]

手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第3张图片
image.png
  • 2、将反编译生成的资源文件和渠道的资源文件合并,合并assets、libs、res及AndroidManifest.xml等资源文件(目前是手动合并)
手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第4张图片
image.png
  • 3、根据资源文件合成R.java文件,进入cmd,执行命令:

aapt package -f -m -J [R.java输出路径] -S [res路径] -I [android.jar路径]-M [AndroidManifest.xml路径]

手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第5张图片
image.png
  • 4、编译生成的R.class文件,进入cmd ,执行命令:

javac -source 1.7 -target 1.7 -encoding UTF-8 [R.java路径]

手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第6张图片
image.png
  • 5、合成R.jar文件,进入cmd,执行命令:

jar cvf [输入目录及名称] *

手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第7张图片
image.png
  • 6、将.jar 转化为.dex文件(注意只能单个转换,批量转换需要循环),进入cmd,执行命令:

java -jar [dx.jar路径] --dex --output=[输出路径] [待转化jar路径]

手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第8张图片
image.png
  • 7、将.dex转化为smail代码(注意只能单个转换,批量转换需要循环),进入cmd,执行命令:

java -jar [baksmali.jar路径] -o [输入文件夹] [待转化dex文件]

手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第9张图片
image.png
  • 8、回编译生成apk包进入cmd,执行命令:

apktool b [待编译资源路径] -o [生成apk路径]

手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第10张图片
image.png
  • 9、生成apk包体签名

jarsigner -verbose -keystore [keystore文件路径] -signedjar [签名后生成的apk路径] [待签名的apk路径] [别名]

手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第11张图片
image.png

至此完整的打包流程就完毕了,至此就可以把渠道对应的资源打到游戏包体里面去了可以运行下前后的包体看下效果:

打包前的登录界面


手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第12张图片
image.png

打包后的登录界面


手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第13张图片
image.png

关于游戏的整体打包流程的实现就大体介绍到这里。其中涉及到的脚本命名不太清楚的,可自行查阅资料。

自动化打包项目架构设计

上面跟大家分析了及实现了生成一个游戏_渠道包的整体过程,但是都是命令行一步一步讲解实现,过程很麻烦,效率也很低。根本达不到日常的开发及应用效果,下面咱们来聊聊整体搭建自动化打包项目,并开发一个打包工具出来。

PS:这里的项目架构设计会大概说明下前端的交互设计,但是涉及到前端知识就不涉及具体实现(可以看看效果图)。

架构设计的产品输出

产品定位:任何一款接入聚合SDK的任意版本游戏都能通过自动化打包系统快速的生成任意渠道任意版本的游戏包体。

注意这里的任意含义:
任意版本游戏:同一款游戏会有多个地区版本
任意渠道:同一款同版本的游戏可以快速接入任意一家已合作的渠道SDK
任意渠道任意版本:同一款同版本的游戏可以快速接入同一家已合作的渠道的任意版本SDK。

架构设计的功能模块设计

根据产品的定位,可以快速确认产品的功能设计大概会几个模块内容:游戏管理、渠道管理、打包任务管理、自动化打包工具。可以细分为两部分:后台管理系统部分和前端打包部分

后台管理系统
  • 1、游戏管理:
    主要是管理游戏参数配置,登录、支付开关、计费点等信息


    image.png
  • 2、渠道管理:
    主要是渠道信息管理、版本管理、参数管理、渠道资源配置管理。


    image.png
前端打包系统
  • 1、打包任务界面管理:
    会给打包工具提供输出当前的打包任务的信息:包含游戏版本、渠道参数信息、渠道参数配置信息,打包过程的编译参数等。
网页版打包系统
image.png
桌面版打包系统
手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第14张图片
image.png

ps:自己开发的有点丑,这个可以参考易接的界面会更好一些

  • 2、自动化打包工具:
    自动化打包的核心,接收打包任务,接收打包资源(游戏资源、渠道资源、配置资源等),自动化处理生成apk。

自动化打包工具(自动化打包项目核心)

好了,来到该系列的核心内容了。自动化打包工具,又要敲代码了,好开心。。。

项目结构搭建

废话不多说了,代码经过三次重构优化,简单附上一张项目结构图

手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)_第15张图片
image.png

项目代码模块实现

打包任务接口设计
    """
    # taskId                       任务ID
    # gameName                     游戏名称
    # gameId                       游戏ID
    # gameVersion                  游戏版本
    # gameApkName                  游戏母包名称
    # channelName                  渠道名称
    # channelId                    渠道ID
    # channelVersion               渠道版本
    # isLocal                      是否是本地打包(区分服务器打包,主要处理差异化配置)
    # signId                       签名文件ID,默认为0, 本地桌面打包默认设置为1
    # keystore                     签名文件名称,为默认
    # alias                        签名文件别名,为默认
    # storepass                    签名文件密码,为默认
    # keypass                      签名文件别名密码,为默认

    """

    # 基准包任务
    task = BuildApkTask('180', 'TESTGame', '1', '1.0.0', 'GameSDKFrame.apk', 'lexiang' '1', '1.0.0')
    # 开始打包任务
    task.buildApk()

资源合并模块处理

这里资源合并处理跟渠道资源包一一对应:assets/libs/res/manifest/icon/r文件等。

    # 开始打包的核心逻辑,第一步:合并资源文件,包括assets/libs/res等目录资源
    self.logger.info(u'开始合并资源....')
    status, result = merge_resources(self.taskId, self.Tools, self.TempPath, self.BaseChannelPath, self.ChannelId,
                                     self.ChannelVersion, self.compile_config)
    if status == 0:
        self.logger.info(u'合并资源成功\n')
    else:
        self.logger.info(result)
        self.logger.info(u'合并资源失败\n')
        return status, result

    # 第二步: 配置渠道的闪屏图片及特殊配置文件等
    self.logger.info(u'合并配置文件....')
    status, result = merge_config(self.TempPath, self.BaseChannelPath)
    if status == 0:
        self.logger.info(u'合并配置文件成功\n')
    else:
        self.logger.info(result)
        self.logger.info(u'合并配置文件失败\n')
        return status, result

    # 第三步:合并游戏的Icon和角标资源
    self.logger.info(u'开始合并角标....')
    status, result = merge_icon(self.taskId, self.TempPath, self.BaseChannelPath)
    if status == 0:
        self.logger.info(u'合并角标成功\n')
    else:
        self.logger.info(result)
        self.logger.info(u'合并角标失败\n')
        return status, result

    # 第四步:合并Manifest.xml
    self.logger.info(u'开始合并Manifest.xml文件....')
    status, result, package_name = merge_manifest(self.taskId, self.TempPath, self.BaseChannelPath, self.ChannelId,
                                                  self.ChannelVersion, self.compile_config)
    if status == 0:
        self.logger.info(u'合并Manifest.xml文件成功\n')
    else:
        self.logger.info(result)
        self.logger.info(u'合并Manifest.xml文件失败\n')
        return status, result

    # 第五步:根据资源生成对应的R文件
    self.logger.info(u'开始生成R文件....')
    status, result = create_r_files(self.taskId, self.Tools, self.TempPath, self.BaseChannelPath,
                                    self.ChannelId, self.ChannelVersion, self.compile_config, package_name)
    if status == 0:
        self.logger.info(u'生成R文件成功\n')
    else:
        self.logger.info(result)
        self.logger.info(u'生成R文件失败\n')
        return status, result

渠道资源差异化处理

主要特处理特殊渠道的参数配置,闪屏逻辑,是否有微信登录支付类文件等。

# 定义特殊渠道父类
class SpecialChannel(object):

    def __init__(self, channel_name):
        self.channel_name = channel_name

    # 修改assets_resource
    def modify_assets_resource(self, channel_path, channel_version, config):
        print "%s modify_assets_resource" % self.channel_name
        return 0, "%s modify_assets_resource" % self.channel_name

    # 修改res_resource
    def modify_res_resource(self, channel_path, channel_version, config):
        print "%s modify_res_resource" % self.channel_name
        return 0, "%s modify_res_resource" % self.channel_name

    # 修改manifest_resource
    def modify_manifest_resource(self, channel_path, channel_version, config):
        print "%s modify_manifest_resource" % self.channel_name
        return 0, "%s modify_manifest_resource" % self.channel_name

    # 修改微信回调包名.wxapi.xxx.java问题
    def modify_wx_callback_resource(self, tools_path, temp_path, channel_path, channel_version, config):
        print "%s modify_wx_callback_resource" % self.channel_name
        return 0, "%s modify_wx_callback_resource" % self.channel_name
#
#  修改渠道assets目录资源统一入口, 根据渠道的id来分发, version来做版本版本控制
#
def modify_channel_assets_resource(channel_path, channel_id, channel_version, config):

    # 默认特殊渠道
    special_channel = special.SpecialChannel('special_channel')

    if channel_id == '28':  # 应用宝渠道YSDK
        special_channel = ysdk.YsdkChannel('ysdk')

    status, result = special_channel.modify_assets_resource(channel_path, channel_version, config)
    return status, result


#
#  修改渠道res目录资源统一入口, 根据渠道的id来分发, version来做版本版本控制
#
def modify_channel_res_resource(channel_path, channel_id, channel_version, config):

    # 默认特殊渠道
    special_channel = special.SpecialChannel('special_channel')

    if channel_id == '26':  # 360渠道SDK
        special_channel = qihoo.QihooChannel('360')

    status, result = special_channel.modify_res_resource(channel_path, channel_version, config)
    return status, result


#
#  修改渠道AndroidManifest.xml资源统一入口, 根据渠道的id来分发, version来做版本版本控制
#
def modify_channel_manifest(channel_path, channel_id, channel_version, config):

    # 默认修改包名
    try:
        modify_manifest_package_name(channel_path, config)
    except Exception as e:
        return 1, u'modify manifest package_name fail' + str(e)

    # 默认特殊渠道
    special_channel = special.SpecialChannel('special_channel')

    if channel_id == '17':  # OPPO渠道SDK
        special_channel = oppo.OppoChannel('oppo')

    elif channel_id == '28':  # 应用宝渠道SDK
        special_channel = ysdk.YsdkChannel('ysdk')

    status, result = special_channel.modify_manifest_resource(channel_path, channel_version, config)
    return status, result


#
#  处理下,渠道微信登录、支付等相关功能需在包名下配置: 包名.wxapi.xxx.java问题
#
def modify_channel_wx_callback(tools_path, temp_path, channel_path, channel_id, channel_version, config):

    # 默认特殊渠道
    special_channel = special.SpecialChannel('special_channel')

    if channel_id == '28':  # 应用宝渠道SDK
        special_channel = ysdk.YsdkChannel('ysdk')

    status, result = special_channel.modify_wx_callback_resource(tools_path, temp_path, channel_path, channel_version, config)
    return status, result


#
#  处理下,渠道闪屏问题 和 修改游戏主入口问题
#
def modify_channel_splash_and_main(game_path, channel_id, channel_version, config):

    status, result = modify_splash_and_gameMain(game_path, channel_id, channel_version, config)
    return status, result

界面打包示例:

打包.gif
结语:

关于自动化打包就到这里,下一篇会详细自动化打包遇到的坑点及代码优化过程。手游SDK — 第七篇(游戏打包篇(下)- 自动化打包踩坑记录)。

完整的自动化打包工具项目,本人已开源。可到可到开源项目打包工具下载:PackageApkTool

该工具后续持续完善,欢迎大家star

如果觉得我的文章对你有帮助,请随意赞赏。您的支持将鼓励我继续创作!

你可能感兴趣的:(手游SDK — 第六篇(游戏打包篇(中)- 自动化打包))