Android 如何使用SyncAdapter处理远程服务器的RESTful更新

Android 如何使用SyncAdapter处理远程服务器的RESTful更新,android,rest,sync,android-contentprovider,android-syncadapter,Android,Rest,Sync,Android Contentprovider,Android Syncadapter,我观看了Google I/O REST演讲并阅读了幻灯片: 我仍然不清楚如何很好地处理远程服务器抛出的更新错误。我已经实现了自己的ContentProvider和SyncAdapter。考虑这种情况: 通过REST呼叫更新用户的联系方式: 使用ContentResolver请求更新 My ContentProvider立即更新应用程序的本地Sqlite数据库并请求同步(根据Google I/O对话中的建议) 调用My SyncAdapter.onPerformSync()并执行REST调用以更新

我观看了Google I/O REST演讲并阅读了幻灯片:

我仍然不清楚如何很好地处理远程服务器抛出的更新错误。我已经实现了自己的ContentProvider和SyncAdapter。考虑这种情况:

通过REST呼叫更新用户的联系方式:

  • 使用ContentResolver请求更新
  • My ContentProvider立即更新应用程序的本地Sqlite数据库并请求同步(根据Google I/O对话中的建议)
  • 调用My SyncAdapter.onPerformSync()并执行REST调用以更新远程数据
  • 远程服务器响应“错误:无效电话号码”(例如)
  • 我的问题是,SyncAdapter向我的ContentProvider发出此更改需要从应用程序的本地数据库中备份的信号,以及向我的活动发出更新请求失败(并传递从服务器返回的错误消息)的最佳方式是什么?

    我的活动需要在等待结果时显示进度微调器,并知道请求是成功还是失败


    对于使用来自服务器的内容更新本地应用程序数据库,SyncAdapter模式对我来说完全有意义,而且我可以很好地工作。但是对于从
    应用程序到服务器的更新,我似乎找不到处理上述情况的好方法


    还有一件事……;)

    假设我调用ContentResolver.notifyChange(uri,null,true);从我的ContentProvider的update()方法中
    true
    以及
    android:supportsupload=“true”
    将导致调用我的SyncAdapter的onPerformSync()。很好,但是在onPerformSync()中,我如何告诉应该同步哪个URI?我不想每次收到同步请求时都刷新整个数据库。但您甚至不能将捆绑包传递到notifyChangeCall()中,以传递给onPerformSync()


    我看到的所有onPerformSync()示例都非常简单,没有使用自定义的ContentProvider,有没有真实的示例?这些文件有点像鸟巢。维吉尔·多布扬斯基,先生,你把我丢在河里,没有桨。

    观察家的设计模式呢?您的活动是否可以是SyncAdapter或数据库的观察者?这样,当更新失败时,适配器将通知其观察者,然后可以对更改的数据执行操作。SDK中有很多可观察的类,看看哪一个在您的情况下工作得最好

    如果您的目标是~API级别7,简短的回答是“不要”。这种情况在以后的API中可能有所改善,但事实上。。。我强烈建议完全避免使用SyncAdapter;它的文档记录非常差,“自动”帐户/身份验证管理的代价很高,因为它的API也很复杂,文档记录不足。除了最简单的用例之外,API的这一部分还没有考虑清楚

    这就是我最终选择的模式。在我的活动中,我有一个从自定义处理程序超类添加的简单处理程序(可以检查
    m_bStopped
    bool):

    该活动将调用REST请求,如下所示。注意,处理程序被传递到WebClient类(用于构建/发出HTTP请求等的帮助器类)。WebClient在接收返回给活动的HTTP响应消息时使用此处理程序,并让它知道已接收到数据,在我的情况下,数据存储在SQLite数据库中(我建议这样做)。在大多数活动中,我会调用
    mHandler.stopHandler()onPause()和
    mHandler.startHandler()中的code>onResume()
    中使用code>以避免HTTP响应被信号传回非活动活动等。这是一种非常稳健的方法

    final Bundle bundle = new Bundle();
    bundle.putBoolean(WebAPIRequestHelper.REQUEST_CREATESIMKITORDER, true);
    bundle.putString(WebAPIRequestHelper.REQUEST_PARAM_KIT_TYPE, sCVN);       
    final Runnable runnable = new Runnable() { public void run() {
        VendApplication.getWebClient().processRequest(null, bundle, null, null, null,
                        mHandler, NewAccountActivity.this);
        }};
    mRequestThread = Utils.performOnBackgroundThread(runnable);
    
    在主线程上调用
    Handler.handleMessage()
    。因此,您可以在此处停止进度对话框并安全地执行其他活动

    我声明了一个ContentProvider:

    <provider android:name="au.com.myproj.android.app.webapi.WebAPIProvider"
              android:authorities="au.com.myproj.android.app.provider.webapiprovider"
              android:syncable="true" />
    
    因此,您可以在活动中通过数据库获取游标,如下所示:

    mCursor = this.getContentResolver().query (
              WebAPIProvider.PRODUCTS_URI, null, 
              Utils.getProductsWhereClause(this), null, 
              Utils.getProductsOrderClause(this));
    startManagingCursor(mCursor);
    
    我发现
    org.apache.commons.lang3.text.StrSubstitutor
    类在构建REST API所需的笨拙XML请求方面非常有帮助,我必须与REST API集成,例如在
    WebAPIRequestHelper
    中,我有如下帮助方法:

    public static String makeAuthenticateQueryString(Bundle params)
    {
        Map<String, String> valuesMap = new HashMap<String, String>();
        checkRequiredParam("makeAuthenticateQueryString()", params, REQUEST_PARAM_ACCOUNTNUMBER);
        checkRequiredParam("makeAuthenticateQueryString()", params, REQUEST_PARAM_ACCOUNTPASSWORD);
    
        valuesMap.put(REQUEST_PARAM_APIUSERNAME, API_USERNAME);
        valuesMap.put(REQUEST_PARAM_ACCOUNTNUMBER, params.getString(REQUEST_PARAM_ACCOUNTNUMBER));
        valuesMap.put(REQUEST_PARAM_ACCOUNTPASSWORD, params.getString(REQUEST_PARAM_ACCOUNTPASSWORD));
    
        String xmlTemplate = VendApplication.getContext().getString(R.string.XMLREQUEST_AUTHENTICATE_ACCOUNT);
        StrSubstitutor sub = new StrSubstitutor(valuesMap);
        return sub.replace(xmlTemplate);
    }
    
    你应该在HTTP请求上设置一些超时,这样如果移动数据丢失,或者从Wifi切换到3G,应用程序就不会永远等待。如果发生超时,这将导致引发异常

        // Set the timeout in milliseconds until a connection is established.
        int timeoutConnection = 30000;
        HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
        // Set the default socket timeout (SO_TIMEOUT) in milliseconds which is the timeout for waiting for data.
        int timeoutSocket = 30000;
        HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
        HttpClient client = new DefaultHttpClient(httpParameters);          
    
    所以总的来说,SyncAdapter和Accounts之类的东西让我非常痛苦,花费了我很多时间,却毫无收获。ContentProvider相当有用,主要用于游标和事务支持。SQLite数据库非常好。处理程序类非常棒。我现在将使用AsyncTask类,而不是像上面那样创建自己的线程来生成HTTP请求


    我希望这个杂乱无章的解释能对某些人有所帮助。

    android Observer类似乎都不完整或不完整。例如,DataSetoObserver和ContentObserver都接收
    onChanged(boolean)
    调用,没有工具传递有关更改的任何附加信息。您所能做的就是为给定的URI调用ContentResolver.registerContentObserver(),这a)对我来说太粗糙,b)仅适用于ContentProvider,而不适用于SyncAdapter(它没有此类方法)。我想我会根据类似的原则推出自己的解决方案,但允许传递额外的参数。也许你能帮上忙。谢谢你的解释。您能添加WebClient界面的更多详细信息吗?更新了WebClient的更多详细信息。回答很好,信息非常丰富。你认为自2013年8月以来情况发生了变化吗?同步适配器概念
    public static String makeAuthenticateQueryString(Bundle params)
    {
        Map<String, String> valuesMap = new HashMap<String, String>();
        checkRequiredParam("makeAuthenticateQueryString()", params, REQUEST_PARAM_ACCOUNTNUMBER);
        checkRequiredParam("makeAuthenticateQueryString()", params, REQUEST_PARAM_ACCOUNTPASSWORD);
    
        valuesMap.put(REQUEST_PARAM_APIUSERNAME, API_USERNAME);
        valuesMap.put(REQUEST_PARAM_ACCOUNTNUMBER, params.getString(REQUEST_PARAM_ACCOUNTNUMBER));
        valuesMap.put(REQUEST_PARAM_ACCOUNTPASSWORD, params.getString(REQUEST_PARAM_ACCOUNTPASSWORD));
    
        String xmlTemplate = VendApplication.getContext().getString(R.string.XMLREQUEST_AUTHENTICATE_ACCOUNT);
        StrSubstitutor sub = new StrSubstitutor(valuesMap);
        return sub.replace(xmlTemplate);
    }
    
    public synchronized void processRequest(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult, Handler handler, Context context)
    {
        // Helper to construct the query string from the query params passed in the extras Bundle.
        HttpUriRequest request = createHTTPRequest(extras);
        // Helper to perform the HTTP request using org.apache.http.impl.client.DefaultHttpClient.
        InputStream instream = executeRequest(request, syncResult);
    
        /*
         * Process the result.
         */
        if(extras.containsKey(WebAPIRequestHelper.REQUEST_GETBALANCE))
        {
            GetServiceBalanceResponse xmlDoc = parseXML(GetServiceBalanceResponse.class, instream, syncResult);
            Assert.assertNotNull(handler);
            Message m = handler.obtainMessage(WebAPIClient.GET_BALANCE_RESPONSE, xmlDoc);
            m.sendToTarget();
        }
        else if(extras.containsKey(WebAPIRequestHelper.REQUEST_GETACCOUNTINFO))
        {
          ...
        }
        ...
    
    }
    
        // Set the timeout in milliseconds until a connection is established.
        int timeoutConnection = 30000;
        HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
        // Set the default socket timeout (SO_TIMEOUT) in milliseconds which is the timeout for waiting for data.
        int timeoutSocket = 30000;
        HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
        HttpClient client = new DefaultHttpClient(httpParameters);