手游SDK — 第五篇(游戲打包篇(上)- 打包系統(tǒng)設(shè)計)

hi,各位看官們。前面已經(jīng)大概介紹了如何搭建一個比較符合業(yè)務(wù)場景的客戶端架構(gòu)實現(xiàn)。下面我們走進(jìn)這個系列的游戲打包篇系列。這個系列更多的是講解如何將SDK的資源通過打包系統(tǒng)生成一個游戲_渠道包的。

回歸第一篇序言的介紹,游戲上線都是要走應(yīng)用商店的,游戲要走應(yīng)用商品,一般都會接對應(yīng)的渠道SDK,行業(yè)內(nèi)的說法是聯(lián)運,這是比較主流的合作方式。游戲打包也主要是處理渠道SDK的接入問題的。

聯(lián)運— 即手游CP和手游渠道聯(lián)合運營一款游戲,手游CP提供產(chǎn)品、運營和客服,手游渠道提供用戶,手游CP需要接入渠道方的SDK,才能上線運營,雙方按照分成比例進(jìn)行分成。因為接入了渠道的SDK,所以數(shù)據(jù)后臺用的是渠道方的,結(jié)算時是渠道分錢給CP。

關(guān)于術(shù)語介紹可以看看這個游戲行業(yè)常見術(shù)語

游戲打包思考?

從最開始的設(shè)計開始就希望做成,游戲不需要關(guān)注渠道SDK的差異性,聚合SDK可以統(tǒng)一化對接N多的渠道SDK,但是會發(fā)現(xiàn)一個問題就是,接口雖然統(tǒng)一化處理了,但是渠道的資源配置文件怎么處理呢?游戲如何只接一次SDK就可以生成對應(yīng)的渠道游戲包體呢?

很明顯嘛:apk包反編譯!!!,搞事情不反編譯怎么解決問題嘛。

所以,一般聚合SDK會預(yù)接入一個模擬測試渠道SDK提供給CP接入,該模擬測試渠道SDK只是簡單的登錄、支付界面交互,用于走通聚合SDK的邏輯而已,游戲接入后的包體稱之為游戲母包。后續(xù)的真正渠道是通過打包系統(tǒng)反編譯后打到游戲母包的。(可以參考 手游SDK —第三篇(架構(gòu)代碼實現(xiàn)篇)的GameSDK_Channel_Test模塊的模擬渠道實現(xiàn))

打包系統(tǒng)整體設(shè)計

在手游行業(yè)里面,大家或多或少都聽說過易接和quick這兩家比較有代表性的公司。因為易接和quick的打包工具算是業(yè)界里面做的比較快捷方便了,本系列的文章也是粗淺的跟大家講講打包大概過程及實現(xiàn)。

大家可以先看看整體的一個打包流程設(shè)計圖:

打包整體流程設(shè)計:

image.png

首先游戲會接入封裝好測試渠道的SDK生成一個游戲-SDK.apk母包,然后獲取到對應(yīng)的渠道資源包及渠道參數(shù),輸入到自動化打包系統(tǒng),最終生成個游戲-渠道.apk包體。最終這個包體會上架的對應(yīng)的渠道平臺去審核發(fā)行。

這里會大概分三部分:游戲母包、渠道資源包、自動化打包系統(tǒng)。
游戲母包:接入已封裝測試渠道的SDK,對接統(tǒng)一化接口,走通整體SDK的登錄、支付、數(shù)據(jù)上報交互邏輯的游戲包體;
渠道資源包:聚合SDK對接渠道SDK接口后,封裝給打包系統(tǒng)的整合資源包,通常資源的結(jié)構(gòu)形式會跟自動化打包系統(tǒng)對應(yīng);
自動化打包系統(tǒng):輸入游戲母包和渠道資源包后,自動化解包、合并資源、封包,最終輸出游戲_渠道包。

這里比較核心的部分分兩部分,游戲母包和渠道資源包、自動化打包系統(tǒng),前者可以參考前面部分SDK架構(gòu)設(shè)計及實現(xiàn)。后者也打包系列核心內(nèi)容,后續(xù)會詳細(xì)介紹。
目前市面上有兩種形式的打包系統(tǒng),一種是類似易接和quick的打包工具;另外一種是網(wǎng)頁版的打包工具,也就是服務(wù)器打包,可參考下圖。

