IM推送之客户端SDK开发(二)

IM Android端SDK开发过程中遇到的问题

华为手机

  • 我们曾经采用Native守护进程保活的方案做到了在android5.0系统以下的手机可以做到push进程被杀死后能够重启,后来在华为一些机型手机上出现严重的耗电问题,同时也发现在这些机型上Native层的守护进程无效,push进程被杀死后无法正常拉起,具体原因我们没有再分析,只是直接把Native层的守护进程去掉了,之所以直接去掉是考虑到不同的Android定制系统导致的兼容性问题,还有就是现在市面上很多Android机子的系统都升级到5.0以上了
  • 华为手机屏幕锁屏后,app进程,push进程会被直接杀死,因为华为手机的android系统上有一个锁屏后保持运行的开关设置,如果这个开关打开,app就能保持锁屏后台运行,如果关闭,app在锁屏后就会立即被杀死,那么问题来了,这个开关在app安装的时候如果没有提示设置,对于QQ,微信都是默认打开的,而对于大部分app都是默认关闭的,所以只能手动打开这个开关来保持后台运行
  • 华为Android6.0手机打开锁屏后保持运行开关,push进程是不会被杀死了,但是锁屏后运行一段时间后app的im连接会自己断开,log日志打印发现是app的网络不通(Network is unreachable),然后只要一点开app,网络马上就好了,im连接马上就能重连成功,找了半天才发现华为Android6.0系统的手机有一个省电开关,要设置成性能模式才能一直保持连接,我也是醉了,但是QQ,微信不用修改这个设置也可以正常,我猜测还是系统底层的电量优化策略有一个白名单,在白名单里面的就有一些过滤条件让这些白名单的app不会断网,而不在白名单内的app就会在一定时间后被断网
  • 华为手机有一个禁止互相唤醒的开关,如果开关打开会出现如下现象:app1发送广播或者通过service来通知app2,app2如果是停止运行的状态,那么app2就无法收到广播,start service返回值为null,bind service返回false,aidl调用也失败;但是如果app2的进程没有被杀死,app2还是能够正常收到广播的,start service,bind service,aidl也都正常运行

魅族手机

  • 魅族手机和华为手机一样,app有一个白名单,锁屏后,不再白名单内的app相关进程会被全部杀死
  • 魅族android5.1系统的手机已经加入了手机休眠后禁止互相唤醒的设置功能了,默认都是打开的,我也尝试了,在手机锁屏一段时间后,app之间不能再通过广播,service,aidl来通信了,和华为手机不同的是,魅族手机上,就算app进程没被杀死,也无法收到其他app发过来的广播,service,aidl也是无法使用

小米手机

  • 小米手机发现一个问题,在手机锁屏后IM连接维持一段时间后就会莫名奇妙的断开,心跳包也不会继续发送,Network is unreachable,感觉像是手机锁屏了一段时间后针对单个app就不能联网了,但是一点开app就立马能收到消息了,然后去设置了下神隐模式之后就一切正常了

一些杂七杂八的手机上广播收不到或者广播接受延迟严重

  • 确实遇到过一些这种坑爹的情况,一般就是由于手机配置太低了,后台运行程序太多了,Android系统又是4.0的,所以有很多程序都杀不掉导致占用了大量的内存资源,所以广播接收非常慢,这种情况只能忽略,建议换手机了

TCP数据丢失

  • 在一些网络环境很不好的情况下,例如2G网络,客户端的socket已经write数据成功了,但是服务端还是没有收到数据,或者服务端已经把数据发送出去了,但是客户端的socket没有read到数据,这种就是由于网络环境太差了,导致数据在链路中传输的某一个环节丢失了,但是TCP是有send和ack的,在一定时间内没有收到ack就会重发,但是有时候就是一端发送了,另一端收不到数据,过了很长时间还是收不到
  • 上述问题的解决方案:解决这种情况就是重发数据包,一般每一种协议包都有一个对应的协议包反馈,收到协议反馈有一个超时时间,如果超过这个时间还是没有收到协议包反馈,那么就重新再发送一次数据,我们的做法是每个协议数据包都会重复发送5次,每次间隔的时间会递增,如果5次都重发失败那么就提示发送数据失败
  • 上述问题的原因分析:TCP协议保证了消息的必达性,所以,如果是数据丢失了,发送端和接收端的socket都没有报异常,那么我觉得可能原因就是数据在某个时刻网络条件很差,导致数据在链路传输的过程中传输的很慢,所以一直没有收到数据,在做了重发机制后,消息到达率有了很大提升,但是重发的消息都能正常发送,那么原始丢失的数据为啥莫名奇妙的就没了呢。。。这里我猜测可能是发送端(服务器)要不就是接收端(Android移动端)的Socket接收逻辑有bug,要不就是Android端Socket底层实现有问题导致接收到的数据直接被“吞掉了”!!!

SDK耗电问题

  • 心跳包策略不合适导致频繁的唤醒,心跳包超时监测有用到Wake Lock的,因此组织CPU休眠,延长CPU唤醒时间造成手机耗电
  • 网络环境不稳定或者网络环境不好导致频繁的断线,断线后又进行重连,造成手机耗电(因为在重连实现中有用到Wake Lock,虽然有及时的释放,但是唤醒CPU的时间依然是增加了的)
  • 其实导致耗电的根本原因就是程序占用CPU,要不就是Wake Lock导致CPU不能休眠,要不就是Alarm来唤醒CPU
  • 之前程序还有遇到一个bug就是在push进程的Service里面执行了一段循环代码,例如循环的从队列里面取任务执行,不小心写成一个死循环,导致手机锁屏了之后代码还是无限的在循环执行,占用了大量的CPU资源,造成了很严重的耗电
  • 不要在一些Service里面执行死循环的代码,因为Service本身就是一只在后台运行的,只要Service不被销毁,你的循环逻辑的代码将会一直跑下去
public class PushService extends Service {

    private static final String tag = PushService.class.getSimpleName();

    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    private Queue<Runnable> taskQueue = new LinkedList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(tag, this.getPackageName() + ":" + this.getClass().getSimpleName() + " onCreate.");
        Runnable task = null;
        while (!executorService.isShutdown()) {
            if ((task = taskQueue.poll()) != null) {
                task.run();
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(tag, this.getPackageName() + ":" + this.getClass().getSimpleName() + " onDestroy.");
        executorService.shutdownNow();
    }
}

上面例子的代码就会导致while循环无限的执行,假如这个Service是前台Service,在手机锁屏后它的优先级依然很高,而while循环里面的poll()方法并不是阻阻塞的,因此会一直重复的跑这段代码导致CUP即使在手机锁屏的时候仍然高速的在运行着循环代码,会造成严重的耗电
- 针对队列的任务处理一定要谨慎,因为从队列里面取出任务来执行一定都是循环的取的,如果队列堆积的任务太多,导致循环执行太久也会造成耗电,因为占用了CPU资源去执行代码,我们的log日志工具保存到文件就是用任务队列实现的,当压力测试SDK一次性接受1万条消息的时候,那内存就表上来了,跟了下发现日志保存队列里面积压了4千多个任务,这时候即使手机锁屏,也还会不断的把队列中的任务执行完然后CPU才会休眠下去的,同样会造成严重的耗电,耗内存,好在release版本的日志都是关闭的
- 有时候手机亮屏,并保持程序一直在前台,其实并不会耗电,为啥?以为这时候程序根本没有跑任何代码,这时候APP占用的是内存,但是并不占有CPU的资源去进行任何运算
耗电的根本原因就是占用CPU资源去进行计算机的计算工作(运行代码),无论是wifi,数据网,GPS等都是通过调用CPU去进行一些程序操作的,至于为什么wifi会比数据网省电,为什么GPS会那么耗电,那是因为有些操作可能调用到底层的硬件,说白了有些操作可能进行大量的CPU运算,耗电这里后面有单独有一篇文章来说明

SDK中push进程的内存占用

  • 首先,保证在push进程里面没有任何内存泄露,因为push进程是常驻的,而且代码运行的频率很高,如果存在内存泄露,短时间内push进程的内存占用就会飙升,当超过最大限制内存后会被手机系统直接杀死,内存泄露可用MAT配合LeakCanary进行检测
  • 降低常驻内存占用,因为push进程是一直存在的,所以push进程启动之后一定会有些初始化过程,并且把初始化数据一直保存在内存中,这些数据无非是一些对象内存占用,所以可以把一些高频读写的对象数据保存在内存中,把一些频率并不高的对象保存到文件中,数据库中,这样在用到的时候再去文件或者数据库中读取,这里的文件数据库就相当于磁盘缓存,你也可以把对象直接存在内存,然后可以实现类似LruCache一样的算法,当对象在一定时间内没有使用就从内存中释放掉

心跳机制实现

