文档章节

自己动手做推送

爱看博客
 爱看博客
发布于 2015/10/22 10:06
字数 2456
阅读 19
收藏 1
点赞 0
评论 0

最近一个月一直在考虑实现一种让Android开发者一个人就能完成的推送功能库。因为现有的推送功能,全部都需要服务器端配合,不断测试,即使使用第三方库也需要很长一段时间的测试。这里就是我最近研究的一个小小的成果:http://git.oschina.net/kymjs/KJPush

推送功能在Android应用开发中已经非常普遍了,本文就是来探讨下Android中推送的底层原理与实现推送功能的一些解决方案。

1、什么是推送?

     当我们开发需要和服务器交互的应用程序时,基本上都需要获取服务器端的数据,比如开源中国客户端,在有人评论或回复你的时候,客户端需要知道,并作出相应处理。要获取服务器上的信息,有两种方法:第一种是客户端使用Pull(拉)的方式,就是隔一段时间就去服务器上获取一下信息,看是否有更新的信息出现。第二种就是服务器使用Push(推送)的方式,当服务器端有新信息了,则把最新的信息Push到客户端上。这样,客户端就能自动的接收到消息。

      Push是服务端主动发消息给客户端,现在有很多第三方推送框架:例如百度推送、极光推送、个推等等,都是基于之前说的第二种方式也就是服务器使用Push的方式。因为第一时间知道数据发生变化的是服务器自己,所以Push的优势是实时性高。但服务器主动推送需要单独开发一套能让客户端持久连接的服务端程序。但有些情况下并不需要服务端主动推送,而是在一定的时间间隔内客户端主动发起查询,这种时候就应该使用Pull的方式去获取。很多人认为Push方式没有任何消耗,其实不然采用Push方式需要长时间维持一条客户端与服务器端通信的socket长连接,依旧是很费流量与电量。如果轮询策略配置的好,消耗的电与数据流量绝不比维持一个socket连接使用的多。譬如有这样一个app,实时性要求不高,每天只要能获取10次最新数据就能满足要求了,这种情况显然轮询更适合一些,推送显得太浪费,而且更耗电。

2、如何实现轮询请求

第一种方式是在一个Service中创建一个计时器,如下代码是在网上找的一段类似实现(节选)

/**
 * 短信推送服务类,在后台长期运行,每个一段时间就向服务器发送一次请求
 * @author jerry
 */
public class PushSmsService extends Service {
    
    @Override
    public void onCreate() {
        this.client = new AsyncHttpClient();
        this.myThread = new MyThread();
        this.myThread.start();
        super.onCreate();
    }
    
