1.坑从何处来?
很多APP都有截屏分享的功能,在某个详情页按下截屏键,将当前页面绘制成UIImage,再贴上一个二维码。通过微信/微博/QQ之类的渠道分享出去。很常见,对吧?
但是有用户反映二维码尺寸太小,URL太长以至于生成的二维码太密集,导致识别率不高。于是产品狗提出了解决方案:1.二维码尺寸放大。2.将原本的URL压缩成短链接。
放大二维码so easy,这里就略过了。而第二步压缩长链接需要走后台的接口。于是乎新的截屏分享流程如下:
1.出发截屏操作
2.将现有的长链接通过后台接口变换成短链接
3.成功返回短链接则用短链接生成二维码
4.返回短链接失败则继续用原来的长链接生成二维码(此处有坑!)
这个流程看起来没什么毛病,但是没过多久就有人反馈了bug。。。
2.问题重现
昨天下午被叫过去,看到了如下一张图,
看到此图,我一脸懵逼,这特么是个啥,为了方便说明,正常情况应该是类似于这样
这种截图分享的方式相信大家实现的方法大同小异,截取当前屏幕的内容生成UIImage,然后放到一个UIImageView里,然后根据URL生成二维码,再add到这个UIImageView上,最后绘制为一个有二维码的UIImage。新建一个UIWindow来展示这个弹出的浮层。基本就和上面一样了。
我最无法理解的是,明明截屏的内容和二维码是在一张图上,为啥后面截屏的内容没了,就剩个码,而且下面分享button也不见了。。。询问发现bug的人,只说了截屏发微信乱点点再切回来就成这样了,而且还卡死了,只能杀掉APP。终于在昨天深夜,有大佬找到了稳定复现的方法:打开飞行模式,进入详情页,在内容没有加载好的时候立刻截屏退到后台,过一会再回到前台就这样了。
有了稳定复现的方法,就可以开始debug了!
3.定位原因
我按照步骤开始重现问题,关键点就在于网络请求超时后,依然继续走了后续的流程,导致APP还在后台的时候,弹出了新的window。我就重点跟踪了这个window的创建过程!
+ (UIWindow *)sharedWindow{
static UIWindow *sharedWindow = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedWindow = [[UIWindow alloc] initWithFrame:[[[UIApplication sharedApplication] keyWindow] bounds]];
});
return sharedWindow;
}
重点在于keywindow上,因为此时APP在后台,po出来可以看到keywindow是nil,所以[[[UIApplication sharedApplication] keyWindow] bounds]取出来的frame是{0,0,0,0}。所以新弹出的window才不能响应任何手势,显得APP像是卡死了一样。
于是我就想,那我换一种取frame的方式:[UIScreen mainScreen].bounds,这会怎么样呢?这次得到了下面的样子:
因为获取到了正常的frame,弹出的window看起来稍微有点正常了,黑屏不是就是因为APP在后台,所以获取不到任何屏幕上的信息,绘制出来自然就是一团黑,这是在预料之内的。而且此时新弹出的window也能正常响应手势了。点击空白了新弹出的window消失了,但此时有了另一个问题,APP的主window不见了,所以又卡死了。。当然主window消失就是另一个问题了,这里就不展开说了。
4.总结
发现了问题所在,解决起来也就有很多方法了。
回想一下造成这个bug的根本原因就是[[UIApplication sharedApplication] keyWindow]是nil,所以截屏获取不到内容,弹出的window的frame为0。
APP在后台获取不到keyWindow!
APP在后台获取不到keyWindow!
APP在后台获取不到keyWindow!