注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。

原文链接:http://developer.android.com/training/sync-adapters/running-sync-adapter.html


在这系列课程中之前的一些课程中,你学习了如何创建一个封装数据传输代码的同步适配器组件,以及如何添加其它的组件以允许你将同步适配器集成到系统当中。现在已已经拥有了所有你需要的东西,来安装包含有一个同步适配器的应用,但是没有任何代码是去运行同步适配器的。

你应该基于计划任务的调度或者一些事件的间接结果。例如,你可能希望你的同步适配器运行一个定期的计划任务,每隔一段时间或每天的一个固定的时间。或者你还希望当设备上的数据发生变化后,执行你的同步适配器。你应该避免将运行同步适配器作为用户某个行为的直接结果,因为这样做的话你就无法利用同步适配器框架可以按计划调度的特性。例如,你应该在UI中避免使用刷新按钮。

你可以有下列选项来运行你的同步适配器:

当服务器数据变更时:

运行同步适配器以响应来自服务器的消息,指明服务端的数据变化了。这一选项允许从服务器刷新数据到设备上,这一方法避免降低性能,或者由于轮询服务器所造成的电量损耗。

当设备变化变更时:

当设备上的数据发生变化时,运行同步适配器。这一选项允许你将修改后的数据从设备发送给服务器,如果你需要保证服务器端的数据一致保持最新,那么这一选项非常有用。如果你将数据存储于你的内容提供器,那么这一选项的实现将会非常直接。如果你使用的是一个空内容提供器,检测数据的变化可能会比较困难。

当系统发送了一个网络消息:

当Android系统发送了一个网络消息来保持TCP/IP连接开启时,运行同步适配器。这个消息是网络框架的一个基本部分。使用这一选项是自动运行同步适配器的一个方法。可以考虑配合基于时间间隔的同步适配器一起使用。

每隔固定的时间间隔后:

在你定的时间间隔过了之后,运行同步适配器,或者在每天的固定时间运行它。

按照要求:

运行同步适配器以响应用户的行为。然而,为了提供最佳的用户体验,你应该主要依赖更多自动类型的选项。使用自动化的选项,你可以节省大量的电量以及网络资源。

本课程的后续部分会详细介绍每个选项。


一). 当服务器数据变化时,运行同步适配器

如果你的应用从服务器传输数据,且服务器的数据频繁的发生变化,你可以使用一个同步适配器通过下载数据来响应服务端数据的变化。要运行同步适配器,让服务端向你的应用的BroadcastReceiver发送一条特殊的消息。要响应这条消息,可以调用ContentResolver.requestSync()方法,来向同步适配器框架发出信号,让它运行你的同步适配器。

谷歌云消息(Google Cloud Messaging,GCM)提供了你需要的服务端组件和设备端组件,来让这一消息提供能够运行。使用GCM激活数据传输比通过向服务器轮询的方式要更加可靠,也更加有效。因为轮询需要一个一直处于活跃状态的Service,而GCM使用的BroadcastReceiver仅在消息到达时会激活。另外,即使没有更新的内容,定期的轮询也会消耗大量的电池电量,而GCM仅在需要时才会发出消息。

Note:

如果你使用GCM,通过一个到所有安装了你的应用的设备的广播,来激活你的同步适配器,要记住他们会在同一时间(粗略地)收到你的消息。这回导致在同一时间有多个同步适配器的实例在运行,进而导致服务器和网络的负载过重。要避免这一情况,你应该考虑让每个设备的同步适配器启动的事件有所差异。

