2015-02-13 4 views
5

Пожалуйста, не закрывайте это, ИМХО это приличный и, возможно, полезный вопрос программирования.Android: лучшая практика для выполнения асинхронных операций в getView()

Пожалуйста, я читаю много вещей, и я сбиваюсь с толку, потому что читаю разные мнения и разные подходы.

Проблема заключается в следующем:

в getView() в качестве Adapter мне нужно выполнить некоторые асинхронные операции, как проверка в образование в Интернете, и обновить представление, основанное на этом.

Я использовал следующий подход:

каждый раз getView() называется я начать Thread

, но мой подход, как заработал мне много критики:

https://stackoverflow.com/a/28484345/1815311

https://stackoverflow.com/a/28484335/1815311

https://stackoverflow.com/a/28484351/1815311

public View getView(int position, View convertView, ViewGroup parent) { 
    ViewHolder holder;    
    if (convertView == null) {     
     //...   
    } 
    else {     
     //... 
    }  

    Thread th= new Thread(new Runnable() { 

      @Override 
      public void run() { 
       mActivity.runOnUiThread(new Runnable() { 
        @Override 
        public void run() { 

         CheckSomeInfoOverTheInternet(url, new myCallback { 


          @Override 
          public void onSuccess() { 
          holder.textview.setText("OK"); 
          } 

          @Override 
          public void onFailre() { 
          holder.textview.setText("NOT OK!!!!"); 
          }  

         }); 
        } 
       }); 


      } 
     }); 

    th.start(); 

    return convertView; 
} 

Пожалуйста, что было бы лучше всего для такого?


Пожалуйста, обратите внимание, я не ищу решение для выполнения сетевых запросов в getView(), но, скорее, как обновленный вид в зависимости от результата на асинхронный вызов.

+1

главная проблема: WT * является 'CheckSomeInfoOverTheInternet' isin't это ASync уже? ... нормальный путь: создать обработчик в потоке пользовательского интерфейса (позволяет называть его UIHandler) ... создавать HandlerThread (HT) ... отправлять долго работающие вещи в HT с некоторым обратным вызовом ... если HT позаботится о своем вызове работы обратный вызов с использованием UIHandler ... конец истории .. – Selvin

+0

Из вашего вопроса выше вы говорите, что даже если вы выполняете асинхронную операцию в вашем getView, вам все равно придется ждать результатов, так как вы используете указанные результаты для обновления Ваше мнение. Это так? Я думаю, что лучший подход - это выполнить вашу асинхронную операцию перед обновлением вашего представления, а затем просто передать результаты в 'getView'. Таким образом, вы не ожидаете выполнения операции из 'getView' – Willis

+1

... конечно, только один UIHandler и HandlerThread для каждого адаптера - не для вызова getView ... также это почти то же самое, что и под AsyncTask (новый один с исполнителем) ... и runOnUiThread - это не что иное, как публикация runnable для обработчика UI – Selvin

ответ

2

Для этого есть несколько оценок. Хотя то, что вы делаете, действительно не подходит.

  1. AsyncTask

    • Нить Объединив здесь делается внутри, так что вам не нужно возиться с того, что
    • Сво более чистого подход к вашей проблеме, а не порождая отдельные нити.
    • Если ваш пользователь меняет экран во время вызова API, вы также можете указать cancel the call.
    • Вам необходимо включить notifyDatasetChanged()
    • Вам необходимо переопределить очень мало функций для достижения требуемых функций.
  2. AsyncTaskLoader

    • Это дает вам больше контроля, но вы теряете на несколько неявно заданных функций
    • Вам нужно больше знаний, чтобы использовать это и должны быть хорошо разбираются с классами, такими как LoaderManager, Loader.
    • change is self trigerring Скажите, измените ли вы базовый набор данных, изменения будут автоматически запускаться и обеспечивать изменение вашего пользовательского интерфейса.
  3. Handlers и Темы

    • Это один stpe выше текущей appraoch но обеспечивают способ более ПРЕИМУЩЕСТВА
    • Вы можете абстрагировать создание потоков и обеспечить обработчик, который будет обрабатывать вызов для все ваши идентификаторы.
    • Вы можете поставить очереди на потоки и отправленные сообщения.
    • если экран изменился, вы могли бы remove callbacks and messages.

В заключение отметим, что основные недостатки текущего подхода: - это потеря связи, когда изменения должны быть сделаны. - явное создание нескольких потоков

В то время как последнее является серьезной проблемой, тем более «заметной» проблема будет первой.

Существует и может быть несколько других подходов, основанных на требуемом элементе управления, и ваш опыт с обратными вызовами android и управлением потоками. Но эти три (по моему мнению) наиболее подходят.

PS: общая точка во всех этих подходах,

public View getView(int position, View convertView, ViewGroup  parent) { 
    ViewHolder holder;    
    if (convertView == null) {     
     //...   
    } 
    else {     
     //... 
    }  

    //execute a task for your given id where task could be: 
    //1. AsyncTask 
    //2. AsyncTaskLoader 
    //3. Handlers and thread 
    //call notifyDataSetChanged() in all the cases, 




    return convertView; 
} 

