类别归档:App开发

RSS feed of App开发

最后更新于 .

是的,现在是凌晨2点,而我还在写这篇博客。

因为从4月份开始,苹果的这封邮件里面的错误就必须得处理了。

Dear Developer,

We identified one or more issues with a recent delivery for your app, "PandaDemo" 0.1 (232343). Your delivery was successful, but you may wish to correct the following issues in your next delivery:

ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of new apps that use ...

最后更新于 .

最近把unity升级到了2019.3.4f1。
至于为什么要升级,em。。。好吧,我承认我是馋她身子(笑)。
毕竟我两年前第一次接触unity的时候就觉得UI丑了,而这次终于新版本换了扁平化的UI,深得我意,没道理不升级啊。

但是没想到,这就是恶梦的开始。。

其实半年前我们原来的客户端研发还在的时候,就尝试升级过unity,但是后来回退了,而现在,我切实知道什么原因了。

这里主要是通过几个案例,来分享一下定位bug的分析过程,以及我这几年总结的一个定位bug的杀手锏。

一. Android平台Spine动画失效,游戏内全是白块

如图:

这个问题是当时回滚版本的主要原因。

因为自己对unity不是很熟,对客户端代码也是刚接过来,所以这个问题查起来也是一头雾水。 由于没有办法通过看代码来定位问题(看不懂),所以我们只能用别的方法了。

先把能解决的解决一下:

  1. 升级spine的运行库版本。
  2. 升级spine编辑的版本,重新导出动画。

但是很遗憾并没有解决。。
不过这里说个题外话,这里的升级还是很有用的,官方确实了改进了unity2019.3的兼容性,从而很方便的解决了局内物品白块和局外某个英雄动画无法播放的问题。

OK,先来看几个现象:

  1. 虽然都是使用了spine动画,但是局外动画没问题,只有局内动画有问题。
  2. 局内动画使用像素风格,并且支持皮肤换装;局外动画为正常风格,不支持皮肤换装。

从上面的现象可以思考几个问题的可能性:

  1. 像素图片的Filter Mode ...

最后更新于 .

游戏内小红点算是一个极其常用的功能了,之前在德州里面也有过实现。
然而之前的实现实在是乱七八糟,所以这次也是将其做了彻底的重写,并把方案跟大家分享一下。

我们将游戏内小红点可以分为三类:

  1. 服务器小红点-服务器自动清除

    比如,我们常见的每日任务,成长任务,活动等。
    以每日任务举例:
    当有任务奖励可以领取时,在每日任务按钮上就显示小红点。
    当任务奖励全部领取完毕后,小红点消失。

  2. 服务器小红点-客户端告知服务器清除

    比如,信箱功能,新好友通知,好友申请通知。
    以信箱举例:
    有新邮件,则信箱按钮就显示小红点。
    打开信箱后,如果信箱内分标签页,则判断标签页下的邮件列表,如果有新邮件,则在标签上显示小红点。
    当标签页打开之后,标签页上小红点消失。 当所有标签页的小红点都消失后,信箱按钮上的小红点消失

  3. 客户端小红点-客户端自己维护

    比如聊天功能。

    登录时,拉取所有未读消息,如果有消息的话,大厅聊天按钮需要显示小红点。
    之后,当收到新的好友消息的时候,大厅聊天按钮需要显示小红点。
    当点击聊天按钮进入具体的聊天页面时,每个有新消息的好友页签,需要显示小红点。
    当点击该页签时,小红点消失。
    当所有页签的小红点消失后,大厅聊天按钮的小红点消失。

接下里我们说一下具体的实现。

首先,所有服务器小红点的状态,在用户登录的时候,就应该返回。
所以我们在登录协议里面增加了一个red_points字段 ...

最后更新于 .

一. 前言

2016年6月17日凌晨5点钟,我们完成了服务器端V3版本的重构,切换的过程十分平滑且没有对线上用户产生任何影响。

这也正式标志着,我们的游戏服务器进入了一个全新的阶段。

我们上一次的重构是在 2014年12月23日,现在看看,时间过的真快啊。

而熟悉我的人应该知道,我特意为上一次重构写过一篇《游戏服务器端架构升级之路》,其中详细的讲述了我们游戏服务器从农业时代跨越到工业时代的历程。

而这次V3版本的重构,我将其定义为第二次工业革命。也许它没有那么的强大和完美,但是他切实的解决了现存的大部分问题。