打包流程交互設(shè)計圖

image.png

簡單分析下兩種打包系統(tǒng)(左為桌面打包,右為網(wǎng)頁打包):
1、桌面版的打包工具:用戶可以通過下載對應(yīng)的應(yīng)用安裝后就可以使用,打包的核心過程都是安裝應(yīng)用里面。用戶導(dǎo)入游戲母包后,通過服務(wù)器下載不同渠道的資源包,填好相應(yīng)的渠道配置后,完成打包過程。

2、網(wǎng)頁版的打包工具:用戶打開相應(yīng)的打包網(wǎng)頁,上傳對應(yīng)的游戲母包、選擇不同的打包渠道列表,選擇渠道資源,填好相應(yīng)的渠道配置后,給打包服務(wù)器發(fā)送打包命令,完成打包過程。

優(yōu)劣:
桌面打包,用戶只需要下載渠道資源就可以使用,不需要上傳和下載游戲母包的。但是需要適配不用的電腦系統(tǒng),可能需要開發(fā)多個版本的打包應(yīng)用。而且桌面打包本地調(diào)試會更方便。

網(wǎng)頁打包,對系統(tǒng)的適配性小,用戶只要打開網(wǎng)頁就可以,但是用戶需要上傳對應(yīng)的游戲母包,特別是游戲包體比較大耗費時間,且耗費服務(wù)存儲資源。不過服務(wù)器打包的速度比桌面打包更快,幾十個包體一會可能就打完了。

總結(jié)下,整體分析完打包系統(tǒng),下面咱們來慢慢看怎么一步步實現(xiàn)打包系統(tǒng)。(這里說明下,網(wǎng)頁打包涉及到j(luò)s知識,這個不在這里討論)

游戲與手游SDK的交互

上面已經(jīng)說明:整體打包系統(tǒng)分三部分游戲母包、渠道資源包和自動化打包系統(tǒng),形象來說前面兩個是原料,后面一個是加工的工具。結(jié)合前面的SDK框架說明,一起來看看,怎么生成原料的。

第一步:

游戲需要先接入已封裝測試渠道封裝的SDK,生成游戲_SDK母包。通常游戲調(diào)用SDK的方式:接口+資源文件。跟常規(guī)android開發(fā)不一樣的地方是,android開發(fā)會通過AndroidStudio或者Eclipse等開發(fā)IDE引入SDK工程,然后關(guān)聯(lián),run一下,包體就出來了。但是游戲開發(fā)是引擎開發(fā),可能不會或很少用到android的開發(fā)IDE的,游戲的做法是通過中間件接入SDK的接口,然后通過資源目錄的形式,將SDK的對應(yīng)資源打到游戲包體里面的。因此,SDK對外的提供方式,基本上都是接口說明+資源(僅供參考)

image.png

需要注意的是,libs里面的資源文件大多是.jar形式而不是.arr形式,而且androidStudio的jniLibs目錄的so文件也需放到libs下。(這個不要問我為什么,我也不太清楚游戲是如何編譯的,猜測應(yīng)該歷史原因,以前android開發(fā)大多是基于Eclipse的,游戲很多工程目錄還是按之前的來)

好了,假設(shè)游戲已經(jīng)接入SDK_測試渠道,走完整體的登錄交互邏輯生成一個游戲母包給到自動化打包系統(tǒng)。

第二步:

生成渠道資源包,這里的渠道資源包跟前面提供給游戲的資源方式類似(僅供參考),但是會有一些特殊的配置目錄:


image.png

大概說明下特殊目錄:
1、config:配置信息,可以是SDK配置,打包編譯參數(shù)配置,角標(biāo),Icon配置等
2、splash:閃屏圖片
3、wxcallback:特殊處理微信登錄和支付回調(diào)的.java類文件

不過在這里打斷下,不管是提供給游戲的SDK資源形式還是生產(chǎn)渠道資源包,都會涉及到SDK源碼,需要處理下代碼安全性的問題。

