Q: Can I keep Android App alive in background?

  • 内容
  • 评论
  • 相关

一些手机app(如微信、QQ等)有新消息来到达,手机屏幕即使在锁屏状态下也会亮起,并提示用户有新消息。但是,一般情况下手机锁屏后,Android系统为了省电以及减少CPU消耗,在一段时间后会使系统进入休眠状态,这时,Android系统中CPU会保持在一个相对较低的功耗状态,而收到新消息必定有网络请求,而网络请求是消耗CPU的操作,那么如何在锁屏状态乃至系统进入休眠后,仍然保持系统的网络状态以及通过程序唤醒手机呢?答案就是Android中的WakeLock机制。

官方对于WakeLock的解释:

PowerManager:This class gives you control of the power state of the device.
PowerManager.WakeLock: lets you say that you need to have the device on.

PowerManager负责对Android设备电源相关进行管理,而系统通过各种锁对电源进行控制,WakeLock是一种锁机制,只要有人拿着这把锁,系统就无法进入休眠阶段。既然要保持应用程序一直在后台运行,那自然要获得这把锁才可以保证程序始终在后台运行。

WakeLock 使用代码:

WakeLock wakeLock = null;
//获取电源锁,保持该服务在屏幕熄灭时仍然获取CPU时,保持运行
private void acquireWakeLock()
{
if (null == wakeLock)
{
PowerManager pm = (PowerManager)this.getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE, "PostLocationService");
if (null != wakeLock)
{
wakeLock.acquire();
}
}
}

//释放设备电源锁
private void releaseWakeLock()
{
if (null != wakeLock)
{
wakeLock.release();
wakeLock = null;
}
}

上面第一个方法是获取锁,第二个方法是释放锁,一旦获取锁后,及时屏幕在熄灭或锁屏长时间后,系统后台一直可以保持获取到锁的应用程序运行。获取到PowerManager的实例pm后,再通过newWakeLock方法获取wakelock的实例,其中第一个参数是指定要获取哪种类型的锁,不同的锁对系统CPU、屏幕和键盘有不同的影响,第二个参数是自定义名称。

各种锁的类型对CPU 、屏幕、键盘的影响:

PARTIAL_WAKE_LOCK:保持CPU 运转,屏幕和键盘灯有可能是关闭的。

SCREEN_DIM_WAKE_LOCK:保持CPU 运转,允许保持屏幕显示但有可能是灰的,允许关闭键盘灯

SCREEN_BRIGHT_WAKE_LOCK:保持CPU 运转,允许保持屏幕高亮显示,允许关闭键盘灯

FULL_WAKE_LOCK:保持CPU 运转,保持屏幕高亮显示,键盘灯也保持亮度

ACQUIRE_CAUSES_WAKEUP:强制使屏幕亮起,这种锁主要针对一些必须通知用户的操作.

ON_AFTER_RELEASE:当锁被释放时,保持屏幕亮起一段时间

最后声明权限:


开发过程所遇到的问题

1、android如何能够在后台偷偷的运行

实现代码

IntentFilter filter = new IntentFilter(Intent.ACTION_TIME_TICK);
MyBroadcastReceiver receiver = new MyBroadcastReceiver();
acquireWakeLock();//获取电源锁
registerReceiver(receiver, filter);//注册广播

以上代码放到主activity的onresume中

其中MyBroadcastReceiver的代码如下

//自定义广播
public class MyBroadcastReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
/**
* 在众多的Intent的action动作中,Intent.ACTION_TIME_TICK是比较特殊的一个,
* 根据SDK描述:Broadcast Action: The current time has changed.
* Sent every minute. You can not receive this through
* components declared in manifests, only by exlicitly
* registering for it withContext.registerReceiver()
* 意思是说这个广播动作是以每分钟一次的形式发送。但你不能通过在manifest.xml
* 里注册的方式接收到这个广播,只能在代码里通过registerReceiver()方法注册。
*/
if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) {

// 检查Service状态
boolean isServiceRunning = false;
ActivityManager manager = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager
.getRunningServices(Integer.MAX_VALUE)) {
if ("com.text.ac.action.MY_SERVICE".equals(service.service
.getClassName()))
// Service的类名
{
isServiceRunning = true;
}

}
if (!isServiceRunning) {
Intent i = new Intent(context, MessageService.class);
context.startService(i);
}
}
}

}
这样就可以使你的程序在后台悄悄的运行了,每分钟监控一次服务是否启动,以保证服务一直运行,这样就可以做其他的事情了
2、怎么才能在手机锁屏的情况也能继续后台运行我的android程序呢

保证程序不会在锁屏的情况被清理掉

实现代码

