耍流氓之Service关闭又自动启(AlarmManager、PendingIntent、BroadcastReceiver、Service)

原文地址:http://blog.csdn.net/arui319/article/details/7040980#

==============================================================================

首先要说的是,用户可能把这种做法视为流氓软件。大部分时候,程序员也不想把软件做成流氓软件,没办法,领导说了算。

我们在使用某些Android应用的时候,可能会发现安装了某应用以后,会有一些服务也会随之运行。而且,这些服务每次都会随着手机开机而启动。有的服务做的更绝,当用户停止该服务器以后,过了一段时间,服务又自动运行了。虽然,从用户的角度来说,这种方式比较流氓。但是,从程序员的角度来说,这是如何做到的呢?经过研究,我发现有一种方式是可以实现的。下面就和大家分享。

先简单介绍,一会儿会贴上全部代码。

如何做到开机启动?

这个比较简单,网上的资料够多,只要实现一个BroadcastReceiver,监听手机启动完成的事件ACTION_BOOT_COMPLETED即可。需要注意的是,好像不能用模拟器,要用手机测试。

那如何做到启动一个Service,并且在用户关闭后能自动又启动了呢?

一般的,都会在上面说到的BroadcastReceiver的实现里面,监听手机启动完成后,启动一个Service,这是一般的做法。问题是,用户可以关闭掉该Service。那么怎样才能使它被关闭掉以后,再次启动呢?聪明的你一定立即就想到了,如果不直接启动Service,而是启动一个timmer,或者alarmManager,然后每隔一段时间去启动Service,就可以了。

还是看下面的全部代码吧,不过多解释了。这些代码中还是有不少概念的,不熟悉AlarmManager、PendingIntent、BroadcastReceiver、Service等等这些概念的同学可以百度一下。

package com.arui.framework.android.daemonservice;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;

public class BootBroadcast extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent mintent) {

		if (Intent.ACTION_BOOT_COMPLETED.equals(mintent.getAction())) {
			// 启动完成
			Intent intent = new Intent(context, Alarmreceiver.class);
			intent.setAction("arui.alarm.action");
			PendingIntent sender = PendingIntent.getBroadcast(context, 0,
					intent, 0);
			long firstime = SystemClock.elapsedRealtime();
			AlarmManager am = (AlarmManager) context
					.getSystemService(Context.ALARM_SERVICE);

			// 10秒一个周期,不停的发送广播
			am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstime,
					10 * 1000, sender);
		}

	}
}

 

package com.arui.framework.android.daemonservice;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class Alarmreceiver extends BroadcastReceiver {
	@Override
	public void onReceive(Context context, Intent intent) {

		if (intent.getAction().equals("arui.alarm.action")) {
			Intent i = new Intent();
			i.setClass(context, DaemonService.class);
			// 启动service 
			// 多次调用startService并不会启动多个service 而是会多次调用onStart
			context.startService(i);
		}
	}
}

 

package com.arui.framework.android.daemonservice;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class DaemonService extends Service {

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

    @Override
    public void onCreate() {
    	super.onCreate();
    	Log.v("=========", "***** DaemonService *****: onCreate");
    }

    @Override
    public void onStart(Intent intent, int startId) {
    	Log.v("=========", "***** DaemonService *****: onStart");
    	// 这里可以做Service该做的事
    }
}

 

下面是manifest文件的代码。

 

		<receiver 
			android:name="com.arui.framework.android.daemonservice.BootBroadcast"
			android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
			<intent-filter>
				<action android:name="android.intent.action.BOOT_COMPLETED" />
			</intent-filter>
		</receiver>
		<receiver 
			android:name="com.arui.framework.android.daemonservice.Alarmreceiver" >
			<intent-filter>
				<action android:name="arui.alarm.action" />
			</intent-filter>
		</receiver>
        <service
            android:name="com.arui.framework.android.daemonservice.DaemonService" >
        </service>

 

继续讨论这个问题。

如果用户停止整个应用(在管理应用程序中停止应用,或者第三方软件停止整个应用),此时整个进程被杀死,所有的服务自然也被杀死了,timmer,或者alarmManager也就停止了。此时就不会再定期启动服务了。

那么,怎么才能做到,用户或者第三方软件无法停止整个应用呢。我们可以再注册一个系统级别的监听(BroadcastReceiver),来监听系统级别的消息,再次启动timmer,或者alarmManager。这样,即使应用被杀死了,隔一段时间,应用还会自动启动。具体的,就不在这里展开了。

全局定时器AlarmManager

自从知道了android中Timer在手机锁屏的时候不会正常倒计时后,就寻找到了另外一种全局的计时器AlarmManager,它是android中提供的一个系统服务,即使手机在锁屏的情况下也能倒计时。短时间的计时是没问题的,长时间的倒计时正在进行测试。

下面先了解下AlarmManager的一些方法和属性:

AlarmManager 包含的主要方法:

// 取消已经注册的与参数匹配的定时器
void cancel(PendingIntent operation)
//注册一个新的延迟定时器
void set(int type, long triggerAtTime, PendingIntent operation)
//注册一个重复类型的定时器
void setRepeating(int type, long triggerAtTime, long interval, PendingIntent operation)
//注册一个非精密的重复类型定时器
void setInexactRepeating (int type, long triggerAtTime, long interval, PendingIntent operation)
//设置时区
void setTimeZone(String timeZone)

定时器主要类型:

public static final int ELAPSED_REALTIME
// 当系统进入睡眠状态时,这种类型的闹铃不会唤醒系统。直到系统下次被唤醒才传递它,该闹铃所用的时间是相对时间,是从系统启动后开始计时的,包括睡眠时 间,可以通过调用SystemClock.elapsedRealtime()获得。系统值是3 (0x00000003)。

public static final int ELAPSED_REALTIME_WAKEUP
//能唤醒系统,用法同ELAPSED_REALTIME,系统值是2 (0x00000002) 。

public static final int RTC
//当系统进入睡眠状态时,这种类型的闹铃不会唤醒系统。直到系统下次被唤醒才传递它,该闹铃所用的时间是绝对时间,所用时间是UTC时间,可以通过调用 System.currentTimeMillis()获得。系统值是1 (0x00000001) 。

public static final int RTC_WAKEUP
//能唤醒系统,用法同RTC类型,系统值为 0 (0x00000000) 。

Public static final int POWER_OFF_WAKEUP
//能唤醒系统,它是一种关机闹铃,就是说设备在关机状态下也可以唤醒系统,所以我们把它称之为关机闹铃。使用方法同RTC类型,系统值为4(0x00000004)。

当你的应用不在运行,而此时你仍然需要你的应用去执行一些操作(比如,短信拦截),只有这种时候才使用AlarmManager, 其他正常情况下的,推荐使用Handler。

AlarmManager 生命周期:

repeating AlarmManager一旦启动就会一直在后台运行(除非执行cancel方法),可以在“应用管理”中看到这个应用状态是正在运行。 “强行停止”可以让Alarmmanager停掉。

尝试了几种任务管理器, 都只能重置计数器(确实释放内存了),但都无法关闭定时器,只有系统自带的“强行停止”奏效。

如果某个AlarmManager已经启动, 程序又再次去启动它,只要PendingIntent是一样,那么之前那个AlarmManager会被release掉。

如何使用AlarmManager?

使用AlarmManager共有三种方式, 都是通过PendingIntent。

getActivity(Context, int, Intent, int)

getBroadcast(Context, int, Intent, int)

getService(Context, int, Intent, int)

==========================代码示例===========================================

package com.aibb.alarmmanagertest;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;

public class AlarmManagerService extends Service{

private class AlarmOnBroadcastReciver extends BroadcastReceiver{

@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(“com.aibb.alarmamanger.start”)){
logTimeMsg(“alarm manager end:”);
}
}

}

@Override
public void onCreate() {
super.onCreate();
//一定要注册reveicer
AlarmOnBroadcastReciver reve = new AlarmOnBroadcastReciver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(“com.aibb.alarmamanger.start”);
this.registerReceiver(reve, intentFilter);

startAlarmManager();
}

@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}

private void startAlarmManager() {
Intent intent =new Intent(“com.aibb.alarmamanger.start”);
PendingIntent sender=PendingIntent
.getBroadcast(this, 0, intent, 0);
//开始时间
long firstime=SystemClock.elapsedRealtime();//System.currentTimeMillis();//SystemClock.elapsedRealtime();
long currenttime = System.currentTimeMillis();
firstime+=1000;
AlarmManager am=(AlarmManager)getSystemService(ALARM_SERVICE);

//如果是周期性的执行,就需要使用SystemClock.elapsedRealtime() time since boot
/*am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP
, firstime, 5*1000, sender);*/
// am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstime+180*1000, sender);
am.set(AlarmManager.RTC_WAKEUP, currenttime+1800*1000, sender);
logTimeMsg(“alarm manager start:”);
}

private static final String TAG = AlarmManagerActivity.class.getSimpleName();

private Writer logWriter;

private File logFile;