  • 首先一定要选择合适的智能心跳策略,难点在于要考虑弱网环境下,心跳如何探测不会导致耗电,因为弱网可能会导致连接频繁的断开,这时候探测到的最终稳定心跳区间可能很小,会频繁的唤醒手机造成耗电问题,一句话,心跳上调容易,心跳下调的条件要苛刻
  • 想在手机休眠的时候唤醒执行心跳包发送就只能用AlarmManager的RTC来wake,在一些手机上是有对alarm做对其唤醒的,那么认命吧,无能为力
  • 关于JobScheduler也能定时唤醒执行任务,这个并不适合用来实现心跳,JobScheduler本来的设计就是用来省电的,休眠后,定时执行的时刻是不准确的,就像对其唤醒一样,可能把一个短时间段内的任务一起唤醒执行
  • 心跳包数据一定要小,我们的心跳包数据没加密前是2Byte,加密后是7Byte,因为心跳本来就是属于冗余数据,它的作用就是用来维持,检测TCP连接的有效性,所以数据包越小,网络传输速度越快,网络数据丢包的概率越小,同时也省流量
  • 断网环境下要终止心跳发送,终止断线重连,网络都断了,心跳肯定发送失败,重连也不会成功的,同时监听网了切换广播,当网络连上后立刻进行重连
  • 心跳超时时间,参考网上开源的AsyncHttpClient和OkHttp,他们默认的http请求超时是10秒,我们定的是20秒,时间短一点实时性高,但是很容易心跳就超时,然后断线重连,时间长一点实时性降低,但是可以更加适应弱网环境,这里可以做一个动态心跳超时,当心跳连续成功发送了一定次数就缩短超时时间,当心跳在一段时间内频繁的失败达到一定次数后就延长超时时间来适应弱网环境

多进程,APP之间的数据共享问题

  • SDK实现了TCP多路服用,因此肯定有一些共用数据是要共享的这时候怎么办?
  • 可以把数据存SD卡中,然后APP各自去读取文件数据,但是这么做太不安全了,首先数据文件可以很容易被获取并查看到,哪怕是经过加密的数据,第二,数据文件很容易就被用户误删了
  • content provider
  • 我们采用的做法是数据共享由宿主APP进行维护,都在宿主APP的push进程中,由于push进程由一个Service来维护,各个app可以bind到这个Service上,然后通过定义aidl接口来获取共享数据,甚至是修改共享数据,但是这样做就依赖了aidl接口了,一旦一些手机开启了禁止互相唤醒,那么TCP多路复用就失效了

SDK程序问题如何分析

  • 在SDK开发过程中规范的打印详细的log日志,这样SDK代码跑到哪个逻辑流程了一清二楚
  • 有时候没办法实时抓到日志,可以把日志保存到文件中,然后就可以直接分析文件日志
  • 当SDK发布出去使用的时候一般都是关闭日志打印,关闭日志保存的,那么当用户反馈问题的时候很难分析问题到底在哪,我们的做法是做一个隐藏的日志开关,用户遇到问题了,可以让用户打开这个开关,然后让用户把保存下来的日志文件打包发给我们;但是有些用户不知道怎么把日志文件发送给我们,电脑文盲怎么办?没关系,再做一个日志上报开关,用户只需点击开关按钮就能把日志直接上传到我们的后台,然后我们就可以去查看后台的文件日志了;那么问题又来了,有些问题必须实时跟踪怎么办?没关系,我们的日志工具也开发了实时日志上报功能,具体做法是通过Http请求一次性上传100条日志信息,然后直接在后端服务器把这一百条日志实时打印出来,这样用户在那里操作app,在网络好的情况下,后台马上就能查看到日志信息了,就像把app用usb接入android studio查看日志一样

猜你喜欢

转载自blog.csdn.net/lhd201006/article/details/52832141