SDK開發(fā)跟app開發(fā)不太一樣,總結(jié)來說:SDK開發(fā)是隱藏內(nèi)部實現(xiàn)細(xì)節(jié),對外提供公共的訪問方式以及結(jié)果回調(diào)。SDK的用戶更多的開發(fā)者,既然是開發(fā)者,嘿嘿。大伙都懂的,或多或少都會一些普通用戶不太會的一些開發(fā)技術(shù),利用這些技術(shù)總可以研究一些不太利于源碼的事情,比如利用dex2jar、jd-gui、apktool這樣的工具去反編譯包體,研究我們的源碼。這就要求開發(fā)者在代碼做一些安全措施,代碼混淆是最常見的一種。不了解dex2jar、jd-gui、apktool的同學(xué)可以參考下,而且后續(xù)也會講到反編譯來打包:apktool、dex2jar、jd-gui的區(qū)別及詳解

SDK源碼的混淆

說起源碼混淆,對于android開發(fā)來說可能都不陌生,或多或少都接觸到,通常開發(fā)只需要在開發(fā)的IDE工程下配置對應(yīng)的混淆文件就可以了,如:在AS可以通過配置proguard-rules.pro文件,相關(guān)的配置規(guī)則可以參考下: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、第一步當(dāng)然是將.java文件轉(zhuǎn)化為.class文件,這讓我依稀想起了剛學(xué)java時,txt文件手?jǐn)]語法的日子。執(zhí)行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);
    }

}

進(jìn)入源碼目錄,執(zhí)行cmd,輸入命令(javac 命令細(xì)節(jié)可自行查資料)

javac -d [輸出目錄] -sourcepath src *.java
image.png
2、第二步就是將.class文件編譯成.jar文件

進(jìn)入.class 目錄,執(zhí)行cmd,輸入命令(jar 命令細(xì)節(jié)可自行查資料)

jar cvf [輸入目錄及名稱] *
image.png
3、第三步就是將.jar混淆

前面已經(jīng)說明了下,可以通過ProguardGui工具來進(jìn)行混淆,下面再來看下腳本是如何混淆的
準(zhǔn)備工作:proguard.jar / rt.jar(jdk環(huán)境下java\jre_1.7.5\lib的jar) 和 混淆配置文件

執(zhí)行cmd,輸入命令

java -jar [proguard.jar路徑] -injars [待混淆jar路徑] -outjars [輸出混淆jar路徑] libraryjars [依賴資源] @[混淆配置信息]

相關(guān)參數(shù)可參考:開源混淆工具ProGuard配置詳解及配置實例

image.png

到這里就一步一步分析完源碼生成混淆jar的過程,那代碼如何自動化處理呢?

下面是代碼執(zhí)行過程

    /**
     * 編譯生成混淆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);

        //創(chuàng)建一個Temp工程
        Project tmpBuildProject = new Project();
        String projectName = "tmpBuildProject";
        String tmpBuildProjectPath = outputPath + File.separator + projectName;
        tmpBuildProject.setName(projectName);
        tmpBuildProject.setPath(tmpBuildProjectPath);

        //創(chuàng)建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, "構(gòu)建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, "構(gòu)建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, "構(gòu)建SDK-混淆jar出錯", e);
        }

        return new ErrorMsg(Utils.OK, "ok");
    }

詳情可看github項目: 手游SDK框架Demo 中的GameSDKBuildJarTool模塊

至此代碼混淆就先介紹到這里。各位可拓展思路做成可視化界面工具,界面顯示選擇混淆Project列表,選擇不同的Project即可生成對應(yīng)的渠道/插件混淆功能jar,在對外接口不變的情況下,只需要替換源碼jar和資源文件即可。

結(jié)語:

關(guān)于打包系統(tǒng)的整體設(shè)計就到這里,下一篇會詳細(xì)介紹打包系統(tǒng)的核心,自動化打包手游SDK — 第六篇(游戲打包篇(中)- 自動化打包)。

如果覺得我的文章對你有幫助,請隨意贊賞。您的支持將鼓勵我繼續(xù)創(chuàng)作!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容