1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
|
page.title=Running a Sync Adapter
trainingnavtop=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>This lesson teaches you how to:</h2>
<ol>
<li><a href="#RunByMessage">Run the Sync Adapter When Server Data Changes</a>
<li><a href="#RunDataChange">Run the Sync Adapter When Content Provider Data Changes</a></li>
<li><a href="#RunByNetwork">Run the Sync Adapter After a Network Message</a></li>
<li><a href="#RunPeriodic">Run the Sync Adapter Periodically</a></li>
<li><a href="#RunOnDemand">Run the Sync Adapter On Demand</a></li>
</ol>
<h2>You should also read</h2>
<ul>
<li>
<a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>
</li>
</ul>
<h2>Try it out</h2>
<div class="download-box">
<a href="http://developer.android.com/shareables/training/BasicSyncAdapter.zip" class="button">Download the sample</a>
<p class="filename">BasicSyncAdapter.zip</p>
</div>
</div>
</div>
<p>
In the previous lessons in this class, you learned how to create a sync adapter component that
encapsulates data transfer code, and how to add the additional components that allow you to
plug the sync adapter into the system. You now have everything you need to install an app that
includes a sync adapter, but none of the code you've seen actually runs the sync adapter.
</p>
<p>
You should try to run your sync adapter based on a schedule or as the indirect result of some
event. For example, you may want your sync adapter to run on a regular schedule, either after a
certain period of time or at a particular time of the day. You may also want to run your sync
adapter when there are changes to data stored on the device. You should avoid running your
sync adapter as the direct result of a user action, because by doing this you don't get the full
benefit of the sync adapter framework's scheduling ability. For example, you should avoid
providing a refresh button in your user interface.
</p>
<p>
You have the following options for running your sync adapter:
</p>
<dl>
<dt>
When server data changes
</dt>
<dd>
Run the sync adapter in response to a message from a server, indicating that server-based
data has changed. This option allows you to refresh data from the server to the device
without degrading performance or wasting battery life by polling the server.
</dd>
<dt>When device data changes</dt>
<dd>
Run a sync adapter when data changes on the device. This option allows you to send
modified data from the device to a server, and is especially useful if you need to ensure
that the server always has the latest device data. This option is straightforward to
implement if you actually store data in your content provider. If you're using a stub
content provider, detecting data changes may be more difficult.
</dd>
<dt>
When the system sends out a network message
</dt>
<dd>
Run a sync adapter when the Android system sends out a network message that keeps the
TCP/IP connection open; this message is a basic part of the networking framework. Using
this option is one way to run the sync adapter automatically. Consider using it in
conjunction with interval-based sync adapter runs.
</dd>
<dt>
At regular intervals
</dt>
<dd>
Run a sync adapter after the expiration of an interval you choose, or run it at a certain
time every day.
</dd>
<dt>On demand</dt>
<dd>
Run the sync adapter in response to a user action. However, to provide the best user
experience you should rely primarily on one of the more automated options. By using
automated options, you conserve battery and network resources.
</dd>
</dl>
<p>
The rest of this lesson describes each of the options in more detail.
</p>
<h2 id="RunByMessage">Run the Sync Adapter When Server Data Changes</h2>
<p>
If your app transfers data from a server and the server data changes frequently, you can use
a sync adapter to do downloads in response to data changes. To run the sync adapter, have
the server send a special message to a {@link android.content.BroadcastReceiver} in your app.
In response to this message, call {@link android.content.ContentResolver#requestSync
ContentResolver.requestSync()} to signal the sync adapter framework to run your
sync adapter.
</p>
<p>
<a href="{@docRoot}google/gcm/index.html">Google Cloud Messaging</a> (GCM) provides both the
server and device components you need to make this messaging system work. Using GCM to trigger
transfers is more reliable and more efficient than polling servers for status. While polling
requires a {@link android.app.Service} that is always active, GCM uses a
{@link android.content.BroadcastReceiver} that's activated when a message arrives. While polling
at regular intervals uses battery power even if no updates are available, GCM only sends
messages when needed.
</p>
<p class="note">
<strong>Note:</strong> If you use GCM to trigger your sync adapter via a broadcast to all
devices where your app is installed, remember that they receive your message at
roughly the same time. This situation can cause multiple instance of your sync adapter to run
at the same time, causing server and network overload. To avoid this situation for a broadcast
to all devices, you should consider deferring the start of the sync adapter for a period
that's unique for each device.
<p>
The following code snippet shows you how to run
{@link android.content.ContentResolver#requestSync requestSync()} in response to an
incoming GCM message:
</p>
<pre>
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(ACCOUNT, AUTHORITY, null);
...
}
...
}
...
}
</pre>
<h2 id="RunDataChange">Run the Sync Adapter When Content Provider Data Changes</h2>
<p>
If your app collects data in a content provider, and you want to update the server whenever
you update the provider, you can set up your app to run your sync adapter automatically. To do
this, you register an observer for the content provider. When data in your content provider
changes, the content provider framework calls the observer. In the observer, call
{@link android.content.ContentResolver#requestSync requestSync()} to tell the framework to run
your sync adapter.
</p>
<p class="note">
<strong>Note:</strong> If you're using a stub content provider, you don't have any data in
the content provider and {@link android.database.ContentObserver#onChange onChange()} is
never called. In this case, you have to provide your own mechanism for detecting changes to
device data. This mechanism is also responsible for calling
{@link android.content.ContentResolver#requestSync requestSync()} when the data changes.
</p>
<p>
To create an observer for your content provider, extend the class
{@link android.database.ContentObserver} and implement both forms of its
{@link android.database.ContentObserver#onChange onChange()} method. In
{@link android.database.ContentObserver#onChange onChange()}, call
{@link android.content.ContentResolver#requestSync requestSync()} to start the sync adapter.
</p>
<p>
To register the observer, pass it as an argument in a call to
{@link android.content.ContentResolver#registerContentObserver registerContentObserver()}. In
this call, you also have to pass in a content URI for the data you want to watch. The content
provider framework compares this watch URI to content URIs passed in as arguments to
{@link android.content.ContentResolver} methods that modify your provider, such as
{@link android.content.ContentResolver#insert ContentResolver.insert()}. If there's a match, your
implementation of {@link android.database.ContentObserver#onChange ContentObserver.onChange()}
is called.
</p>
<p>
The following code snippet shows you how to define a {@link android.database.ContentObserver}
that calls {@link android.content.ContentResolver#requestSync requestSync()} when a table
changes:
</p>
<pre>
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 mUri;
// 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(ACCOUNT, 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
mUri = 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(mUri, true, observer);
...
}
...
}
</pre>
<h2 id="RunByNetwork">Run the Sync Adapter After a Network Message</h2>
<p>
When a network connection is available, the Android system sends out a message
every few seconds to keep the device's TCP/IP connection open. This message also goes to
the {@link android.content.ContentResolver} of each app. By calling
{@link android.content.ContentResolver#setSyncAutomatically setSyncAutomatically()},
you can run the sync adapter whenever the {@link android.content.ContentResolver}
receives the message.
</p>
<p>
By scheduling your sync adapter to run when the network message is sent, you ensure that your
sync adapter is always scheduled to run while the network is available. Use this option if you
don't have to force a data transfer in response to data changes, but you do want to ensure
your data is regularly updated. Similarly, you can use this option if you don't want a fixed
schedule for your sync adapter, but you do want it to run frequently.
</p>
<p>
Since the method
{@link android.content.ContentResolver#setSyncAutomatically setSyncAutomatically()}
doesn't disable {@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()}, your
sync adapter may be triggered repeatedly in a short period of time. If you do want to run
your sync adapter periodically on a regular schedule, you should disable
{@link android.content.ContentResolver#setSyncAutomatically setSyncAutomatically()}.
</p>
<p>
The following code snippet shows you how to configure your
{@link android.content.ContentResolver} to run your sync adapter in response to a network
message:
</p>
<pre>
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";
// 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 automatic syncing for the default account and authority
mResolver.setSyncAutomatically(ACCOUNT, AUTHORITY, true);
...
}
...
}
</pre>
<h2 id="RunPeriodic">Run the Sync Adapter Periodically</h2>
<p>
You can run your sync adapter periodically by setting a period of time to wait between runs,
or by running it at certain times of the day, or both. Running your sync adapter
periodically allows you to roughly match the update interval of your server.
</p>
<p>
Similarly, you can upload data from the device when your server is relatively idle, by
scheduling your sync adapter to run at night. Most users leave their powered on and plugged in
at night, so this time is usually available. Moreover, the device is not running other tasks at
the same time as your sync adapter. If you take this approach, however, you need to ensure that
each device triggers a data transfer at a slightly different time. If all devices run your
sync adapter at the same time, you are likely to overload your server and cell provider data
networks.
</p>
<p>
In general, periodic runs make sense if your users don't need instant updates, but expect to
have regular updates. Periodic runs also make sense if you want to balance the availability of
up-to-date data with the efficiency of smaller sync adapter runs that don't over-use device
resources.
</p>
<p>
To run your sync adapter at regular intervals, call
{@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()}. This schedules your
sync adapter to run after a certain amount of time has elapsed. Since the sync adapter framework
has to account for other sync adapter executions and tries to maximize battery efficiency, the
elapsed time may vary by a few seconds. Also, the framework won't run your sync adapter if the
network is not available.
</p>
<p>
Notice that {@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()} doesn't
run the sync adapter at a particular time of day. To run your sync adapter at roughly the
same time every day, use a repeating alarm as a trigger. Repeating alarms are described in more
detail in the reference documentation for {@link android.app.AlarmManager}. If you use the
method {@link android.app.AlarmManager#setInexactRepeating setInexactRepeating()} to set
time-of-day triggers that have some variation, you should still randomize the start time to
ensure that sync adapter runs from different devices are staggered.
</p>
<p>
The method {@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()} doesn't
disable {@link android.content.ContentResolver#setSyncAutomatically setSyncAutomatically()},
so you may get multiple sync runs in a relatively short period of time. Also, only a few
sync adapter control flags are allowed in a call to
{@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()}; the flags that are
not allowed are described in the referenced documentation for
{@link android.content.ContentResolver#addPeriodicSync addPeriodicSync()}.
</p>
<p>
The following code snippet shows you how to schedule periodic sync adapter runs:
</p>
<pre>
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(
ACCOUNT,
AUTHORITY,
Bundle.EMPTY,
SYNC_INTERVAL);
...
}
...
}
</pre>
<h2 id="RunOnDemand">Run the Sync Adapter On Demand</h2>
<p>
Running your sync adapter in response to a user request is the least preferable strategy
for running a sync adapter. The framework is specifically designed to conserve battery power
when it runs sync adapters according to a schedule. Options that run a sync in response to data
changes use battery power effectively, since the power is used to provide new data.
</p>
<p>
In comparison, allowing users to run a sync on demand means that the sync runs by itself, which
is inefficient use of network and power resources. Also, providing sync on demand leads users to
request a sync even if there's no evidence that the data has changed, and running a sync that
doesn't refresh data is an ineffective use of battery power. In general, your app should either
use other signals to trigger a sync or schedule them at regular intervals, without user input.
</p>
<p>
However, if you still want to run the sync adapter on demand, set the sync adapter flags for a
manual sync adapter run, then call
{@link android.content.ContentResolver#requestSync ContentResolver.requestSync()}.
</p>
<p>
Run on demand transfers with the following flags:
</p>
<dl>
<dt>
{@link android.content.ContentResolver#SYNC_EXTRAS_MANUAL SYNC_EXTRAS_MANUAL}
</dt>
<dd>
Forces a manual sync. The sync adapter framework ignores the existing settings,
such as the flag set by {@link android.content.ContentResolver#setSyncAutomatically
setSyncAutomatically()}.
</dd>
<dt>
{@link android.content.ContentResolver#SYNC_EXTRAS_EXPEDITED SYNC_EXTRAS_EXPEDITED}
</dt>
<dd>
Forces the sync to start immediately. If you don't set this, the system may wait several
seconds before running the sync request, because it tries to optimize battery use by
scheduling many requests in a short period of time.
</dd>
</dl>
<p>
The following code snippet shows you how to call
{@link android.content.ContentResolver#requestSync requestSync()} in response to a button
click:
</p>
<pre>
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 dummy 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);
}
</pre>
|