您的位置:首页 > 移动开发 > Android开发

android设备休眠机制

2015-06-02 12:39 344 查看
如果一开始就对Android手机的硬件架构有一定的了解,设计出的应用程序通常不会成为待机电池杀手,而要设计出正确的通信机制与通信协议也并不困难。但如果不去了解而盲目设计,可就没准了。

首先Android手机有两个处理器,一个叫Application Processor(AP),一个叫Baseband Processor(BP)。AP是ARM架构的处理器,用于运行Linux+Android系统;BP用于运行实时操作系统(RTOS),通讯协议栈运行于BP的RTOS之上。非通话时间,BP的能耗基本上在5mA左右,而AP只要处于非休眠状态,能耗至少在50mA以上,执行图形运算时会更高。另外LCD工作时功耗在100mA左右,WIFI也在100mA左右。一般手机待机时,AP、LCD、WIFI均进入休眠状态,这时Android中应用程序的代码也会停止执行。

Android为了确保应用程序中关键代码的正确执行,提供了Wake Lock的API,使得应用程序有权限通过代码阻止AP进入休眠状态。但如果不领会Android设计者的意图而滥用Wake Lock API,为了自身程序在后台的正常工作而长时间阻止AP进入休眠状态,就会成为待机电池杀手。比如前段时间的某应用,比如现在仍然干着这事的某应用。

首先,完全没必要担心AP休眠会导致收不到消息推送。通讯协议栈运行于BP,一旦收到数据包,BP会将AP唤醒,唤醒的时间足够AP执行代码完成对收到的数据包的处理过程。其它的如Connectivity事件触发时AP同样会被唤醒。那么唯一的问题就是程序如何执行向服务器发送心跳包的逻辑。你显然不能靠AP来做心跳计时。Android提供的Alarm Manager就是来解决这个问题的。Alarm应该是BP计时(或其它某个带石英钟的芯片,不太确定,但绝对不是AP),触发时唤醒AP执行程序代码。那么Wake Lock API有啥用呢?比如心跳包从请求到应答,比如断线重连重新登陆这些关键逻辑的执行过程,就需要Wake Lock来保护。而一旦一个关键逻辑执行成功,就应该立即释放掉Wake Lock了。两次心跳请求间隔5到10分钟,基本不会怎么耗电。除非网络不稳定,频繁断线重连,那种情况办法不多。

网上有说使用AlarmManager,因为AlarmManager 是Android 系统封装的用于管理 RTC 的模块,RTC (Real Time Clock) 是一个独立的硬件时钟,可以在 CPU 休眠时正常运行,在预设的时间到达时,通过中断唤醒 CPU。

移动互联网应用现状

因为手机平台本身、电量、网络流量的限制,移动互联网应用在设计上跟传统 PC 上的应用很大不一样,需要根据手机本身的特点,尽量的节省电量和流量,同时又要尽可能的保证数据能及时到达客户端。

为了解决数据同步的问题,在手机平台上,常用的方法有2种。一种是定时去服务器上查询数据,也叫Polling,还有一种手机跟服务器之间维护一个 TCP 长连接,当服务器有数据时,实时推送到客户端,也就是我们说的 Push。

从耗费的电量、流量和数据送达的及时性来说,Push 都会有明显的优势,但 Push 的实现和维护成本相对较高。在移动无线网络下维护长连接,相对也有一些技术上的难度。本文试图给大家介绍一下我们极光推送在 Android 平台上是如何维护长连接。

移动无线网络的特点

因为 IP v4 的 IP 量有限,运营商分配给手机终端的 IP 是运营商内网的 IP,手机要连接 Internet,就需要通过运营商的网关做一个网络地址转换(Network Address Translation,NAT)。简单的说运营商的网关需要维护一个外网 IP、端口到内网 IP、端口的对应关系,以确保内网的手机可以跟 Internet 的服务器通讯。



图片源自 cisco.com.

NAT 功能由图中的 GGSN 模块实现。

大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 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 问题。

C2000K