下面的代码展示了如何运行requestSync()以响应一个接收到的GCM消息:

  1. public class GcmBroadcastReceiver extends BroadcastReceiver {
  2. ...
  3. // Constants
  4. // Content provider authority
  5. public static final String AUTHORITY = "com.example.android.datasync.provider"
  6. // Account type
  7. public static final String ACCOUNT_TYPE = "com.example.android.datasync";
  8. // Account
  9. public static final String ACCOUNT = "default_account";
  10. // Incoming Intent key for extended data
  11. public static final String KEY_SYNC_REQUEST =
  12. "com.example.android.datasync.KEY_SYNC_REQUEST";
  13. ...
  14. @Override
  15. public void onReceive(Context context, Intent intent) {
  16. // Get a GCM object instance
  17. GoogleCloudMessaging gcm =
  18. GoogleCloudMessaging.getInstance(context);
  19. // Get the type of GCM message
  20. String messageType = gcm.getMessageType(intent);
  21. /*
  22. * Test the message type and examine the message contents.
  23. * Since GCM is a general-purpose messaging system, you
  24. * may receive normal messages that don't require a sync
  25. * adapter run.
  26. * The following code tests for a a boolean flag indicating
  27. * that the message is requesting a transfer from the device.
  28. */
  29. if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)
  30. &&
  31. intent.getBooleanExtra(KEY_SYNC_REQUEST)) {
  32. /*
  33. * Signal the framework to run your sync adapter. Assume that
  34. * app initialization has already created the account.
  35. */
  36. ContentResolver.requestSync(ACCOUNT, AUTHORITY, null);
  37. ...
  38. }
  39. ...
  40. }
  41. ...
  42. }

二). 当内容提供器的数据变化时,运行同步适配器

如果你的应用在一个内容提供器中收集数据,并且你希望当你更新提供器的时候一起更新服务器的数据,你可以配置你的同步适配器来让它自动运行。要做到这一点,你首先应该为内容提供器注册一个观察器。当你的内容提供器的数据发生了变化以后,内容提供器框架会调用观察器。在观察器中,调用requestSync()来告诉框架运行你的同步适配器。

Note:

如果你使用的是一个空的内容提供器,那么你在内容提供器中没有任何数据,并且onChange()方法从来没有被调用。在这种情况下,你不得不提供你自己的机制来检测设备数据的变化。这一机制还要负责当数据发生变化时调用requestSync()

为了给你的内容提供器创建一个观察器,继承ContentObserver类,并且实现onChange()方法的几种形式。在onChange()中,调用requestSync()来启动同步适配器。

要注册观察器,将它作为参数传递给registerContentObserver()。在这个调用中,你还要传递一个你先要监视的内容URI。内容提供器框架会将这个监视的URI和通过ContentResolver方法(如ContentResolver.insert())所传递过来的修改了你的提供器的URI进行对比,如果匹配上了,那么你所实现的ContentObserver.onChange()将会被调用。

下面的代码片段展示了如何定义一个ContentObserver,当表发生变化时调用requestSync()

  1. public class MainActivity extends FragmentActivity {
  2. ...
  3. // Constants
  4. // Content provider scheme
  5. public static final String SCHEME = "content://";
  6. // Content provider authority
  7. public static final String AUTHORITY = "com.example.android.datasync.provider";
  8. // Path for the content provider table
  9. public static final String TABLE_PATH = "data_table";
  10. // Account
  11. public static final String ACCOUNT = "default_account";
  12. // Global variables
  13. // A content URI for the content provider's data table
  14. Uri mUri;
  15. // A content resolver for accessing the provider
  16. ContentResolver mResolver;
  17. ...
  18. public class TableObserver extends ContentObserver {
  19. /*
  20. * Define a method that's called when data in the
  21. * observed content provider changes.
  22. * This method signature is provided for compatibility with
  23. * older platforms.
  24. */
  25. @Override
  26. public void onChange(boolean selfChange) {
  27. /*
  28. * Invoke the method signature available as of
  29. * Android platform version 4.1, with a null URI.
  30. */
  31. onChange(selfChange, null);
  32. }
  33. /*
  34. * Define a method that's called when data in the
  35. * observed content provider changes.
  36. */
  37. @Override
  38. public void onChange(boolean selfChange, Uri changeUri) {
  39. /*
  40. * Ask the framework to run your sync adapter.
  41. * To maintain backward compatibility, assume that
  42. * changeUri is null.
  43. ContentResolver.requestSync(ACCOUNT, AUTHORITY, null);
  44. }
  45. ...
  46. }
  47. ...
  48. @Override
  49. protected void onCreate(Bundle savedInstanceState) {
  50. super.onCreate(savedInstanceState);
  51. ...
  52. // Get the content resolver object for your app
  53. mResolver = getContentResolver();
  54. // Construct a URI that points to the content provider data table
  55. mUri = new Uri.Builder()
  56. .scheme(SCHEME)
  57. .authority(AUTHORITY)
  58. .path(TABLE_PATH)
  59. .build();
  60. /*
  61. * Create a content observer object.
  62. * Its code does not mutate the provider, so set
  63. * selfChange to "false"
  64. */
  65. TableObserver observer = new TableObserver(false);
  66. /*
  67. * Register the observer for the data table. The table's path
  68. * and any of its subpaths trigger the observer.
  69. */
  70. mResolver.registerContentObserver(mUri, true, observer);
  71. ...
  72. }
  73. ...
  74. }

三). 在一个网络消息之后,运行同步适配器

