21

Google I / O RESTの講演を見て、スライドを読みました:http ://www.google.com/events/io/2010/sessions/developing-RESTful-android-apps.html

たとえば、リモートサーバーによってスローされた更新エラーを適切に処理する方法については、まだ少しわかりません。独自のContentProviderとSyncAdapterを実装しました。このシナリオを考えてみましょう。

REST呼び出しを介してユーザーの連絡先の詳細を更新します。

  1. ContentResolverを使用して更新を要求します。
  2. 私のContentProviderは、アプリのローカルSqliteデータベースをすぐに更新し、同期を要求します(Google I / Oトークの推奨事項に従って)。
  3. SyncAdapter.onPerformSync()が呼び出され、REST呼び出しを実行してリモートデータを更新します。
  4. リモートサーバーは「エラー:無効な電話番号」で応答します(たとえば)。

私の質問は、SyncAdapterがこの変更をアプリのローカルデータベースからバックアウトする必要があることをContentProviderに通知し、更新要求が失敗したことをアクティビティに通知する(そして返されたエラーメッセージを渡す)ための最良の方法は何ですか?サーバーから)?

私のアクティビティでは、結果を待っている間に進行状況スピナーを表示し、リクエストが成功したか失敗したかを知る必要があります。


サーバーからのコンテンツでローカルアプリデータベースを更新する場合、SyncAdapterパターンは私にとって完全に理にかなっており、私はそれを正常に機能させています。しかし、アプリからサーバーの更新については、上記のシナリオを処理するための良い方法を見つけることができないようです。


そして別のこと...;)

ContentResolver.notifyChange(uri、null、true);を呼び出したとします。ContentProviderのupdate()メソッド内から。trueとともにandroid:supportsUploading="true"、SyncAdapterのonPerformSync()が呼び出されます。すばらしいですが、onPerformSync()内で、どのURIを同期する必要があるかをどのように判断できますか?同期要求を受け取るたびにDB全体を単純に更新したくありません。ただし、バンドルをnotifyChangeCall()に渡して、onPerformSync()に渡すこともできません。

onPerformSync()について私が見たすべての例は非常に単純であり、カスタムContentProviderを使用していませんが、実際の例はありますか?そして、ドキュメントは少し鳥の巣です。Virgil Dobjanschi、サー、あなたは私をパドルなしで小川に残しました。

4

2 に答える 2

3

簡単な答えは、〜APIレベル7をターゲットにしている場合、「しない」です。状況は後のAPIで改善された可能性がありますが、そうであったように...SyncAdapterを完全に回避することを強くお勧めします。文書化が非常に不十分であり、「自動」アカウント/認証管理は、そのAPIも複雑で文書化が不十分であるため、高額になります。APIのこの部分は、最も些細なユースケースを超えて考え抜かれていません。

これが私が最終的に使用したパターンです。私のアクティビティの中には、カスタムHandlerスーパークラスからの単純な追加を含むハンドラーがありました(m_bStoppedブール値をチェックできます):

private ResponseHandler mHandler = new ResponseHandler();

class ResponseHandler extends StopableHandler {

    @Override
    public void handleMessage(Message msg) {
        if (isStopped()) {
            return;
        }
        if (msg.what == WebAPIClient.GET_PLANS_RESPONSE) {
            ...
        } 
        ...
    }
}

アクティビティは、以下に示すようにRESTリクエストを呼び出します。ハンドラーはWebClientクラス(HTTPリクエストなどを構築/作成するためのヘルパークラス)に渡されることに注意してください。WebClientは、アクティビティにメッセージを返すHTTP応答を受信するときにこのハンドラーを使用し、データが受信され、私の場合はSQLiteデータベースに保存されたことを通知します(これをお勧めします)。ほとんどのアクティビティでは、HTTP応答が非アクティブなアクティビティなどにシグナルで返されるのを避けるために、呼び出しと呼び出しを行います。これは、非常に堅牢なアプローチであることがわかりましたmHandler.stopHandler();onPause()mHandler.startHandler();onResume()

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" />

そして、SQLiteデータベースへのアクセスを作成および管理するためにそれを実装しました:

public class WebAPIProvider extends ContentProvider

したがって、次のように、アクティビティのデータベースにカーソルを合わせることができます。

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リクエストを作成するのに非常に役立つことがわかりました。たとえば、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);
}

これを適切なエンドポイントURLに追加します。

WebClientクラスがHTTPリクエストを実行する方法の詳細は次のとおりです。これはprocessRequest()、Runnableで以前に呼び出されたメソッドです。上記handlerのIに結果をメッセージで返すために使用されるパラメーターに注意してください。ResponseHandlerSyncAdaptersyncResultが指数バックオフなどを行うために使用するisinoutパラメーター。executeRequest()さまざまなエラーカウントなどをインクリメントして、で使用します。繰り返しになりますが、文書化が非常に不十分で、PITAが機能します。優れたSimpleXMLlibparseXML()を活用します。

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))
    {
      ...
    }
    ...

}

モバイルデータがドロップアウトした場合、またはWifiから3Gに切り替わった場合にアプリが永久に待機しないように、HTTPリクエストにタイムアウトを設定する必要があります。これにより、タイムアウトが発生した場合に例外がスローされます。

    // 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データベースは本当に良かった。そして、Handlerクラスは素晴らしいです。上記のように独自のスレッドを作成してHTTPリクエストを生成する代わりに、AsyncTaskクラスを使用します。

このとりとめのない説明が誰かに少し役立つことを願っています。

于 2012-08-18T04:55:32.090 に答える
1

オブザーバーのデザインパターンはどうですか?あなたの活動はSyncAdapterまたはデータベースのオブザーバーになることができますか?そうすれば、更新が失敗したときに、アダプターはオブザーバーに通知し、変更されたデータを処理できます。SDKには多数のObservableクラスがあり、どのクラスが状況に最も適しているかを確認してください。http://developer.android.com/search.html#q=Observer&t=0

于 2011-11-04T01:54:58.437 に答える