一文了解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相关的开发和渠道包相关的就介绍完了,后面回对打包遇到的一些问题进行补充,若是你对游戏联运有兴趣,也欢迎留言交流。

相关文章
相关标签/搜索