19

Tôi đã theo dõi Google I/O REST của nói chuyện và đọc các slide: http://www.google.com/events/io/2010/sessions/developing-RESTful-android-apps.htmlLàm thế nào để xử lý các bản cập nhật RESTful của máy chủ từ xa với SyncAdapter

tôi vẫn còn một chút không rõ ràng về cách độc đáo xử lý, chẳng hạn, một lỗi cập nhật do máy chủ từ xa gửi. Tôi đã triển khai ContentProvider và SyncAdapter của riêng mình. Hãy xem xét kịch bản này:

Cập nhật Chi tiết liên lạc của người dùng thông qua cuộc gọi REST:

  1. Yêu cầu cập nhật bằng một ContentResolver.
  2. Trình cung cấp nội dung của tôi ngay lập tức cập nhật cơ sở dữ liệu Sqlite cục bộ của ứng dụng và yêu cầu Đồng bộ hóa (theo đề xuất trong Google I/O talk).
  3. My SyncAdapter.onPerformSync() được gọi và thực hiện cuộc gọi REST để cập nhật dữ liệu từ xa.
  4. Máy chủ từ xa phản hồi bằng "L ERI: Số điện thoại không hợp lệ" (ví dụ).

Câu hỏi của tôi là, cách tốt nhất cho SyncAdapter để ra hiệu cho ContentProvider tôi rằng sự thay đổi này cần phải được sao lưu ra khỏi cơ sở dữ liệu địa phương của ứng dụng, và cũng để ra hiệu cho Hoạt động của tôi rằng yêu cầu cập nhật thất bại là gì (và chuyển các thông báo lỗi được trả về từ Máy chủ)?

Hoạt động của tôi cần hiển thị trình quay số tiến trình trong khi đợi kết quả và biết yêu cầu đã thành công hay thất bại.


Để cập nhật cơ sở dữ liệu ứng dụng cục bộ có nội dung từ máy chủ, mẫu SyncAdapter hoàn toàn phù hợp với tôi và tôi hoạt động tốt. Nhưng đối với các bản cập nhật từ ứng dụng đến máy chủ, tôi dường như không thể tìm thấy cách hay để xử lý trường hợp trên.


Và điều khác ...;)

Nói rằng tôi gọi ContentResolver.notifyChange (uri, null, true); từ bên trong phương thức update() của ContentProvider. true cùng với android:supportsUploading="true" sẽ làm cho onPerformSync() của SyncAdapter của tôi được gọi. Tuyệt vời, nhưng bên trong onPerformSync(), làm cách nào để tôi biết URI nào tôi nên đồng bộ hóa? Tôi không muốn đơn giản làm mới toàn bộ DB của mình mỗi khi tôi nhận được yêu cầu Đồng bộ hóa. Nhưng bạn thậm chí không thể vượt qua một Bundle vào notifyChangeCall() để được truyền cho onPerformSync().

Tất cả các ví dụ tôi đã thấy trên onPerformSync() đã rất đơn giản và không sử dụng Trình tùy chỉnh nội dung tùy chỉnh, bất kỳ ví dụ thực tế nào ngoài đó? Và các tài liệu là một chút của tổ chim. Virgil Dobjanschi, Thưa ngài, anh đã để lại cho tôi con lạch mà không có một mái chèo.

+0

Tôi đang vật lộn với [thiết lập tương tự] (http://stackoverflow.com/questions/11906172/synchronize-android-client-and-rest-server). Có lẽ bạn có thể giúp đỡ. – JJD

Trả lời

3

Câu trả lời ngắn gọn, nếu bạn đang nhắm mục tiêu ~ API cấp 7, là "Không". Tình hình có thể đã được cải thiện trong các API sau nhưng vì nó ...Tôi rất muốn khuyên bạn nên tránh SyncAdapter hoàn toàn; tài liệu này rất kém và quản lý tài khoản/xác thực "tự động" có giá cao vì API cho nó cũng phức tạp và không được ghi chép. Điều này một phần của API đã không được nghĩ đến thông qua các trường hợp sử dụng tầm thường nhất.

Vì vậy, đây là mẫu mà tôi đã kết thúc. Bên trong các hoạt động của tôi, tôi đã có một bộ xử lý với một bổ sung đơn giản từ một lớp cha tùy chỉnh Handler (có thể kiểm tra cho một bool 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) { 
      ... 
     } 
     ... 
    } 
} 

Các hoạt động sẽ gọi yêu cầu REST của như hình dưới đây. Lưu ý, trình xử lý được chuyển qua lớp WebClient (lớp trợ giúp để xây dựng/tạo các yêu cầu HTTP, v.v.). WebClient sử dụng trình xử lý này khi nó nhận được phản hồi HTTP cho thông điệp quay lại hoạt động và cho nó biết dữ liệu đã được nhận và, trong trường hợp của tôi, được lưu trữ trong cơ sở dữ liệu SQLite (mà tôi muốn giới thiệu). Trong hầu hết các Hoạt động, tôi sẽ gọi mHandler.stopHandler(); trong onPause()mHandler.startHandler(); trong onResume() để tránh phản hồi HTTP được báo hiệu trở lại Hoạt động không hoạt động, v.v ... Đây là một cách tiếp cận khá mạnh mẽ.

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() được gọi trên chuỗi chính. Vì vậy, bạn có thể dừng các hộp thoại tiến trình của mình ở đây và thực hiện các thao tác khác một cách an toàn.

tôi tuyên bố một ContentProvider:

<provider android:name="au.com.myproj.android.app.webapi.WebAPIProvider" 
      android:authorities="au.com.myproj.android.app.provider.webapiprovider" 
      android:syncable="true" /> 

Và thực hiện nó để tạo và quản lý truy cập vào db SQLite:

public class WebAPIProvider extends ContentProvider 

Vì vậy, bạn có thể có được con trỏ trên cơ sở dữ liệu trong hoạt động của mình như thế này :

mCursor = this.getContentResolver().query (
      WebAPIProvider.PRODUCTS_URI, null, 
      Utils.getProductsWhereClause(this), null, 
      Utils.getProductsOrderClause(this)); 
startManagingCursor(mCursor); 

Tôi tìm thấy lớp org.apache.commons.lang3.text.StrSubstitutor là imme rất hữu ích trong việc xây dựng các yêu cầu XML vụng về theo yêu cầu của API REST mà tôi đã tích hợp với ví dụ. trong WebAPIRequestHelper Tôi có các phương pháp trợ giúp như:

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); 
} 

Tôi sẽ thêm vào URL điểm cuối thích hợp.

Dưới đây là một số chi tiết khác về cách lớp WebClient thực hiện yêu cầu HTTP. Đây là phương thức processRequest() được gọi trước đó trong Runnable. Chú ý tham số handler được sử dụng để thông báo kết quả trở lại ResponseHandler Tôi đã mô tả ở trên. Các syncResult là trong tham số được sử dụng bởi các SyncAdapter để làm ngược lại theo cấp số nhân vv Tôi sử dụng nó trong executeRequest(), gia tăng số lỗi khác nhau của nó vv Một lần nữa, rất kém tài liệu và một PITA để có được làm việc. parseXML() tận dụng tuyệt vời Simple XML lib.

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

} 

Bạn nên đặt một số thời gian chờ trên yêu cầu HTTP để ứng dụng không đợi mãi nếu dữ liệu di động bị loại bỏ hoặc chuyển từ Wifi sang 3G. Điều này sẽ gây ra một ngoại lệ để được ném nếu thời gian chờ xảy ra.

// 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);   

Vì vậy, tổng thể, SyncAdapter và tài khoản công cụ là một nỗi đau tổng số và chi phí cho tôi rất nhiều thời gian cho không đạt được. ContentProvider khá hữu ích, chủ yếu cho hỗ trợ con trỏ và giao dịch. Cơ sở dữ liệu SQLite thực sự tốt. Và lớp Handler thật tuyệt vời.Tôi sẽ sử dụng lớp AsyncTask bây giờ thay vì tạo các Threads của riêng bạn như tôi đã làm ở trên để sinh ra các yêu cầu HTTP.

Tôi hy vọng lời giải thích rambling này sẽ giúp ai đó một chút.

+0

Tôi đánh giá cao sự giải thích của bạn. Bạn có thể vui lòng thêm chi tiết hơn về giao diện * WebClient * của bạn không? – JJD

+0

Cập nhật với nhiều chi tiết hơn trên WebClient. –

+0

Câu trả lời hay, rất thông tin. Bạn có nghĩ mọi thứ đã thay đổi kể từ tháng 8 năm 2013 không? SyncAdapter khái niệm phù hợp với nhu cầu của tôi rất độc đáo. –

1

Điều gì về mẫu thiết kế Observer? Hoạt động của bạn có thể là người quan sát của SyncAdapter hoặc cơ sở dữ liệu không? Bằng cách đó khi cập nhật không thành công, bộ điều hợp sẽ thông báo cho các quan sát viên của mình và sau đó có thể hành động trên dữ liệu đã thay đổi. Có một loạt các lớp có thể quan sát trong SDK, xem lớp nào hoạt động tốt nhất trong trường hợp của bạn. http://developer.android.com/search.html#q=Observer&t=0

+0

Các lớp học Observer android tất cả dường như không đầy đủ hoặc một phần. Ví dụ DataSetObserver và ContentObserver đều nhận được các cuộc gọi 'onChanged (boolean)', không có cơ sở nào để truyền bất kỳ thông tin bổ sung nào về thay đổi. Tất cả những gì bạn có thể làm là gọi ContentResolver.registerContentObserver() cho một URI đã cho, là a) quá thô cho các mục đích của tôi và b) chỉ áp dụng cho ContentProvider, không phải là SyncAdapter (không có phương thức như vậy). Tôi nghĩ rằng tôi sẽ cuộn giải pháp của riêng tôi dựa trên các nguyên tắc tương tự nhưng cho phép thông số bổ sung được thông qua. –

Các vấn đề liên quan