    private class MyThread extends Thread {
        @Override
        public void run() {
            String url = "你请求的网络地址";
            while (flag) {
                // 每个10秒向服务器发送一次请求
                Thread.sleep(10000);
                // 采用get方式向服务器发送请求
                client.get(url, new AsyncHttpResponseHandler() {
                    @Override
                    public void onSuccess(int statusCode, Header[] headers,
                            byte[] responseBody) {
                        try {
                            JSONObject result = new JSONObject(new String(
                                    responseBody, "utf-8"));
                            int state = result.getInt("state");
                            // 假设偶数为未读消息
                            if (state % 2 == 0) {
                                String content = result.getString("content");
                                String date = result.getString("date");
                                String number = result.getString("number");
                                notification(content, number, date);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
}

但是用Sleep,TimerTask,都会增大Service被系统回收的可能,更合适的方法是使用AlarmManager这个系统的计时器去管理。

实现方法如下,你可以在这里看到完整的实现方式

private void startRequestAlarm() {
        cancelRequestAlarm();
        // 从1秒后开始,每隔2分钟执行getOperationIntent()
        // 注意,这个2分钟只是正常情况下的2分钟,实际情况可能不同系统的处理策略而被延长,比如坑爹的粗粮系统上可能被延长至5分钟
        mAlarmMgr.setRepeating(AlarmManager.RTC_WAKEUP,
                System.currentTimeMillis() + 1000, KJPushConfig.PALPITATE_TIME,
                getOperationIntent());
    }

    /**
     * 即使启动PendingIntent的原进程结束了的话,PendingIntent本身仍然还存在,可在其他进程(
     * PendingIntent被递交到的其他程序)中继续使用.
     * 如果我在从系统中提取一个PendingIntent的,而系统中有一个和你描述的PendingIntent对等的PendingInent,
     * 那么系统会直接返回和该PendingIntent其实是同一token的PendingIntent,
     * 而不是一个新的token和PendingIntent。然而你在从提取PendingIntent时,通过FLAG_CANCEL_CURRENT参数,
     * 让这个老PendingIntent的先cancel()掉,这样得到的pendingInten和其token的就是新的了。
     */
    private void cancelRequestAlarm() {
        mAlarmMgr.cancel(getOperationIntent());
    }

    /**
     * 采用轮询方式实现消息推送<br>
     * 每次被调用都去执行一次{@link #PushReceiver}onReceive()方法
     * 
     * @return
     */
    private PendingIntent getOperationIntent() {
        Intent intent = new Intent(this, PushReceiver.class);
        intent.setAction(KJPushConfig.ACTION_PULL_ALARM);
        PendingIntent operation = PendingIntent.getBroadcast(this, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        return operation;
    }

这样就可以在最大程度上解决因为自己实现计时器造成的计时不准确或计时器被系统回收的问题。

但是仅仅这样还没办法实现一个完善且稳定的轮询推送库,做推送最大的问题有三个:电量消耗,数据流量消耗,服务持久化。

3、电量消耗优化与数据流量消耗优化:

这两个问题其实可以合并成一个问题,因为请求服务器其实也是一个费电的事情。与维持一个长连接类似,要实现推送功能,不管是维持一个长连接或者是定时请求服务器都需要耗费网络数据流量,而只不过长连接是一个细水长流不断耗费,而轮询是一次一大断数据的耗费。这样就需要一种可行的策略去配置,让轮询按照我们想要的方式去执行。目前我采用的思路是当手机处于GPRS模式时降低轮询的频率,每5分钟请求一次服务器,当手机处于WiFi模式时每2分钟请求一次服务器,同时设置如果熄灭屏幕则停止推送请求,当屏幕熄灭20秒后杀死推送进程,这样不仅不需要考虑维护一个进程的消耗同时也节省了数据流量的使用。

4、服务持久化

相信这是一个很多人都遇到的问题,网上也有很多类似的问题,像QQ微信这种应用做的就非常好,不管使用第三方手机助手或者使用系统停止一个应用(不是设置里面的那种停止,是长按Home键的那种),后台Service都不会被回收。很可惜,我目前只能做到保证一个Service不被第三方手机助手回收,可以防止部分手机长按Home键停止,但是例如粗粮的MIUI系统,依旧会杀死我的Service且无法恢复。目前为止我依旧没有找到一个公开的完美解决办法,如果你知道如何解决,请不吝指教。下面我就简单讲讲如何最大程度的维护一个Service。

以前在做音乐播放器的时候,相信很多人都遇到了,在应用开启过多的时候,后台播放音乐的Service独立进程会被系统杀死。

在Android的ActivityManager中有一个内部类RunningAppProcessInfo,用来记录当前系统中进程的状态,如下是其中的一些值:

/**
         * Constant for {@link #importance}: this is a persistent process.
         * Only used when reporting to process observers.
         * @hide
         */
        public static final int IMPORTANCE_PERSISTENT = 50;

        /**
         * Constant for {@link #importance}: this process is running the
         * foreground UI.
         */
        public static final int IMPORTANCE_FOREGROUND = 100;
        
        /**
         * Constant for {@link #importance}: this process is running something
         * that is actively visible to the user, though not in the immediate
         * foreground.
         */
        public static final int IMPORTANCE_VISIBLE = 200;
        
        /**
         * Constant for {@link #importance}: this process is running something
         * that is considered to be actively perceptible to the user.  An
         * example would be an application performing background music playback.
         */
        public static final int IMPORTANCE_PERCEPTIBLE = 130;
        
        /**
         * Constant for {@link #importance}: this process is running an
         * application that can not save its state, and thus can't be killed
         * while in the background.
         * @hide
         */
        public static final int IMPORTANCE_CANT_SAVE_STATE = 170;
        
        /**
         * Constant for {@link #importance}: this process is contains services
         * that should remain running.
         */
        public static final int IMPORTANCE_SERVICE = 300;
        
        /**
         * Constant for {@link #importance}: this process process contains
         * background code that is expendable.
         */
        public static final int IMPORTANCE_BACKGROUND = 400;
        
        /**
         * Constant for {@link #importance}: this process is empty of any
         * actively running code.
         */
        public static final int IMPORTANCE_EMPTY = 500;

一般数值大于RunningAppProcessInfo.IMPORTANCE_SERVICE的进程都长时间没用或者空进程了

一般数值大于RunningAppProcessInfo.IMPORTANCE_VISIBLE的进程都是非可见进程,也就是在后台运行着

第三方清理软件清理的一般是大于IMPORTANCE_VISIBLE的值,所以要想不被杀死就需要将自己的进程降低到IMPORTANCE_VISIBLE以下,也就是可见进程的程度。在每一个Service中有一个方法叫startForeground,也就是以可见进程的模式启动,这里是在SDK源码中的实现与注释,可以看到,它会在通知栏持续显示一个通知,但只需要将id传为0即可避免通知的显示。当然要取消这种可见进程等级的设置只需要调用stopForgeround即可。

/**
     * Make this service run in the foreground, supplying the ongoing
     * notification to be shown to the user while in this state.
     * By default services are background, meaning that if the system needs to
     * kill them to reclaim more memory (such as to display a large page in a
     * web browser), they can be killed without too much harm.  You can set this
     * flag if killing your service would be disruptive to the user, such as
     * if your service is performing background music playback, so the user
     * would notice if their music stopped playing.
     */
    public final void startForeground(int id, Notification notification) {
        try {
            mActivityManager.setServiceForeground(
                    new ComponentName(this, mClassName), mToken, id,
                    notification, true);
        } catch (RemoteException ex) {
        }
    }

这里由于篇幅有限就讲这么多了,希望详细了解进程优先级提升的可以看看ActivityManager源码中的定义以及KJPush中的实现方式。

版权声明:本文原创,转载请注明来自 http://kymjs.com/

本文转载自:http://blog.csdn.net/kymjs/article/details/42705871

共有 人打赏支持
爱看博客
粉丝 5
博文 103
码字总数 23887
作品 0
深圳

暂无文章

expect脚本同步文件、expect脚本指定host和要同步的文件、构建文件分发系统

expect脚本同步文件 更改权限 执行脚本 查看执行结果 expect eof需要加上,作用是等脚本命令执行完再进行退出 expect脚本指定host和要同步的文件 更改权限,执行脚本 构建文件分发系统 需求背...

Zhouliang6
18分钟前
1
0
Hive应用:外部分区表

Hive应用:外部分区表 介绍 Hive可以创建外部分区表。创建表的时候,分区要在建表语句中体现。建完之后,你不会在表中看到数据,需要进行分区添加,使用alter语句进行添加。然后数据才会显示...

星汉
28分钟前
0
0
点击Enter登录

1. 效果 2. 实现过程(记得引入jq文件) //6.回车事件 登录 $(function() { document.onkeydown = function(event) { var e = event || window.event || arguments.callee.caller.arguments......

Lucky_Me
33分钟前
1
0
点击菜单内容切换

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .menu{ height: 38px; background-color: #eeeeee; line-height: 38px; } .mao{ ......

南桥北木
今天
1
0
OSChina 周六乱弹 —— 妹子和游戏哪个更好玩

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @andonny :分享唐朝乐队的单曲《国际歌》 《国际歌》- 唐朝乐队 手机党少年们想听歌,请使劲儿戳(这里) @举个栗子- :日常祈雨 邪恶的大祭...

小小编辑
今天
494
6
流利阅读笔记32-20180721待学习

“人工智能”造假:只有人工,没有智能 Lala 2018-07-21 1.今日导读 当今社会,擅长单个方面的人工智能已经盛行,手机借助 AI 智慧防抖技术帮助大家拍出清晰照片,谷歌研发的 AI 助手将可以帮...

aibinxiao
今天
7
0
我的成长记录(一)

今天突然精神抖擞,在我的博客下新开一项分类>成长记录,专门记录每隔一段时间我的一点感悟吧。因为今天才专门花时间新开这样一个分类,所以以前有过的一些感悟没有记录下来,现在已经想不起...

dtqq
今天
1
0
机器学习管理平台 MLFlow

最近工作很忙,博客一直都没有更新。抽时间给大家介绍一下Databrick开源的机器学习管理平台-MLFlow。 谈起Databrick,相信即使是不熟悉机器学习和大数据的工程湿们也都有所了解,它由Spark的...

naughty
今天
12
0
idea tomcat 远程调试

tomcat 配置 编辑文件${tomcat_home}/bin/catalina.sh,在文件开头添加如下代码。    CATALINA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=7829" Idea端配......

qwfys
今天
2
0
遍历目录下的文件每250M打包一个文件

#!/usr/bin/env python # -*- utf-8 -*- # @Time : 2018/7/20 0020 下午 10:16 # @Author : 陈元 # @Email : abcmeabc@163.com # @file : tarFile.py import os import tarfile import thr......

寻爱的小草
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部