注意: 对于大多数后台处理用例,我们推荐使用 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
- 与触发同步适配器的事件关联的
Account
对象。如果您的服务器不使用帐户,则无需使用此对象中的信息。 - Extras
- 一个
Bundle
,包含触发同步适配器的事件发送的标志。 - Authority
- 系统中的内容提供程序授权机构。您的应用必须有权访问此提供程序。通常,该授权机构对应于您自己应用中的内容提供程序。
- 内容提供程序客户端
- 指向由 Authority 参数指定的内容提供程序的
ContentProviderClient
。ContentProviderClient
是内容提供程序的轻量级公共接口。它具有与ContentResolver
相同的基础功能。如果您使用内容提供程序为您的应用存储数据,您可以使用此对象连接到该提供程序。否则,您可以忽略它。 - 同步结果
- 一个
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
的类,实例化您的同步适配器组件,并获取 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()
方法中。以下代码片段向您展示了如何执行此操作
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. */ } } ... }
添加同步适配器元数据文件
要将您的同步适配器组件插入框架中,您需要向框架提供描述该组件并提供额外标志的元数据。元数据指定了您为同步适配器创建的帐户类型,声明了与您的应用关联的内容提供程序授权机构,控制了与同步适配器相关的部分系统用户界面,并声明了其他同步相关标志。在您的应用项目的 /res/xml/
目录中存储的特殊 XML 文件中声明此元数据。您可以为该文件指定任何名称,但通常将其称为 syncadapter.xml
。
此 XML 文件包含一个单独的 XML 元素 <sync-adapter>
,它具有以下属性
android:contentAuthority
- 您的内容提供程序的 URI 授权机构。如果您在上一课创建桩内容提供程序中为您的应用创建了桩内容提供程序,请使用您在添加到应用清单中的
<provider>
元素中为属性android:authorities
指定的值。此属性在在清单中声明提供程序部分中有更详细的描述。
如果您使用同步适配器将数据从内容提供程序传输到服务器,则此值应与您用于该数据的内容 URI 授权机构相同。此值也是您在应用清单中声明提供程序的<provider>
元素的android:authorities
属性中指定的授权机构之一。 android:accountType
- 同步适配器框架要求的帐户类型。该值必须与您在创建认证器元数据文件时提供的帐户类型值相同,如添加认证器元数据文件部分所述。它也是您在添加框架要求的帐户部分的代码片段中为常量
ACCOUNT_TYPE
指定的值。 - 设置属性
-
-
android:userVisible
- 设置同步适配器帐户类型的可见性。默认情况下,与帐户类型关联的帐户图标和标签在系统“设置”应用的帐户部分中可见,因此除非您有易于与您的应用关联的帐户类型或域,否则应使您的同步适配器不可见。如果您的帐户类型不可见,您仍然可以在您的应用某个活动中使用用户界面允许用户控制您的同步适配器。
-
android:supportsUploading
- 允许您将数据上传到云端。如果您的应用只下载数据,请将其设置为
false
。 -
android:allowParallelSyncs
- 允许您的同步适配器组件的多个实例同时运行。如果您的应用支持多个用户帐户并希望允许多个用户并行传输数据,请使用此属性。如果您从不运行多个数据传输,此标志无效。
-
android:isAlwaysSyncable
- 向同步适配器框架指示它可以在您指定的任何时间运行您的同步适配器。如果您想以编程方式控制何时运行您的同步适配器,请将此标志设置为
false
,然后调用requestSync()
来运行同步适配器。要了解有关运行同步适配器的更多信息,请参阅课程运行同步适配器
-
以下示例显示了使用单个占位符帐户且仅执行下载的同步适配器的 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
。
由于同步适配器组件运行在网络和设备之间传输数据的代码,因此您需要请求访问 Internet 的权限。此外,您的应用需要请求读取和写入同步适配器设置的权限,以便您可以从应用中的其他组件以编程方式控制同步适配器。您还需要请求一项特殊权限,允许您的应用使用您在课程创建桩认证器中创建的认证器组件。
要请求这些权限,请将以下内容作为 <manifest>
的子元素添加到您的应用清单中
-
android.permission.INTERNET
- 允许同步适配器代码访问 Internet,以便它可以从设备下载或上传数据到服务器。如果您之前已经请求过此权限,则无需再次添加。
-
android.permission.READ_SYNC_SETTINGS
- 允许您的应用读取当前的同步适配器设置。例如,您需要此权限才能调用
getIsSyncable()
。 -
android.permission.WRITE_SYNC_SETTINGS
- 允许您的应用控制同步适配器设置。您需要此权限才能使用
addPeriodicSync()
设置定期同步适配器运行。调用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>
元素设置一个由系统发送的意图操作 android.content.SyncAdapter
触发的过滤器,用于运行同步适配器。触发过滤器时,系统会启动您创建的绑定服务,在此示例中为 SyncService
。属性 android:exported="false"
只允许您的应用和系统访问 Service
。属性 android:process=":sync"
告诉系统在名为 sync
的全局共享进程中运行 Service
。如果您的应用中有多个同步适配器,它们可以共享此进程,从而减少开销。
<meta-data>
元素提供了您之前创建的同步适配器元数据 XML 文件的名称。 android:name
属性表示此元数据用于同步适配器框架。 android:resource
元素指定元数据文件的名称。
您现在已经拥有了同步适配器的所有组件。下一课将向您展示如何告诉同步适配器框架运行您的同步适配器,无论是响应事件还是定期调度。