一文了解Android遊戲SDK開發

去年從平安離職以後,加入了一家遊戲公司,負責遊戲SDK相關的業務開發和維護工做,通過半年來的摸索,對於遊戲SDK的開發有了必定的理解,下面就對遊戲SDK開發涉及到的知識點進行簡單的梳理。java

SDK

SDK(Software Development Kit)是軟件開發工具包的縮寫,通常來講,SDK是用於給開發人員提供進行應用程序開發的工具的,這樣程序員就能夠快速的開發出應用軟件,省去了編寫硬件代碼和基礎代碼框架的過程,咱們常見的Android SDK就屬於這一類。除了這種比較大的SDK,咱們平時開發的library也屬性SDK,只不過功能比較單一,適用的場合也比較簡單,如短視頻SDK、推送SDK,分享SDK等。
而咱們所作的遊戲SDK主要是用於第三方遊戲開發接入咱們的帳號體系和支付體系,相似於友盟分享等聚合SDK。python

遊戲SDK

遊戲SDK是啥

在遊戲行業中,會存在兩個最基本的角色,即遊戲開發和遊戲運營,一個遊戲能不能成功,除了技術體驗好以外,運營是一門很重要的學問,他們的關係以下圖所示。
在這裏插入圖片描述
正如前面說描述的同樣,遊戲和運營每每是單獨開來的,除非像騰訊、網易這些頭部大公司,不只能夠本身研發遊戲,還有實力本身推廣和運營遊戲。不過,事實上,不少小的遊戲開發商就那麼幾我的或者幾十我的,根本沒有本身的運營能力,而市面上正好有專業的遊戲運營公司,這時候它們就開始合做了。android

在這裏插入圖片描述
在上面的圖例中,小紅是作社交App的娛樂公司,日活幾千萬,想讓本身平臺多元化,好比作個遊戲下載的功能,給用戶下載,用戶以爲好玩,可能就會付費買裝備,可是有個問題,小紅並不會作遊戲,若是單開一個產品線去研發遊戲,投入是至關巨大的,因此想到能不能去外面接遊戲進來。git

遊戲SDK的流程圖

遊戲SDK最核心的功能就是登陸和支付,其它都是一些運營相關的,例如埋點、數據統計、崩潰等等。其中,登陸的流程大致以下。
在這裏插入圖片描述
而支付的流程大致是先SDK,而後再通知遊戲支付結構,流程以下所示。
在這裏插入圖片描述程序員

遊戲SDK開發要求

遊戲SDK做爲基礎SDK,一般須要遵循一些基本的開發規範,例如,儘可能少用第三方庫、減小對外接口、明確技術文檔等。github

少依賴

做爲SDK,咱們應該儘可能少使用開源庫或者說不用開源庫,儘可能直接使用系統提供的庫,實在不行也能夠手寫網絡框架,手寫數據庫等等,主要基於如下兩個方面考慮。算法

  • 減少SDK體積 ;
  • 避免第三方接入的時候發生依賴衝突

解決依賴衝突

固然,依賴庫並非說不能用,有時候一些數據統計的庫就須要依賴第三方庫,那麼對於這種狀況衝突是不可避免的,一般解決衝突有兩種常見的手段。數據庫

強制使用某個版本,例如:緩存

configurations.all {
    resolutionStrategy {
        force 'com.android.support:support-v4:26.1.0'   //解決v4包衝突,強制使用這個版本的v4包
    }
}

不少作應用開發的都知道,若是一個項目中重複使用了某個庫,那麼可使用exclude排除某個依賴,以下:安全

implementation("com.xxx.xxx:xx") {
     exclude group: 'com.android.support'
}

exclude是最經常使用的解決依賴衝突的方式,但若是多個依賴庫引入不一樣版本的其它庫,須要分別寫好多個exclude,顯然第一種方式比較簡單粗暴。

減小對外接口

對於SDK開發,對外的接口儘可能越少越好。以遊戲SDK爲例,對外暴露的接口通常有SDK初始化、登陸、支付等,以下所示。

定義接口

interface IGame {

    // 一、在Application中調用,
    fun registerApp(context: ApplicationContext, appId: String)

    // 二、在activity中初始化
    fun init(activity: Activity)

    // 三、業務接口,登陸、支付等等
    fun login(loginCallBack: LoginCallBack)
    
    fun pay(product: Product, payCallBack: PayCallBack)
    ...
}

接口實現:

class GameImpl : IGame{