当一个网络连接可获得时,Android系统会每隔几秒发送一条消息来保持TCP/IP连接打开。这一消息也会传递到每个应用的ContentResolver中。通过调用setSyncAutomatically(),你可以在ContentResolver收到消息后,运行同步适配器。

当网络可获得的时候,通过调度你的同步适配器运行,来保证你的同步适配器在可以获得网络时都会被调度。如果不是每次数据变化时就要以数据传输来响应,但是又希望自己的数据会被定期地更新,那么可以用这一选项。类似地,如果你不想要给你的同步适配器配置一个定期调度,但你希望经常运行它,你也可以使用这一选项。

由于setSyncAutomatically()方法不会禁用addPeriodicSync(),你的同步适配器可能会在一小段时间内重复地被激活。如果你想要定期地运行你的同步适配器,你应该禁用setSyncAutomatically()

下面的代码片段向你展示如何配置你的ContentResolver来运行你的同步适配器,响应网络消息:

  1. public class MainActivity extends FragmentActivity {
  2. ...
  3. // Constants
  4. // Content provider authority
  5. public static final String AUTHORITY = "com.example.android.datasync.provider";
  6. // Account
  7. public static final String ACCOUNT = "default_account";
  8. // Global variables
  9. // A content resolver for accessing the provider
  10. ContentResolver mResolver;
  11. ...
  12. @Override
  13. protected void onCreate(Bundle savedInstanceState) {
  14. super.onCreate(savedInstanceState);
  15. ...
  16. // Get the content resolver for your app
  17. mResolver = getContentResolver();
  18. // Turn on automatic syncing for the default account and authority
  19. mResolver.setSyncAutomatically(ACCOUNT, AUTHORITY, true);
  20. ...
  21. }
  22. ...
  23. }

四). 定期地运行同步适配器

你可以设置一个每次运行期间的间隔时间来定期运行你的同步适配器,或者在每天的固定时间运行,或者两者都有。定期地运行你的同步适配器可以允许你粗略地观察你的服务器更新间隔。

同样地,当你的服务器相对来说比较空闲时,你可以从设备更新数据,方法可以是在夜间定期调用同步适配器。大多数用户晚上会不关机并对收集充电,所以这一方法是可行的。而且,那个时间设备不会运行其他的任务除了你的同步适配器。如果你使用这个方法的话,你需要注意每台设备会在略微不同的时间激活数据传输。如果所有设备在同一时间运行你的同步适配器,那么你的服务器将很有可能负载过重。

一般来说,如果你的用户不需要实时更新,但希望定期更新,定期运行会很有用。如果你希望在获取实时数据和一个更小的同步适配器的效率及不过度使用用户设备资源这两者之间进行一个平衡,那么定期执行是一个不错的选择。

要定期运行你的同步适配器,调用addPeriodicSync()。这样每隔一段时间,同步适配器就会运行。由于同步适配器框架会考虑其他同步适配器的执行,并尝试最大化电池效率,间隔时间会动态做出细微调整。同时,如果网络不可获得,框架不会运行你的同步适配器。

注意,addPeriodicSync()方法不会每天某个时间自动运行。要让你的同步适配器每天某个时间内自动执行,使用一个重复计时器作为激发器。重复计时器的更多细节可以阅读:AlarmManager。如果你使用setInexactRepeating()方法来设置每天激活的时间具有一些变化,你仍然应该将不同设备的同步适配器的运行时间随机化,使得它们的执行交错开来。

