注意:我们建议使用 WorkManager 作为大多数后台处理用例的推荐解决方案。请参阅 后台处理指南,了解哪种解决方案最适合您。
应用中的同步适配器组件可封装负责传输数据的任务的代码 在设备和服务器之间传输数据基于您在 您的应用,则同步适配器框架会运行同步适配器组件中的代码。要将 同步适配器组件添加到应用中,则需要添加以下部分:
- 同步适配器类。
- 用于将数据传输代码封装到与同步适配器兼容的接口中的类 框架。
-
绑定
Service
。 - 可让同步适配器框架运行同步适配器中的代码的组件 类。
- 同步适配器 XML 元数据文件。
- 包含同步适配器相关信息的文件。框架会将此文件读取 了解如何加载和安排数据传输。
- 应用清单中的声明。
- 此 XML 声明绑定服务并指向特定于同步适配器的元数据。
本课将介绍如何定义这些元素。
创建同步适配器类
在本课程的这一部分中,您将学习如何创建同步适配器类,用于封装 数据传输代码。创建该类包括扩展同步适配器基类,定义 类构造函数,并实现在其中定义数据传输的方法 任务。
扩展同步适配器基类
要创建同步适配器组件,首先需要扩展
AbstractThreadedSyncAdapter
并编写其构造函数。使用
构造函数来运行设置任务。
就像您使用 Activity.onCreate()
设置
活动。例如,如果您的应用使用内容提供程序来存储数据,则应使用构造函数
以获取 ContentResolver
实例。由于
Android 平台版本 3.0 中添加了构造函数,以支持 parallelSyncs
参数,您需要创建两种形式的构造函数以保持兼容性。
注意:同步适配器框架旨在与同步适配器配合使用 这些组件属于单例实例介绍了如何实例化同步适配器组件 将在本单元后面的部分 将同步适配器绑定到框架。
以下示例展示了如何实现
AbstractThreadedSyncAdapter
及其构造函数:
Kotlin
/** * Handle the transfer of data between a server and an * app, using the Android sync adapter framework. */ class SyncAdapter @JvmOverloads constructor( context: Context, autoInitialize: Boolean, /** * Using a default argument along with @JvmOverloads * generates constructor for both method signatures to maintain compatibility * with Android 3.0 and later platform versions */ allowParallelSyncs: Boolean = false, /* * If your app uses a content resolver, get an instance of it * from the incoming Context */ val mContentResolver: ContentResolver = context.contentResolver ) : AbstractThreadedSyncAdapter(context, autoInitialize, allowParallelSyncs) { ... }
Java
/** * 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 contentResolver; /** * 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 */ contentResolver = 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 */ contentResolver = context.getContentResolver(); ... }
添加数据传输代码
同步适配器组件不会自动执行数据传输,相反,
封装您的数据传输代码,以便同步适配器框架可以运行
在后台进行数据传输,而无需您的应用参与。当框架准备就绪时
同步应用数据后,它会调用方法的实现,
onPerformSync()
。
为了便于将数据从主应用代码传输到同步适配器组件,
同步适配器框架调用
onPerformSync()
替换为
以下参数:
- 账号
-
与触发的事件相关联的
Account
对象 同步适配器如果您的服务器不使用账号,则不需要使用 信息。 - 其他功能
-
一个
Bundle
,包含由触发同步的事件发送的标志 适配器。 - 职权
- 系统中内容提供器的授权。您的应用必须有权访问 该提供商。通常,该授权与您自己的应用中的内容提供程序相对应。 。
- content provider 客户端
-
一个
ContentProviderClient
,用于通过 权威性论据。ContentProviderClient
是轻量级公共资源 与内容提供程序相关联它的基本功能与ContentResolver
。如果您使用 content provider 来存储数据 您可以使用此对象连接到提供程序。否则,您可以忽略 。 - 同步结果
-
用于向同步发送信息的
SyncResult
对象 适配器框架的方法。
以下代码段显示了
onPerformSync()
:
Kotlin
/* * 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 fun onPerformSync( account: Account, extras: Bundle, authority: String, provider: ContentProviderClient, syncResult: SyncResult ) { /* * Put the data transfer code here. */ }
Java
/* * 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()
特定于
应用的数据同步要求和服务器连接协议,则有一些
您的实现应执行的常规任务:
- 连接到服务器
- 虽然您可以假定在数据传输开始时网络可用, 同步适配器框架不会自动连接到服务器。
- 下载和上传数据
- 同步适配器不会自动执行任何数据传输任务。如果您想下载 并将其存储在内容提供程序中,则必须提供 请求数据,下载数据,然后将其插入提供程序。同样,如果您希望 将数据发送到服务器时,您必须从文件、数据库或提供程序中读取数据, 执行必要的上传请求您还必须处理 数据转移作业是否正在运行。
- 处理数据冲突或确定数据的新近度
- 同步适配器不会自动处理服务器上的数据和数据之间的冲突 。此外,它不会自动检测服务器上的数据是否比 反之亦然。相反,您必须提供自己的算法, 处理这种情况。
- 清理。
- 始终关闭与服务器的连接,并在结束时清理临时文件和缓存 数据转移。
注意:同步适配器框架运行
onPerformSync()
在
后台线程,因此你不必设置自己的后台处理。
除了与同步相关的任务外,您还应尝试将常规
与网络相关的任务,并将其添加到
onPerformSync()
。
通过将所有网络任务集中到此方法中,您可以节省
启动和停止网络接口所需的资源。详细了解如何进一步提高网络访问权限
请参阅在不消耗电池电量的情况下传输数据培训课程,其中介绍了几种网络访问
可以添加到数据传输代码中的一些任务。
将同步适配器绑定到框架
现在,您已将您的数据传输代码封装在同步适配器组件中,但是
为框架提供对您代码的访问权限为此,您需要创建一个
Service
,用于从同步适配器传递特殊的 Android binder 对象
添加到框架中有了这个 binder 对象,框架就可以调用
onPerformSync()
方法和
向其传递数据
将同步适配器组件实例化为
onCreate()
方法。通过实例化
onCreate()
中的组件,您可以延迟
创建该容器,直到 Service 启动为止,这发生在框架首次尝试运行您的
数据转移。您需要以线程安全的方式实例化组件,以防出现同步
适配器框架会将您的同步适配器的多次执行加入队列,以响应触发器或
。
例如,以下代码段展示了如何创建实现
绑定 Service
,实例化您的同步适配器组件,并获取
Android binder 对象:
Kotlin
package com.example.android.syncadapter /** * Define a Service that returns an [android.os.IBinder] for the * sync adapter class, allowing the sync adapter framework to call * onPerformSync(). */ class SyncService : Service() { /* * Instantiate the sync adapter object. */ override fun onCreate() { /* * Create the sync adapter as a singleton. * Set the sync adapter as syncable * Disallow parallel syncs */ synchronized(sSyncAdapterLock) { sSyncAdapter = sSyncAdapter ?: SyncAdapter(applicationContext, true) } } /** * Return an object that allows the system to invoke * the sync adapter. * */ override fun onBind(intent: Intent): IBinder { /* * 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() * * We should never be in a position where this is called before * onCreate() so the exception should never be thrown */ return sSyncAdapter?.syncAdapterBinder ?: throw IllegalStateException() } companion object { // Storage for an instance of the sync adapter private var sSyncAdapter: SyncAdapter? = null // Object to use as a thread-safe lock private val sSyncAdapterLock = Any() } }
Java
package com.example.android.syncadapter; /** * Define a Service that returns an <code><a href="/reference/android/os/IBinder.html">IBinder</a></code> 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(); } }
注意:如需查看同步适配器绑定服务的更详细示例, 查看示例应用。
添加框架所需的账号
同步适配器框架要求每个同步适配器都具有一种账号类型。您已声明
部分中的账号类型值
添加身份验证器元数据文件。现在,您必须在
Android 系统。要设置账号类型,请添加使用账号类型的占位账号
调用 addAccountExplicitly()
。
调用此方法的最佳位置是
onCreate()
方法
打开 activity。以下代码段展示了如何执行此操作:
Kotlin
... // Constants // The authority for the sync adapter's content provider const val AUTHORITY = "com.example.android.datasync.provider" // An account type, in the form of a domain name const val ACCOUNT_TYPE = "example.com" // The account name const val ACCOUNT = "placeholderaccount" ... class MainActivity : FragmentActivity() { // Instance fields private lateinit var mAccount: Account ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... // Create the placeholder account mAccount = createSyncAccount() ... } ... /** * Create a new placeholder account for the sync adapter */ private fun createSyncAccount(): Account { val accountManager = getSystemService(Context.ACCOUNT_SERVICE) as AccountManager return Account(ACCOUNT, ACCOUNT_TYPE).also { newAccount -> /* * 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. */ } } } ... }
Java
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 = "placeholderaccount"; // Instance fields Account mAccount; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Create the placeholder account mAccount = CreateSyncAccount(this); ... } ... /** * Create a new placeholder 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. */ } } ... }
添加同步适配器元数据文件
要将同步适配器组件插入框架,您需要提供框架
其中包含描述组件并提供其他标记的元数据。元数据指定
您为同步适配器创建的账号类型,声明 content provider 授权
与您的应用关联的、控制系统界面中与同步适配器相关的部分,
并声明其他与同步相关的标志在存储在
应用项目中的 /res/xml/
目录。你可以为该文件指定任何名称
但通常称为 syncadapter.xml
。
此 XML 文件包含一个 XML 元素 <sync-adapter>
,该元素具有
以下属性:
android:contentAuthority
-
内容提供程序的 URI 授权。如果您为以下服务创建了桩内容提供器:
在上一课创建存根内容提供程序中构建桩内容提供程序,请使用您为
属性
android:authorities
在您添加到应用清单的<provider>
元素中。该属性为 部分 在清单中声明提供程序。
如果您使用同步适配器将数据从内容提供器传输到服务器, 值应与您对该数据使用的内容 URI 授权相同。此值 也是您在android:authorities
<provider>
元素的属性,用于在应用清单中声明您的提供程序。 android:accountType
-
同步适配器框架所需的账号类型。值必须相同
作为您在创建身份验证器元数据文件时提供的账号类型值,例如
添加身份验证器元数据文件部分中所述。该值也是您为
本部分代码段中的
ACCOUNT_TYPE
常量 添加框架所需的账号。 - 设置属性
-
-
android:userVisible
- 设置同步适配器账号类型的可见性。默认情况下, 与账号类型相关联的账号图标和标签会显示在 账号部分,因此您应该进行同步 除非您将某个账号类型或网域 应用互动情况如果您将账号类型设为不可见,您仍然可以允许用户执行以下操作: 通过应用的某个 activity 中的界面控制同步适配器。
-
android:supportsUploading
-
允许您将数据上传到云端。如果应用仅适用于应用,请将此属性设为
false
下载数据。 -
android:allowParallelSyncs
- 允许同步适配器组件的多个实例同时运行。 如果您的应用支持多个用户账号,并且您希望允许多个用户账号,请使用此选项。 并行传输数据。如果您从不运行,则此标志不会产生任何影响 进行多次数据传输
-
android:isAlwaysSyncable
-
向同步适配器框架表明它可以在任何时间运行您的同步适配器
指定的时间如果您希望以编程方式控制
将此标志设置为
false
,然后调用requestSync()
,以运行 同步适配器。如需详细了解如何运行同步适配器,请参阅 运行同步适配器
-
以下示例展示了使用单个占位符账号和 只支持下载
<?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
您所添加的任何项目
由于同步适配器组件运行的代码在网络和 您需要请求访问互联网的权限。此外,您的应用需要 来请求读取和写入同步适配器设置的权限,以便您可以控制同步 适配器。您还需要请求 一种特殊权限,可让应用使用您创建的身份验证器组件 创建存根身份验证程序一课。
如需请求这些权限,请将以下内容作为
<manifest>
:
-
android.permission.INTERNET
- 允许同步适配器代码访问互联网,以便下载或上传数据 从设备传输到服务器如果您 。
-
android.permission.READ_SYNC_SETTINGS
-
允许您的应用读取当前的同步适配器设置。例如,您需要
权限,以便调用
getIsSyncable()
。 -
android.permission.WRITE_SYNC_SETTINGS
-
允许应用控制同步适配器设置。您需要有此权限才能
使用
addPeriodicSync()
设置定期运行同步适配器。调用 API 时不需要此权限requestSync()
。要详细了解 运行同步适配器,请参阅运行同步适配器。
以下代码段展示了如何添加这些权限:
<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="false" android:process=":sync"> <intent-filter> <action android:name="android.content.SyncAdapter"/> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" /> </service>
通过
<intent-filter>
元素设置由 intent 操作触发的过滤器
android.content.SyncAdapter
,由系统发送以运行同步适配器。当过滤器
触发了,系统将启动您创建的绑定服务,在本例中为
SyncService
。属性
android:exported="false"
仅允许您的应用和系统访问
Service
。属性
android:process=":sync"
指示系统在名为 Service
的全局共享进程中运行
sync
。如果您的应用中有多个同步适配器,那么它们可以共享此过程,
从而降低开销
通过
<meta-data>
元素提供您之前创建的同步适配器元数据 XML 文件的名称。
通过
android:name
属性指明这是用于同步适配器框架的元数据。通过
android:resource
元素指定该元数据文件的名称。
现在您已拥有同步适配器的所有组件。下一课将介绍如何 告知同步适配器框架运行您的同步适配器,可以在响应事件时运行,也可以在 定期更新。