@Override 
public void notifyDataSetChanged() { 
     super.notifyDataSetChanged(); 
     //do any tasks which you feel are required 
} 

PPS: Вы также можете посмотреть в DataSetObserver снова автоматизировать ваши требования.

5

Это абсолютно не лучший способ обновить информацию в ListView. Метод getView должен просто создать представление из данных, которые у вас уже есть. Конечно, не нужно ничего запускать, чтобы получить дополнительную информацию.

Лучшим советом, который я мог бы вам дать, является предварительное получение данных. Потяните данные, обновите ArrayList, к которому подключен ваш Adapter, затем звоните adapter.notifyDataSetChanged(). Это позволит перерисовать всю вашу информацию.

Вытащите данные за один раз - не на мелкие детали. Это лучший и самый разумный способ сделать это.

+1

Это делает жизнь простой, но она практически применима для небольших наборов данных. В ситуациях, когда пользователь может перемещаться по большему количеству данных, чем вы можете предварительно кэшировать, потребуется некоторая форма загрузки по требованию. –

+0

Так будет ли «RecyclerView» помощь в таких случаях? Или, может быть, «AsyncLoader»? – Droidekas

1

Вы можете использовать что-то вроде этого:

public View getView(int position, View convertView, 
     ViewGroup parent) { 
    ViewHolder holder; 

    ... 

    holder.position = position; 

    new ThumbnailTask(position, holder) 
      .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null); 

    return convertView; 
} 

private static class ThumbnailTask extends AsyncTask { 
    private int mPosition; 
    private ViewHolder mHolder; 

    public ThumbnailTask(int position, ViewHolder holder) { 
     mPosition = position; 
     mHolder = holder; 
    } 

    @Override 
    protected Cursor doInBackground(Void... arg0) { 
     // Download bitmap here 
    } 

    @Override 
    protected void onPostExecute(Bitmap bitmap) { 
     if (mHolder.position == mPosition) { 
      mHolder.thumbnail.setImageBitmap(bitmap); 
     } 
    } 
} 

private static class ViewHolder { 
    public ImageView thumbnail; 
    public int position; 
} 

Кроме того, для еще более высокой производительности вы должны добавить осведомленность взаимодействия к вашему ListView адаптер так, чтобы он не вызывает каких-либо асинхронной операции в строке после, скажем, жестом fling на ListView, что означает, что прокрутка выполняется настолько быстро, что не имеет смысла даже запускать асинхронную операцию. Когда прокрутка останавливается или собирается остановиться, это когда вы хотите начать фактически показывать тяжелый контент для каждой строки.

Очень хороший пример можно найти здесь: https://code.google.com/p/shelves/

+0

Даже проще: используйте библиотеку для загрузки изображений, которая делает для вас всю тяжелую работу :-) – JonasCz

1

Volley библиотека, кажется, отведенными некоторые из распространенной скороговорки для использования ...

Для более точного управления сетевых запросов и кэширования, вы можете посмотреть на: http://developer.android.com/training/volley/simple.html#send

Как на диаграмме Architechture, по сделав запрос, он просматривает кэш, и в случае промаха пытается сетевой запрос и добавляет его в кэше для последующего использования, Кроме того, кажется, вы можете предоставить пользовательский запрос повторного повторного запроса s uiting ваши потребности и обрабатывать/недействить кеш как и когда нужно.

Для углубленного эталонным вы могли бы взглянуть на - https://android.googlesource.com/platform/frameworks/volley/+/master/src/main/java/com/android/volley

+0

Спасибо jzy, см. Мое редактирование: все дело не в том, как выполнять сетевые запросы в 'getView', а, скорее, как для обновления представления, созданного в зависимости от результата асинхронного обратного вызова. –

1

ИМХО, лучшая практика будет не выполнять асинхронные операции в GetView(). Класс Adapter должен только преобразовывать объекты в представления, и вы должны держать его как можно более простым.

Вы можете столкнуться с проблемами, если вы запустите асинхронную задачу (или поток), которая ссылается на вновь созданный View, если задача заканчивается после того, как представление больше не отображается на экране или если оно было переработано адаптером в результате прокрутки ,

Вам следует рассмотреть возможность выполнения асинхронных операций за пределами адаптера. Запустите операцию и обновите список после его завершения. Вы можете обновить весь список или некоторые элементы, когда асинхронная операция будет завершена.

Операции могут выполняться в AsyncTask вашей деятельности или в специализированном сервисе. Избегайте создания новых потоков, потому что создание потоков является дорогостоящим, и если по какой-то причине вы вызываете его слишком часто, у вас может закончиться нехватка памяти. AsyncTasks использует пул потоков, и поэтому у вас не будет этой проблемы.

+0

