为什么ContentResolver.requestSync不会触发同步?

Ben*_*Ben 112 android android-contentprovider android-syncadapter

我正在尝试实现Google IO中讨论的内容提供商同步适配器模式- 幻灯片26.我的内容提供商正在工作,当我从Dev Tools Sync Tester应用程序触发它时,我的同步工作,但是当我调用ContentResolver时.来自我的ContentProvider的requestSync(帐户,权限,捆绑),我的同步永远不会被触发.

ContentResolver.requestSync(
        account, 
        AUTHORITY, 
        new Bundle());
Run Code Online (Sandbox Code Playgroud)

编辑 - 添加的清单片段我的清单xml包含:

<service
    android:name=".sync.SyncService"
    android:exported="true">
    <intent-filter>
        <action
            android:name="android.content.SyncAdapter" />
    </intent-filter>
    <meta-data android:name="android.content.SyncAdapter"
    android:resource="@xml/syncadapter" />
</service>
Run Code Online (Sandbox Code Playgroud)

- 编辑

与我的同步服务关联的syncadapter.xml包含:

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"  
    android:contentAuthority="AUTHORITY"
    android:accountType="myaccounttype"
    android:supportsUploading="true"
/>
Run Code Online (Sandbox Code Playgroud)

不确定其他代码会有用.传递给requestSync的帐户是"myaccounttype",传递给调用的AUTHORITY与我的syc适配器xml匹配.

ContentResolver.requestSync是否是请求同步的正确方法?看起来同步测试工具直接绑定到服务并调用启动同步,但这似乎违背了与同步架构集成的目的.

如果这是请求同步的正确方法,那么同步测试器为什么会工作,而不是我对ContentResolver.requestSync的调用?我需要在捆绑中传递一些东西吗?

我正在运行2.1和2.2的设备上的模拟器中进行测试.

jcw*_*ger 280

呼叫requestSync()仅适用于系统已知的{Account,ContentAuthority}对.您的应用需要经过多个步骤告诉Android您能够使用特定类型的帐户同步特定类型的内容.它在AndroidManifest中执行此操作.

1.通知Android您的应用程序包提供同步

首先,在AndroidManifest.xml中,您必须声明您有一个同步服务:

<service android:name=".sync.mySyncService" android:exported="true">
   <intent-filter>
      <action android:name="android.content.SyncAdapter" /> 
    </intent-filter>
    <meta-data 
        android:name="android.content.SyncAdapter" 
        android:resource="@xml/sync_myapp" /> 
</service>
Run Code Online (Sandbox Code Playgroud)

<service>标签的name属性是连接同步的类的名称...我将在一秒钟内与之交谈.

设置导出的true使其对其他组件可见(需要这样ContentResolver才能调用它).

意图过滤器允许它捕获请求同步的意图.(这Intent来自ContentResolver您调用ContentResolver.requestSync()或相关的调度方法时.)

<meta-data>标签将在下面讨论.

2.为Android提供用于查找SyncAdapter的服务

所以班级本身......这是一个例子:

public class mySyncService extends Service {

    private static mySyncAdapter mSyncAdapter = null;

    public SyncService() {
        super();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (mSyncAdapter == null) {
            mSyncAdapter = new mySyncAdapter(getApplicationContext(), true);
        }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return mSyncAdapter.getSyncAdapterBinder();
    }
}
Run Code Online (Sandbox Code Playgroud)

你的类必须扩展Service或者它的一个子类必须实现public IBinder onBind(Intent),并且必须SyncAdapterBinder在调用时返回...你需要一个类型的变量AbstractThreadedSyncAdapter.所以你可以看到,那几乎是那个班级的一切.它的唯一原因是提供一个服务,它为Android提供了一个标准界面,可以向您的班级查询您SyncAdapter自己的内容.

3.提供class SyncAdapter实际执行同步的功能.

mySyncAdapter是存储真实同步逻辑的地方.它的onPerformSync()方法在同步时被调用.我想你已经有了这个.

4.在Account-type和Content Authority之间建立绑定

再回顾AndroidManifest,<meta-data>我们服务中的那个奇怪的标签是建立ContentAuthority和帐户之间绑定的关键部分.它从外部引用另一个xml文件(无论你喜欢什么,都可以调用它,与你的app相关.)让我们看一下sync_myapp.xml:

<?xml version="1.0" encoding="utf-8" ?> 
<sync-adapter 
    xmlns:android="http://schemas.android.com/apk/res/android"   
    android:contentAuthority="com.android.contacts"
    android:accountType="com.google" 
    android:userVisible="true" /> 
Run Code Online (Sandbox Code Playgroud)

好的,那又是什么呢?它告诉Android我们定义的同步适配器(在<service>包含<meta-data>引用此文件的标记的标记的name元素中调用的类...)将使用com.google样式帐户同步联系人.

您的所有contentAuthority字符串必须全部匹配,并与您正在同步的内容匹配 - 如果您正在创建自己的数据库,那么这应该是您定义的字符串,或者如果您正在同步,则应该使用一些现有的设备字符串数据类型(如联系人或日历事件或你有什么.)以上("com.android.contacts")碰巧是联系人类型数据的ContentAuthority字符串(惊讶,惊讶).

accountType还必须匹配已输入的已知帐户类型之一,或者必须匹配您正在创建的帐户类型(这涉及创建AccountAuthenticator的子类以在您的服务器上获取身份验证......值得一篇文章本身.)同样,"com.google"是用于标识... google.com样式帐户凭据的已定义字符串(同样,这不应该是一个惊喜.)

5.在给定的Account/ContentAuthority对上启用同步

最后,必须启用同步.您可以在控制面板的"帐户和同步"页面中执行此操作,方法是转到您的应用并在匹配的帐户中设置应用旁边的复选框.或者,您可以在应用中的某些设置代码中执行此操作:

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
Run Code Online (Sandbox Code Playgroud)

要进行同步,必须启用您的帐户/权限对进行同步(如上所述),并且必须设置系统上的整体全局同步标志,并且设备必须具有网络连接.

如果您的帐户/权限同步或全局同步被禁用,则调用RequestSync()确实有效 - 它设置了一个已请求同步的标志,并将在同步启用后立即执行.

另外,根据mgv,ContentResolver.SYNC_EXTRAS_MANUAL在requestSync的extras包中设置为true将要求android强制同步,即使全局同步已关闭(尊重您的用户!)

最后,您可以使用ContentResolver函数再次设置定期计划同步.

6.考虑多个帐户的含义

可以有多个相同类型的帐户(在一个设备上设置两个@ gmail.com帐户或两个facebook帐户,或两个Twitter帐户等等).您应该考虑这样做的应用含义. ..如果您有两个帐户,您可能不希望尝试将它们同步到同一个数据库表中.也许您需要指定一次只能激活一个,并在切换帐户时刷新表并重新同步.(通过查询存在的帐户的属性页).也许你为每个帐户创建一个不同的数据库,可能是不同的表,也许是每个表中的一个关键列.所有应用程序都具体而且值得一些思考. ContentResolver.setIsSyncable(Account account, String authority, int syncable)可能有兴趣在这里. setSyncAutomatically()控制是否选中取消选中某个帐户/权限对,同时setIsSyncable()提供取消选中该行的方法并使其灰显,以便用户无法将其打开.您可以将一个帐户设置为Syncable,另一个帐户不可同步(dsabled).

7.注意ContentResolver.notifyChange()

一件棘手的事情.ContentResolver.notifyChange()ContentProviders用于通知Android本地数据库已更改的函数.这有两个功能,首先,它会导致游标跟随内容uri更新,反过来重新查询和无效并重绘ListView等等......这非常神奇,数据库会ListView自动更新并且只是更新.真棒.此外,当数据库发生更改时,Android将为您请求同步,即使在正常计划之外也是如此,以便这些更改从设备中取出并尽快同步到服务器.也很棒.

但是有一个边缘的情况.如果你从服务器拉出来,并将更新推送到服务器,ContentProvider它会尽职尽责地调用notifyChange(),android会去,"哦,数据库更改,最好把它们放在服务器上!" (Doh!)编写良好的ContentProviders将有一些测试来查看更改是来自网络还是来自用户,syncToNetwork如果是这样,将设置布尔标志为false,以防止这种浪费的双重同步.如果您正在将数据输入a ContentProvider,那么您应该弄清楚如何使其工作 - 否则,当只需要一个时,您最终会执行两次同步.

8.感到高兴!

准备好所有这些xml元数据并启用同步后,Android将知道如何为您连接所有内容,并且同步应该开始工作.在这一点上,很多很好的东西只会点击到位,它会感觉像魔术一样.请享用!

  • 即使全局同步设置已关闭,您也可以请求同步.只需在extras Bundle中添加一个`ContentResolver.SYNC_EXTRAS_MANUAL`设置为true,你就会强制同步:) (22认同)
  • ContentResolver.setSyncAutomatically(account,AUTHORITY,true); (11认同)
  • @kaciula:我不知道,但是设备会记住它需要同步,并且一旦打开全局同步就会启动它.你真的不应该试图在这个问题上胜过用户 - 尤其是"全局同步关闭"是在常规情况下节省电池的关键方法之一.如果你真的担心数据没有被同步,可以考虑一个弹出窗口,告诉用户数据为什么不移动,如果它已经坐了一段时间.这样,您可以教育意外错误配置设备的用户,并在他们忘记时提醒高级用户. (2认同)
  • 可能值得补充一点,如果你想使用addPeriodicSync(),它似乎只有你自己setSyncAutomatically() - 我添加了绝望,试图让SOMETHING工作.我知道这不是原始问题的一部分,但这是一个完整的答案! (2认同)