addPeriodicSync()方法不会禁用setSyncAutomatically(),所以你可能会在一小段时间内获取多个同步执行。同样,仅有一些同步适配器的控制标识会在addPeriodicSync()方法中被允许。不允许的标识在该方法的文档中可以查看。

下面的代码样例展示了如何定期执行同步适配器:

  1. public class MainActivity extends FragmentActivity {
  2. ...
  3. // Constants
  4. // Content provider authority
  5. public static final String AUTHORITY = "com.example.android.datasync.provider";
  6. // Account
  7. public static final String ACCOUNT = "default_account";
  8. // Sync interval constants
  9. public static final long MILLISECONDS_PER_SECOND = 1000L;
  10. public static final long SECONDS_PER_MINUTE = 60L;
  11. public static final long SYNC_INTERVAL_IN_MINUTES = 60L;
  12. public static final long SYNC_INTERVAL =
  13. SYNC_INTERVAL_IN_MINUTES *
  14. SECONDS_PER_MINUTE *
  15. MILLISECONDS_PER_SECOND;
  16. // Global variables
  17. // A content resolver for accessing the provider
  18. ContentResolver mResolver;
  19. ...
  20. @Override
  21. protected void onCreate(Bundle savedInstanceState) {
  22. super.onCreate(savedInstanceState);
  23. ...
  24. // Get the content resolver for your app
  25. mResolver = getContentResolver();
  26. /*
  27. * Turn on periodic syncing
  28. */
  29. ContentResolver.addPeriodicSync(
  30. ACCOUNT,
  31. AUTHORITY,
  32. null,
  33. SYNC_INTERVAL);
  34. ...
  35. }
  36. ...
  37. }

五). 按需求执行同步适配器

运行你的同步适配器来响应一个用户需求是运行一个同步适配器最不推荐的策略。框架是被特别设计成根据计划运行同步适配器时最大化保留电量。在数据变化时响应一个同步适配器的同步选项应该有效地使用电量,因为电量是用来提供新数据的。

相比之下,允许用户按照需求运行同步适配器意味着同步适配器会自己运行,这对电量和网络来说会导致使用效率的下降。同时,向用户提供同步,会让用户甚至没有证据表明数据发生变化了以后也请求一个更新,这会导致对电量的低效率使用,一般来说,你的应用应该使用其它信号来激活一个同步更新或者定期地去做它们,而不是依赖于用户的输入。

然而,如果你仍然想要按照需求运行同步适配器,将同步适配器标识设置为人为运行的同步适配器,之后调用ContentResolver.requestSync()

使用下列标识来执行按需求的数据传输:

SYNC_EXTRAS_MANUAL

强制执行人为的同步更新。同步适配器框架会忽略当前的设置,如被setSyncAutomatically()方法设置的标识。

SYNC_EXTRAS_EXPEDITED

强制同步立即执行。如果你不设置此项,系统可能会在运行同步需求之前等待一小段时间,因为它会尝试通过将多个请求在一小段时间内调度来尝试优化电量。