二. 背景

之前的文章已经说过,V2版本的服务器的几个优点:

  1. 支持服务器代码热更新而不影响外网服务
  2. 架构模式足够简单:push-pull

但是,其简单的架构也存在一些缺点:

  1. 业务模块之间容易互相影响

    比如两个游戏玩法 A 和 B,内部使用的逻辑、存储服务器都完全不同,但是在worker层却是共用的。

    所以一旦玩法A的服务器出现问题导致处理变慢,那么worker就会被堵住,而玩法B也会跟着遭殃。

    同时,即时在一个业务模块内,也存在请求处理优先级的问题,比如拉取牌局记录和跟注,要尽量避免跟注这种核心逻辑受到影响。

  2. 限制了游戏逻辑的实现方式

    V2的多worker的模式,导致worker必须限制为无状态的,因为worker可能处理任何一个请求,而一个请求也可能被分配到任何一个worker上。

    这一点是之前解决服务器热重启的关键,但同时也限制了我们代码逻辑多样性的实现。

    比如我们的游戏桌子数据是存储在redis中,所有人对桌子的写操作可能同时分配到多个worker上,而为了避免写冲突,我们不得不通过redis来实现分布式锁 ...

最后更新于 .

之前在文章里面有提到过,很多事情,并没有绝对的对错,只是度的问题。而度的衡量又取决于时、势二字。所以当形势逼人的时候,基本就是这件事情非做不可的时候了。

先说下背景,公司的服务器一直用的阿里云,包括mysql、redis也都是买了ECS自己搭建的。这里面有几个原因:

  1. 创业的时候,阿里云只提供mysql的存储,redis的存储还没提供。
  2. 没钱,即时现在去看redis的存储价格也是贵的吓人。

这样自己来搞存储有好处也有坏处。 好处:

  1. 完全可控,比如连接数限制,内存限制,存储限制。还有数据备份的灵活性等等。
  2. 强迫团队服务器研发要有存储运维能力。
  3. 省钱

坏处:

  1. 冷备、热备方案不完善。
  2. 存储运维的成本较高,需要长时间积累。

ok,问题就是这样,接下来再来说一下我们之前的冷备和热备方案。 可以说极其简陋:

  1. mysql、redis每天10点冷备,备份到本地磁盘和阿里云OSS
  2. redis使用rdb落地,每60秒至少有1次写就会触发落地。

这样做的问题其实挺多的,主要几个:

  1. mysql dump的时候会导致游戏卡顿,即使加了 --single-transaction 参数 也仅仅是缓解
  2. 冷备频率过低,真出现问题数据已经太久
  3. 没有热备,风险较大

针对这些问题,我们先做了mysql备份的优化 ...

最后更新于 .

其实打算做游戏内热更新也是几个月之前的事情了,在方案经历了数次变迁之后,最近才终于应用到了外网的bugfix中。
但是就目前数据来看,热更新由于要下载资源,会使新用户的进入门槛变高,所以留存收到了一定影响,基本降低了10个点。
当然,也可能是热更新的功能存在bug。

好了,我们还是进入正题吧!

方案一

最早的时候,我们想用一种类似打patch的方式来更新。
即将lua代码和资源打成一个zip包,而每个zip包只要代码或者资源发生变化,都会有一个自增的小版本号(大版本号为打在apk或者ipa里的版本号)。
不同的zip包之间diff就可以生成一个patch文件,而为了开发的简单,这个patch列表只会将文件路径列出来,之后客户端要去完整的下载新的文件覆盖。

然而这个方案有很多的问题。

  1. 小版本号不好维护 因为代码和资源会不停的修改,而如果人工来维护小版本号,会极其复杂。如果通过机器来维护也是个很麻烦的工程

  2. patch包太多 如果我们将小版本号的patch包先生成好放到服务器上,那么如果我们有100个小版本,就会有非常多 1->100, 2->100, 3->100 ... 这种格式的patch包。 并且如果小版本号继续增长,patch包的增长基本会失控。

  3. 消耗大量流量,速度缓慢 也许有同学会说,不如只有 1->2, 2->3, 3->4 这样的单个小版本之间的升级patch,客户端自己挨个下载就好。

    但是这样客户端如果是个很小的版本号,就不会不停的重复下载,导致迟迟进入不了游戏,并且流量也大量消耗。

