2011-11-03 15 views
7

Đầu tiên là vấn đề:Android - Chiến lược nhóm chủ đề và Trình tải có thể được sử dụng để triển khai nó?

  • tôi đang làm việc trên các ứng dụng sử dụng nhiều FragmentLists trong một tùy chỉnh FragmentStatePagerAdapter. Có thể có, số lượng đáng kể các đoạn như vậy có thể từ 20 đến 40.
  • Mỗi đoạn là một danh sách trong đó mỗi mục có thể chứa văn bản hoặc hình ảnh.
  • Các hình ảnh cần phải được tải lên không đồng bộ từ các trang web và được lưu trữ để bộ nhớ cache tạm thời và cũng để SD nếu có
  • Khi Fragment đi ra khỏi màn hình bất kỳ cập nhật và hoạt động hiện tại nên bị hủy bỏ (không bị tạm dừng)

Triển khai đầu tiên của tôi theo sau nổi tiếng image loader code từ Google. Vấn đề của tôi với mã đó là về cơ bản nó tạo ra một thể hiện của AsyncTask cho mỗi hình ảnh. Mà trong trường hợp của tôi giết chết ứng dụng thật nhanh.

Vì tôi đang sử dụng gói tương thích v4, tôi đã nghĩ rằng việc sử dụng Trình tải tùy chỉnh mở rộng AsyncTaskLoader sẽ giúp tôi kể từ khi triển khai bên trong một nhóm luồng. Tuy nhiên để ngạc nhiên khó chịu của tôi nếu tôi thực thi mã này nhiều lần mỗi lời gọi sau đây sẽ làm gián đoạn trước đó. Nói rằng tôi có điều này trong phương pháp ListView#getView tôi:

getSupportLoaderManager().restartLoader(0, args, listener); 

Phương pháp này được thực hiện trong vòng lặp cho mỗi mục trong danh sách đó đi vào xem. Và như tôi đã nói - mỗi lời kêu gọi sau sẽ chấm dứt cuộc gọi trước đó. Hoặc ít nhất đó là những gì xảy ra dựa trên LogCat

