手游SDK — 第四篇(游戏打包篇)
hi,各位看官们。前面已经大概介绍了如何搭建一个比较符合业务场景的客户端架构实现,下面我们走进这个系列的最后一篇:游戏打包篇。说句实话,这篇我想了好久,不知道怎么去写。因为这部分更多的是讲解命令行,讲脚本运行的,而且整体的打包流程还需要跟服务端做页面交互,跟客户端开发离得有点远,自己的水平也有限,怕讲不清楚。哎,慢慢写咯,尽量给大伙整明白咯。
游戏与手游SDK的交互
通常游戏调用SDK的方式:接口+资源文件。跟常规android开发不一样的地方是,android开发会通过AndroidStudio或者Eclipse等开发IDE引入SDK工程,然后关联,run一下,包体就出来了。但是游戏开发是引擎开发,可能不会或很少用到android的开发IDE的,游戏的做法是通过中间件接入SDK的接口,然后通过资源目录的形式,将SDK的对应资源打到游戏包体里面的。因此,SDK对外的提供方式,基本上都是接口说明+资源
image.png需要注意的是,libs里面的资源文件大多是.jar形式而不是.arr形式,而且androidStudio的jniLibs目录的so文件也需放到libs下。(这个不要问我为什么,我也不太清楚游戏是如何编译的,猜测应该历史原因,以前android开发大多是基于Eclipse的,游戏很多工程目录还是按之前的来)
SDK源码的混淆
SDK开发跟app开发不太一样,总结来说:SDK开发是隐藏内部实现细节,对外提供公共的访问方式以及结果回调。SDK的用户更多的开发者,既然是开发者,嘿嘿。大伙都懂的,或多或少都会一些普通用户不太会的一些开发技术,利用这些技术总可以研究一些不太利于源码的事情,比如利用dex2jar、jd-gui、apktool这样的工具去反编译包体,研究我们的源码。这就要求开发者在代码做一些安全措施,代码混淆是最常见的一种。
不了解dex2jar、jd-gui、apktool的同学可以参考下,而且后续也会讲到反编译来打包:
apktool、dex2jar、jd-gui的区别及详解
说起源码混淆,对于android开发来说可能都不陌生,或多或少都接触到,通常开发只需要在开发的IDE工程下配置对应的混淆文件就可以了,如:在AS可以通过配置proguard-rules.pro文件。但是这不是要讲的东西,相关的配置规则可以参考下:
Android混淆打包那些事儿
本模块想讲的是如何通过脚本混淆。
先给大伙介绍一下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路径]
image.png
那问题来了,这个只是单个文件的编译呀,多个文件怎么办呢?再多建个文件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
image.png
2、第二步就是将.class文件编译成.jar文件
进入.class 目录,执行cmd,输入命令(jar 命令细节可自行查资料)
jar cvf [输入目录及名称] *
image.png
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配置详解及配置实例
image.png到这里就分析完源码到混淆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项目:SDKBuildJarDemo
至此代码混淆就先介绍到这里。各位可拓展思路做成可视化界面工具,界面显示选择混淆Project列表,选择不同的Project即可生成对应的渠道/插件混淆功能jar,在对外接口不变的情况下,只需要替换源码jar和资源文件即可。
游戏打包
下面给大伙讲讲游戏打包这块。回归第一篇序言的介绍,游戏上线都是要走应用商店的,游戏要走应用商品,一般都会接对应的渠道SDK,行业内的说法是联运,这是比较主流的合作方式。游戏打包也主要是处理渠道SDK的接入问题的。
联运— 即手游CP和手游渠道联合运营一款游戏,手游CP提供产品、运营和客服,手游渠道提供用户,手游CP需要接入渠道方的SDK,才能上线运营,双方按照分成比例进行分成。因为接入了渠道的SDK,所以数据后台用的是渠道方的,结算时是渠道分钱给CP。
关于术语介绍可以看看这个游戏行业常见术语
游戏打包思考?
从最开始的设计开始就希望做成,游戏不需要关注渠道SDK的差异性,聚合SDK可以统一化对接N多的渠道SDK,但是会发现一个问题就是,接口虽然统一化处理了,但是渠道的资源配置文件怎么处理呢?游戏如何只接一次SDK就可以生成对应的渠道游戏包体呢?
回归游戏与SDK的交互方式,一般SDK提供接口和资源,聚合SDK已经处理了接口的统一化问题,资源的统一化怎么处理呢?
很明显嘛:apk包反编译!!!,搞事情不反编译怎么解决问题嘛。
所以,为了方便,一般聚合SDK会预接入一个模拟测试渠道SDK提供给CP接入,该模拟测试渠道SDK只是简单的登录、支付界面交互,用于走通聚合SDK的逻辑而已,游戏接入后的包体称之为游戏母包。后续的真正渠道是通过打包系统打到游戏母包的。(可以参考 手游SDK —第三篇(架构代码实现篇)的GameSDK_Channel_Test模块的模拟渠道实现)
打包流程设计:
image.png游戏打包过程:
以 手游SDK框架Demo的工程及乐享渠道SDK为例子,下面讲解下打包的实现过程:
1、准备工作
-
根据手游SDK框架Demo,Project_JeHe工程关联Channel_Test测试渠道生成对应的混淆sdk_test_V_1.0.0.jar,由对外Demo工程GameSDKFrameDemo生成SDK包:GameSDKFrame.apk。(模拟游戏母包)
-
根据手游SDK框架Demo,预接入乐享渠道源码,并将源码的混淆生成对应的渠道jar包:sdk_lexiang_V_1.0.0.jar
混淆jar生成步骤示例:
第一步:源码工程接入如图(源码可在SDKBuildJarDemo/ProjectResource下载):
image.pngpublic class LexiangSDK extends Channel{
.... 具体源码看Demo....
}
第二步:将乐享渠道源码放到SDKBuildJarDemo/ProjectResource中,并选择需混淆源码工程配置project_list列表,生成混淆jar。接入如图:
image.png第三步:合成渠道资源固定目录格式(将混淆jar放libs中),目录格式可根据项目来定:
image.png2、打包过程
1、将GameSDKFrame.apk反编译成资源文件(GameSDKFrame.apk模拟游戏母包),进入cmd,执行命令:
apktool d -f [待编译apk路径] -o [输出资源路径]
image.png
2、将反编译生成的资源文件和渠道的资源文件合并(为方便,放到temp目录下),主要是合并assets、libs、res及AndroidManifest.xml等资源文件(目前是手动合并)
image.png3、根据资源文件合成R.java文件(平时遇到R文件丢失,可以看下这个脚本运行看看),进入cmd,执行命令:
aapt package -f -m -J [R.java输出路径] -S [res路径] -I [android.jar路径]-M [AndroidManifest.xml路径]
aapt p -f -m -J [R.java输出路径] -S [res路径] -I [android.jar路径]-M [AndroidManifest.xml路径]
image.png
4、编译生成的R.java文件,进入cmd ,执行命令:
javac [R.java路径]
javac -source 1.7 -target 1.7 [R.java路径]
javac -source 1.7 -target 1.7 -encoding UTF-8 [R.java路径]
image.png
5、合成R.jar文件,进入cmd,执行命令:
jar cvf [输入目录及名称] *
image.png
6、将.jar 转化为.dex文件(注意只能单个转换,批量转换需要循环),进入cmd,执行命令:
java -jar [dx.jar路径] --dex --output=[输出路径] [待转化jar路径]
image.png
image.png
7、将.dex转化为smail代码(注意只能单个转换,批量转换需要循环),进入cmd,执行命令:
java -jar [baksmali.jar路径] -o [输入文件夹] [待转化dex文件]
image.png
image.png
8、回编译生成apk包(注意需要将temp目录下的libs和r目录删除,为方便,保留一份到temp_copy中),进入cmd,执行命令:
apktool b [待编译资源路径] -o [生成apk路径]
image.png
image.png
9、生成apk包体签名
jarsigner -verbose -keystore [keystore文件路径] -signedjar [签名后生成的apk路径] [待签名的apk路径] [别名]
image.png
至此完整的打包流程就完毕了,可以运行下前后的包体看下效果:
打包前的登录界面
image.png打包后的登录界面
image.png
至此就可以把渠道对应的资源打到游戏包体里面去了。再结合之前的手游SDK框架分析,可以通过配置文件,工程模块的分类做到快速的打不同的功能包,渠道包了。
3、打包过程图解
image.png结语:
关于手游SDK的打包流程的实现就大体介绍到这里。打包篇的很多实现会涉及到脚本命名,脚本命令不太清楚的,可自行查阅资料。打包流程Demo的地址: BuildApkDemo。
BuildApkDemoDemo 有BuildAPKDemo.zip 主要是命令行的执行过程 和 SDKBuildAPkDemo_py为命令行的python的Demo,替换下签名文件就可运行了。主要是为了加深理解。
完整的打包过程项目,本人已开源做成了一个打包工具,可到可到开源项目打包工具下载:PackageApkTool
该工具后续持续完善,欢迎大家star