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