下面的代码片段将向你展示如何调用requestSync()来响应一个按钮的点击:

  1. public class MainActivity extends FragmentActivity {
  2. ...
  3. // Constants
  4. // Content provider authority
  5. public static final String AUTHORITY =
  6. "com.example.android.datasync.provider"
  7. // Account type
  8. public static final String ACCOUNT_TYPE = "com.example.android.datasync";
  9. // Account
  10. public static final String ACCOUNT = "default_account";
  11. // Instance fields
  12. Account mAccount;
  13. ...
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. ...
  18. /*
  19. * Create the dummy account. The code for CreateSyncAccount
  20. * is listed in the lesson Creating a Sync Adapter
  21. */
  22.  
  23. mAccount = CreateSyncAccount(this);
  24. ...
  25. }
  26. /**
  27. * Respond to a button click by calling requestSync(). This is an
  28. * asynchronous operation.
  29. *
  30. * This method is attached to the refresh button in the layout
  31. * XML file
  32. *
  33. * @param v The View associated with the method call,
  34. * in this case a Button
  35. */
  36. public void onRefreshButtonClick(View v) {
  37. ...
  38. // Pass the settings flags by inserting them in a bundle
  39. Bundle settingsBundle = new Bundle();
  40. settingsBundle.putBoolean(
  41. ContentResolver.SYNC_EXTRAS_MANUAL, true);
  42. settingsBundle.putBoolean(
  43. ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
  44. /*
  45. * Request the sync for the default account, authority, and
  46. * manual sync settings
  47. */
  48. ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
  49. }

【Android Developers Training】 96. 运行一个同步适配器的更多相关文章

  1. 【Android Developers Training】 92. 序言:使用同步适配器传输数据

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  2. 【Android Developers Training】 95. 创建一个同步适配器

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  3. 【Android Developers Training】 94. 创建一个空内容提供器(Content Provider)

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  4. 【Android Developers Training】 93. 创建一个空验证器

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  5. 【Android Developers Training】 105. 显示一个位置地址

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  6. 【Android Developers Training】 4. 启动另一个Activity

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  7. 【Android Developers Training】 3. 构建一个简单UI

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  8. 【Android Developers Training】 2. 运行你的应用

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  9. 【Android Developers Training】 1. 创建一个Android项目工程

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

随机推荐

  1. jQuery插件ImgAreaSelect 实例讲解一(头像上传预览和裁剪功能)

    上一节随笔中,我们已经知道了关于jQuery插件ImgAreaSelect基本的知识:那么现在看一下实例: 首先,要知道我们应该实现什么功能? (1)图片能够实现上传预览功能 (2)拖拽裁剪图片,使其 ...

  2. Configure HttpClient correctly

    References: [1] http://dev.bizo.com/2013/04/sensible-defaults-for-apache-httpclient.html We have hit ...

  3. Java Regex match IP address

    Reference: [1] https://www.mkyong.com/regular-expressions/how-to-validate-ip-address-with-regular-ex ...

  4. maven如何修改本地仓库与中央仓库

    摘要: 运行Maven的时候,Maven所需要的任何构件都是直接从本地仓库获取的.如果本地仓库没有,它会首先尝试从远程仓库下载构件至本地仓库,然后再使用本地仓库的构件. 什么是Maven仓库 在不用M ...

  5. 读阿里巴巴Java开发手册v1.2.0之工程结构有感【架构篇】

    首先,把昨天那俩条sql语句的优化原因给大家补充一下,第一条效率极低,第二条优化后的,sql语句截图如下: 经过几个高手的评论和个人的分析: 第一条sql语句查询很慢是因为它首先使用了in关键字查询, ...

  6. Html_Task4(知识点:水平居中+垂直居中/position/float/border-radius)

    任务四:定位和居中问题 任务目标 实践HTML/CSS布局方式 深入了解position等CSS属性 任务描述 实现如 示例图(点击打开) 的效果 灰色元素水平垂直居中,有两个四分之一圆位于其左上角和 ...

  7. Swoole笔记(二)

    本文示例代码详见:https://github.com/52fhy/swoole_demo. Task 我们可以在worker进程中投递一个异步任务到task_worker池中.此函数是非阻塞的,执行 ...

  8. 学习Spark——那些让你精疲力尽的坑

    这一个月我都干了些什么-- 工作上,还是一如既往的写bug并不亦乐乎的修bug.学习上,最近看了一些非专业书籍,时常在公众号(JackieZheng)上写点小感悟,我刚稍稍瞄了下,最近五篇居然都跟技术 ...

  9. 【Python的迭代器,生成器】

    一.可迭代对象和迭代器 1.迭代的概念 上一次输出的结果为下一次输入的初始值,重复的过程称为迭代,每次重复即一次迭代,并且每次迭代的结果是下一次迭代的初始值 注:循环不是迭代 while True: ...

  10. [IR] BWT+MTF+AC

    BWT (Burrows–Wheeler_transform)数据转换算法 MTF(Move-to-front transform)数据转换 基于统计的压缩算法:游程编码 良心PPT: bwt_bas ...