/**
* 获取电源锁,保持该服务在屏幕熄灭时仍然获取CPU时,保持运行
* WakeLock 类型以及说明:
PARTIAL_WAKE_LOCK:保持CPU 运转,屏幕和键盘灯有可能是关闭的。
SCREEN_DIM_WAKE_LOCK:保持CPU 运转,允许保持屏幕显示但有可能是灰的,允许关闭键盘灯
SCREEN_BRIGHT_WAKE_LOCK:保持CPU 运转,允许保持屏幕高亮显示,允许关闭键盘灯
FULL_WAKE_LOCK:保持CPU 运转,保持屏幕高亮显示,键盘灯也保持亮度
ACQUIRE_CAUSES_WAKEUP:强制使屏幕亮起,这种锁主要针对一些必须通知用户的操作.
ON_AFTER_RELEASE:当锁被释放时,保持屏幕亮起一段时间
WakeLock的设置是 Activiy 级别的,不是针对整个Application应用的
权限获取
(使用找一个即可)
你可能还需要

*/
private void acquireWakeLock() {
if (null == wakeLock) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
| PowerManager.ON_AFTER_RELEASE, getClass()
.getCanonicalName());
if (null != wakeLock) {
Log.i(TAG, "call acquireWakeLock");
wakeLock.acquire();
}
}
}

// 释放设备电源锁
private void releaseWakeLock() {
if (null != wakeLock && wakeLock.isHeld()) {
Log.i(TAG, "call releaseWakeLock");
wakeLock.release();
wakeLock = null;
}
}

解释:获取手机的电源锁

释放电源锁在主activity中的onpause中

@Override
protected void onPause() {
super.onPause();
releaseWakeLock();
}

service代码如下
public class MessageService extends Service {
//获取消息线程
private MessageThread messageThread = null;

//点击查看
private Intent messageIntent = null;
private PendingIntent messagePendingIntent = null;

//通知栏消息
private int messageNotificationID = 1000;
private Notification messageNotification = null;
private NotificationManager messageNotificatioManager = null;

public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
//初始化
messageNotification = new Notification();
messageNotification.icon = R.drawable.icon;
messageNotification.tickerText = "新消息";
messageNotification.defaults = Notification.DEFAULT_SOUND;
messageNotificatioManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
//点击跳转的activity
messageIntent = new Intent(this, ExTextActivity.class);
messagePendingIntent = PendingIntent.getActivity(this,0,messageIntent,0);

//开启线程
messageThread = new MessageThread();
messageThread.isRunning = true;
messageThread.start();
Toast.makeText(MessageService.this, "aaaa", Toast.LENGTH_LONG).show();
super.onCreate();
}
/**
* 重写onStartCommand方法,使用StartForeground(int,Notification)方法来启动service。
* 注:前台服务会在状态栏显示一个通知,最典型的应用就是音乐播放器,只要在播放状态下,
* 就算休眠也不会被杀,如果不想显示通知,只要把参数里的int设为0即可
* 小米手机需要设置允许自动启动;设置步骤:安全中心-授权管理-允许自动启动
* 其他类型手机,只要设置允许自动启动即可
*/
public void onStartCommand(){
Notification notification = new Notification(R.drawable.icon,"wf update service is running",System.currentTimeMillis());
messagePendingIntent=PendingIntent.getService(this, 0, messageIntent, 0);

notification.setLatestEventInfo(this, "WF Update Service","wf update service is running!", messagePendingIntent);

//让该service前台运行,避免手机休眠时系统自动杀掉该服务

//如果 id 为 0 ,那么状态栏的 notification 将不会显示。

startForeground(0, notification);
}
/**
* 从服务器端获取消息
*
*/
class MessageThread extends Thread{
//运行状态,下一步骤有大用
public boolean isRunning = true;
@SuppressWarnings("deprecation")
public void run() {
while(isRunning){
try {
//休息10分钟
Thread.sleep(5000);
//获取服务器消息
String serverMessage = getServerMessage();

if(serverMessage!=null&&!"".equals(serverMessage)){
//此处获取要现实的数据
//更新通知栏
messageNotification.setLatestEventInfo(MessageService.this,"值班快报","新密市一煤矿发生坍塌事故。。。"+serverMessage,messagePendingIntent);
messageNotificatioManager.notify(messageNotificationID, messageNotification);
//每次通知完,通知ID递增一下,避免消息覆盖掉
messageNotificationID++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
public void onDestroy() {
// System.exit(0);
//或者,二选一,推荐使用System.exit(0),这样进程退出的更干净
messageThread.isRunning = false;
super.onDestroy();
}
/**
* 这里以此方法为服务器Demo,仅作示例
* @return 返回服务器要推送的消息,否则如果为空的话,不推送
*/
public String getServerMessage(){
return "!";
}
}