使用IntentService是一个直接了当的方式来处理后台任务。
但是它有一些局限性,例如无法直接与用户交互;任务是同步进行的,下个任务的执行必须等到上一个任务的完成;它无法被中断。尽管如此但是在大多数情况下,使用它还是一个较好的选择。下面我们来用用它:
使用步骤:
a.创建IntentService,我们创建它的子类:
<span style="font-size:14px;">public class RSSPullService extends IntentService {
@Override
protected void onHandleIntent(Intent workIntent) {
// Gets data from the incoming Intent
String dataString = workIntent.getDataString();
...
// Do work here, based on the contents of dataString
...
}
}</span>
还有其他IntentService中的回调方法,如onStartCommand()等是被系统自动回调的,我们不要再重写这些方法。
b.在Manifest中注册:
<span style="font-size:14px;"><application
android:icon="@drawable/icon"
android:label="@string/app_name">
...
<!--
Because android:exported is set to "false",
the service is only available to this app.
-->
<service
android:name=".RSSPullService"
android:exported="false"/>
...
<application/></span>
注意:这里的service的启动是通过显示intent启动的,所以不要添加 intent filter
c.启动IntentService:可以在activity或者fragment中来启动
<span style="font-size:14px;">/*
* Creates a new Intent to start the RSSPullService
* IntentService. Passes a URI in the
* Intent's "data" field.
*/
mServiceIntent = new Intent(getActivity(), RSSPullService.class);
mServiceIntent.setData(Uri.parse(dataUrl));
// Starts the IntentService
getActivity().startService(mServiceIntent); </span>
d.回调结果给启动该服务的activity或者fragment:推荐的方式是在IntentService中使用LocalBroadcastManager来将数据返回,就是发广播了。
<span style="font-size:14px;">public final class Constants {
...
// Defines a custom Intent action
public static final String BROADCAST_ACTION =
"com.example.android.threadsample.BROADCAST";
...
// Defines the key for the status "extra" in an Intent
public static final String EXTENDED_DATA_STATUS =
"com.example.android.threadsample.STATUS";
...
}
public class RSSPullService extends IntentService {
...
/*
* Creates a new Intent containing a Uri object
* BROADCAST_ACTION is a custom Intent action
*/
Intent localIntent =
new Intent(Constants.BROADCAST_ACTION)
// Puts the status into the Intent
.putExtra(Constants.EXTENDED_DATA_STATUS, status);
// Broadcasts the Intent to receivers in this app.
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
...
}</span>
定义一个广播接受者:
<span style="font-size:14px;">// Broadcast receiver for receiving status updates from the IntentService
private class DownloadStateReceiver extends BroadcastReceiver
{
// Prevents instantiation
private DownloadStateReceiver() {
}
// Called when the BroadcastReceiver gets an Intent it's registered to receive
@
public void onReceive(Context context, Intent intent) {
...
/*
* Handle Intents here.
*/
...
}
}</span>
启动广播接收器:
<span style="font-size:14px;"> // The filter's action is BROADCAST_ACTION
IntentFilter mStatusIntentFilter = new IntentFilter(
Constants.BROADCAST_ACTION);
// Adds a data filter for the HTTP scheme
mStatusIntentFilter.addDataScheme("http");
// Instantiates a new DownloadStateReceiver
DownloadStateReceiver mDownloadStateReceiver =
new DownloadStateReceiver();
// Registers the DownloadStateReceiver and its intent filters
LocalBroadcastManager.getInstance(this).registerReceiver(
mDownloadStateReceiver,
mStatusIntentFilter);</span>
a.初始化loader
<span style="font-size:14px;">/*
* Initializes the CursorLoader. The URL_LOADER value is eventually passed
* to onCreateLoader().
*/
getLoaderManager().initLoader(URL_LOADER, null, this);</span>
b.重写一些回调方法 <span style="font-size:14px;"> /*
* Callback that's invoked when the system has initialized the Loader and
* is ready to start the query. This usually happens when initLoader() is
* called. The loaderID argument contains the ID value passed to the
* initLoader() call.
*/
@Override
public Loader<Cursor> onCreateLoader(int loaderID, Bundle bundle)
{
/*
* Takes action based on the ID of the Loader that's being created
*/
switch (loaderID) {
case URL_LOADER:
// Returns a new CursorLoader
return new CursorLoader(
getActivity(), // Parent activity context
mDataUrl, // Table to query
mProjection, // Projection to return
null, // No selection clause
null, // No selection arguments
null // Default sort order
);
default:
// An invalid id was passed in
return null;
}
}
/*
* Defines the callback that CursorLoader calls
* when it's finished its query
*/
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
...
/*
* Moves the query results into the adapter, causing the
* ListView fronting this adapter to re-display
*/
mAdapter.changeCursor(cursor);
}</span>
当手机处于闲置,然后熄灭屏幕后,系统会基本上关闭CPU,来防止电池的电量被快速耗尽。但是如果我们在这种情况下需要依然进行一些操作,例如在播放movies时保证屏幕一直处于亮的状态;例如不必要一定屏幕一直亮着,但是要保证cpu一直工作。
a.保证屏幕亮屏:
在activity中调用 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
或者在activity的布局文件的根布局中使用 android:keepScreenOn="true"
一般情况下,你不必程序调用来清除该标记。
如果你有这个需求的话,例如间隔多长时间后清除该标记getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON).
b.保证cpu一直工作:PowerManager
我们只有在极端情况下才这么做,而且保证不要持有它们太长时间,因为这会非常影响电池的使用寿命。
需要权限:<uses-permission android:name="android.permission.WAKE_LOCK" />
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"MyWakelockTag");
wakeLock.acquire();
在你的后台任务执行完后,尽快的wakelock.release().
或者使用WakefulBroadcastReceiver,
使用这个广播启动一个后台服务,在服务结束后 MyWakefulReceiver.completeWakefulIntent(intent);释放CPU
public class MyWakefulReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Start the service, keeping the device awake while the service is
// launching. This is the Intent to deliver to the service.
Intent service = new Intent(context, MyIntentService.class);
startWakefulService(context, service);
}
}
public class MyIntentService extends IntentService {
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
NotificationCompat.Builder builder;
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
// Do the work that requires your app to keep the CPU running.
// ...
// Release the wake lock provided by the WakefulBroadcastReceiver.
MyWakefulReceiver.completeWakefulIntent(intent);
}
}
4.使用AlarmManager:进行后台定时循环操作
可以让你的app独立于application之外。你可以使用它来实现例如每天都要执行一个服务获取天气。
闹钟有如下特点:
可以让你使用广播启动服务或其他操作、
可以让你的操作独立于application,甚至即使手机是休眠的。
可以让你不必运行后台service,减少app的资源消耗的同时安排任务。
我们一般有这个需求,在后台每隔一段时间与服务器同步数据。下面是一些建议
a.随机触发网络请求,不要在某一个具体的时间来请求,减少服务器压力
b.保证alarm的频率中等
c.非必要下不要唤醒设备,选择alarm type
选择闹钟类型:
有2个通常使用的闹钟,elapsed real time基于手机开机的时间
eal time clock uses 基于UTC,实时时间,与时区有关
一般推荐使用elapsed real time
闹钟有这些类型:
ELAPSED_REALTIME,基于时间的逃逸数量,以手机启动开始,当手机休眠时也可以,但是不会唤醒手机
ELAPSED_REALTIME_WAKEUP,与上一个类似,但是会唤醒手机
RTC,在指定时刻,不会唤醒手机
RTC_WAKEUP,与上一个类似,会唤醒手机
下面列举少许例子说明:
<span style="font-size:14px;"> ELAPSED_REALTIME_WAKEUP说明
30分钟后执行,之后每隔30分钟执行一次、
// Hopefully your alarm will have a lower frequency than this!
alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
AlarmManager.INTERVAL_HALF_HOUR,
AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);
只执行一次
private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
...
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() +
60 * 1000, alarmIntent);
RTC_WAKEUP. 说明
// Set the alarm to start at approximately 2:00 p.m.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 14);
// With setInexactRepeating(), you have to use one of the AlarmManager interval
// constants--in this case, AlarmManager.INTERVAL_DAY.
alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
AlarmManager.INTERVAL_DAY, alarmIntent);
//Wake up the device to fire the alarm at precisely 8:30 a.m., and every 20 minutes thereafter:
private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
...
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
// Set the alarm to start at 8:30 a.m.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 8);
calendar.set(Calendar.MINUTE, 30);
// setRepeating() lets you specify a precise custom interval--in this case,
// 20 minutes.
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
1000 * 60 * 20, alarmIntent); </span>
<span style="font-size:14px;">取消闹钟:
// If the alarm has been set, cancel it.
if (alarmMgr!= null) {
alarmMgr.cancel(alarmIntent);
}</span>
在手机设备开机的时候启动闹钟:
默认情况下,当手机关机后,再次重启会取消掉所有的闹钟了。在这种情况下,我们可以这样做来保证手机再次开机的时候自动启动闹钟。
<span style="font-size:14px;">使用步骤:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
public class SampleBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
// Set the alarm here.
}
}
}
<receiver android:name=".SampleBootReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>
这个广播有些特别,一旦你启动了它,即使手机重启了,也还会存在的。
我们使能开启:
ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
在需要的时候关闭:
ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);</span>