В [tutorial] (http://developer.android.com/training/displaying-bitmaps/display-bitmap.html) на веб-сайте разработчиков Android они загружают изображения асинхронно в 'getView()'. Они также показывают, как избежать проблем, если ваше изображение (изображение) больше не отображается на экране, когда заканчивается ваша задача. – JonasCz

+0

Загрузка изображений асинхронно сильно отличается от загрузки чего-либо из Интернета. Загрузка изображений всегда будет успешной, и это займет больше времени. Кроме того, вполне вероятно, что эти изображения будут использоваться только этими конкретными видами. Если вы извлекаете что-то из Интернета, возможно, вам придется реализовать сложные механизмы повтора, сохранить результаты для будущего использования или что-то еще. Это сделает его намного сложнее. –

+0

Вы конкретно не говорили о загрузке материалов из Интернета :-). Если вы хотите загружать изображения асинхронно из Интернета, для этого есть разные библиотеки. (Picasso, Volley ...) Если вы хотите загрузить другие материалы из Интернета, для этого есть и библиотеки, что делает его очень легким. – JonasCz

1

Итак, сделав шаг назад и глядя на это полностью абстрактно, кажется, вопрос, который вы задаете, заключается в том, как управлять некоторым потенциально долговременным действием (сетью или иным образом), которое повлияет на внешний вид просмотр списка.

Учитывая, что предположение, вы столкнулись с двумя основными проблемами:

  1. нити сравнительно дорого, чтобы начать
  2. Мнения возвращаемые GetView() перерабатываются и могут изменить «элемент», когда пользователь прокручивает список

Проблема 1 может быть решена с помощью ThreadPoolExecutor (или любого другого механизированного устройства, которое вы чувствуете). Идея состоит в том, что потоки также перерабатываются, поэтому они не занимают столько времени, чтобы раскручиваться.

Вопрос 2 немного сложнее. Вы можете отменить операцию потока, когда представление будет переработано (есть несколько способов сделать это), но вам нужно решить, является ли потеря усилий приемлемым (пользователь может прокрутить ваш вид назад на экране, и вам нужно начните снова). Вы можете сохранить результат своей долговременной задачи против элемента списка (или некоторого держателя данных, связанного с элементом списка), но вам нужно опасаться нехватки памяти (иногда могут использоваться кэширование или lru-кэширование в последнее время Вот). Это позволит вам сохранить ваши усилия и обновить список только в том случае, если он все еще находится на экране. Флаг в данных вашего элемента можно использовать, чтобы указать, что у вас уже есть данные, а не загружать его снова.

Извините, у меня нет достаточно времени, чтобы подробно остановиться на данный момент. Для меня время :-)

Удачи. Я напишу еще, если у меня будет время. С наилучшими пожеланиями, CJ

4

EDIT

Я думаю, что это интересный вопрос, стоит какого-то "канонического" решения

Google I/O 2013 : PI предлагает вам смотреть это Google ввода-вывода с 2013 года. У них чистые объяснения, многие из этих материалов там. На все ваши вопросы будет дан ответ. Это канон.

Я использовал Volley library здесь. Если вы прочтете документы, вы увидите, что Volley работает по фоновым потокам. Поэтому нет необходимости выполнять ваши задачи async. Поскольку другие уже затронули проблемы использования Threads, я не буду говорить об этом. Пусти в код непосредственно :)

Следующая служил мне хорошо, когда мои ListViews или GridViews или любые другие виды зависят от информации из Интернета:

  1. Создание интерфейса: WebInfoUpdateReceiver.java

    public interface WebInfoUpdateReceiver { 
    
        public void receive(Foo [] fooItems); 
    
    } 
    
  2. Создать класс для загрузки вещи: Downloader.java

    public class Downloader { 
        private Context mContext; 
        private WebInfoUpdateReceiver mReceiver; 
    
        public Downloader(WebInfoUpdateReceiver receiver) { 
         mReceiver = receiver; 
        } 
    
        public void downloadStuff() { 
        MyStringRequest request = new MyStringRequest(Request.Method.GET,requestUrl, new Response.Listener<String>() { 
        @Override 
        public void onResponse(String response) { 
         // parse the response into Foo[] 
    
         mReceiver.update(foo); 
          } 
         } 
        } 
    }, new Response.ErrorListener() { 
        @Override 
        public void onErrorResponse(VolleyError error) { 
    
        } 
    }); 
    RequestQueue queue = Volley.newRequestQueue(mContext); 
    queue.add(request); 
        } 
    
    } 
    
  3. Теперь делают ваши действия реализуют интерфейс:

    public class Activity extends Activity implements WebInfoUpdateReceiver { 
    
    public void receive(Foo [] fooItems) { 
        // convert the array into arrayList 
        adapter.insert(arraylist); 
        adapter.notifyDataSetChanged(); 
    } 
        } 
    
+0

Спасибо Rafiduzzaman! См. Мои изменения: проблема более общая: проблема в том, как обновить представление, созданное в 'getView()', в зависимости от результата асинхронного обратного вызова. –

+0

Почему это так сложно? Это действительно зависит от того, какие взгляды у вас есть и какие мнения вы хотите обновить. У вас есть загруженная информация, проанализирована ее арраистом объектов и передана адаптеру, как и обычным списком. Теперь вы просто обновляете представления в getView(). Я предлагаю вам изучить это http://www.vogella.com/tutorials/AndroidListView/article.html – rafid059

Смежные вопросы