注意:我们建议使用 WorkManager 作为大多数后台处理用例的推荐解决方案。请参阅 后台处理指南,了解哪种解决方案最适合您。
在本课程的前几节课中,您学习了如何创建同步适配器组件, 封装了数据传输代码,以及如何添加可让您 将同步适配器插入系统中。您现在已经有了安装 中包含一个同步适配器,但是您所看到的代码实际上并未运行同步适配器。
您应该尝试根据计划运行同步适配器,或根据某些尝试的间接结果运行同步适配器 事件。例如,您可能想要定期运行同步适配器 特定时间段或一天中的特定时段您可能还需要运行同步 适配器。您应该避免在 同步适配器是用户操作的直接结果,因为这样做会导致您无法获得完整 受益于同步适配器框架的调度功能。例如,您应该避免 在界面中提供一个刷新按钮
您可以选择以下选项来运行同步适配器:
- 当服务器数据发生变化时
- 响应来自服务器的消息(表明基于服务器)运行同步适配器 数据发生了变化通过此选项,您可以将服务器中的数据刷新到设备 而不会因为轮询服务器而降低性能或浪费电池寿命。
- 在设备数据改变时运行
- 当设备上的数据发生更改时运行同步适配器。通过此选项,您可以 修改了数据从设备传输到服务器,如果您需要确保 确保服务器始终拥有最新的设备数据。这个选项非常简单 实际上是在内容提供程序中存储数据如果您使用桩 content provider,检测数据更改可能会比较困难。
- 定期
- 在您选择的时间间隔到期后运行同步适配器,或在特定时间间隔运行 。
- 点播
- 运行同步适配器以响应用户操作。不过,为了向最优质的用户 则应该主要依靠自动化程度更高的选项之一。使用 自动化选项,则可以节省电池电量和网络资源。
本课的其余部分将更详细地介绍每个选项。
在服务器数据改变时运行同步适配器
如果您的应用从服务器传输数据且服务器数据经常变化,您可以使用
一个同步适配器,用于执行下载以响应数据更改。要运行同步适配器,请执行以下操作:
服务器向应用中的 BroadcastReceiver
发送一条特殊消息。
为响应此消息,请调用 ContentResolver.requestSync()
以指示同步适配器框架运行您的
同步适配器。
Google 云消息传递 (GCM) 同时提供
您需要确保消息传递系统正常运行所需的服务器和设备组件。使用 GCM 触发
比轮询服务器的状态更可靠、更高效。轮询时
需要始终有效的 Service
,GCM 会使用
收到邮件时激活的 BroadcastReceiver
。轮询时
经常会消耗电池电量,即使没有可用的更新,GCM 也只会发送
消息。
注意:如果您使用 GCM 通过向 请注意,安装了您应用的设备会通过 大致相同这种情况可能会导致同步适配器的多个实例运行 造成服务器和网络过载为避免直播出现这类情况 所有设备,则应考虑将同步适配器的启动推迟一段时间 对每台设备而言都是独一无二的
以下代码段展示了如何
requestSync()
回复
收到的 GCM 消息:
Kotlin
... // Constants // Content provider authority const val AUTHORITY = "com.example.android.datasync.provider" // Account type const val ACCOUNT_TYPE = "com.example.android.datasync" // Account const val ACCOUNT = "default_account" // Incoming Intent key for extended data const val KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST" ... class GcmBroadcastReceiver : BroadcastReceiver() { ... override fun onReceive(context: Context, intent: Intent) { // Get a GCM object instance val gcm: GoogleCloudMessaging = GoogleCloudMessaging.getInstance(context) // Get the type of GCM message val messageType: String? = gcm.getMessageType(intent) /* * Test the message type and examine the message contents. * Since GCM is a general-purpose messaging system, you * may receive normal messages that don't require a sync * adapter run. * The following code tests for a a boolean flag indicating * that the message is requesting a transfer from the device. */ if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE == messageType && intent.getBooleanExtra(KEY_SYNC_REQUEST, false)) { /* * Signal the framework to run your sync adapter. Assume that * app initialization has already created the account. */ ContentResolver.requestSync(mAccount, AUTHORITY, null) ... } ... } ... }
Java
public class GcmBroadcastReceiver extends BroadcastReceiver { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Account type public static final String ACCOUNT_TYPE = "com.example.android.datasync"; // Account public static final String ACCOUNT = "default_account"; // Incoming Intent key for extended data public static final String KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST"; ... @Override public void onReceive(Context context, Intent intent) { // Get a GCM object instance GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context); // Get the type of GCM message String messageType = gcm.getMessageType(intent); /* * Test the message type and examine the message contents. * Since GCM is a general-purpose messaging system, you * may receive normal messages that don't require a sync * adapter run. * The following code tests for a a boolean flag indicating * that the message is requesting a transfer from the device. */ if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType) && intent.getBooleanExtra(KEY_SYNC_REQUEST)) { /* * Signal the framework to run your sync adapter. Assume that * app initialization has already created the account. */ ContentResolver.requestSync(mAccount, AUTHORITY, null); ... } ... } ... }
在内容提供器数据改变时运行同步适配器
如果您的应用在内容提供程序中收集数据,并且您希望每当
更新提供程序时,您可以将应用设置为自动运行同步适配器。待办事项
为此,您需要为 content provider 注册一个观察器。如果内容提供方中的数据
更改,content provider 框架会调用观察器。在观察器中,调用
requestSync()
:告知框架运行
同步适配器
注意:如果您使用的是桩内容提供器,则其中没有任何数据
content provider,而 onChange()
是
从未调用过。在这种情况下,您必须提供自己的机制来检测
设备数据。此机制还负责调用
requestSync()
。
要为内容提供程序创建观察器,请扩展类
ContentObserver
,并实现其
onChange()
方法。在
onChange()
,拨打电话
requestSync()
,用于启动同步适配器。
要注册观察器,请在调用
registerContentObserver()
。在
调用时,您还必须传入要监视的数据的内容 URI。内容
提供程序框架将此监视 URI 与作为参数传入的内容 URI 进行比较
ContentResolver
方法,用于修改提供程序,例如
ContentResolver.insert()
。如果有匹配项,您的
ContentObserver.onChange()
的实现
调用该方法。
以下代码段展示了如何定义 ContentObserver
调用 requestSync()
的方法,
更改:
Kotlin
// Constants // Content provider scheme const val SCHEME = "content://" // Content provider authority const val AUTHORITY = "com.example.android.datasync.provider" // Path for the content provider table const val TABLE_PATH = "data_table" ... class MainActivity : FragmentActivity() { ... // A content URI for the content provider's data table private lateinit var uri: Uri // A content resolver for accessing the provider private lateinit var mResolver: ContentResolver ... inner class TableObserver(...) : ContentObserver(...) { /* * Define a method that's called when data in the * observed content provider changes. * This method signature is provided for compatibility with * older platforms. */ override fun onChange(selfChange: Boolean) { /* * Invoke the method signature available as of * Android platform version 4.1, with a null URI. */ onChange(selfChange, null) } /* * Define a method that's called when data in the * observed content provider changes. */ override fun onChange(selfChange: Boolean, changeUri: Uri?) { /* * Ask the framework to run your sync adapter. * To maintain backward compatibility, assume that * changeUri is null. */ ContentResolver.requestSync(account, AUTHORITY, null) } ... } ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... // Get the content resolver object for your app mResolver = contentResolver // Construct a URI that points to the content provider data table uri = Uri.Builder() .scheme(SCHEME) .authority(AUTHORITY) .path(TABLE_PATH) .build() /* * Create a content observer object. * Its code does not mutate the provider, so set * selfChange to "false" */ val observer = TableObserver(false) /* * Register the observer for the data table. The table's path * and any of its subpaths trigger the observer. */ mResolver.registerContentObserver(uri, true, observer) ... } ... }
Java
public class MainActivity extends FragmentActivity { ... // Constants // Content provider scheme public static final String SCHEME = "content://"; // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Path for the content provider table public static final String TABLE_PATH = "data_table"; // Account public static final String ACCOUNT = "default_account"; // Global variables // A content URI for the content provider's data table Uri uri; // A content resolver for accessing the provider ContentResolver mResolver; ... public class TableObserver extends ContentObserver { /* * Define a method that's called when data in the * observed content provider changes. * This method signature is provided for compatibility with * older platforms. */ @Override public void onChange(boolean selfChange) { /* * Invoke the method signature available as of * Android platform version 4.1, with a null URI. */ onChange(selfChange, null); } /* * Define a method that's called when data in the * observed content provider changes. */ @Override public void onChange(boolean selfChange, Uri changeUri) { /* * Ask the framework to run your sync adapter. * To maintain backward compatibility, assume that * changeUri is null. */ ContentResolver.requestSync(mAccount, AUTHORITY, null); } ... } ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Get the content resolver object for your app mResolver = getContentResolver(); // Construct a URI that points to the content provider data table uri = new Uri.Builder() .scheme(SCHEME) .authority(AUTHORITY) .path(TABLE_PATH) .build(); /* * Create a content observer object. * Its code does not mutate the provider, so set * selfChange to "false" */ TableObserver observer = new TableObserver(false); /* * Register the observer for the data table. The table's path * and any of its subpaths trigger the observer. */ mResolver.registerContentObserver(uri, true, observer); ... } ... }
定期运行同步适配器
您可以设置运行间隔时长,以定期运行同步适配器。 还是在一天的某些时段运行实验,或者两者兼而有之。运行同步适配器 定期更新可让您大致按照服务器的更新间隔进行更新。
同样,您可以在服务器相对空闲时上传设备中的数据,方法是 将同步适配器安排在夜间运行。大多数用户会保持开机和接通电源 因此这个时间通常可用。此外,设备不会从以下时间开始运行其他任务: 相同。不过,如果您采用这种方法 每个设备触发数据传输的时间略有不同。如果所有设备都运行您的 同步适配器的同时,可能会使服务器和移动网络提供商的数据过载 。
一般来说,如果用户不需要即时更新,而是希望能够 定期更新如果您希望在 更新最新数据,高效运行较小的同步适配器,避免过度使用设备 资源。
要定期运行同步适配器,请调用
addPeriodicSync()
。此设置会安排您的
同步适配器在经过一定时间后运行。由于同步适配器框架
必须考虑其他同步适配器的执行,并尽量提高电池效率,
所用时间可能会有几秒钟差异。此外,如果存在以下情况,框架不会运行您的同步适配器:
网络不可用。
请注意,addPeriodicSync()
不会
在一天中的特定时间运行同步适配器。要以大约
使用重复闹钟作为触发条件。更多重复闹钟
AlarmManager
参考文档的详细信息。如果您使用
setInexactRepeating()
方法要设置
存在一定变化的时段触发器,则仍应随机选择开始时间,
确保在不同设备之间交错运行的同步适配器。
addPeriodicSync()
方法不支持
停用setSyncAutomatically()
,
因此您可能会在相对较短的时间内运行多次同步此外,只有少数
在对
addPeriodicSync()
;你对
的参考文档中
addPeriodicSync()
。
以下代码段展示了如何安排定期运行同步适配器:
Kotlin
// Content provider authority const val AUTHORITY = "com.example.android.datasync.provider" // Account const val ACCOUNT = "default_account" // Sync interval constants const val SECONDS_PER_MINUTE = 60L const val SYNC_INTERVAL_IN_MINUTES = 60L const val SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE ... class MainActivity : FragmentActivity() { ... // A content resolver for accessing the provider private lateinit var mResolver: ContentResolver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... // Get the content resolver for your app mResolver = contentResolver /* * Turn on periodic syncing */ ContentResolver.addPeriodicSync( mAccount, AUTHORITY, Bundle.EMPTY, SYNC_INTERVAL) ... } ... }
Java
public class MainActivity extends FragmentActivity { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Account public static final String ACCOUNT = "default_account"; // Sync interval constants public static final long SECONDS_PER_MINUTE = 60L; public static final long SYNC_INTERVAL_IN_MINUTES = 60L; public static final long SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE; // Global variables // A content resolver for accessing the provider ContentResolver mResolver; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Get the content resolver for your app mResolver = getContentResolver(); /* * Turn on periodic syncing */ ContentResolver.addPeriodicSync( mAccount, AUTHORITY, Bundle.EMPTY, SYNC_INTERVAL); ... } ... }
按需运行同步适配器
根据用户请求运行同步适配器是不可取的策略 用于运行同步适配器该框架专为节省电池电量而设计 它就会根据时间表运行同步适配器根据数据运行同步的选项 (因为电量用于提供新数据),所以,这些更改会有效地利用电池电量。
相比之下,允许用户按需运行同步意味着同步会自行运行, 网络资源和电力资源的利用效率低下此外,提供按需同步会引导用户 即使没有证据表明数据发生了更改,也请求同步,并且运行同步 不刷新数据即是对电池电量的无效使用。一般来说,您的应用应: 使用其他信号触发同步或将同步设定为定期运行,而无需用户输入。
但是,如果您仍希望按需运行同步适配器,请针对
手动同步适配器运行,然后调用
ContentResolver.requestSync()
。
使用以下标志运行按需传输:
-
SYNC_EXTRAS_MANUAL
-
强制手动同步。同步适配器框架会忽略现有设置,
例如
setSyncAutomatically()
设置的标志。 -
SYNC_EXTRAS_EXPEDITED
- 强制立即启动同步。如果您不设置此属性,系统可能会等待好几次 因为它会尝试通过 在短时间内调度多个请求
以下代码段展示了如何调用
requestSync()
响应按钮操作
点击:
Kotlin
// Constants // Content provider authority val AUTHORITY = "com.example.android.datasync.provider" // Account type val ACCOUNT_TYPE = "com.example.android.datasync" // Account val ACCOUNT = "default_account" ... class MainActivity : FragmentActivity() { ... // Instance fields private lateinit var mAccount: Account ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... /* * Create the placeholder account. The code for CreateSyncAccount * is listed in the lesson Creating a Sync Adapter */ mAccount = createSyncAccount() ... } /** * Respond to a button click by calling requestSync(). This is an * asynchronous operation. * * This method is attached to the refresh button in the layout * XML file * * @param v The View associated with the method call, * in this case a Button */ fun onRefreshButtonClick(v: View) { // Pass the settings flags by inserting them in a bundle val settingsBundle = Bundle().apply { putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true) putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true) } /* * Request the sync for the default account, authority, and * manual sync settings */ ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle) }
Java
public class MainActivity extends FragmentActivity { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Account type public static final String ACCOUNT_TYPE = "com.example.android.datasync"; // Account public static final String ACCOUNT = "default_account"; // Instance fields Account mAccount; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... /* * Create the placeholder account. The code for CreateSyncAccount * is listed in the lesson Creating a Sync Adapter */ mAccount = CreateSyncAccount(this); ... } /** * Respond to a button click by calling requestSync(). This is an * asynchronous operation. * * This method is attached to the refresh button in the layout * XML file * * @param v The View associated with the method call, * in this case a Button */ public void onRefreshButtonClick(View v) { // Pass the settings flags by inserting them in a bundle Bundle settingsBundle = new Bundle(); settingsBundle.putBoolean( ContentResolver.SYNC_EXTRAS_MANUAL, true); settingsBundle.putBoolean( ContentResolver.SYNC_EXTRAS_EXPEDITED, true); /* * Request the sync for the default account, authority, and * manual sync settings */ ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle); }