使用IntentService完成后台任务
使用之前我们需要了解,IntentService允许在一个非UI线程完成一些后台任务,但是也有其限制:
- 它不能直接参与UI交互,需要把结果传递到UI线程,也就是传递给Activity
- 它在完成后台任务时是串行的
- 运行中的任务是不能被中断的
- 创建一个类继承自IntentService,并实现其抽象方法
onHandleIntent()
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
...
}
}
<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/>
发送后台任务请求到IntentService
1.new一个显式的Intent,把后台任务需要的数据设置进去:
mServiceIntent = new Intent(getActivity(), RSSPullService.class);
mServiceIntent.setData(Uri.parse(dataUrl));
2.调用
startService()
getActivity().startService(mServiceIntent);
返回后台任务执行结果
利用LocalBroadcastManager
,反馈执行结果到调用它的Activity,LocalBroadcastManager
,会限制Intent只发送给同一app内的组件。首先new一个包含执行结果,或者过程状态的Intent,其次通过LocalBroadcastManager.sendBroadcast()方法,把intent发送给app内的任意组件。
实例如下:
在IntentService中发送结果:
public final class Constants {
public static final String BROADCAST_ACTION =
"com.example.android.threadsample.BROADCAST";
public static final String EXTENDED_DATA_STATUS =
"com.example.android.threadsample.STATUS";
}
public class RSSPullService extends IntentService {
Intent localIntent =
new Intent(Constants.BROADCAST_ACTION)
.putExtra(Constants.EXTENDED_DATA_STATUS, status);
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
...
}
创建监听结果的广播接收者:
private class ResponseReceiver extends BroadcastReceiver
{
private DownloadStateReceiver() {
}
public void onReceive(Context context, Intent intent) {
/*
* Handle Intents here.
*/
}
}
在Activity中注册这个广播接收者:
public class DisplayActivity extends FragmentActivity {
...
public void onCreate(Bundle stateBundle) {
...
super.onCreate(stateBundle);
...
// 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");
...
- 通过继承
FragmentActivity
,可以允许使用CursorLoader功能。
FragmentActivity
,可以允许使用CursorLoader功能。
public class PhotoThumbnailFragment extends FragmentActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
...
}
- 调用
LoaderManager.initLoader()
初始化查询
LoaderManager.initLoader()
初始化查询可以在onCreate或者onCreateView中初始化
private static final int URL_LOADER_ID = 0;
public View onCreateView(LayoutInflater inflater,ViewGroup viewGroup,Bundle bundle) {
getLoaderManager().initLoader(URL_LOADER_ID, null, this);
...
}
注意:
getLoaderManager()只有在Fragment中可以调用,在FragmentActivity中需要用
getSupportLoaderManager()来获取
LoaderManager
- 开始查询
在此方法中需要返回CursorLoader,可以在返回CursorLoader的同时就把查询的参数设定好,也可以先返回一个不带参数的CursorLoader
@Override
public Loader<Cursor> onCreateLoader(int loaderID, Bundle bundle)
{
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;
}
}
- 处理查询结果
onCreateLoader方法返回非空的CursorLoader后,后台马上开始查询,当查询结束后,系统会回调onLoadFinished()方法,onLoadFinished()中的一个形参Cursor则包含了查询的结果。
除了onCreateLoader()和 onLoadFinished()
之外,还需要实现接口中的另外一个抽象方法onLoaderReset(),当CursorLoader
发现查询结果与上次相比有变化时此方法会被系统回调,同时重新做查询操作,并通过onLoadFinished()返回查询结果。
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mThemeAdapter.swapCursor(data);
}
如果是SimpleCursorAdapter则是:
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
mAdapter.changeCursor(cursor);
}
同时注意,为了防止内存泄漏需要在CursorLoader reset的时候删除旧Cursor的引用:
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mThemeAdapter.swapCursor(null);
}
或者SimpleCursorAdapter:
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.changeCursor(null);
}
管理设备的Awake(清醒)
- 有些电影播放器app,可能需要始终保持屏幕在常亮状态
- 还有一些并不需要屏幕保持常亮,但是在完成一些任务之前CPU不能关闭
保持屏幕常亮
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
这个方法的好处是,不需要特殊的权限,也不需要担心释放资源等问题(相比用wake lock的方式,下面会讨论此方式)
RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
...
</RelativeLayout>
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
.
保持cup不关闭:
WAKE_LOCK
权限:
<uses-permission android:name="android.permission.WAKE_LOCK" />
直接申请wakelock的方式:
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); Wakelock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag"); wakeLock.acquire();一定要记得,尽量早的用这个方法 wakelock.release()释放wake lock,防止消耗系统电量。
使用WakefulBroadcastReceiver
<receiver android:name=".MyWakefulReceiver"></receiver>
下面的代码,用
startWakefulService()方法启动MyIntentService
,这个方法会让WakefulBroadcastReceiver在Service启动时申请
wake lock:
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);
}
}
当Service完成后台工作时,调用MyWakefulReceiver.completeWakefulIntent()来释放wake lock,行参intent和传入的intent是同一个。
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);
}
}
使用Alarm
Alarm机制基于AlarmManager类:
- 可以定时发送intent
- 可以结合广播接收者,来启动service,完成后台任务
- 即使app不在运行,甚至设备处于睡眠状态,任然可以激活后台任务
- 不需要后台service始终运行,也可以规划后台任务的执行
原则:
- 如果周期性的请求网络记得添加一个随机数
- alarm周期不要设置的过短
- 不要毫无根据的唤醒系统(有些alarm类型可以把系统从睡眠状态唤醒)
- alarm的触发时间尽量不要设置太细,例如尽量用
setInexactRepeating()
而不是setRepeating()
.因为使用setInexactRepeating(),系统会尽量把多个时间相近的alarm集中到一起触发系统,这样可以减少唤醒系统的次数,从而延长电池使用时间。 - 尽量用ELAPSED_REALTIME类型alarm,而不要用real time clock,因为前者更加可靠
- 首先选择一个合适的alarm类型
- 设置好触发时间,如果时间是一个过去的时间点,那么alarm会被马上触发
- 设置alarm的间隔周期,例如,一天,一个小时,甚至5秒等
- 设置一个alarm到时后需要发送的pending intent,如果后来的alarm和前面的alarm设置的pending intent一样,那么前面的alarm会被后面的取代
四个版本的alarm列举如下:
ELAPSED_REALTIME
—基于系统boot后的时间轴,不会唤醒系统,包含系统睡眠时间ELAPSED_REALTIME_WAKEUP
—基于系统boot后的时间轴,会唤醒系统,然后发出pending intentRTC
—基于系统实际时间,不会唤醒系统RTC_WAKEUP
—基于系统实际时间,会先唤醒系统然后再发出pending intent
ELAPSED_REALTIME_WAKEUP 例子:
alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
AlarmManager.INTERVAL_HALF_HOUR,
AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);
一分钟之后如果
系统睡眠就先唤醒系统,然后发出intent,不重复
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 实例
// 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);
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);
确定定alarm是否需要精细的时间执行
- 像闹钟这类app需要精细的时间点触发,那就用setRepeating()这个方法
- 尽量使用
setInexactRepeating()
方法,但是这一方法有个弊端,重复时间必须用系统规定的时间,例如INTERVAL_FIFTEEN_MINUTES
,INTERVAL_DAY等等。
如何取消alarm
if (alarmMgr!= null) { alarmMgr.cancel(alarmIntent); }