综上所述,方案一有太多的弊端。
这也是为什么我们做完了之后迟迟不敢上线的原因 ...

最后更新于 .

一. 发现问题

最近新版本上线,公司内部的几台iphone测试机测试都一切正常,但是外网用户却频频反馈崩溃。

在友盟上看了错误率统计,曲线如下:

可以看出,错误率确实有了很大的上浮。基本可以确认一定是出问题了。

二. 还原崩溃现场

我们游戏用得cocos2dx-lua,既然是崩溃,那就一定是到了c++端了,我们点击友盟上数量最多的那个看一下:

到了这里,遇到了第一个难点,因为按照友盟文档,使用他的错误dump工具是无效的,一旦看不到具体的错误内容,基本就无法继续了。

(而因为这个问题,发现有人推荐bugly,打算在下一个版本里面试用一下,再给大家反馈)

既然友盟提供的工具不行,我们就得像别的办法,起码现在错误的原始内容是有了,只是要想办法还原出来而已。

仔细研究了一下,友盟其实也是使用了mac原本就提供的工具来做了二次封装,那不如直接用原生的工具了。

具体方法如下:

  1. 找到当时出包得那个archive文件,显示包文件,在其中找到如下两个文件放到同一目录:

    xxx.app
    xxx.app.dSYM

    其中 xxx.app.dSYM 即为符号表文件。

  2. 查看其对应的UUID,一定要与crash log的UUID一致

    $ dwarfdump --uuid xxx.app ...

最后更新于 .

之前一直用友盟的自动更新功能,但是友盟一直没有内置实现强制更新的功能,如果要在其基础上模拟实现会很麻烦,所以干脆就自己做了。

其实实现上比较简单,这里跟大家介绍下。

1. web接口

需要提供一个接口供客户端查询更新状态,并且在需要更新时,告知客户端新APK地址。

接口参数如下:

  • package   包名,因为有时候会出现同一个应用换包名打包的情况
  • version 版本号,即android清单文件里面的versionCode
  • channel 渠道号
  • os 操作系统,android/ios。ios 这里仅作预留。

之所以传入这些字段,是要在与服务器端的包匹配时,务必满足:

package, channel, os 相等,并且服务器端的version 大于 客户端传入的version

代码如下:

os = request.GET.get('os')
pkg_name = request.GET.get('package')
channel = request.GET.get('channel')
version = request ...

最后更新于 .

这几天的心情非常好,主要原因是我们把服务器端的架构升级到了 2.0,这样最大的一个好处就是:

Server重启完全不会影响外网服务

所以,也是想趁此机会,服务器端整个发展的历程,跟大家分享一下,干货比较多,框架代码也会全部开源:)

 

一. 农业时代

创业最重要的就是一个“快”字,所以最开始的时候,所有的架构都以快速出模型为前提。

而常看我博客的朋友应该知道我对python情有独钟,所以自然的,python成为了我开发服务端框架的语言。

python自带的多线程tcp服务器框架非常简单:ThreadingTCPServer,即每个链接一个线程的模式:

import SocketServer

class RequestHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        f = self.request.makefile('r')

        while True:
            message = f.readline()
            if not message:
                print 'client closed'
                break
            print "message, len: %s, content ...

最后更新于 .

发现自己经常会一篇文章写了(一)之后,很久都不写(二),搞得最后自己都快要忘记了,所以这次赶紧把统一支付的文章给补上。

上次的文章中将统一支付的v1版本已经讲解ok了,但是还剩下两个问题:

  • 服务器端没有办法做分布式
  • 客户端对支付sdk进行插件式管理十分困难

 

我们一个个来说

一. 解决服务器端分布式的问题

解决这个问题的核心思路比较简单:

之前我们是把event的通知放在进程内存中,现在我们做成网络通信

由于支付的请求量本身不属于高并发,所以就放弃了打算直接写通知server的想法,转而看一下有没有什么简单的解决方案。

而由于自己之前redis的使用经历,恰好知道redis有一个pubsub模式,很适合做这种监听和通知的工作。

python的实现示例代码如下:

import time
import config
from share.vals import rds
from share.utils import safe_str
from gevent.timeout import Timeout
from urllib import quote, unquote

class RedisPubSub(object):
    """
    用redis订阅/发布消息
    """
    # 订阅频道
    sub_key ...