/**
* 记录Timer的执行时间
* @param location
*/
public void logTimeMsg(String prefixStr) {
if (null == logFile) {
logFile = new File(Environment.getExternalStorageDirectory(),”AlarmManagerTime.txt”);
}
if (null == logWriter) {
try {
logWriter = new FileWriter(logFile, true);
} catch (Exception e) {
Log.e(“EnvironmentChangeReceiver”, e.getMessage(), e);
}
}
try {
String jsonString = prefixStr;
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
String time = sdf.format(date);;
logWriter.write(jsonString+”:” +time+ “\r\n”);
logWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}

}

android Timer运行时的问题

这几天有个APP需要在刷了NFC后一段时间内保持一个状态不改变,比如10min内一直保持一个状态,然后再到了10min时做一个判断,更新其最新状态。当时一想到这个场景,立马就想到了使用android中定时器Timer来实现这个功能,起初也没多想,直到测试的时候才发现这个Timer在运行时有很多问题。比如,Timer在手机锁屏的情况下就会停止运行,当开屏的时候才会继续倒计时,还有就是Timer在锁屏的情况下可能会被回收。

在网上查阅帖子后,找到了一篇测试Timer的文章,写的很好。

原文地址是:http://blog.csdn.net/lzqjfly/article/details/8021551

============================================================================

目标: 希望采用Timer来计时,要求在服务中运行,每10分钟记录一次数据。但是采用timer来做了以后,发现统计的次数没有达到预期的目标。甚至没有运行,以下是在测试情况

1.为了能够看到测试效果,将循环时间设置为2秒

本打算用服务做测试,但为了方便就用activity做测试

package?com.test.timertest;
  1. /**
  2. ?*?对计时器的测试
  3. ?*/
  4. import?java.util.Timer;
  5. import?java.util.TimerTask;
  6. import?android.app.Activity;
  7. import?android.os.Bundle;
  8. import?android.os.Handler;
  9. import?android.os.Message;
  10. import?android.widget.TextView;
  11. public?class?TimerActivity?extends?Activity?{
  12. ????private?TextView?txtCount;
  13. ????private?int?count;
  14. ????//处理界面
  15. ????private??Handler?handler?=?new?Handler(){
  16. ????????public?void?handleMessage(android.os.Message?msg)?{
  17. ????????????if(msg.arg1?==?1){
  18. ????????????????txtCount.setText(String.valueOf(count));
  19. ????????????}
  20. ????????};
  21. ????};
  22. ????@Override
  23. ????protected?void?onCreate(Bundle?savedInstanceState)?{
  24. ????????//?TODO?Auto-generated?method?stub
  25. ????????super.onCreate(savedInstanceState);
  26. ????????setContentView(R.layout.activity_main);
  27. ????????this.txtCount?=?(TextView)findViewById(R.id.count);
  28. ????????new?Timer().schedule(countTask,?10,?2000);??//延迟10毫秒,每2秒钟执行一次?????
  29. ????}
  30. ????//任务
  31. ????TimerTask?countTask?=?new?TimerTask()?{
  32. ????????@Override
  33. ????????public?void?run()?{
  34. ????????????//?TODO?Auto-generated?method?stub
  35. ????????????count?++;
  36. ????????????Message?msg?=?new?Message();
  37. ????????????msg.arg1?=?1;
  38. ????????????handler.sendMessage(msg);
  39. ????????}
  40. ????};
  41. }

结果:

1.将手机与电脑连接测试,改程序正常,能够一直运行。并且按下电源键后仍然能够正常运行,统计的次数也正常

2.手机与电脑断开连接后,然后重新运行改程序,该程序能正常运行。然后按下电源键,手机处于待机状态,过一段时间后在看屏幕上的次数,发现次数没有动,不知道为啥???

3.手机与电脑断开连接后,运行程序,然后按home键,在手机没有处于待机的状态下,统计的次数发生变化,能够正常运行。但是如果手机处于待机状态后,程序不在运行。

问题: 手机待机后会让大部分程序不在运行(除电话,短信等)。难道这是系统的保护机制???