    override fun registerApp(context: ApplicationContext, appId: String) {
        //appid相關
    }

    override fun init(activity: Activity) {
        //初始化邏輯,例如顯示懸浮窗
    }

    override fun login(loginCallBack: LoginCallBack) {
        //登陸邏輯
    }

    override fun pay(product: Product, payCallBack: PayCallBack) {
        //支付邏輯
    }
    ...

}

實現類是咱們SDK內部的處理邏輯,咱們不但願被外部訪問到,外部只須要知道有 IGame這個接口中的方法就行,所以,咱們能夠再寫個單例的管理類來給外部使用,以下所示。

/**
 * 單例的SDK管理類
 */
object GameSDKManager :IGame{

    //實現類私有化
    private val gameImpl: IGame  by lazy { GameImpl() }

    override fun registerApp(application: Application, appId: String) {
        gameImpl.registerApp(application,appId)
    }

    override fun init(activity: Activity) {
        gameImpl.init(activity)
    }

    override fun login(loginCallBack: LoginCallBack) {
        gameImpl.login(loginCallBack)
    }

    override fun pay(product: Product, payCallBack: PayCallBack) {
        gameImpl.pay(product,payCallBack)
    }

}

實際使用時,外部經過GameSDKManager.xxx來調用SDK中的方法,之後要提供其它方法,只要修改 IGame接口,而後在 GameSDKManager 和 GameImpl 類中分別進行實現便可。

SDK開發注意點

遊戲SDK開發相比應用開發來講,技術難度通常不大,問題大多出在跟遊戲對接的時候。可能會出現一些問題, 好比ClassNotFound、Resource not found、依賴衝突、崩潰等等,對於這類問題,咱們須要先分析問題的緣由,而後確認由誰負責,最後肯定修改方案。

1, SDK須要支持Eclipse

和應用開發不一樣,不少遊戲仍是使用Eclipse進行開發的,因此在對接遊戲時須要提供Eclipse版本。須要說明的是,Eclipse 不能使用Android Studio版本的SDK,搭建Eclipse的Android環境須要使用ADT插件,具體怎麼使用請參考官網。

因爲SDK的產物是aar,而Eclipse只能依賴jar包和library,通常都用jar包依賴,所以先將aar解壓出來,把裏面的classes.jar拷貝出來重命名,而後在Eclipse中依賴這個jar包,同時SDK的資源文件、libs目錄下的jar包也須要拷貝到Eclipse項目中。

2,setContentView相關問題

2.1 問題描述

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_test)
    
}

上面的代碼是一段再常見不過的代碼了,可是這段代碼在打包aar的時候,Android Studio接入沒問題,可是打成jar包,Eclipse接入的時候會奔潰,日誌以下:

Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x13d6b6
        at android.content.res.ResourcesImpl.getValue(ResourcesImpl.java:246)
        at android.content.res.Resources.loadXmlResourceParser(Resources.java:2256)
        at android.content.res.Resources.getLayout(Resources.java:1228)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:427)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:380)
        at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:555)
        at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161)
        at luyao.util.ktx.base.BaseVMActivity.onCreate(BaseVMActivity.kt:25)

咱們打開源代碼文件,而後將鼠標放到R.layout.activity_test上,以下圖所示。
在這裏插入圖片描述
搞過Android開發的同窗都知道,上面的常量是在AAPT打包的階段生成的,是一個R常量。對於Android工程是如何打包的,下面讓咱們來簡單的回顧下流程:

  1. 使用AAPT(或者AAPT2)工具打包資源文件,生成R.class文件,resources.arsc資源索引表等;
  2. 若是有AIDL的跨進程調用,須要將AIDL 轉換成Jave代碼;
  3. 將Java代碼編譯成.class字節碼文件;
  4. 使用dex工具將.class文件轉換成Dalvik 字節碼,也就是.dex文件;
  5. 經過ApkBuilde工具將.dex文件和其它資源文件打包成未簽名的apk;
  6. 經過簽名工具給apk簽名,v1簽名使用jarsigner、v2簽名使用apksigner(sdk 25版本開始提供)

Android的打包流程能夠查看:Android打包流程
apk編譯的第一個階段,AAPT會打包資源文件,生成R.class文件和resources.arsc資源索引表。對於library項目,在打包aar的時候,aar中並不須要生成 resources.arsc 資源索引表,資源的id跟資源文件的映射關係記錄在R.txt中,以下圖所示。
在這裏插入圖片描述
Eclipse由於只能接入jar包,也就是解壓aar後取出裏面的classes.jar,當咱們把資源文件拷貝到Eclipse,再編譯apk的時候,資源文件會對應一個新的資源id,而aar中classes.jar裏引用的資源id是不變的,因此就會出現上面的問題。

