注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。
原文链接:http://developer.android.com/training/sync-adapters/creating-sync-adapter.html
在你应用中的同步适配器组件会封装在设备和服务器之间传输数据的任务代码。基于你提供的调度和触发器,同步适配器框架会在同步适配器组件中运行你的代码。要将同步适配组件添加到你的应用,你需要添加下列部件:
同步适配器类
这是一个将你的数据传输代码封装到一个接口中,该接口与同步适配器框架兼容。
捆绑Service
一个组件,它可以允许同步适配器框架在你的同步适配器类中运行代码。
同步适配器的XML元数据文件
一个文件,包含了你的同步适配器信息。框架会读取该文件并确定应该如何加载并调度你的数据传输任务。
在应用清单文件的声明
在XML文件中声明的捆绑服务,并指出同步适配器的元数据。
这节课将会向你展示如何定义这些元素。
一). 创建一个同步适配器类
在这部分课程中,你将会学习如何创建同步适配器类,该类封装了数据传输的代码。创建该类并包含继承的同步适配器的基类,为该类定义构造函数,并实现你定义的数据传输任务的方法。
继承同步适配器基类:AbstractThreadedSyncAdapter
要创建同步适配器组件,首先继承AbstractThreadedSyncAdapter,然后编写它的构造函数。每次你的同步适配器组件创建的时候,构造函数就会执行配置任务,和你使用Activity.onCreate()配置Activity是一样的。例如,如果你的应用使用一个内容提供器来存储数据,那么使用构造函数来获取一个ContentResolver实例。由于从Android 3.0开始添加了第二种形式的构造函数,来支持parallelSyncs参数,所以你需要创建两种形式的构造函数来保证兼容性。
Note:
同步适配器框架是设计成和同步适配器组件的单例一起工作的。实例化同步适配器组件的更多细节,可以阅读:Bind the Sync Adapter to the Framework。
下面的代码展示了如何实现AbstractThreadedSyncAdapter和它的构造函数:
/** * Handle the transfer of data between a server and an * app, using the Android sync adapter framework. */ public class SyncAdapter extends AbstractThreadedSyncAdapter { ... // Global variables // Define a variable to contain a content resolver instance ContentResolver mContentResolver; /** * Set up the sync adapter */ public SyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); /* * If your app uses a content resolver, get an instance of it * from the incoming Context */ mContentResolver = context.getContentResolver(); } ... /** * Set up the sync adapter. This form of the * constructor maintains compatibility with Android 3.0 * and later platform versions */ public SyncAdapter( Context context, boolean autoInitialize, boolean allowParallelSyncs) { super(context, autoInitialize, allowParallelSyncs); /* * If your app uses a content resolver, get an instance of it * from the incoming Context */ mContentResolver = context.getContentResolver(); ... }
在onPerformSync()中添加数据传输代码
同步适配器组件并不会自动地执行数据传输。相反地,它只是对你的数据传输代码进行封装,所以同步适配器框架可以在后台执行数据传输,而不会涉及到你的应用。当框架准备同步你的应用数据时,它会调用你的onPerformSync()方法的实现。
为了便于将你的数据从你的应用程序代码转移到同步适配器组件,同步适配器框架调用onPerformSync(),它具有下面的参数:
账户(Account)
该Account对象是和激活同步适配器的事件相关联的。如果你的服务不需要使用账户,你不需要使用这个对象内的信息。
额外数据(Extras)
一个Bundle对象,它包含了激活同步适配器的时间所具有的的标识。
权威(Authority)
系统内容提供器的权威。你的应用必须要有访问它的权限。通常,权威对应于你应用的内容提供器。
内容提供器客户端(Content provider client)
一个内容提供器的ContentProviderClient对象是由权威参数所指定的。一个ContentProviderClient是一个内容提供器的轻量级共有接口。它的基本功能和一个ContentResolver一样。如果你正在使用一个内容提供器来存储你的应用的数据,你可以用该对象和提供器连接。否则的话你可以忽略它。
同步结果(Sync result)
一个SyncResult对象,你可以使用它来将信息发送到同步适配器框架。
下面的代码片段展示了onPerformSync()函数的整体架构:
/* * Specify the code you want to run in the sync adapter. The entire * sync adapter runs in a background thread, so you don‘t have to set * up your own background processing. */ @Override public void onPerformSync( Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { /* * Put the data transfer code here. */ ... }
但是实际的onPerformSync()实现是要根据你的应用数据的同步需求以及服务器的连接协议来制定的,有一些你应该要实现的基本任务,如下所示:
连接到一个服务器
尽管你可以假定当你开始传输数据时,可以获取到网络连接,但是同步适配器框架并不会自动地连接到一个服务器。
下载和上传数据
一个同步适配器不会自动执行数据传输。如果你想要从一个服务器下载数据并将它存储到一个内容提供器中,你必须提供请求数据,下载数据和将数据插入到提供器里的代码。同样地,如果你想把数据发送到一个服务器,你必须要从一个文件,数据库或者提供器中读取数据,并且发送必须的上传请求。你也需要处理在你执行数据传输时所发生的网络错误。
处理数据冲突或者确定当前的数据是怎样的
一个同步适配器不会自动地解决服务器数据与设备数据的冲突。同时,它也不会检测服务器上的数据是否比设备上的数据要新,反之亦然。因此,你必须提供处理此状况的算法。
清理
在数据传输的尾声,记得要关闭网络连接,清除临时文件盒缓存。
Note:
同步适配器框架在一个后台线程中执行onPerformSync()方法,所以你不需要配置你自己的后台处理任务。
另外,你应该尝试将你的定期网络相关的任务结合起来,并将它们添加到onPerformSync()中。通过将所有网络任务集中到该方法中,你可以节省由启动和停止网络接口所造成的电量损失。有关更多如何在进行网络访问时更高效地使用电池,可以阅读:Transferring Data Without Draining the Battery(博客链接:http://www.cnblogs.com/jdneo/p/3620713.html),它描述了一些你的数据传输代码可以包含的网络访问任务。
二). 将同步适配器和框架进行绑定
你现在在一个同步适配器框架中已经封装了你的数据传输代码,但是你必须向框架提供你的代码。为了做这一点,你需要创建一个捆绑Service,它将一个特殊的Android binder对象从同步适配器组件传递给框架。有了这一binder对象,框架可以激活onPerformSync()方法并将数据传递给binder对象。
在你的服务的onCreate()方法中将你的同步适配器组件实例化为一个单例。通过在onCreate()方法中实例化该组件,你可以延迟到服务启动后再创建它,这会在框架第一次尝试执行你的数据传输时发生。你需要通过一种线程安全的方法来实例化组件,防止同步适配器框架会将你的同步适配器对于激活和调度的响应排成多个执行队列。
作为例子,下面的代码片段展示了你应该如何创建一个捆绑Service的类的实现,实例化你的同步适配器组件,并获取Android binder对象:
package com.example.android.syncadapter; /** * Define a Service that returns an IBinder for the * sync adapter class, allowing the sync adapter framework to call * onPerformSync(). */ public class SyncService extends Service { // Storage for an instance of the sync adapter private static SyncAdapter sSyncAdapter = null; // Object to use as a thread-safe lock private static final Object sSyncAdapterLock = new Object(); /* * Instantiate the sync adapter object. */ @Override public void onCreate() { /* * Create the sync adapter as a singleton. * Set the sync adapter as syncable * Disallow parallel syncs */ synchronized (sSyncAdapterLock) { if (sSyncAdapter == null) { sSyncAdapter = new SyncAdapter(getApplicationContext(), true); } } } /** * Return an object that allows the system to invoke * the sync adapter. * */ @Override public IBinder onBind(Intent intent) { /* * Get the object that allows external processes * to call onPerformSync(). The object is created * in the base class code when the SyncAdapter * constructors call super() */ return sSyncAdapter.getSyncAdapterBinder(); } }
Note:
要看更多同步适配器的捆绑服务的例子,可以阅读样例代码。
三). 添加框架所需的账户
同步适配器框架需要每个同步适配器拥有一个账户类型。在Add the Authenticator Metadata File章节中,你声明了账户类型的值。现在你需要在Android系统中配置该账户类型。要配置账户类型,通过调用addAccountExplicitly()添加一个假的账户并使用其账户类型。
最佳的调用该方法的地方是在你的应用的启动Activity的onCreate()方法中。下面的代码样例展示了你应该如何做:
public class MainActivity extends FragmentActivity { ... ... // Constants // The authority for the sync adapter‘s content provider public static final String AUTHORITY = "com.example.android.datasync.provider" // An account type, in the form of a domain name public static final String ACCOUNT_TYPE = "example.com"; // The account name public static final String ACCOUNT = "dummyaccount"; // Instance fields Account mAccount; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Create the dummy account mAccount = CreateSyncAccount(this); ... } ... /** * Create a new dummy account for the sync adapter * * @param context The application context */ public static Account CreateSyncAccount(Context context) { // Create the account type and default account Account newAccount = new Account( ACCOUNT, ACCOUNT_TYPE); // Get an instance of the Android account manager AccountManager accountManager = (AccountManager) context.getSystemService( ACCOUNT_SERVICE); /* * Add the account and account type, no password or user data * If successful, return the Account object, otherwise report an error. */ if (accountManager.addAccountExplicitly(newAccount, null, null))) { /* * If you don‘t set android:syncable="true" in * in your <provider> element in the manifest, * then call context.setIsSyncable(account, AUTHORITY, 1) * here. */ } else { /* * The account exists or some other error occurred. Log this, report it, * or handle it internally. */ } } ... }
四). 添加同步适配器的元数据文件
要将你的同步适配器组件添加到框架中,你需要向框架提供描述组件的元数据,以及额外的标识信息。元数据指定了你未你的同步适配器所创建的账户类型,声明了一个和你的应用相关联的内容提供器权威,对和同步适配器相关的一部分系统用户接口进行控制,并声明了其它同步相关的标识。在你的应用工程中的“/res/xml/”目录下的一个特定的文件内声明这一元数据,你可以为这个文件任意起一个名字,不过通常都叫做:“syncadapter.xml”。
在这一文件中包含了一个单一的XML元素“<sync-adapter>”,并且它包含了下列的属性字段:
android:contentAuthority
你的内容提供器的URI权威。如果你在前一节课程中(博客链接:http://www.cnblogs.com/jdneo/p/3654420.html)为你的应用创建了一个置空的内容提供器,使用你在清单文件中添加的<provider>标签内的android:authorities属性的值。这一属性的更多细节在章节Declare the Provider in the Manifest(博客链接:http://www.cnblogs.com/jdneo/p/3655747.html)中有更多的介绍。
如果你正在使用同步适配器,从内容提供器将数据传输到服务器,这个值应该和你的数据的URI权威的值是一样的。这个值也是你在清单文件中添加的<provider>标签内的android:authorities属性的值。
android:accountType
同步适配器框架所需要的账户类型。这个值必须和你创建验证器的元数据文件中所提供的一致(详细内容可以阅读:Add the Authenticator Metadata File,博客链接:http://www.cnblogs.com/jdneo/p/3655747.html)。这也是你在上一节中代码片段里的常量“ACCOUNT_TYPE”的值。
配置相关属性
android:userVisible
指的是同步适配器框架所需要的账户类型。默认地,和账户类型相关联的账户图标和标签在系统的设置中的账户选项中可以看见,所以你需要将你的同步适配器对用户不可见,除非你拥有一个账户类型或者账户类型,可以轻松地和你的应用相关联。如果你将你的账户类型设置为不可见,你仍然可以允许用户通过你的应用的一个activity内的用户接口来控制你的同步适配器。
android:supportsUploading
允许你将数据上传到云。如果你的应用仅仅下载数据,那么设置为“false”。
android:allowParallelSyncs
允许在同一时间你的同步适配器组件的多个实例运行。如果你的应用支持多个用户账户并且你希望多个用户并行地传输数据,那么使用这个属性。如果你从不执行多个数据传输,这个标识是没用的。
android:isAlwaysSyncable
指明同步适配器框架可以在任何你指定的时间运行你的永不适配器。如果你希望通过代码来控制同步适配器的运行,将这个标识设置为“false”,然后调用requestSync()来执行同步适配器。要学习更多关于运行一个同步适配器的知识,可以阅读:Running a Sync Adapter。
下面的例子展示了一个同步适配器的XML,使用了一个假的账户并只进行下载:
<?xml version="1.0" encoding="utf-8"?> <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" android:contentAuthority="com.example.android.datasync.provider" android:accountType="com.android.example.datasync" android:userVisible="false" android:supportsUploading="false" android:allowParallelSyncs="false" android:isAlwaysSyncable="true"/>
五). 在清单文件中声明同步适配器
一旦你将同步适配器组件添加到了你的应用中,你需要声明相关的权限来使用它,并且你需要声明你所添加的捆绑Service。
由于同步适配器组件运行网络与设备之间传输数据的代码,你需要使用网络的权限。另外,你的应用需要权限来读写同步适配器的配置信息,这样你才能通过你应用中的其它组件去控制同步适配器。你还需要一个特殊的权限允许你的应用使用你在Creating a Stub Authenticator(博客链接:http://www.cnblogs.com/jdneo/p/3654420.html)中所创建的验证器组件。
要请求这些权限,将下列内容添加到你的应用清单文件中,并作为<manifest>标签的子标签:
允许同步适配器访问网络,一致于它可以从设备下载和上传数据到服务器。如果之前请求了该权限,那么你就不需要重复请求了。
android.permission.READ_SYNC_SETTINGS
允许你的应用读取当前的同步适配器配置。例如,你需要该权限来调用getIsSyncable()。
android.permission.WRITE_SYNC_SETTINGS
允许你的应用对同步适配器的配置进行控制。你需要这一权限来执行addPeriodicSync(),以此设置执行同步的时间间隔。调用requestSync()不需要用到该权限。更多信息可以阅读:Running A Sync Adapter。
android.permission.AUTHENTICATE_ACCOUNTS
允许你使用在Creating a Stub Authenticator(博客链接:http://www.cnblogs.com/jdneo/p/3654420.html)中所创建的验证器组件。
下面的代码片段展示了如何添加权限:
<manifest> ... <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/> ... </manifest>
最后,要声明框架使用的捆绑Service和你的同步适配器进行交互,添加下列的XML代码到你的应用清单文件中,作为<application>标签的子标签:
<service android:name="com.example.android.datasync.SyncService" android:exported="true" android:process=":sync"> <intent-filter>com.example.android.datasync.provider <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" /> </service>
<intent-filter>标签配置了一个过滤器,它会被带有“android.content.SyncAdapter”这一action的intent所激活,而这一intent一般是由系统为了运行同步适配器而发出的。当过滤器被激活时,系统会启动你所创建的捆绑服务,在例子中它叫做“SyncService”。属性android:exported="true"允许你应用之外的其它进程(包括系统)访问这一Service。属性android:process=":sync"告诉系统在一个全局共享,且称之为“sync”的进程内运行Service。如果你的应用中有多个同步适配器,那么它们可以共享该进程,这有助于减少开销。
<meta-data>标签提供了你之前为同步适配器所创建的元数据文件。属性android:name指出这一元数据是针对于同步适配器框架的。而android:resource标签则指定了元数据文件的文字。
现在你拥有了所有同步适配器的相关组件。下一节课将讲授如何让同步适配器框架运行你的同步适配器,既可以通过响应一个事件的方式,也可以通过执行一个定期任务调度的方式。