hi,各位看官们。前面已经大概介绍了如何搭建一个比较符合业务场景的客户端架构实现。下面我们走进这个系列的游戏打包篇系列。这个系列更多的是讲解如何将SDK的资源通过打包系统生成一个游戏_渠道包的。
回归第一篇序言的介绍,游戏上线都是要走应用商店的,游戏要走应用商品,一般都会接对应的渠道SDK,行业内的说法是联运,这是比较主流的合作方式。游戏打包也主要是处理渠道SDK的接入问题的。
联运— 即手游CP和手游渠道联合运营一款游戏,手游CP提供产品、运营和客服,手游渠道提供用户,手游CP需要接入渠道方的SDK,才能上线运营,双方按照分成比例进行分成。因为接入了渠道的SDK,所以数据后台用的是渠道方的,结算时是渠道分钱给CP。
关于术语介绍可以看看这个游戏行业常见术语
游戏打包思考?
从最开始的设计开始就希望做成,游戏不需要关注渠道SDK的差异性,聚合SDK可以统一化对接N多的渠道SDK,但是会发现一个问题就是,接口虽然统一化处理了,但是渠道的资源配置文件怎么处理呢?游戏如何只接一次SDK就可以生成对应的渠道游戏包体呢?
很明显嘛:apk包反编译!!!,搞事情不反编译怎么解决问题嘛。
所以,一般聚合SDK会预接入一个模拟测试渠道SDK提供给CP接入,该模拟测试渠道SDK只是简单的登录、支付界面交互,用于走通聚合SDK的逻辑而已,游戏接入后的包体称之为游戏母包。后续的真正渠道是通过打包系统反编译后打到游戏母包的。(可以参考 手游SDK —第三篇(架构代码实现篇)的GameSDK_Channel_Test模块的模拟渠道实现)
打包系统整体设计
在手游行业里面,大家或多或少都听说过易接和quick这两家比较有代表性的公司。因为易接和quick的打包工具算是业界里面做的比较快捷方便了,本系列的文章也是粗浅的跟大家讲讲打包大概过程及实现。
大家可以先看看整体的一个打包流程设计图:
打包整体流程设计:
首先游戏会接入封装好测试渠道的SDK生成一个游戏-SDK.apk母包,然后获取到对应的渠道资源包及渠道参数,输入到自动化打包系统,最终生成个游戏-渠道.apk包体。最终这个包体会上架的对应的渠道平台去审核发行。
这里会大概分三部分:游戏母包、渠道资源包、自动化打包系统。
游戏母包:接入已封装测试渠道的SDK,对接统一化接口,走通整体SDK的登录、支付、数据上报交互逻辑的游戏包体;
渠道资源包:聚合SDK对接渠道SDK接口后,封装给打包系统的整合资源包,通常资源的结构形式会跟自动化打包系统对应;
自动化打包系统:输入游戏母包和渠道资源包后,自动化解包、合并资源、封包,最终输出游戏_渠道包。
这里比较核心的部分分两部分,游戏母包和渠道资源包、自动化打包系统,前者可以参考前面部分SDK架构设计及实现。后者也打包系列核心内容,后续会详细介绍。
目前市面上有两种形式的打包系统,一种是类似易接和quick的打包工具;另外一种是网页版的打包工具,也就是服务器打包,可参考下图。
打包流程交互设计图
简单分析下两种打包系统(左为桌面打包,右为网页打包):
1、桌面版的打包工具:用户可以通过下载对应的应用安装后就可以使用,打包的核心过程都是安装应用里面。用户导入游戏母包后,通过服务器下载不同渠道的资源包,填好相应的渠道配置后,完成打包过程。
2、网页版的打包工具:用户打开相应的打包网页,上传对应的游戏母包、选择不同的打包渠道列表,选择渠道资源,填好相应的渠道配置后,给打包服务器发送打包命令,完成打包过程。
优劣:
桌面打包,用户只需要下载渠道资源就可以使用,不需要上传和下载游戏母包的。但是需要适配不用的电脑系统,可能需要开发多个版本的打包应用。而且桌面打包本地调试会更方便。
网页打包,对系统的适配性小,用户只要打开网页就可以,但是用户需要上传对应的游戏母包,特别是游戏包体比较大耗费时间,且耗费服务存储资源。不过服务器打包的速度比桌面打包更快,几十个包体一会可能就打完了。
总结下,整体分析完打包系统,下面咱们来慢慢看怎么一步步实现打包系统。(这里说明下,网页打包涉及到js知识,这个不在这里讨论)
游戏与手游SDK的交互
上面已经说明:整体打包系统分三部分游戏母包、渠道资源包和自动化打包系统,形象来说前面两个是原料,后面一个是加工的工具。结合前面的SDK框架说明,一起来看看,怎么生成原料的。
第一步:
游戏需要先接入已封装测试渠道封装的SDK,生成游戏_SDK母包。通常游戏调用SDK的方式:接口+资源文件。跟常规android开发不一样的地方是,android开发会通过AndroidStudio或者Eclipse等开发IDE引入SDK工程,然后关联,run一下,包体就出来了。但是游戏开发是引擎开发,可能不会或很少用到android的开发IDE的,游戏的做法是通过中间件接入SDK的接口,然后通过资源目录的形式,将SDK的对应资源打到游戏包体里面的。因此,SDK对外的提供方式,基本上都是接口说明+资源(仅供参考)
需要注意的是,libs里面的资源文件大多是.jar形式而不是.arr形式,而且androidStudio的jniLibs目录的so文件也需放到libs下。(这个不要问我为什么,我也不太清楚游戏是如何编译的,猜测应该历史原因,以前android开发大多是基于Eclipse的,游戏很多工程目录还是按之前的来)
好了,假设游戏已经接入SDK_测试渠道,走完整体的登录交互逻辑生成一个游戏母包给到自动化打包系统。
第二步:
生成渠道资源包,这里的渠道资源包跟前面提供给游戏的资源方式类似(仅供参考),但是会有一些特殊的配置目录:
大概说明下特殊目录:
1、config:配置信息,可以是SDK配置,打包编译参数配置,角标,Icon配置等
2、splash:闪屏图片
3、wxcallback:特殊处理微信登录和支付回调的.java类文件
不过在这里打断下,不管是提供给游戏的SDK资源形式还是生产渠道资源包,都会涉及到SDK源码,需要处理下代码安全性的问题。
SDK开发跟app开发不太一样,总结来说:SDK开发是隐藏内部实现细节,对外提供公共的访问方式以及结果回调。SDK的用户更多的开发者,既然是开发者,嘿嘿。大伙都懂的,或多或少都会一些普通用户不太会的一些开发技术,利用这些技术总可以研究一些不太利于源码的事情,比如利用dex2jar、jd-gui、apktool这样的工具去反编译包体,研究我们的源码。这就要求开发者在代码做一些安全措施,代码混淆是最常见的一种。不了解dex2jar、jd-gui、apktool的同学可以参考下,而且后续也会讲到反编译来打包:apktool、dex2jar、jd-gui的区别及详解
SDK源码的混淆
说起源码混淆,对于android开发来说可能都不陌生,或多或少都接触到,通常开发只需要在开发的IDE工程下配置对应的混淆文件就可以了,如:在AS可以通过配置proguard-rules.pro文件,相关的配置规则可以参考下:Android混淆打包那些事儿。但是这不是要讲的东西,要讲的是:SDK源码如何自动生成混淆jar。
在讲解源码混淆之前,先给大伙介绍一下ProguardGui界面化工具,主要是界面化混淆jar包。其实也是google官方提供混淆方式,比较方便。ProguardGui界面化工具使用
那SDK源码如何自动生成混淆jar呢?
下面以Hello World为例子讲解下
public class HelloWorld {
public static void main(String[] args){
System.out.println("hello world");
}
}
1、第一步当然是将.java文件转化为.class文件,这让我依稀想起了刚学java时,txt文件手撸语法的日子。执行cmd,输入命令:
javac [java路径]
那问题来了,这个只是单个文件的编译呀,多个文件怎么办呢?再多建个文件TestA.java 看看。将这两个文件放到test目录下
public class HelloWorld {
public static void main(String[] args){
System.out.println("hello world");
test();
}
public static void test(){
TestA testA = new TestA();
testA.Test();
}
}
public class TestA {
public String name = "asdfa";
public void Test(){
System.out.println("name:"+name);
}
}
进入源码目录,执行cmd,输入命令(javac 命令细节可自行查资料)
javac -d [输出目录] -sourcepath src *.java
2、第二步就是将.class文件编译成.jar文件
进入.class 目录,执行cmd,输入命令(jar 命令细节可自行查资料)
jar cvf [输入目录及名称] *
3、第三步就是将.jar混淆
前面已经说明了下,可以通过ProguardGui工具来进行混淆,下面再来看下脚本是如何混淆的
准备工作:proguard.jar / rt.jar(jdk环境下java\jre_1.7.5\lib的jar) 和 混淆配置文件
执行cmd,输入命令
java -jar [proguard.jar路径] -injars [待混淆jar路径] -outjars [输出混淆jar路径] libraryjars [依赖资源] @[混淆配置信息]
相关参数可参考:开源混淆工具ProGuard配置详解及配置实例
到这里就一步一步分析完源码生成混淆jar的过程,那代码如何自动化处理呢?
下面是代码执行过程
/**
* 编译生成混淆jar包过程
* @param projectList 工程配置
* @param jarOutputPath jar包输出路径
* @return
*/
private ErrorMsg buildJar(List<Project> projectList, String jarOutputPath){
File jarFile = new File(jarOutputPath);
String outputPath = jarFile.getParent();
System.out.println(outputPath);
//创建一个Temp工程
Project tmpBuildProject = new Project();
String projectName = "tmpBuildProject";
String tmpBuildProjectPath = outputPath + File.separator + projectName;
tmpBuildProject.setName(projectName);
tmpBuildProject.setPath(tmpBuildProjectPath);
//创建temp工程目录,Java 和 libs
String projectSrcPath = tmpBuildProjectPath + File.separator + Project.JAVA_RELATIVE_PATH;
try{
FileUtils.createDirectoriesIfNonExists(outputPath);
FileUtils.createDirectoriesIfNonExists(tmpBuildProjectPath);
FileUtils.createDirectoriesIfNonExists(projectSrcPath);
}catch (Exception e){
return new ErrorMsg(Utils.ERROR, e.getMessage(), e);
}
//将各个项目的java文件下的源文件拷贝到 tmpBuildProject 的src文件下。
try{
for (Project project : projectList){
String tmpProjectSrcPath = project.getPath() + File.separator + Project.JAVA_RELATIVE_PATH;
if (FileUtils.exists(tmpProjectSrcPath)){
FileUtils.copy(tmpProjectSrcPath, projectSrcPath, false);
}
}
}catch (Exception e){
return new ErrorMsg(Utils.ERROR, e.getMessage(), e);
}
// .java and .jar compile to .class
String classPath = getClasspath(tmpBuildProject,projectList);
String classFilesOutputPath = tmpBuildProjectPath + File.separator + "classes";
try {
FileUtils.createDirectory(classFilesOutputPath);
JavaTool.compile(projectSrcPath, classPath, classFilesOutputPath, null);
}catch (Exception e){
return new ErrorMsg(Utils.ERROR, "构建SDK-编译.java to .class出错", e);
}
// .class compile to .jar
String noProguardJar = outputPath + File.separator + "no_proguard.jar";
try{
JavaTool.classFilesToJar(classFilesOutputPath, noProguardJar, null);
}catch (Exception e){
return new ErrorMsg(Utils.ERROR, "构建SDK-编译.class打包jar出错", e);
}
//proguard jar
try{
String mapping = outputPath + File.separator + "proguard_mapping.txt";
URL url = ClassLoader.getSystemClassLoader().getResource("proguard_config.pro");
String proguardConfigFilePath = url.getFile();
ProGuardTool.run(noProguardJar, classPath, proguardConfigFilePath, mapping, jarOutputPath);
}catch (Exception e){
return new ErrorMsg(Utils.ERROR, "构建SDK-混淆jar出错", e);
}
return new ErrorMsg(Utils.OK, "ok");
}
详情可看github项目: 手游SDK框架Demo 中的GameSDKBuildJarTool模块
至此代码混淆就先介绍到这里。各位可拓展思路做成可视化界面工具,界面显示选择混淆Project列表,选择不同的Project即可生成对应的渠道/插件混淆功能jar,在对外接口不变的情况下,只需要替换源码jar和资源文件即可。
结语:
关于打包系统的整体设计就到这里,下一篇会详细介绍打包系统的核心,自动化打包手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)。
如果觉得我的文章对你有帮助,请随意赞赏。您的支持将鼓励我继续创作!