2.采用线程的Sleep处理;

  1. package?com.test.timertest;
  2. /**
  3. ?*?对计时器的测试
  4. ?*/
  5. import?java.util.Timer;
  6. import?java.util.TimerTask;
  7. import?android.app.Activity;
  8. import?android.os.Bundle;
  9. import?android.os.Handler;
  10. import?android.os.Message;
  11. import?android.widget.TextView;
  12. public?class?TimerActivity?extends?Activity?{
  13. ????private?TextView?txtCount;
  14. ????private?int?count;
  15. ????//处理界面
  16. ????private??Handler?handler?=?new?Handler(){
  17. ????????public?void?handleMessage(android.os.Message?msg)?{
  18. ????????????if(msg.arg1?==?1){
  19. ????????????????txtCount.setText(String.valueOf(count));
  20. ????????????}
  21. ????????};
  22. ????};
  23. ????@Override
  24. ????protected?void?onCreate(Bundle?savedInstanceState)?{
  25. ????????//?TODO?Auto-generated?method?stub
  26. ????????super.onCreate(savedInstanceState);
  27. ????????setContentView(R.layout.activity_main);
  28. ????????this.txtCount?=?(TextView)findViewById(R.id.count);
  29. //??????new?Timer().schedule(countTask,?10,?2000);??//延迟10毫秒,每2秒钟执行一次?????
  30. ????????new?CountThread().start();
  31. ????}
  32. ????class?CountThread?extends?Thread{
  33. ????????@Override
  34. ????????public?void?run()?{
  35. ????????????while(true){
  36. ????????????????count?++;
  37. ????????????????Message?msg?=?new?Message();
  38. ????????????????msg.arg1?=?1;
  39. ????????????????handler.sendMessage(msg);
  40. ????????????????try?{
  41. ????????????????????Thread.sleep(2000);
  42. ????????????????}?catch?(InterruptedException?e)?{
  43. ????????????????????//?TODO?Auto-generated?catch?block
  44. ????????????????????e.printStackTrace();
  45. ????????????????}
  46. ????????????}
  47. ????????}
  48. ????}
  49. //??//任务
  50. //??TimerTask?countTask?=?new?TimerTask()?{
  51. //??????
  52. //??????@Override
  53. //??????public?void?run()?{
  54. //??????????//?TODO?Auto-generated?method?stub
  55. //??????????count?++;
  56. //??????????Message?msg?=?new?Message();
  57. //??????????msg.arg1?=?1;
  58. //??????????handler.sendMessage(msg);
  59. //??????}
  60. //??};
  61. }

采用Sleep的处理结果和上面1中的一样,怀疑是不是activity和thread有区别,于是采用线程来处理,并将结果保存到xml中

服务如下:

  1. package?com.test.timertest;
  2. import?java.util.Timer;
  3. import?java.util.TimerTask;
  4. import?android.app.Service;
  5. import?android.content.Context;
  6. import?android.content.Intent;
  7. import?android.content.SharedPreferences;
  8. import?android.content.SharedPreferences.Editor;
  9. import?android.os.IBinder;
  10. public?class?CountService?extends?Service?{
  11. ????@Override
  12. ????public?IBinder?onBind(Intent?intent)?{
  13. ????????//?TODO?Auto-generated?method?stub
  14. ????????return?null;
  15. ????}
  16. ????@Override
  17. ????public?void?onCreate()?{
  18. ????????//?TODO?Auto-generated?method?stub
  19. ????????super.onCreate();
  20. ????????new?Timer().schedule(countTask,?10,?2000);???//2秒钟
  21. ????}
  22. ????//?任务
  23. ????TimerTask?countTask?=?new?TimerTask()?{
  24. ????????@Override
  25. ????????public?void?run()?{
  26. ????????????saveAppCount();
  27. ????????}
  28. ????};
  29. ????//?保存数据
  30. ????private?void?saveAppCount()?{
  31. ????????int?count?=?getAppCount()?+?1;
  32. ????????SharedPreferences?sf?=?getSharedPreferences(“appcount”,
  33. ????????????????Context.MODE_PRIVATE);
  34. ????????Editor?editor?=?sf.edit();
  35. ????????editor.putInt(“count”,?count);
  36. ????????editor.commit();
  37. ????}
  38. ????//?获取数据
  39. ????public?int?getAppCount()?{
  40. ????????SharedPreferences?spf?=?getSharedPreferences(“appcount”,
  41. ????????????????Context.MODE_PRIVATE);
  42. ????????return?spf.getInt(“count”,?0);
  43. ????}
  44. }

显示数据的activity

  1. package?com.test.timertest;
  2. /**
  3. ?*?对计时器的测试
  4. ?*/
  5. import?android.app.Activity;
  6. import?android.content.Context;
  7. import?android.content.Intent;
  8. import?android.content.SharedPreferences;
  9. import?android.os.Bundle;
  10. import?android.widget.TextView;
  11. public?class?TimerActivity?extends?Activity?{
  12. ????private?TextView?txtCount;
  13. ????@Override
  14. ????protected?void?onCreate(Bundle?savedInstanceState)?{
  15. ????????//?TODO?Auto-generated?method?stub
  16. ????????super.onCreate(savedInstanceState);
  17. ????????setContentView(R.layout.activity_main);
  18. ????????this.txtCount?=?(TextView)findViewById(R.id.count);
  19. ????????SharedPreferences?spf?=?getSharedPreferences(“appcount”,
  20. ????????????????Context.MODE_PRIVATE);
  21. ????????int?count?=??spf.getInt(“count”,?0);
  22. ????????txtCount.setText(String.valueOf(count));
  23. ????????Intent?intent?=?new?Intent(this,CountService.class);
  24. ????????startService(intent);
  25. ????}
  26. }

测试结果::

1.手机和电脑连接,手机处于调试模式,不管是按下电源键让手机处于待机状态还是按下home键,服务都能够正常的统计数据

2.手机与电脑断开连接,不管是手机自动处于待机状态还是主动按下电源键让手机处于待机状态,服务里面的线程都没有正常的记录数据。 求解 ???

最终结合网上资料采用AlarmManager 控制计时操作,能够保证系统在sleep的时候发出广播,达到统计的目的

  1. package?com.test.timertest;
  2. /**
  3. ?*?对计时器的测试
  4. ?*/
  5. import?java.util.Timer;
  6. import?android.app.Activity;
  7. import?android.app.AlarmManager;
  8. import?android.app.PendingIntent;
  9. import?android.content.Context;
  10. import?android.content.Intent;
  11. import?android.content.SharedPreferences;
  12. import?android.os.Bundle;
  13. import?android.os.SystemClock;
  14. import?android.widget.TextView;
  15. public?class?TimerActivity?extends?Activity?{
  16. ????private?TextView?txtCount;
  17. ????public?final?String?ACTION?=?“com.test.timertest.alarmreciver”;
  18. ????@Override
  19. ????protected?void?onCreate(Bundle?savedInstanceState)?{
  20. ????????//?TODO?Auto-generated?method?stub
  21. ????????super.onCreate(savedInstanceState);
  22. ????????setContentView(R.layout.activity_main);
  23. ????????this.txtCount?=?(TextView)findViewById(R.id.count);
  24. ????????SharedPreferences?spf?=?getSharedPreferences(“appcount”,
  25. ????????????????Context.MODE_PRIVATE);
  26. ????????int?count?=??spf.getInt(“count”,?0);
  27. ????????txtCount.setText(String.valueOf(count));
  28. //??????Intent?intent?=?new?Intent(this,CountService.class);
  29. //??????startService(intent);
  30. ????????//闹钟全局变量
  31. ????????AlarmManager?am?=?(AlarmManager)getSystemService(ALARM_SERVICE);
  32. ????????Intent?intent?=?new?Intent(ACTION);
  33. ????????PendingIntent?sender?=?PendingIntent.getBroadcast(this,?0,?intent,?0);
  34. ????????long?firsttime?=?SystemClock.elapsedRealtime();
  35. ????????firsttime??+=?2*1000;
  36. ????????am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,firsttime,?2*1000,sender);??//AlarmManager.ELAPSED_REALTIME_WAKEUP?这里要用这个类型的tiype才能保证系统在sleep的时候也能发广播,不懂的可以去看文档的介绍
  37. ????}
  38. }

接受广播的类

  1. package?com.test.timertest;
  2. import?android.content.BroadcastReceiver;
  3. import?android.content.Context;
  4. import?android.content.Intent;
  5. import?android.content.SharedPreferences;
  6. import?android.content.SharedPreferences.Editor;
  7. public?class?AlarmReciver?extends?BroadcastReceiver?{
  8. ????@Override
  9. ????public?void?onReceive(Context?context,?Intent?intent)?{
  10. ????????//?TODO?Auto-generated?method?stub
  11. ????????saveAppCount(context);
  12. ????}
  13. ????//?保存数据
  14. ????private?void?saveAppCount(Context?context)?{
  15. ????????int?count?=?getAppCount(context)?+?1;
  16. ????????SharedPreferences?sf?=?context.getSharedPreferences(“appcount”,
  17. ????????????????Context.MODE_PRIVATE);
  18. ????????Editor?editor?=?sf.edit();
  19. ????????editor.putInt(“count”,?count);
  20. ????????editor.commit();
  21. ????}
  22. ????//?获取数据
  23. ????public?int?getAppCount(Context?context)?{
  24. ????????SharedPreferences?spf?=?context.getSharedPreferences(“appcount”,
  25. ????????????????Context.MODE_PRIVATE);
  26. ????????return?spf.getInt(“count”,?0);
  27. ????}
  28. }