知道這個問題後,要解決這個問題,那麼SDK裏面使用資源id就須要動態去獲取,不能使用R文件裏面的常量。

2.2 動態獲取資源Id

谷歌提供了相關的API(getIdentifier),能夠經過資源名稱獲取資源id,getIdentifier的源碼以下。

/**
     * Return a resource identifier for the given resource name.  A fully
     * qualified resource name is of the form "package:type/entry".  The first
     * two components (package and type) are optional if defType and
     * defPackage, respectively, are specified here.
     * 
     * <p>Note: use of this function is discouraged.  It is much more
     * efficient to retrieve resources by identifier than by name.
     * 
     * @param name The name of the desired resource.
     * @param defType Optional default resource type to find, if "type/" is
     *                not included in the name.  Can be null to require an
     *                explicit type.
     * @param defPackage Optional default package to find, if "package:" is
     *                   not included in the name.  Can be null to require an
     *                   explicit package.
     * 
     * @return int The associated resource identifier.  Returns 0 if no such
     *         resource was found.  (0 is not a valid resource ID.)
     */
    public int getIdentifier(String name, String defType, String defPackage) {
        return mResourcesImpl.getIdentifier(name, defType, defPackage);
    }

其中,第一個參數是資源名稱,如一個TextView定義的id叫tv_title;第二個參數是類型,如 string、xml、style、layout 等等,跟R.class文件裏面的內部類是對應的;第三個參數是應用的包名。例如,下面是第二個參數的R文件對應。

在這裏插入圖片描述

爲了方便動態獲取資源的Id,咱們能夠封裝個工具,以下所示:

object ResourceUtil {

    //緩存資源id
    private val idMap: HashMap<String, Int> = HashMap()

    private fun getIdByName(context: Context, defType: String, name: String): Int {
        
        //緩存
        val key = defType + "_" + name
        val value: Int? = idMap.get(key)
        value?.let {
            return it
        }

        //獲取資源id
        val identifier = context.resources.getIdentifier(name, defType, context.packageName)
        identifier?.let {
            idMap.put(key, identifier)
        }
        return identifier
    }
    
    /**
     * 獲取佈局文件的資源ID,defType傳 layout
     */
    fun getIdFromLayout(context: Context, name: String): Int {
        return getIdByName(context, "layout", name)
    }
    ...

而後,咱們將setContentView(R.layout.test) 須要修改爲以下的方式便可。

setContentView(ResourceUtil.getIdFromLayout(context, "test"))

2.3 AAPT資源打包

如下是Android 官網給的apk的打包流程,以下下圖所示。
在這裏插入圖片描述

在apk編譯的第一個階段,須要使用AAPT(Android Asset Packaging Tool縮寫)打包資源文件,產物以下。

  • res文件夾內的圖片及xml資源(xml被編譯成二進制);
  • assets文件夾(不會生成資源id)
  • 二進制AndroidManifest.xml
  • 資源索引表 resources.arsc
  • R.class文件

咱們須要重點關注的是資源索引表 resources.arsc,resources.arsc 文件的數據格式比較複雜,咱們能夠將apk文件拖到Android Studio中,而後選擇 resources.arsc打開,以下圖所示。

在這裏插入圖片描述
能夠發現,resources.arsc文件會包含不少的資源索引,打開layoyt文件,會發現該文件 主要由id(資源id)、name(資源名稱)、value(資源路徑)均可以經過這個索引表來互相轉換,前面說過 Resources#getIdentifier(String name, String defType, String defPackage),之因此能夠經過資源名稱獲取到資源id,固然仍是要藉助 resources.arsc 這個資源索引表。

遊戲SDK支持與維護

若是是普通的遊戲SDK,那麼只要保證接入方可以成功接入SDK就完事了。然而,遊戲SDK的支持還須要對接入遊戲SDK的遊戲進行驗收,確保遊戲SDK的功能正常,可以正常提交應用市場。而且,隨着SDK的版本升級,功能會增長,須要驗收的功能會愈來愈多,例如:驗證簽名,SDK有檢查更新的功能,token過時,遊戲須要作退出登陸邏輯等等。下面是遊戲SDK支持的一些場景和處理方式,以及經驗分享。

日誌開關

衆所周知,無論是應用開發仍是SDK開發,release版本通常都是關閉了日誌的,咱們須要使用日誌復現問題,經常使用的有兩種方式:

  1. 參考開發者模式的開關,設置某個控件的點擊事件,例如在連續點擊5次的時候打開日誌開關。而後將日誌數據持久化,例如保存到sp,在SDK初始化的時候去讀這個開關。
  2. 使用一些數據SDK,而後將數據上報到後臺,考慮到SDK內部信息安全,咱們能夠本身開關相關的SDK。

配置參數

有時候,咱們提供的Demo工程是運行是正常的,可是第三方遊戲接入的時候常常會出現一些問題,多是他們的Android SDK版本不同,或者一些配置沒有嚴格按照文檔來寫,做爲SDK的開發者,我但願這些配置的問題接入方能夠本身發現和處理,這就須要在遊戲SDK中增長檢測的邏輯。

1,檢查更新的功能

從Android 8.0 版本開始,調起應用安裝頁面須要用戶顯式打開未知來源開關,下面是系統的相關檢察源碼。
在這裏插入圖片描述
對於這個問題,首先想到是接入方沒有聲明安裝權限,以下。

<!--安裝apk須要的權限-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

而後,咱們去掉權限聲明驗證發現仍是會拋異常,說明不是這個緣由。最後,通過分析發現當 targetSdkVersion 小於26的話, packageManager.canRequestPackageInstalls()一直會返回false,因此各大應用市場已經陸續要求targetSdkVersion必須26或以上,爲了保證SDK的更新功能正常,在SDK初始化的時候,添加以下檢測代碼。

在這裏插入圖片描述

2,FileProvider 須要增長配置檢查

因爲 7.0以後安裝apk須要經過FileProvider來獲取url,因此須要在Manifest.xml添加以下代碼。
在這裏插入圖片描述
若是是Android Studio打包,通常會自動讀取build.gradle中的_PACKAGENAME_的值來替換佔位符_PACKAGENAME_的數據,若是是Eclipse打包,佔位符_PACKAGENAME_則原封不動,不會被替換。無論使用Eclipse打包就會報一個空指針,以下所示是Eclipse代碼。

FileProvider.getUriForFile(context,
                    context.packageName + ".fileprovider", file)

那如何保證接入方必定有配置FileProvider,而且配置正確呢?咱們能夠增長配置檢測以下代碼。

在這裏插入圖片描述
在上面的代碼中,咱們能夠在sdk初始化的時候去私有目錄建立一個空文件,而後經過 getUriFormFile 方法觸發FileProvider獲取url的邏輯,若是有異常則說明FileProvider配置不對,直接給出錯誤信息。

3,簽名驗證

遊戲方接入遊戲SDK以後打包成apk,這個apk須要在咱們平臺上線,咱們但願統一apk簽名, 因此在驗收apk的時候還須要確認apk的簽名。要查看apk簽名,咱們可使用命令行和工具兩種方式。查看簽名的命令以下:

v2簽名

keytool -printcert -jarfile xxx.apk
或者
apksigner verify -v --print-certs xxx.apk

在這裏插入圖片描述
v1簽名
若是apk是使用v1簽名,那麼比較麻煩,首先須要解壓apk,找到META-INFO目錄下的 CERT.RSA,而後執行以下命令。

keytool -printcert -file CERT.RSA

除了使用命令方式外,咱們還可使用工具來察看,如macOS的fHash等軟件,將apk文件拖到軟件中便可,以下所示。

在這裏插入圖片描述

遊戲渠道包

作過Android應用開發的同窗對於渠道包確定不會陌生,因爲Android應用市場衆多,若是要上不一樣的應用市場,那麼就須要打不一樣的渠道包。Android多渠道包除了可使用Gradle多渠道打包外,還可使用美團Walle、友盟等多渠道工具進行多渠道打包。

無論,對於遊戲SDK來講,單純使用Walle並不適合,由於大部分遊戲發行商,默認的apk簽名方式都是v1簽名。參考過大多數的遊戲渠道分發公司,基本都是使用打包腳本進行打包,而且打包腳本通常使用python開發。

Android v1的簽名是基於JAR 的,簽名jar Signature來自JDK,Android v2的簽名是基於APK Signature Scheme v2,是Android 7.0版本引入的,而最新的v2是對v2版本的優化,適用於Android 9.0及以上版本 。它們的區別以下:

  • V1:應該是經過ZIP條目進行驗證,這樣APK 簽署後可進行許多修改 - 能夠移動甚至從新壓縮文件。
  • V2:驗證壓縮文件的全部字節,而不是單個 ZIP 條目,所以,在簽名後沒法再更改(包括zipalign)。正因如此,如今在編譯過程當中,咱們將壓縮、調整和簽署合併成一步完成。好處顯而易見,更安全並且新的簽名可縮短在設備上進行驗證的時間(不須要費時地解壓縮而後驗證),從而加快應用安裝速度。

v1簽名

衆所周知,apk文件其實就是一個帶簽名信息的zip文件,根據zip文件格式規範,zip文件末尾有一部分元數據表明zip文件註釋,正確修改這一部分數據不會對zip文件形成破壞,以下所示。
在這裏插入圖片描述

針對v1簽名,還有其它渠道包打包方案,可是大部分都存在效率問題,例如利用gradle的productFlavors屬性打渠道包,速度慢;或者利用META-INF目錄不被簽名校驗的特色,加入文件名爲渠道名的空文件,可是讀取渠道的時候比較慢,由於須要解壓apk,涉及文件的讀取。

v2簽名

V2簽名塊中有個區塊能夠添加一些附屬信息,而且不會被簽名校驗,將自定義渠道信息寫入這個區塊,生成渠道包。能夠參考下美團Walle。正如前文所說,咱們使用的python打包腳本,應該不存在上面的問題。

Apk反編譯與重打包

反編譯

在Android逆向工程中,有一個很重要的工具,那就是Apktool。首先,咱們到Apktool官網下載下工具,固然咱們也能夠從其餘地方進行下載。下載apktool.jar、apktool可執行腳本,放到 /usr/local/bin/ 目錄下,而後 command + x 設置權限就能夠了。

在這裏插入圖片描述

而後,咱們使用apktool d命令反編譯apk文件,以下所示。

apktool d demo.apk

在這裏插入圖片描述

其中,若是不指定目錄那麼默認會輸出到原目錄,若是須要指定目錄,那麼可使用-o 參數來指定輸出目錄。反編譯以後,接下來就能夠修改資源文件或者字節碼。

回編譯

修改資源文件或者字節碼文件後,咱們須要回編譯包,回編譯的命令以下。

apktool b demo -o unsign.apk

在這裏插入圖片描述
不過,上面的輸出的是未簽名的apk,須要簽名才能安裝到手機上。

apk簽名

對於Android應用開發來講,能夠直接使用Android Studio來製做一個簽名文件。可是,單獨給一個未簽名的apk簽名,就須要藉助簽名工具,v1簽名是使用jarsigner,v2簽名是使用apksigner。其中,v1簽名的命令以下:

jarsigner -verbose
-keystore [簽名文件路徑]
-keypass [密碼]
-storepass [密碼]
-signedjar [輸出apk路徑] [須要簽名的apk路徑]
-digestalg [摘要算法的名稱如SHA1]
-sigalg [簽名算法的名稱如MD5withRSA]
[證書別名]

例如,我有一個簽名文件叫 demo.keystore,別名密碼都是 123456,那麼簽名命令以下。

jarsigner -verbose -keystore demo.keystore -keypass 123456 -storepass 123456 -signedjar sign.apk unsign.apk -digestalg SHA1 -sigalg MD5withRSA 123456

若是須要使用v2 簽名,因爲v2簽名使用的是apkSigner,在SDK build-tools下,注意在版本25以上纔有。
在這裏插入圖片描述
使用apkSigner簽名的命令以下所示。

apksigner sign
--ks [簽名文件]
--ks-pass pass:[密碼]
--out [輸出apk路徑]
[須要簽名的apk]

例如,簽名文件叫 demo.keystore,別名密碼都是 123456,那麼apkSigner簽名命令以下。

apksigner sign --ks demo.keystore --ks-pass pass:123456 --out sign_v2.apk unsign.apk

apksigner 簽名過程沒有任何提示,能夠結合驗證簽名命令一塊兒使用,以下所示。

apksigner verify -v --print-certs sign_v2.apk

到此,Android 遊戲SDK相關的開發和渠道包相關的就介紹完了,後面回對打包遇到的一些問題進行補充,若是你對遊戲聯運有興趣,也歡迎留言交流。

相關文章
相關標籤/搜索