去年從平安離職以後,加入了一家遊戲公司,負責遊戲SDK相關的業務開發和維護工做,通過半年來的摸索,對於遊戲SDK的開發有了必定的理解,下面就對遊戲SDK開發涉及到的知識點進行簡單的梳理。java
SDK(Software Development Kit)是軟件開發工具包的縮寫,通常來講,SDK是用於給開發人員提供進行應用程序開發的工具的,這樣程序員就能夠快速的開發出應用軟件,省去了編寫硬件代碼和基礎代碼框架的過程,咱們常見的Android SDK就屬於這一類。除了這種比較大的SDK,咱們平時開發的library也屬性SDK,只不過功能比較單一,適用的場合也比較簡單,如短視頻SDK、推送SDK,分享SDK等。
而咱們所作的遊戲SDK主要是用於第三方遊戲開發接入咱們的帳號體系和支付體系,相似於友盟分享等聚合SDK。python
在遊戲行業中,會存在兩個最基本的角色,即遊戲開發和遊戲運營,一個遊戲能不能成功,除了技術體驗好以外,運營是一門很重要的學問,他們的關係以下圖所示。
正如前面說描述的同樣,遊戲和運營每每是單獨開來的,除非像騰訊、網易這些頭部大公司,不只能夠本身研發遊戲,還有實力本身推廣和運營遊戲。不過,事實上,不少小的遊戲開發商就那麼幾我的或者幾十我的,根本沒有本身的運營能力,而市面上正好有專業的遊戲運營公司,這時候它們就開始合做了。android
在上面的圖例中,小紅是作社交App的娛樂公司,日活幾千萬,想讓本身平臺多元化,好比作個遊戲下載的功能,給用戶下載,用戶以爲好玩,可能就會付費買裝備,可是有個問題,小紅並不會作遊戲,若是單開一個產品線去研發遊戲,投入是至關巨大的,因此想到能不能去外面接遊戲進來。git
遊戲SDK最核心的功能就是登陸和支付,其它都是一些運營相關的,例如埋點、數據統計、崩潰等等。其中,登陸的流程大致以下。
而支付的流程大致是先SDK,而後再通知遊戲支付結構,流程以下所示。程序員
遊戲SDK做爲基礎SDK,一般須要遵循一些基本的開發規範,例如,儘可能少用第三方庫、減小對外接口、明確技術文檔等。github
做爲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開發相比應用開發來講,技術難度通常不大,問題大多出在跟遊戲對接的時候。可能會出現一些問題, 好比ClassNotFound、Resource not found、依賴衝突、崩潰等等,對於這類問題,咱們須要先分析問題的緣由,而後確認由誰負責,最後肯定修改方案。
和應用開發不一樣,不少遊戲仍是使用Eclipse進行開發的,因此在對接遊戲時須要提供Eclipse版本。須要說明的是,Eclipse 不能使用Android Studio版本的SDK,搭建Eclipse的Android環境須要使用ADT插件,具體怎麼使用請參考官網。
因爲SDK的產物是aar,而Eclipse只能依賴jar包和library,通常都用jar包依賴,所以先將aar解壓出來,把裏面的classes.jar拷貝出來重命名,而後在Eclipse中依賴這個jar包,同時SDK的資源文件、libs目錄下的jar包也須要拷貝到Eclipse項目中。
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工程是如何打包的,下面讓咱們來簡單的回顧下流程:
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文件裏面的常量。
谷歌提供了相關的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"))
如下是Android 官網給的apk的打包流程,以下下圖所示。
在apk編譯的第一個階段,須要使用AAPT(Android Asset Packaging Tool縮寫)打包資源文件,產物以下。
咱們須要重點關注的是資源索引表 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有檢查更新的功能,token過時,遊戲須要作退出登陸邏輯等等。下面是遊戲SDK支持的一些場景和處理方式,以及經驗分享。
衆所周知,無論是應用開發仍是SDK開發,release版本通常都是關閉了日誌的,咱們須要使用日誌復現問題,經常使用的有兩種方式:
有時候,咱們提供的Demo工程是運行是正常的,可是第三方遊戲接入的時候常常會出現一些問題,多是他們的Android SDK版本不同,或者一些配置沒有嚴格按照文檔來寫,做爲SDK的開發者,我但願這些配置的問題接入方能夠本身發現和處理,這就須要在遊戲SDK中增長檢測的邏輯。
從Android 8.0 版本開始,調起應用安裝頁面須要用戶顯式打開未知來源開關,下面是系統的相關檢察源碼。
對於這個問題,首先想到是接入方沒有聲明安裝權限,以下。
<!--安裝apk須要的權限--> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
而後,咱們去掉權限聲明驗證發現仍是會拋異常,說明不是這個緣由。最後,通過分析發現當 targetSdkVersion 小於26的話, packageManager.canRequestPackageInstalls()一直會返回false,因此各大應用市場已經陸續要求targetSdkVersion必須26或以上,爲了保證SDK的更新功能正常,在SDK初始化的時候,添加以下檢測代碼。
因爲 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配置不對,直接給出錯誤信息。
遊戲方接入遊戲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及以上版本 。它們的區別以下:
衆所周知,apk文件其實就是一個帶簽名信息的zip文件,根據zip文件格式規範,zip文件末尾有一部分元數據表明zip文件註釋,正確修改這一部分數據不會對zip文件形成破壞,以下所示。
針對v1簽名,還有其它渠道包打包方案,可是大部分都存在效率問題,例如利用gradle的productFlavors屬性打渠道包,速度慢;或者利用META-INF目錄不被簽名校驗的特色,加入文件名爲渠道名的空文件,可是讀取渠道的時候比較慢,由於須要解壓apk,涉及文件的讀取。
V2簽名塊中有個區塊能夠添加一些附屬信息,而且不會被簽名校驗,將自定義渠道信息寫入這個區塊,生成渠道包。能夠參考下美團Walle。正如前文所說,咱們使用的python打包腳本,應該不存在上面的問題。
在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,須要簽名才能安裝到手機上。
對於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相關的開發和渠道包相關的就介紹完了,後面回對打包遇到的一些問題進行補充,若是你對遊戲聯運有興趣,也歡迎留言交流。