11-03 13:33:34.910: V/LoaderManager(14313): restartLoader in LoaderManager: args=Bundle[{URL=http://blah-blah/pm.png}] 
11-03 13:33:34.920: V/LoaderManager(14313): Removing pending loader: LoaderInfo{405d44c0 #2147483647 : ImageLoader{405118a8}} 
11-03 13:33:34.920: V/LoaderManager(14313): Destroying: LoaderInfo{405d44c0 #2147483647 : ImageLoader{405118a8}} 
11-03 13:33:34.920: V/LoaderManager(14313): Enqueuing as new pending loader 

Sau đó tôi nghĩ rằng có thể cung cấp id duy nhất cho mỗi trình tải sẽ giúp ích cho vấn đề nhưng dường như không có sự khác biệt nào. Kết quả là tôi kết thúc với hình ảnh dường như ngẫu nhiên và các ứng dụng không bao giờ tải ngay cả 1/4 những gì tôi cần.

Các Câu hỏi

  • Điều gì sẽ là cách để sửa chữa Loader để làm những gì tôi muốn (và có cách nào?)
  • Nếu một cách tốt để tạo AsyncTask hồ bơi không phải là những gì và là có lẽ làm việc thực hiện nó?

Để cung cấp cho bạn ý tưởng về mã, đây là phiên bản rút gọn của Trình tải nơi tải xuống/lưu logic thực tế nằm trong lớp ImageManager riêng biệt.

public class ImageLoader extends AsyncTaskLoader<TaggedDrawable> { 
     private static final String TAG = ImageLoader.class.getName(); 
     /** Wrapper around BitmapDrawable that adds String field to id the drawable */ 
     TaggedDrawable img; 
     private final String url; 
     private final File cacheDir; 
     private final HttpClient client; 


    /** 
    * @param context 
    */ 
    public ImageLoader(final Context context, final String url, final File cacheDir, final HttpClient client) { 
     super(context); 
     this.url = url; 
     this.cacheDir = cacheDir; 
     this.client = client; 
    } 

    @Override 
    public TaggedDrawable loadInBackground() { 
     Bitmap b = null; 
     // first attempt to load file from SD 
     final File f = new File(this.cacheDir, ImageManager.getNameFromUrl(url)); 
     if (f.exists()) { 
      b = BitmapFactory.decodeFile(f.getPath()); 
     } else { 
      b = ImageManager.downloadBitmap(url, client); 
      if (b != null) { 
       ImageManager.saveToSD(url, cacheDir, b); 
      } 
     } 
     return new TaggedDrawable(url, b); 
    } 

    @Override 
    protected void onStartLoading() { 
     if (this.img != null) { 
      // If we currently have a result available, deliver it immediately. 
      deliverResult(this.img); 
     } else { 
      forceLoad(); 
     } 
    } 

    @Override 
    public void deliverResult(final TaggedDrawable img) { 
     this.img = img; 
     if (isStarted()) { 
      // If the Loader is currently started, we can immediately deliver its results. 
      super.deliverResult(img); 
     } 
    } 

    @Override 
    protected void onStopLoading() { 
     // Attempt to cancel the current load task if possible. 
     cancelLoad(); 
    } 

    @Override 
    protected void onReset() { 
     super.onReset(); 
     // Ensure the loader is stopped 
     onStopLoading(); 
     // At this point we can release the resources associated with 'apps' 
     // if needed. 
     if (this.img != null) { 
      this.img = null; 
     } 

    } 

} 
+0

'AsyncTask' đã sử dụng một nhóm. Các hồ bơi đi lên đến 128 chủ đề IIRC, có thể là nguồn gốc của khó khăn của bạn. Bạn luôn có thể triển khai nhóm luồng của riêng mình bằng cách sử dụng các lớp 'java.util.concurrent'. – CommonsWare

+1

Nếu sự phát triển của bạn nhắm mục tiêu đến Android 3.0 (API Cấp 11), bạn có thể sử dụng API mới được thêm [AsyncTask.executeOnExecutor()] (http://developer.android.com/reference/android/os/AsyncTask.html#executeOnExecutor% 28java.util.concurrent.Executor,% 20Params ...% 29) kiểm soát tốt hồ bơi thread của bạn với vòng đời tạo ra AsyncTask của bạn. – yorkw

+0

Tuy nhiên, AsynkTask chỉ có thể được thực hiện một lần vì vậy tôi cần tạo một cá thể cho mỗi hình ảnh tôi đang tải. làm cho nó 60 và đó là rất nhiều đối tượng – Bostone

Trả lời

11

Ok, vì vậy những điều đầu tiên trước tiên. AsyncTask đi kèm với Android không nên làm hỏng ứng dụng của bạn hoặc khiến ứng dụng bị lỗi. AsyncTasks chạy trong một hồ bơi thread, nơi có nhiều nhất là 5 chủ đề thực sự thực hiện cùng một lúc. Trong khi bạn có thể xếp hàng nhiều nhiệm vụ được thực thi, chỉ có 5 tác vụ được thực hiện tại một thời điểm. Bằng cách thực hiện những điều này trong threadpool nền họ không nên có bất kỳ tác dụng trên ứng dụng của bạn cả, họ chỉ nên chạy trơn tru.

Sử dụng AsyncTaskLoader sẽ không giải quyết được sự cố của bạn nếu bạn không hài lòng với hiệu suất trình tải AsyncTask. AsyncTaskLoader chỉ lấy giao diện bộ tải và kết nối nó với AsyncTask. Vì vậy, nó chủ yếu là bản đồ onLoadFinished -> onPostExecute, onStart -> onLoadInBackground. Vì vậy, đó là điều chính xác.

Chúng tôi sử dụng cùng một mã trình tải hình ảnh cho ứng dụng của chúng tôi khiến một asynctask được đặt vào hàng đợi của luồng đi lang thang mỗi khi chúng tôi cố tải hình ảnh. Trong ví dụ của google, họ liên kết các lần xem hình ảnh với nhiệm vụ không đồng bộ của nó để họ có thể hủy tác vụ không đồng bộ nếu họ cố gắng sử dụng lại số lần xem hình ảnh trong một số loại bộ điều hợp. Bạn nên thực hiện một chiến lược tương tự ở đây. Bạn nên liên kết số lần xem hình ảnh của mình với tác vụ không đồng bộ đang tải hình ảnh trong nền. Khi bạn có một đoạn không hiển thị, bạn có thể xoay vòng qua các khung nhìn hình ảnh của bạn được liên kết với đoạn đó và hủy các tác vụ tải. Chỉ cần sử dụng AsyncTask.cancel() sẽ hoạt động tốt.

Bạn cũng nên cố gắng triển khai cơ chế lưu vào bộ nhớ hình ảnh đơn giản ví dụ xem hình ảnh không đồng bộ mô tả. Chúng tôi chỉ đơn giản là tạo ra một hashmap tĩnh mà đi từ url -> weakreference. Bằng cách này, các hình ảnh có thể được tái chế khi chúng cần là vì chúng chỉ được giữ với một tham chiếu yếu.

Dưới đây là một phác thảo của việc tải hình ảnh mà chúng ta làm

public class LazyLoadImageView extends ImageView { 
     public WeakReference<ImageFetchTask> getTask() { 
     return task; 
    } 

    public void setTask(ImageFetchTask task) { 
     this.task = new WeakReference<ImageFetchTask>(task); 
    } 

    private WeakReference<ImageFetchTask> task; 

     public void loadImage(String url, boolean useCache, Drawable loadingDrawable){ 

     BitmapDrawable cachedDrawable = ThumbnailImageCache.getCachedImage(url); 
     if(cachedDrawable != null){ 
      setImageDrawable(cachedDrawable); 
      cancelDownload(url); 
      return; 
     } 

     setImageDrawable(loadingDrawable); 

     if(url == null){ 
      makeDownloadStop(); 
      return; 
     } 

     if(cancelDownload(url)){ 
      ImageFetchTask task = new ImageFetchTask(this,useCache); 
      this.task = new WeakReference<ImageFetchTask>(task); 
      task.setUrl(url); 
      task.execute(); 
     } 


     ...... 

     public boolean cancelDownload(String url){ 

     if(task != null && task.get() != null){ 

      ImageFetchTask fetchTask = task.get(); 
      String downloadUrl = fetchTask.getUrl(); 

      if((downloadUrl == null) || !downloadUrl.equals(url)){ 
       fetchTask.cancel(true); 
       return true; 
      } else 
       return false; 
     } 

     return true; 

      } 
    } 

Vì vậy, chỉ cần xoay qua xem hình ảnh của bạn có trong đoạn của bạn và sau đó hủy bỏ chúng khi bạn da mảnh và hiển thị chúng khi đoạn của bạn là nhìn thấy được.

+0

Đây là chiến lược tôi đã sử dụng mặc dù tôi sử dụng ModernAsyncTask với hồ bơi được tùy chỉnh, tham chiếu yếu và hủy tải lên không được hiển thị. Nhưng tôi nghĩ Loader không chỉ đơn giản là sử dụng lại AsyncTask. Ví dụ, tôi có thể thấy rằng nếu tôi lên lịch thực thi nhiều lần trong kế tiếp nhanh, lệnh gọi thứ hai hủy bỏ lệnh trước đó. Tôi đoán tôi sẽ phải chi tiêu một đêm chỉ cần thiết lập các dự án mẫu và đi qua các mã để hiểu đầy đủ cách Loader hoạt động – Bostone

+2

Loader không làm gì cả. AsyncTaskLoader là triển khai Trình tải bằng cách sử dụng AsyncTask. Theo như luồng đi, nó thêm * không có gì * mà không có trong AsyncTask. Khi nó cần phải hủy bỏ một cái gì đó, nó chỉ hủy AsyncTask mà bạn có thể tự mình làm với một AsyncTask. – hackbod

+0

Được rồi, nếu Dianne nói vậy :) Nói rằng - Tôi vẫn thấy hành vi mà nhiều nhiệm vụ không được xếp hàng nhưng bị hủy thay vì chỉ có người cuối cùng còn lại. Đó có phải là hành vi đúng đắn không? – Bostone

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