注意:我们建议使用 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.MESS&&AGE_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_T&&YPE_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); }