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

Android中后台任务的最佳实践

2016-06-21 02:13 483 查看
1.使用后台Service:IntentService

使用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>


2.使用CursorLoader,加载Query

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>


3.处理手机的休眠状态:
当手机处于闲置,然后熄灭屏幕后,系统会基本上关闭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>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: