Android推送原理。

Android推送原理。

1、什么是推送?

消息推送最简单的方法就是使用第三方的,比如现在使用比较多的是极光推送,机关推送的技术原理是:移动无线网络长连接
移动互联网络的现状:
因为手机平台本身、电量、网络流量的限制,移动互联网应用在设计上跟传统PC 上的应用很大不一样,需要根据手机本身的特点,尽量的节省电量和流量,同时又要尽可能的保证数据能及时到达客户端。为了解决数据同步的问题,在手机平台上,常用的方法有
2种。一种是定时去服务器上查询数据,也叫Polling,还有一种手机跟服务器之间维护一个TCP 长连接,当服务器有数据时,实时推送到客户端,也就是我们说的Push。从耗费的电量、流量和数据送达的及时性来说,Push 都会有明显的优势,但Push 的实现和维护成本相对较高。在移动无线网络下维护长连接,相对也有一些技术上的难度
移动无线网络的特点:
因为IP v4 的IP 量有限,运营商分配给手机终端的IP 是运营商内网的IP,手机要连接Internet,就需要通过运营商的网关做一个网络地址转换(Network Address Translation,NAT)。简单的说运营商的网关需要维护一个外网IP、端口到内网IP、端口的对应关系,以确保内网的手机可以跟Internet 的服务器通讯。
对于大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰NAT 表中的对应项,造成链路中断。
Android 平台上长连接的实现
为了不让NAT 表失效,我们需要定时的发心跳,以刷新NAT 表项,避免被淘汰。Android 上定时运行任务常用的方法有2种,一种方法用Timer,另一种是AlarmManager。
Timer
Android 的Timer 类可以用来计划需要循环执行的任务,Timer 的问题是它需要用WakeLock 让CPU 保持唤醒状态,这样会大量消耗手机电量,大大减短手机待机时间。这种方式不能满足我们的需求。
AlarmManager
AlarmManager 是Android 系统封装的用于管理RTC 的模块,RTC (Real Time Clock) 是一个独立的硬件时钟,可以在CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒CPU。这意味着,如果我们用AlarmManager 来定时执行任务,CPU 可以正常的休眠,只有在需要运行任务时醒来一段很短的时间。极光推送的Android SDK 就是基于这种技术实现的。

服务器设计
当有大量的手机终端需要与服务器维持长连接时,对服务器的设计会是一个很大的挑战。假设一台服务器维护10万个长连接,当有1000万用户量时,需要有多达100台的服务器来维护这些用户的长连接,这里还不算用于做备份的服务器,这将会是一个巨大的成本问题。那就需要我们尽可能提高单台服务器接入用户的量,也就是业界已经讨论很久了的C10K 问题。

上面只是针对极光推送来说,下面是具体的消息推送的一般有的方式:
1) 通过SMS进行服务器端和客户端的交流通信
在Android平台上,你可以通过拦截SMS消息并且解析消息内容来了解服务器的意图,可以实现完全的实时操作。但是问题是这个方案的成本相对比较高,且依赖于运营商
2) 循环主动定时获取
这种方法需要客户端来做一个定时或者周期性的访问服务器端接口,以获得最新的消息。轮询的频率太慢可能导致某些消息的延迟,太快则会大量消耗网络带宽和电池
3) 持久连接
这个方案可以解决由轮询带来的性能问题,但是还是会消耗手机的电池。我们需要开一个服务来保持和服务器端的持久连接(苹果就和谷歌的C2DM是这种机制)。但是对于Android系统,当系统可用资源较低,系统会强制关闭我们的服务或者是应用,这种情况下连接会强制中断。(Apple的推送服务之所以工作的很好,是因为每一台手机仅仅保持一个与服务器之间的连接,事实上C2DM也是这么工作的。即所有的推送服务都是经由一个代理服务器完成的,这种情况下只需要和一台服务器保持持久连接即可。C2DM=Cloud to Device Messaging)。
 相比之下第三种还是最可行的。为软件编写系统服务或开机启动功能;或者如果系统资源较低,服务被关闭后可以在onDestroy ()方法里面再重启该服务,进而实现持久连接的方式。
 C2DM内置于Android的2.2系统上,无法兼容老的1.6到2.1系统;且依赖于Google官方提供的C2DM服务器,由于国内的网络环境,这个服务经常不可用。
 建立在TCP协议之上的XMPP协议,不仅可提供可这种持久连接的功能,能实现服务器和客户机的双工通信,还能不依赖与系统版本和google服务器的限制,提供了比较好的解决方案。

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中的实现方式。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值