Best Practices for Background Jobs
本文这些课程将告诉你如何在后台运行任务以提高你的app的性能和最小化你的电池消耗。
Running in a Background Service
如何将任务提交给在后台运行的Service来处理来提供UI性能和app的响应
Loading Data in the Background
如和使用CursorLoader查询数据而不影响UI响应
Managing Device Awake State
如何使用重复alarms和唤醒锁来运行后台任务
Running in a Background Service
除非你专门指定,否则你在你的app里做的几乎所有操作都运行在前台,运行在一个专门的叫做UI线程的特定线程里。这可能引起问题,因为UI线程里耗时操作将中断用户界面的响应。这使得你的用户恼怒,甚至引起系统错误。为了避免这,Android框架提供了几个类帮组你减轻操作负担,这些类能让它们运行在各自的后台线程里。这些类里最有用的是IntentService。
本节描述如何实现IntentService,如何开启执行请求,以及返回执行结果给其他的组件。
学习如何产生IntentService
Sending Work Requests to the Background Service
学习如何向IntentService发送执行请求
学习如何使用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
.
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
Learn how to keep the screen or CPU awake as needed,while minimizing the impact on battery life
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的替代方案:
- 如果你的app正在执行长时间运行的HTTP下载,考虑使用
DownloadManager
- 如果你的app正在从其他的server同步数据,考虑产生一个sync adapter.
- 如果你的app依赖后台services,考虑使用 repeating alarms或者 Google Cloud Messaging在特定的间隔时来触发这些services
为了使用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时就马上释放它。
相关推荐
Chapter 9: Best Practices for Function-Based Views Chapter 10: Best Practices for Class-Based Views Chapter 11: Form Fundamentals Chapter 12: Common Patterns for Forms Chapter 13: Templates: Best ...
NHTSA_Cybersecurity Best Practices for Modern Vehicles
High Performance Spark Best Practices for Scaling and Optimizing Apache Spark 英文epub 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除
High Performance Spark Best Practices for Scaling and Optimizing Apache Spark 英文azw3 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除
Defensive Security Handbook: Best Practices for Securing Infrastructure by Lee Brotherston English | 3 Apr. 2017 | ASIN: B06Y18XC5Y | 268 Pages | AZW3 | 3.88 MB Despite the increase of high-profile ...
AWS Best Practices for DDoS Resiliency,是基于AWS上安全服务shield和WAF ALB和cloudfont已经Route53构建适合不同应用部署架构的防拒绝服务攻击的文档,可以帮助用户设计适合的防护机制。
Best Practices for Upgrades to Oracle Database 11g Release 2 CN
Building Software Teams: Ten Best Practices for Effective Software Development English | 31 Dec. 2016 | ISBN: 149195177X | 136 Pages | AZW3/MOBI/EPUB/PDF (conv) | 6.49 MB Why does poor software ...
High Performance Spark Best Practices for Scaling and Optimizing Apache Spark 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或...
Learning Spark Streaming Best Practices for Scaling and Optimizing Apache Spark(Early Release) 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权...
"High Performance Spark Best Practices for Scaling and Optimizing Apache Spark" 这一主题深入探讨了如何最大化利用Spark的性能,以及如何进行有效扩展和优化。以下是一些关键的知识点: 1. **资源管理与调度**...
High Performance Spark: Best Practices for Scaling and Optimizing Apache Spark by Holden Karau English | 25 May 2017 | ASIN: B0725YT69J | 358 Pages | AZW3 | 3.09 MB Apache Spark is amazing when ...
《微服务最佳实践 for Java》是由IBM公司推出的英文版指南,深入探讨了在Java环境中实现微服务架构的各种最佳策略和技巧。微服务架构是一种将单一应用程序拆分为一组小型、独立的服务的方法,每个服务都能在其自己的...
1. SAP Best Practices for SAP S/4HANA - This package provides preconfigured processes and content specifically designed for the SAP S/4HANA platform. It helps customers implement the latest business ...
This document is intended to help those with a basic knowledge of machine learning get the benefit of best practices in machine learning from around Google. It presents a style for machine learning,...
现代车辆的网络安全已经成为一个备受关注的问题,因为随着车辆智能化和互联化程度的不断提升,其面临的网络攻击风险也随之增加。车辆制造商和售后市场设备制造商都需要采取一系列网络安全最佳实践,以确保现代车辆的...
Best Practices for Performance Sending Operations to Multiple Threads
VMware vSphere是VMware公司推出的一款服务器虚拟化平台,它可以实现数据中心的自动化、资源优化和业务连续性。iSCSI(Internet Small Computer System Interface)是一种使用TCP/IP网络存储的协议,允许通过IP网络...
Best Practices for Programming Eclipse and OSGi.pdf Best Practices for Programming Eclipse and OSGi.pdf