针对这个问题,我们专门成立了一个项目,命名为C2000K,顾名思义,我们的目标是单机维持200万个长连接。最终我们采用了多消息循环、异步非阻塞的模型,在一台双核、24G内存的服务器上,实现峰值维持超过300万个长连接。
http://docs.jpush.cn/pages/viewpage.action?pageId=3309821

后记

稳定维护长连接是推送平台的一个基础,极光推送团队将会在这方面长期投入,以保证用户能有效的节省电量、流量,同时数据能实时送达。

以上是极光推送官方的文章,但是看了之后不免有几个疑问。

1)他们号称最高峰值可以达到300W的长连接,但是活跃链接的处理最高是多少呢?

2)消息的平均长度和限制各是多少?

3)消息的及时性怎么样,延时是多少?

4)不知道现在,极光推送的用户大概有多少,所以这个峰值是在生产环境,还是测试环境的数值?

5)我想不通的是为什么,客户端要用AlarmManager来做推送消息的获取?这个消息获取还及时吗?鄙人结识android也有N载。

6)我感兴趣的是,不是极光方案一个月推送了几百万条数据,而是几秒钟或者一分钟可以处理多少。

Android 消息推送方案

  当我们开发需要与服务器交互的应用程序时,基本上都需要获取服务器端的数据。要获取服务器上不定时更新的信息,一般来说有两种方法:第一种是客户端使用pull(拉)的方式,隔一段时间就去服务器上获取一下信息,看是否有更新的信息出现;第二种就是服务器使用push(推送)的方式,当服务器端有更新,则将最新的信息push到客户端上,如此以来,客户端就能自动地接收到消息。

  虽然pull和push两种方式都能实现获取服务器端数据更新的功能,但pull方式的弊端很明显:费流量、费电,需要我们的程序不停地去监测服务器端的更新。

  首先可以来了解一下iOS和Android平台的推送机制:

  iOS 系统的推送(APNS,即 Apple Push Notification Service)依托一个或几个系统常驻进程运作,是全局的(接管所有应用的消息推送),所以可看作是独立于应用之外,而且是设备和苹果服务器之间的通讯,而非应用的提供商服务器。比如腾讯 QQ 的服务器(Provider)会给苹果公司对应的服务器(APNs)发出通知,然后再中转传送到你的设备(Devices)之上。当你接收到通知,打开应用,才开始从腾讯服务器接收数据,跟你之前看到通知里内容一样,但却是经由两个不同的通道而来。

  Android的推送有两种形式,一种是后台Service,一种是GCM

  不过这里要指出的是,Android的这种形式和传统电脑还是有区别的,电脑的后台是保持程序进程在后台运行,而Android是在程序后台Service区域添加一个新的程序专门接收数据。

  要说的话,Android的后台推送机制更加复杂,但是因为可操控部分更多,加上GCM是在2.X之后才加上的而且有可能在系统中并不存在,所以很多软件都是使用Service这种形式。

  本质上,APNs 与 GCM 是类似的技术实现原理:即系统层有一个常驻的 TCP 长连接,一直保持的长连接,即使手机休眠的时候也在保持的长连接。这里对于大部分人来说,最不理解的就是,休眠时候都保持在那里的 TCP 长连接,不会耗电很厉害么

  答案是:不会。这是手机的设计来做到的。TCP长连接有个心跳的时间,在国外可以很长比如30分钟,在国内则因为网络环境复杂一般10分钟。客户端发起的心跳,会短暂地消耗手机电能,但在这个心跳间隔期间,则消耗电能是很少的。当在心跳期间服务器端有推送信息过来时,客户端可以收到并做处理。

  这里有篇文章以 Android 为例做原理解释:

  /jpush_wireless_push_principle/

  极光推送技术原理:无线网络长连接

  再说 APNs 的设计成功处。

  iOS 为了真正地为用户体验负责,不允许应用在后台活动。有了这个限制,但是对于终端设备,应用又是有必要“通知”到达用户的,随时与用户主动沟通起来的(典型的如聊天应用)。

  这就是 APNs 的逻辑所在:iOS 自己做个长驻后台保持连接。所有应用,有必要(申请)并且被允许(用户可以改设置)的话,可以通过 APNs 中转到达用户。

  Android 因为后台可以长驻,尤其是国内的 Android 的手机上 Google自家的推送服务 GCM 处于基本不可用的状态。所以,各App各显神通。聊天类应用的话,大多数直接借用 XMPP 规范里的一些成果。少量如微信有IM底子的,自己开发协议。这些在实现原理上与 APNs / GCM 没有本质的区别,但有一定的技术门槛。而大多数普遍应用,要使用推送的话,则使用轮询的方式简单实现。

  其实,国外如 Urban Airship 自己实现了 Android 上的第三方提供的推送平台。国内如极光推送也实现了第三方的推送平台(技术与微信、GCM、APNs类似)。理论上,如果一个 Android 设备上多款应用都使用极光推送这种第三方推送平台的话,也可以如 APNs 一样达到节省电量、流量消耗的效果。

  另可参考Android实现推送方式解决方案

在开发Android的过程中,我们经常用到的WIFI在休眠情况下默认是会不连接的,这个时候当我们需要保持连接时,该如何解决

不少人说可以在系统设置的WIFI高级选项中将连接设为休眠保持连接,这个办法的确可行,对于开发者来说很容易办到,但是对于用户来说他们一般不会知道这么设置,这个时候该怎么办呢?可以使用如下代码解决

[java]
public void WifiNeverDormancy(Context mContext)
{
ContentResolver resolver = mContext.getContentResolver();

int value = Settings.System.getInt(resolver, Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_DEFAULT);
final SharedPreferences prefs=PreferenceManager.getDefaultSharedPreferences(mContext);

Editor editor = prefs.edit();
editor.putInt(mContext.getString(R.string.wifi_sleep_policy_default), value);

editor.commit();
if(Settings.System.WIFI_SLEEP_POLICY_NEVER != value)
{
Settings.System.putInt(resolver, Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_NEVER);

}
System.out.println("wifi value:"+value);
}

public void WifiNeverDormancy(Context mContext)
{
ContentResolver resolver = mContext.getContentResolver();

int value = Settings.System.getInt(resolver, Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_DEFAULT);
final SharedPreferences prefs=PreferenceManager.getDefaultSharedPreferences(mContext);

Editor editor = prefs.edit();
editor.putInt(mContext.getString(R.string.wifi_sleep_policy_default), value);

editor.commit();
if(Settings.System.WIFI_SLEEP_POLICY_NEVER != value)
{
Settings.System.putInt(resolver, Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_NEVER);

}
System.out.println("wifi value:"+value);
}上面这个函数,会自动修改我们WIFI设置中的高级选项,将其设置为一直保持连接。不用使用其他控件就可以解决。

需要注意的是此函数在调用时必须现在AndroidManifest.xml中声明权限

<uses-permission android:name="android.permission.WRITE_SETTINGS"/>

转】Android设置手机WIFI休眠策略

在开发Android应用中,有很大一部分app是要求手机一直处于网络环境中的。在Android 设置--> WLAN -->点击菜单键 选择 高级 -->休眠状态下保持WLAN连接的下拉列表{始终、仅限充电时、从不(会增加数据流量)}这个设置项中,很多用户并不知道这个选项的存在,如果设置不为始终,那么我们锁屏休眠后,程序将会处于无网络状态,相应的app用户会一直处于离线模式,那么带来的效果可想而知。

那么我们开发中,应对这种情况,我们可以在程序首先开启时,读取这个值,保存起来,待程序退出时,将原先保存的值还原,这样可以很好的解决上述这个问题。

在Android API中这样描述:

public static final String WIFI_SLEEP_POLICY
Since: API Level 3

The policy for deciding when Wi-Fi should go to sleep (which will in turn switch to using the mobile data as an Internet connection).

Set to one of WIFI_SLEEP_POLICY_DEFAULT, WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED, or WIFI_SLEEP_POLICY_NEVER.

Constant Value:"wifi_sleep_policy"

那么我们在开启应用时,首先取出这个值,上代码:
private void setWifiDormancy()
{
int value = Settings.System.getInt(getContentResolver(), Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_DEFAULT);
final SharedPreferences prefs = getSharedPreferences(getString(R.string.wifi_sleep_policy), Context.MODE_PRIVATE);
Editor editor = prefs.edit();
editor.putInt(getString(R.string.wifi_sleep_policy_default), value);
editor.commit();
if(Settings.System.WIFI_SLEEP_POLICY_NEVER != value)
{
Settings.System.putInt(getContentResolver(), Settings.System.WIFI_SLEEP_POLICY, WIFI_SLEEP_POLICY_NEVER);
}
}

在应用退出我们需将这个设置的值还原:
private void restoreWifiDormancy()
{
final SharedPreferences prefs = getSharedPreferences(getString(R.string.wifi_sleep_policy), Context.MODE_PRIVATE);
int defaultPolicy = prefs.getInt(getString(R.string.wifi_sleep_policy_default), Settings.System.WIFI_SLEEP_POLICY_DEFAULT);
Settings.System.putInt(getContentResolver(), Settings.System.WIFI_SLEEP_POLICY, defaultPolicy);
}

这样初始设置,退出还原我们可以让应用一直处于wifi模式下(不过前提是有wifi网络哦)。

from: http://blog.sina.com.cn/s/blog_a85b30ff0101ahzd.html

Android 关于休眠的几个坑点

首先看一下Android Powermanager Class Overview,对Android的几种不同的休眠模式有个大致了解。

如果不进行特别的设置,Android会在一定时间后屏幕变暗,在屏幕变暗后一定时间内,约几分钟,CPU也会休眠,大多数的程序都会停止运行,从而节省电量。但你可以在代码中通过对Powmanager API的调用来设置不同的休眠模式。

Flag Value CPU Screen Keyboard
PARTIAL_WAKE_LOCK On* Off Off
SCREEN_DIM_WAKE_LOCK On Dim Off
SCREEN_BRIGHT_WAKE_LOCK On Bright Off
FULL_WAKE_LOCK On Bright Bright

如上表,最高等级的休眠是屏幕,键盘等,cpu都全部休眠。可以设置不同的模式,让其产生不同的休眠,比如让cpu保持运行。
设置代码如下:



PowerManagerpm =(PowerManager)getSystemService(Context.POWER_SERVICE);

PowerManager.WakeLockwl =pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,"My Tag");

wl.acquire();

..screen will stay on during thissection..

wl.release();


我曾经遇到的几个坑点及解决:

1.向服务器轮询的代码不执行。

曾经做一个应用,利用Timer和TimerTask,来设置对服务器进行定时的轮询,但是发现机器在某段时间后,轮询就不再进行了。查了很久才发 现是休眠造成的。后来解决的办法是,利用系统的AlarmService来执行轮询。因为虽然系统让机器休眠,节省电量,但并不是完全的关机,系统有一部 分优先级很高的程序还是在执行的,比如闹钟,利用AlarmService可以定时启动自己的程序,让cpu启动,执行完毕再休眠。

2.后台长连接断开。

最近遇到的问题。利用Socket长连接实现QQ类似的聊天功能,发现在屏幕熄灭一段时间后,Socket就被断开。屏幕开启的时候需进行重连,但 每次看Log的时候又发现网络是链接的,后来才发现是cpu休眠导致链接被断开,当你插上数据线看log的时候,网络cpu恢复,一看网络确实是链接的, 坑。最后使用了PARTIAL_WAKE_LOCK,保持CPU不休眠。

3.调试时是不会休眠的。
让我非常郁闷的是,在调试2的时候,就发现,有时Socket会断开,有时不会断开,后来才搞明白,因为我有时是插着数据线进行调试,有时拔掉数据线,这 时Android的休眠状态是不一样的。而且不同的机器也有不同的表现,比如有的机器,插着数据线就会充电,有的不会,有的机器的设置的充电时屏幕不变暗 等等,把自己都搞晕了。其实搞明白这个休眠机制,一切都好说了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: