Android中后台任务的最佳实践

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>



你可能感兴趣的:(Android中后台任务的最佳实践)