`

Best Practices for Background Jobs

 
阅读更多

Best Practices for Background Jobs

       本文这些课程将告诉你如何在后台运行任务以提高你的app的性能和最小化你的电池消耗。

 

      

Running in a Background Service

 如何将任务提交给在后台运行的Service来处理来提供UI性能和app的响应

  1. Creating a Background Service
  2. Sending Work Requests to the Background Service
  3. Reporting Work Status

 

Loading Data in the Background

 如和使用CursorLoader查询数据而不影响UI响应   

  1. Running a Query with a CursorLoader
  2. Handling the Results

  

Managing Device Awake State

 如何使用重复alarms和唤醒锁来运行后台任务

  1. Keeping the Device Awake
  2. Scheduling Repeating Alarms

 

      Running in a Background Service

        除非你专门指定,否则你在你的app里做的几乎所有操作都运行在前台,运行在一个专门的叫做UI线程的特定线程里。这可能引起问题,因为UI线程里耗时操作将中断用户界面的响应。这使得你的用户恼怒,甚至引起系统错误。为了避免这,Android框架提供了几个类帮组你减轻操作负担,这些类能让它们运行在各自的后台线程里。这些类里最有用的是IntentService

        本节描述如何实现IntentService,如何开启执行请求,以及返回执行结果给其他的组件。

       

        Creating a Background Service

        学习如何产生IntentService

       Sending Work Requests to the Background Service

        学习如何向IntentService发送执行请求

        Reporting Work Status

       学习如何使用Intent和LocalBroadcastManager来通信 来自于activity的IntentService的执行请求的状态

 

     Creating a Background Service

        IntentService类提供了运行单个后台线程操作的简单易懂的结构。这使得IntentService能处理耗时操作而不影响用户界面响应。还有,IntentService几乎不受用户界面生命周期事件的影响,因此,在某些情况下,AsyncTask将关闭和终止运行,而IntentService能继续运行。

        IntentService有如下几个限制:

  •  它不能和用户界面直接的交互。为了将操作结果更新到UI,你必须将结果发送给Activity
  •   执行请求按序运行。如果一个操作正在IntentService运行,这时你发送另一个执行请求,该请求将等待   直到第一个操作完成
  •    运行在IntentService里的操作不能被中断

        然而,大多数的情况下,对简单的后台操作来说,IntentService是一个更好的方式。

 

       本节讲述如何产生你自己的IntentService子类,也告诉你如何产生被要求实现的回调方法onHandleIntent(),最后,讲述如何在manifest文件里定义IntentService。              

      Create an IntentService

        为了产生IntentService组件,定义一个类继承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
        ...
    }
}

       注意,一个普通的Service的其他的回调方法例如onStartCommand()等自动地被IntentService调用,在IntentService里,你应该避免重写这些回调方法。

 

       IntentService也需要在你的app的manifest文件里声明入口。通过<service>元素声明,<service><application>的子元素。

        

    <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/>

         属性android:name指定了IntentService的类名。

 

        注意<service>元素并没有包含一个意图过滤器。向该IntentService发送执行请求的activity使用显示意图。因此,不需要过滤器。这也意味着仅仅在同一个app或者有相同的用户ID的app里的组件能访问该Service。

       现在你已实现了基本的IntentService类,你能通过Intent对象向其发送执行请求。具体发送执行请求的过程下节将描述。

    

 

      Sending Work Requests to the Background Service

       上节课告诉你如何产生IntentService类。这节课告诉你如何通过发送Intent来触发IntentService来运行操作。Intent可能包含发送给IntentServive处理的数据。你也能在Activity或者Fragment里的任何地方发送Intent到IntentService。

 

      Create and Send a Work Request to an IntentService

       为了产生执行请求,并且发送该请求到IntentService,产生一个显示的Intent,添加执行请求数据到该Intent里,通过调用startService()方法来发送该执行请求到IntentService。

       下面的片段表明了这:

       1.为名为RSSPullService的IntentService产生一个新的、显示的意图       

/*
 * 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));

       2.调用startService()     

// Starts the IntentService
getActivity().startService(mServiceIntent);

     注意,你能在Activity或者Fragment的任何地方发送执行请求。例如,如果你首先需要获取用户输入,你能在响应按钮点击或者相似的手势操作的回调处发送该请求。

 

       一旦你调用了startService(),IntentService执行onHandleIntent()方法里定义的工作,然后自我停止。

 

      下步是报告执行结果给先前的Activity或者Fragment。下节告诉你如何使用BroadcastReceiver来完成结果返回。

 

     Reporting Work Status

       本节讲述如何报告在后台servicve里运行的执行请求的状态给发送请求的组件。例如,这允许你返回执行的结果给Activity对象的UI。发送和接收状态的推荐方式是使用 LocalBroadcastManager,该类能保证你自己的app组件能接收到广播Intent对象,而其他app的组件将不会收到该Intent。

      Report Status From an IntentService

       为了发送在IntentService里执行的操作的结果给其他的组件,首先产生一个包含状态数据的Intent。可选地,你能添加action和数据URI到Intent。

       紧接着,通过调用LocalBroadcastManager.sendBroadcast()发送Intent。这将发送该Intent给你的app的任何注册接收该intent的组件。为了得到LocalBroadcastManager的实例,调用getInstance()。

      例如:

       

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);
...
}

         下一步是在发送执行请求的组件里处理接收到的广播Intent对象。        

      Receive Status Broadcasts from an IntentService

        为了接收广播Intent对象,使用BroadcastReceiver的子类。在该子类里,实现BroadcastReceiver. onReceive() 回调方法,LocalBroadcastManager在接收到广播Intent时将回调该方法。   LocalBroadcastManager将接收到的Intent传递给BroadcastReceiver.onReceive()

      例如:

       

// Broadcast receiver for receiving status updates from the IntentService
private class ResponseReceiver 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.
         */
...
    }
}

         一旦你已定义了 BroadcastReceiver,你能定义匹配特定action、categories和数据的过滤器,为了做这,产生一个IntentFilter。下面第一个片段表明了如何定义该过滤器:

        

// Class that displays photos
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");
        ...

        为了注册BroadcastReceiver 和IntentFilter 到系统,得到LocalBroadcastManager的实例,调用他的 registerReceiver()方法。下面的片段显示了如何注册BroadcastReceiver和他的过滤器:

        

        // Instantiates a new DownloadStateReceiver
        DownloadStateReceiver mDownloadStateReceiver =
                new DownloadStateReceiver();
        // Registers the DownloadStateReceiver and its intent filters
        LocalBroadcastManager.getInstance(this).registerReceiver(
                mDownloadStateReceiver,
                mStatusIntentFilter);
        ...

       一个 BroadcastReceiver能处理不止一种类型的广播Intent对象,每一个有它自己的action。这个特性允许你对于每个action运行不同的代码,不需要为每个action单独定义一个BroadcastReceiver。为了对于相同的 BroadcastReceiver定义另一个IntentFilter,重复调用 registerReceiver()方法,例如:

        

        /*
         * Instantiates a new action filter.
         * No data filter is needed.
         */
        statusIntentFilter = new IntentFilter(Constants.ACTION_ZOOM_IMAGE);
        ...
        // Registers the receiver with the new filter
        LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
                mDownloadStateReceiver,
                mIntentFilter);

         发送一个广播Intent并不开启或者resume一个activity。一个Activity的BroadcastReceiver即使在你的app在后台的时候也会接收和处理Intent对象,但是并不会迫使你的app到前台。如果当你的app不可见时你想要通知用户一个发生在后台的事件时,使用Notification。对应于接收一个广播intent将从不start一个Activity。

 

 

 

Loading Data in the Background

    从ContentProvider里查询你想要展示的数据可能比较耗时。如果你直接在Activity里执行查询操作,可能让UI阻塞,而引起系统ANR。即使没有ANR,用户也可能明显的感到UI延迟。为了避免这些问题,你应该在单独的线程里初始化query,等待查询操作完成后在展示查询结果。

    为了实现这样的需求,你可以使用CursorLoader类。该类能在后台异步的执行查询操作,然后当查询完成后reconnects to 你的Activity。除了能执行后台查询操作外,CusorLoader能在该查询的数据发生改变时,能自动的再次运行查询操作。

    本课告诉你如何使用CursorLoader执行后台查询,本文的例子需要使用V4 Support Library,该包能向下兼容到Android 1.6。    

    Lessons

      Running a Query with a CursorLoader

          Learn how to run a query in the background, using a CursorLoader.

      Handling the Results

           Learn how to handle the Cursor returned from the query, and how to remove references to the currentCursor when the loader framework re-sets the CursorLoader.

   

    Running a Query with a CursorLoader

     CursorLoader能针对ContentProvider在后台执行异步查询,然后返回结果给调用它的Activity或者FragmentActivity。这允许Activity或者FragmentActivity继续和用户交互而查询仍在继续。    

    Define an Activity That Uses CursorLoader

     为了在Activity或者FragmentActivity里使用CursorLoader,使用LoaderCallbacks<Cursor>接口。CursorLoader回调该接口里定义的方法和Activity和FragmentActivity交互。本节和下章节将详细讲叙每个回调方法。

    例如,下面告诉你如何使用支持库的CursorLoader类定义一个FragmentActivity。通过继承FragmentActivity,你能像获取Fragment一样,来获取对CursorLoader的支持:

 

public class PhotoThumbnailFragment extends FragmentActivity implements
        LoaderManager.LoaderCallbacks<Cursor> {
...
}    

     Initialize the Query

      为了初始化查询任务,调用LoaderManager.initLoader().这初始化了后台框架。你能在用户键入了查询数据后做这,或者,在没有任何用户数据的情况下,你能在onCreate() 或者onCreateView()做初始化操作。例如:

     

    // Identifies a particular Loader being used in this component
    private static final int URL_LOADER = 0;
    ...
    /* When the system is ready for the Fragment to appear, this displays
     * the Fragment's View
     */
    public View onCreateView(
            LayoutInflater inflater,
            ViewGroup viewGroup,
            Bundle bundle) {
        ...
        /*
         * Initializes the CursorLoader. The URL_LOADER value is eventually passed
         * to onCreateLoader().
         */
        getLoaderManager().initLoader(URL_LOADER, null, this);
        ...
    }
      注:方法getLoaderManager()仅仅在Fragment类里有效,为了在FragmentActivity里获取到LoaderManager,调用getSupportLoaderManager()

 

     Start the Query

       只要后台框架已初始化,系统会调用你的onCreateLoader()实现。为了开始查询,从该方法返回一个CursorLoader对象。你能实例一个空的CursorLoader,然后使用它的方法定义你的查询,或者你能实例化该对象的同时定义查询任务。

      

/*
* 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;
    }
}
     

 

    Handling the Results

     一旦查询的后台框架有CursorLoader实例对象,它开始在后台查询。当查询完成,后台框架调用onLoadFinished(),这将在下节描述。

      上一章节已介绍了,你应该在CursorLoader类的onCreateLoader()实现方法里开始加载你的数据。然后CursorLoader在LoaderCallbacks.onLoadFinished()方法里提供查询结果给Activity或者FragmentActivity。  LoaderCallbacks.onLoadFinished()方法传入的参数之一是包含查询结果的Cursor对象。你能使用该对象更新你的数据展示或者进行进一步的操作。

     除了onCreateLoader()onLoadFinished()回调方法你需要实现外,你也必须实现onLoaderReset()方法。当CursorLoader检测到与该Cursor相关的数据已发生改变时该方法会被调用。当数据发生改变时,也将重新运行当前的查询。

    Handle Query Results

     为了展示CursorLoader返回的游标数据,使用一个实现了AdapterView的View类,给该类提供一个实现了CursorAdapter类的Adapter。然后系统自动的从Cursor获取数据给View。

    你能在没有任何数据时,就在View和Adapter之间设置联系。然后在onLoadFinished()方法里,你move一个Cursor进入到你的adapter。一旦你move Curosr到adapter,系统会自动的更新View。当你改变游标的内容时,也会自动更新View。

    例如:

    

public String[] mFromColumns = {
    DataProviderContract.IMAGE_PICTURENAME_COLUMN
};
public int[] mToFields = {
    R.id.PictureName
};
// Gets a handle to a List View
ListView mListView = (ListView) findViewById(R.id.dataList);
/*
 * Defines a SimpleCursorAdapter for the ListView
 *
 */
SimpleCursorAdapter mAdapter =
    new SimpleCursorAdapter(
            this,                // Current context
            R.layout.list_item,  // Layout for a single row
            null,                // No Cursor yet
            mFromColumns,        // Cursor columns to use
            mToFields,           // Layout fields to use
            0                    // No flags
    );
// Sets the adapter for the view
mListView.setAdapter(mAdapter);
...
/*
 * 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);
}
     无论在什么时候CursorLoader的cursor变得无效时,CursorLoader将被reset。这通常在与Cursor相关的data改变时发生。在再次执行查询之前,框架调用你实现的onLoaderReset()方法。为了避免内存泄漏,在该回调方法里,你应该删除所有的到当前Cursor的引用。一旦 onLoaderReset() 完成,CursorLoader会再次执行查询操作。例如:

 

    

/*
 * Invoked when the CursorLoader is being reset. For example, this is
 * called if the data in the provider changes and the Cursor becomes stale.
 */
@Override
public void onLoaderReset(Loader<Cursor> loader) {
    
    /*
     * Clears out the adapter's reference to the Cursor.
     * This prevents memory leaks.
     */
    mAdapter.changeCursor(null);
}
 

 

 

 

 

    Managing Device Awake State

      当一部Android设置被闲置时(is left idle),它首先将变暗,然后黑屏,最终关掉(turn of )CPU。这是为了保证设备的电量不被很快的耗尽。然而,有时,你的app可能需要不一样的行为:

  • Apps可能需要保持屏幕亮着,例如游戏和电影app。
  • 其他的一些可能不需要屏幕一直亮着,但需要CPU保持运行知道关键的操作执行完。

    本课告诉你如何在必须保持设备awake的情况下,同时又不耗电。   

    Lessons

     Keeping the Device Awake

           Learn how to keep the screen or CPU awake as needed,while minimizing the impact on battery life

    Scheduling Repeating Alarms

           Learn how to use repeating alarms to schedule operations that take place outside of the lifetime of the application,even if the application is not running and/or the device is asleep

   

     Keeping the Device Awake

       为了省点,空闲的Android设备很快的变得休眠。然而,有时app需要唤醒屏幕或者CPU来完成一些工作。

       你采取的实现方式依赖你的app的业务需要。然而,一般的规则是你应该尽量使用轻量级的实现方式,最小化你的app对系统资源的影响。下面的章节描述了如何处理这种情况。

     Keep the Screen On

       某些app需要保持屏幕亮着,例如游戏和视频app。实现该功能的最好的方式是在你的activity里(仅仅只在activity,绝不要在Servie或者其他的组件里)使用FLAG_KEEP_SCREEN_ON 。例如:

       

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 locks(将被讨论在Keep the CPU On),它不需要特定的权限,平台能正确地管理用户在application之前的切换,同时你的app不需要担心未使用的资源不被释放。

 

        该方式的另一种实现是在你的application的xml布局文件里,使用 android:keepScreenOn定义:

       

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:keepScreenOn="true">
    ...
</RelativeLayout>
       使用android:keepScreenOn="true"等同于使用FLAG_KEEP_SCREEN_ON。你不管使用那种方式都行。在你的activity里编程地设置flag的优点是让你能有机会动态的clear该标志,因此当你想Screen关闭的时候能关闭掉。

 

       注:你不需要自己clear掉 FLAG_KEEP_SCREEN_ON 标志因为Window Manager会保证当app进入到后台或者切换前台时,系统和你的app不出错。除非在你的application正在运行时(例如,你想要屏幕在一定不活动的时间后黑屏)你不再想要屏幕亮着,类似这样特定的需求下,你才需要自己clear掉 FLAG_KEEP_SCREEN_ON 。如果你想要明确地clear该标志,然后允许屏幕关掉,使用clearFlags()

      getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON).

      Keep the CPU On

        如果你需要在设备进入休眠状态之前保持CPU一直处于运行状态而完成一些工作,你能使用 PowerManager系统服务调用wake locks。Wake locks允许你的app控制设备的power状态。

        产生和持有wake locks 对设备电量能有显著的影响。那么你应该在不得不需要时才使用wake locks,尽可能短时间的持有,尽可能早的释放。例如,你应该从不需要在一个activity里使用wake lock。如果你想要在activity里保持屏幕亮着,使用FLAG_KEEP_SCREEN_ON。

        一个使用wake lock的合理的情况可能是后台service,它需要持有wake lock保持CPU一直运行来做一些工作然而屏幕不关掉。还有,由于其对电量的影响,这种做法应尽量避免。

 

        一些使用wake locks的替代方案:

        为了使用wake lock,第一步是在你的manifest文件里添加WAKE_LOCK 权限:

   

 

<uses-permission android:name="android.permission.WAKE_LOCK" />
         如果你的app包含一个广播接收者,该广播接收者使用service做一些工作,你能通过WakefulBroadcastReceiver管理你的wake lock,像在Using a WakefulBroadcastReceiver里被描述的。这是一个更好的方式。如果你不使用该方式,下面显示了如何直接地设置一个wake lock:

 

     

PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
Wakelock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
        "MyWakelockTag");
wakeLock.acquire();
         为了释放wake lock,调用wakelock.release().这释放了你对CPU的占有。为了避免耗电,你应该只要你的app已使用完了wake lock时就马上释放它。    

 

       Using WakefulBroadcastReceiver

        使用一个广播接收者,配合着servcie一起使用,能让你管理后台任务的生命周期。

        WakefulBroadcastReceiver是一个特定类型的广播接收者,它用来产生和管理你的app的PARTIAL_WAKE_LOCK 。一个WakefulBroadcastReceiver 传递work给一个Service(典型地,如一个IntentService),同时确保设备在过渡阶段里不变成休眠状态。当你传递一个work给Service执行时,如果你持有一个wake lock,这样设备可能在work完成前就变成休眠状态了。最终结果是在未来不确定的某一点app可能没有完成work而结束了,这当然不是你想要的。

       使用WakefulBroadcastReceiver的第一步是添加它到你的manifest文件里,如同其他的BroadcastReceiver声明的一样:

       

<receiver android:name=".MyWakefulReceiver"></receiver>
         下面的的代码使用startWakefulService()方法启动MyIntentService,相比于startService(),该方法的不同之处是当service开始时,WakefulBroadcastReceiver持有一个wake锁。使用方法传递给service的intent里含有一个标识该wake锁的extra数据:

 

        

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被finished时,它调用MyWakefulReceiver.completeWakefulIntent() 释放wake lock。completeWakefulIntent()方法有一个intent参数对象,该参数就是刚才从WakefulBroadcastReceiver里开启service传入的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);
    }
}
 

 

     

 

     Scheduling Repeating Alarms   

       

        Alarms(基于AlarmManager类)提供了一种在你的application生命周期之外执行定时操作的方式。例如,你能使用alarm初始化一个耗时操作,例如一天一次开启一个service去下载天气预报信息。

        Alarms有如下这些特点:

  • 能让你发起意图在设定的时间和(或)时间间隔
  • 你能和BroadcastReceiver结合使用,来开启某个service或者执行特定操作。   
  • 这些操作能在你的app之外执行。因此你能使用它们触发events或者action,即使当你的app不是正在运行,或者甚至你的设备是休眠状态。
  • 它们能帮你最小化你的app的资源要求。你不需要依靠定时器或者常驻后台运行的service来调度任务操作。

     注意:对于确保在application的生命周期内的定时操作,推荐考虑使用Handler,配合Timer和Thread的使用来实现。这种方式使得Android更好的控制系统资源。

 

     像上面描述的,对于调度规则的event或者数据查询,重复的alarms是一个好的选择。一个重复的alarm有如下几个方面和特点:

  • alarm类型。更对的讨论,请参见Choose an alarm type
  • 触发时间。如果你指定的触发时间是已经过去,alarm将立即触发。
  • alarm的间隔。例如,一天一次、每个小时、每5秒等等。
  • 当alarm触发时一个pendIntent被绑定和触发。当你使用一个second alarm时,使用相同的pendIntent,它取代之前的alarm。

      如何设计你的repeating alarm对你的app如何使用系统资源有影响,甚至可能导致你的app滥用系统资源。例如,甚至对alarm特别仔细的管理都可能对电量有一个主要的影响。当你设计你的app时,遵循如下的向导:

  • 使得你的alarm频率最小化
  • 不是不得已时不要wake up 设备(该行为取决于alarm类型,像Choose an alarm type里被描述的)。
  • 你的alarm的触发时间不要比下面描述的还要精确:

        如果可能,使用setInexactRepeating() 方法而不是setRepeating()。当你使用setInexactRepeating()时,Android同步多个不inexact重复的alarm,然后同时fire这些alarm。这减少了电量消耗。

       如果你的alarm是基于一个间隔(例如,你的alarm一小时一次)而不是一个确切的触发时间(例如,alarm在7 a.m,然后每20分钟一次),使用ELAPSED_REALTIME类型的alarm。

 

        使用repeating alarm首先考虑的是使用什么类型的alarm。

         对应alarm,有两种时钟类型:"elapsed real time" and "real time clock" (RTC)。Elapsed real time使用“系统启动时间”作为参考,real time clock使用UTC(wall time)时间。这意味Elapsed real time适合设置基于时间流逝(例如,每30一次的alarm)因为它不受本地时间和时区的影响。real time clock可能更适合依赖于当前时区(locale)的alarm。

       这两个类型都有一个“wakeup”版本,那意思是说如果screen off时wake up设备的CPU。这确保了alarm在设定的时间被fire。如果你的app有时间依赖性这是有用的——例如,如果你有一个有限的Window来执行特别的操作。如果你不使用wakeup版本类型的alarm,那么所有repeating alarms将在你的设备再次awake时才会fire。

        如果你只是简单的需要一个特定间隔的alarm(例如,每半小时),使用真实的时间流逝类型(Elapsed real time type)。一般地,这是一个更好的选择。

        如果你需要一个每天特定时间的alarm,那么选择基于时钟的real time clock类型。注意,这种类型方式可能有一些缺点和陷阱(drawbacks)——app可能不能很好的转换到其他时区,如果用户改变了设备的时间设置,那将可能引起你的app不期望行为。

       如下列出了所以类型:

  • ELAPSED_REALTIME ——基于设备启动时间以来的时间量来fire pendIntent,但是不wake up设备。elapsed time包含了任何时间,即使设备是休眠状态的时间。
  • ELAPSED_REALTIME_WAKEUP ——wake up设备,从设备启动时间开始之后特定时间长度的时间后fire该pendIntent而触发响应的事件。
  • RTC ——在特定的时间fire PendIntent,而不wake up设备。
  • RTC_WAKEUP——在特定的时间Wake up设备来发起PendIntent。    

       ELAPSED_REALTIME_WAKEUP examples

        这儿是一些使用ELAPSED_REALTIME_WAKEUP的例子:

         30分钟后Wake up设备去发起alarm,然后每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);
     

 

      Wake up the device to fire a one-time (non-repeating) alarm in one minute:

      

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 examples

      如下是一些使用RTC_WAKEUP的例子。

      在大约2:00p.唤醒设备来发起alarm,然后在每天相同的时间点一天一次:

      

// 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);
  

 

     Decide how precise your alarm needs to be(决定你的alarm需要的精度)

      如上所述,选择alarm的类型常常是产生一个alarm的第一步。更进一步的区别是你需要你的alarm的精度。对于大多数的app, setInexactRepeating() 是正确的选择。当你使用该方法时,Android同步多个不精确的repeating alarm,然后同时fire这些alarm。这减少电量消耗。

       对于很少一部分app,可能对时间的要求比较严格——例如,alarm需要精准的在8:30发起,然后每隔一小时一次——使用setRepeating()。但是如果可能,你还是尽量不要使用exact alarm。

       使用setInexactRepeating(),你不能指定触发的特定的时间间隔。如果需要特定的时间间隔,你能使用setRepeating()。你必须使用时间间隔常量,例如INTERVAL_FIFTEEN_MINUTESINTERVAL_DAY等。查阅AlarmManager查看全部的常量列表。

       

    Cancel an Alarm

       取决于你的app,你可能需要cannel alarm。为了cannel一个alarm,调用Alarm Manager的cannel()方法,传递一个你不再想要fire的 PendingIntent。例如:

        

// If the alarm has been set, cancel it.
if (alarmMgr!= null) {
    alarmMgr.cancel(alarmIntent);
} 

 

    Start an Alarm When the Device Boots

      默认地,当设备关机时所有的alarm被取消。为了避免这发生,你能设计当设备重启时你的app自动地重启repeating alarm。这确保AlarmMananger能继续未完成的alarm任务而不需要用户手动重启alarm。

      步骤如下:

     1.在你的app的manifest文件里声明RECEIVE_BOOT_COMPLETED权限。这将能使你的app收到ACTION_BOOT_COMPLETED 广播 ,该广播将在系统完成booting后发送(这仅仅在app已启动一次时起作用):

       

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
 

 

       2.实现一个BroadcastReceiver 接收广播:

     

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.
        }
    }
}
      3.Add the receiver to your app's manifest file with an intent filter that filters on the  ACTION_BOOT_COMPLETED action:

 

         

<receiver android:name=".SampleBootReceiver"
        android:enabled="false">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"></action>
    </intent-filter>
</receiver>
       注意:在manifest文件里,boot receiver被设置为android:enabled="false"。这意味着只有app明晰地enable该receiver时,该receiver才会被调用。这阻止了boot receiver在不必要时被调起。你能如下enable一个receiver(例如,如果用户设置一个alarm):
      
ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        PackageManager.DONT_KILL_APP);
        一旦你如此enable一个receiver,它将一直是enable状态,即使用户重启设备。换句话说,程序里编程方式enable了该receiver意味着重写了manifest里的设置,即使设备重启。该receiver将保存enable直到app disable它。如下所示,你能disable一个receiver(例如,如果用户cannel一个alarm):
     
ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
        PackageManager.DONT_KILL_APP);
 
 
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics