2011-08-12 25 views
0

我想我可能在這裏錯誤的軌道,所以我張貼這個發現。將AsyncTask與n-tier模型結合使用

我正在開發一個使用大量休息服務請求加載其數據的android應用程序。

我安排我的代碼n層圖案下列:域層(域)/ web服務接入層(DAL)/業務邏輯層(服務)/ UI層(UI)

從我的活動餘啓動AsyncTask從服務層加載項目。當物品加載時,我會顯示一個帶有連續循環圖標的進度對話框。這不是非常用戶友好的,所以我想通過進度指示器顯示進度。

我可以很容易地改變我的dal,以便它首先獲取要提取的項目的id列表,然後逐個提取它們,而不是批量提取。這樣我就可以向用戶報告進度,而每次加載新項目(或2個或更多)時更新ui。

問題是我不知道如何去做這件事。 AsyncTask的所有例子的工作原理是,一切都在繼承AsyncTask的類中發生,並且必須從ui線程執行。

有沒有人可以提供關於如何定義AsyncTask類的簡短代碼示例,以及如何從數據訪問層向UI報告進度?

如果我把所有的代碼放在UI中,就沒有問題,但這不是很好的做法。

什麼,我想要做的

代碼示例:

某處MainApplication我把訂單列表:

public List<Order> orders = null; 

我的活動調用的AsyncTask並啓動它:

OrderActivity.java

public void loadOrdersAsync() { 
    // Create async task instance 
    OrderLoaderAsyncTask loader = new OrderLoaderAsyncTask(); 

    // Start loading 
    loader.doInBackground(); 
} 

OrderLoadAsyncTask.java

public class OrderLoaderAsyncTask extends AsyncTask { 
    @Override 
    public Integer doInBackground(Context.... params) { 
     OrderService service = new OrderService(); 
     MainApplication.orders = service.getAll(); 
    } 

    @Override 
    public void onProgressUpdate(Integer... values) { 
     super.onProgressUpdate(values); 

     // Update progress bar 
     updateProgressBar(values); 
    } 
} 

OrderService.java

public class OrderService { 
    public List<Order> getAll() { 
     // Check to see if we have a cached version 
     // If we don't, we fetch the orders from REST webservice 
     OrderRepository repository = new OrderRepository(); 

     // Get list of ID's to fetch 
     List<Integer> ids = new ArrayList<Integer>; 
     List<Order> orders = new ArrayList<Order>; 
     ids = repository.getIDs(); 

     // For each id, fetch order from webservice 
     for (Integer id : ids) { 
      Order order = repository.getOneById(id); 

      orders.add(order); 

      // Report progress, but how? 
     } 

     return orders; 
    } 
} 

現在在我的服務,我可以保持進步的軌道,但我怎麼彙報這一進展到UI無需移動邏輯構建orderslist我的AsyncTask類?如何從我的服務層報告進度?甚至從我的存儲庫層?

回答

1

我沒有看到用精確的方式做到這一點,REST服務是一種請求 - 回覆,只有在REST服務器中的所有數據層被加載後,纔會收到數據。

我會用一個進度條來做,用一個定時器更新每個500mS。請記住,Timer會觸發另一個線程,因此您需要在您的活動的runOnUiThread()方法內更新UI。

示例代碼:

public class myActivity extends Activity { 

    private Timer myTimer; 

    @Override 
    public void onCreate(Bundle b) { 
     super.onCreate(b); 
     setContentView(R.layout.main); 

     myTimer = new Timer(); 
     myTimer.schedule(new TimerTask() { 
      @Override 
      public void run() { 
       TimerMethod(); 
      } 

     }, 0, 500); // 500mS starting with no delay 
    } 

    private void TimerMethod() 
    { 
      // this runs each time Timer fires 
     this.runOnUiThread(Timer_Tick); 
    } 

    private Runnable Timer_Tick = new Runnable() { 
     public void run() { 

      // do something with my progress bar 
     UpdateMyProgressBar(); 

     } 
    }; 
} 
+0

這不是一個耗時的資源嗎?更進一步說,我認爲這樣做與AsyncTask的性質背道而馳...... –

+0

這可能不是一個很好的做法,但它完全符合我想要做的事情,所以我會接受這種做法好。 –

+0

我已經下定決心了,不需要重複創建一個AsyncTask,記住AsyncTask只能被觸發一次。使用計時器,您可以定期輪詢您的數據源(不管它是什麼,休息服務器,JDBC連接,您的名稱)併發布進度。看看AsyncTask的源代碼,與我在這裏描述的不一樣,而不是他們使用處理程序的定時器。 – ruhalde

1

AsyncTask在進度更新上有一個函數調用,它允許你用你的進度更新UI線程。這是設計來完成你所描述的。

+0

至於代碼示例,這裏的文件是實現該http://developer.android.com/reference/android/os/AsyncTask.html – Pyrodante

+0

我知道的AsyncTask具有該功能。但是,說AsyncTask在它的dobackgroundwork方法中調用一個服務層函數(即在服務類的一個實例上)。服務層依次調用數據訪問層中的函數。我要如何從我的數據訪問函數中更新頂級(在ui中)的進度? –

1

我不鼓勵去一個獲取對象的一個​​原因它會超載你的數據庫,如果你有很多的記錄。考慮使用分頁功能(只有在用戶實際看到屏幕上的項目時才加載)。

你可以通過加載最少的數據(例如只有標題和描述)來顯示在列表中,然後在需要更多信息的情況下稍後獲取其餘數據。

+0

這兩種解決方案都不適合我。這不僅僅是我需要獲取的訂單,還包括訂單狀態,客戶,地址列表......該服務只允許我爲每條記錄獲取ID列表,或者獲取整個事物。但是,我可以限制它在一個請求中獲取的項目數量。在任何情況下,訂單屏幕都會顯示相當多的信息,所以最低限度需要相當多的請求。 –

+0

好吧,我已經想出了一種方法來一次獲取5個(或任何其他數字),因此,如果有20個訂單,則連續4次請求。這樣我就可以更新UI進度條,並在訂單推出時開始顯示訂單。但是,這仍然存在如何從實際執行請求的服務(或dal)層調用onProgressUpdate的問題。 –

2

我想說的第一件事是,我認爲您所描述的n層架構對於大多數移動應用來說有點矯枉過正。這並不意味着你不需要分層你的應用程序和單獨的職責。只是您的描述層對於大型企業應用程序來說確實是一個很好的架構層,但我發現將這些嚴格應用於Android代碼方面的結果與實際上(更好的)Android方法來實現這些目標時系統的對抗。

例如,有一件事情是您希望將您的「服務」與活動完全分開,但仍想更新UI中的某些內容。這不可能。

Android廣泛使用依賴注入。您會注意到,Context通常是各種方法的第一個參數。只是爲了表明如果你想完全從UI中分離出某些東西,你必須創建一個新的'Context'來工作。 Android爲此提供了服務。通過這種方式,您可以完全從UI中分離出工作,並使用意圖在後臺服務和UI之間進行消息傳遞。

但我知道對於某些應用程序或開始Android開發人員的服務似乎有點複雜(他們不是真的)。只是想指出,服務是創建與活動無關的線程的唯一真正方法。有些人通過覆蓋Application類並映射活動和線程的軟引用來使用技巧。這種方式發生配置更改(例如方向更改)時線程不會被終止,並且可以獲取對新創建的活動實例的引用。這些技巧在我看來是反模式。

回到你的問題。

我建議保持簡單。只要知道從UI中啓動一個新線程就意味着當配置發生變化時線程可能會被終止,並且可能會丟失已經從網絡中檢索到的數據。

  1. 編寫一個http實用類,它包裝了一些post/get功能,並可能爲每個請求設置一些http標頭。
  2. 編寫一個簡單的服務層。這些類包含每種方法執行一個簡單任務或休息請求的方法。
  3. 在你的活動使用的AsyncTask它結合了一些服務方法和用戶界面提供反饋

可以改善這一點以後,如果你喜歡,並從的AsyncTask移動邏輯到自己的真正後臺服務,做Intents和BroadcastReceivers的一些中間消息傳遞。

但爲了方便您做不服務的簡單方法的一個簡單的例子:

public class OrderActivity extends Activity { 

    private ProgressDialog progressDialog = null; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main); 

     new OrderLoaderTask().execute(); 
    } 

    class Order { 
     // Add some data 
    } 

    class OrderService { 

     public List<Integer> getOrderIds() { 
      List<Integer> ids = Arrays.asList(new Integer[] {1, 2, 3, 4, 5, 6, 7}); 
      return ids;   
     } 

     public Order getOrder(int id) { 
      // Do real http request through some utility class 
      try { 
       Thread.sleep(2000); 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
      return new Order(); 
     } 

    } 

    class OrderLoaderTask extends AsyncTask<Void, Integer, List<Order>> { 

     @Override 
     protected void onPreExecute() { 
      progressDialog = ProgressDialog.show(TestAndroidActivity.this, "loading", "loading"); 
     } 

     @Override 
     protected void onProgressUpdate(Integer... values) { 
      progressDialog.setMessage(String.format("loading (%s/%s)", values[0], values[1])); 
     } 

     @Override 
     protected List<Order> doInBackground(Void... params) { 

      OrderService service = new OrderService(); 

      List<Order> orders = new ArrayList<Order>(); 

      List<Integer> ids = service.getOrderIds(); 
      for (int i=0; i<ids.size(); i++) { 
       orders.add(service.getOrder(ids.get(i))); 
       publishProgress(i+1, ids.size()); 
      } 
      return orders; 
     } 

     @Override 
     protected void onPostExecute(List<Order> result) { 
      if (result != null) { 
       // Do something with the results! 
      } 
      progressDialog.dismiss(); 
     } 

    } 
} 

注:只要創建新的Android項目複製該代碼,修復導入並運行!

如果你想'重用'AsyncTask中的代碼,移動到它自己的文件,公開類和重寫方法...再次,總是有線程之間需要膠水代碼或接口。

new OrderLoaderTask() { 

    protected void onProgressUpdate(Integer[] values) { 
     // Update progress 
    }; 

    protected void onPostExecute(List<Order> result) { 
     // Do something 
    }; 

}.execute(); 
+0

最後的代碼示例正是我現在正在做的。但問題在於提取訂單的方法未存儲在服務中,因此難以重複使用。更重要的是,我的應用程序將所有內容都緩存在緩存文件中,因此服務也需要考慮到這一點。我想我會看看哪些服務對我的應用程序可能意味着什麼。 –

+0

你需要在多個地方的代碼?如果是這樣,請將AsyncTask公開並放入自己的文件中,在每個Activity中使用匿名AsyncTask,您需要此功能,並覆蓋onPostExecute和onProgessUpdate以獲取每個Activity唯一的代碼。 –