3

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

Возможно, кто-то здесь сможет мне посоветовать, что я должен делать.

В моем проекте я использую две внешние библиотеки:

  • GraphView - открывается вид на графике рисунок
  • EventBus - обеспечивает интерфейс для легкого обмена данными между компонентами приложения

Что касается приложения он имеет следующую структуру:

  MainActivity 
      /  \ 
     /  \ 
     Thread  Fragment 
    (ProcessThread) (GraphFragment) 

Идея состоит в том, что ProcessThread вычисляет данные и обеспечивает постоянный поток значений до GraphFragment по порядку EventBus. В GraphFragment У меня есть один Series, который требуется GraphView.

Для обновления графиков в режиме реального времени в соответствии с example мне нужно сделать новый Runnable, так что я сделал один:

private class PlotsRun implements Runnable{ 

     @Override 
     public void run() { 
      mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100); 
      counter++; 
      mHandler.post(this); 
     } 
} 

и когда я начинаю его из фрагмента onResume() метод все работает как шарм.

К сожалению, как я уже упоминал, я использую внешние данные из другого потока. Чтобы получить его в GraphFragment, я использую (в соответствии с методом documentation) onEventMainThread().

И здесь, независимо от того, что я сделаю, я не могу передать данные, чтобы обновить свой график в PlotsRun объекте. До сих пор я пытался:

  • с помощью Queue - добавить значение в onEventMainThread и получить в PlotsRun. Оказалось, что runnable читает быстрее, чем метод способен обновлять очередь.
  • создание различных буферов - результат вполне такой же, как с Queue.
  • , вызывающий mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100); непосредственно от onEventMainThread - в какой-то момент он получает freez.
  • создание onEvent() метод внутри мой runnable и звонок оттуда mHandler.post() - он блокирует пользовательский интерфейс и обновления выглядят как моментальные снимки.
  • используя все, упомянутое с или без synchronized() блок.

Для меня довольно сложно понять, что это работает, который работает правильно (в какой-то момент).

Как это said на официальном блоге Android вы не можете обновлять пользовательский интерфейс из неинтерфейса. Вот почему я не могу использовать другой поток внутри GraphFragment. Но когда я проверил свой runnable, он работает в основном потоке (UI).Вот почему я не могу создать бесконечный while loop, вместо этого нужно позвонить mHandler.post(this).

И все же он ведет себя как другой поток, потому что он быстрее (чаще), затем onEventMainThread.

Что я могу сделать, чтобы обновлять мои графики (или где я должен смотреть) с использованием данных от ProcessThread?

EDIT1:

Отвечая на Вульфа @ Matt просить я в том числе то, что я думаю, что это самая важная часть кода для этой проблемы со всей необходимой переменной показал, как они были объявлены. Это очень упрощенный пример:

MainActivity:

private ProcessThread testThread = new ProcessThread(); 

@Override 
    protected void onResume() { 
     super.onResume(); 
     testThread.start(); 
    } 


    private class ProcessThread extends Thread{ 
     private float value = 0f; 
     private ReadingsUpdateData updater = new ReadingsUpdateData(values); 
     public void run() { 
      while(true) { 
       value = getRandom(); 
       updater.setData(value); 
       EventBus.getDefault().post(updater); 
      } 
     } 
    } 

GraphFragment:

private LineGraphSeries<DataPoint> mSeries1; 
    long counter = 0; 
    private Queue<ReadingsUpdateData> queue; 

    @Override 
    public void onResume() { 
     super.onResume(); 
     mTimer2.run(); 
    } 

    public void onEventMainThread(ReadingsUpdateData data){ 
     synchronized(queue){ 
      queue.add(data); 
     } 
    } 

    private class PlotsRun implements Runnable{ 

     @Override 
     public void run() { 
      if (queue.size()>0) { 
       mSeries1.appendData(new DataPoint(counter, queue.poll()), true, 100); 
       counter++; 
      } 
      mHandler.post(this); 
     } 
    } 

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

Еще одна вещь, которую нужно добавить - когда я положил простой Log.d и подсчитал переменную внутри onEventMainThread, она обновляла и отображала ее значение правильно, но, к сожалению, logcat не является основным пользовательским интерфейсом.

EDIT2:

В основном это реакция на @MattWolfe comment

mHandler просто переменная объявлена ​​и создана в GrapgFragment:

private final Handler mHandler = new Handler(); 
private Runnable mTimer2; 

Да, это правильно, я использую mHandler.post() без каких-либо задержек. Я попытаюсь использовать некоторую задержку, чтобы увидеть, есть ли разница.

Что я не упоминал ранее, так это то, что ProcessThread предоставляет также данные другим фрагментам - не беспокойтесь, они не мешают друг другу или не разделяют какие-либо ресурсы. Вот почему я использую EventBus.

EDIT3:

Это код, который я использовал в качестве моего друга идеи с другой нитью в GraphFragment и runOnMainThread метод:

private MyThread thread = new MyThread(); 

    private class MyThread extends Thread { 
     Queue<ReadingsUpdateData> inputList; 
     ReadingsUpdateData msg; 

     public MyThread() { 
      inputList = new LinkedList<>(); 
     } 

     public void run() { 
      while(true) { 
       try{ 
        msg = inputList.poll(); 
       } catch(NoSuchElementException nse){ 
        continue; 
       } 
       if (msg == null) { 
        continue; 
       } 
       getActivity().runOnUiThread(new Runnable() { 
        @Override 
        public void run() { 
         mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100); 
         counter++; 
        } 
       }); 
      } 
     } 

     public void onEvent(ReadingsUpdateData data){ 
      inputList.add(data); 
     } 
    } 

К сожалению, он не работает ни.

+0

Вы заглянули в метод runOnUIThread? http://stackoverflow.com/questions/11140285/how-to-use-runonuithread – Proxy32

+0

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

+0

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

ответ

3

Прежде всего,

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

Во-вторых,

Вы можете вызвать функцию appendData() непосредственно из вашей onEventMainThread функции, но, как вы отметили, что такой подход иногда зависает интерфейс, одна из возможных причин такого поведения является то, что вы, вероятно, проводки события слишком часто. Обновление UI слишком часто в конечном итоге повредит интерфейс. Вы можете сделать следующее, чтобы избежать этого:

Updating UI слишком часто может также подвесить UI, Вот решение:

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

Надеюсь, что это поможет!

Edit: (в ответ на комментарий 1 & 2)

Я не уверен, что вы пытались может разместить свой обновленный код даст лучшее представление. но я думаю, вы пытались реализовать функцию проверки времени в onEventMainThread или в PlotsRun runnable, это правильно? Если да, то я боюсь, что это не принесет вам большой пользы. Вместо этого вам нужно выполнить проверку проверки времени внутри ProcessThread и только опубликовать новое событие, если достигнуто пороговое время. По следующим причинам:

1- EventBus на бэкэнд автоматически создает новую runnable и вызывает в ней onEventMainThread. Таким образом, проверка времени обработки внутри ProcessThread приведет к появлению в памяти менее нежелательных запусков, что приведет к меньшему потреблению памяти.

2- Также не нужно поддерживать очередь и запускать новые runnables, просто обновлять данные в onEventMainThread.

Ниже голый минимальный код, чтобы обеспечить доказательство концепции только, Вам нужно будет обновить его в соответствии с вашими потребностями:

ProcessThread класс:

private class ProcessThread extends Thread{ 
    private static final long TIME_THRESHOLD = 100; //100 MS but can change as desired 
    private long lastSentTime = 0; 
    private float value = 0f; 
    private ReadingsUpdateData updater = new ReadingsUpdateData(values); 
    public void run() { 
     while(true) { 
      if (System.currentTimeMillis() - lastSentTime < TIME_THRESHOLD) { 
       try { 
        Thread.sleep(TIME_THRESHOLD - (System.currentTimeMillis() - lastSentTime)); 
       } catch (InterruptedException e) {} 
      } 

      value = getRandom(); 
      updater.setData(value); 
      EventBus.getDefault().post(updater); 
      lastSentTime = System.currentTimeMillis(); 
     } 
    } 
} 

onEventMainThread метод:

public void onEventMainThread(ReadingsUpdateData data){ 
    mSeries1.appendData(new DataPoint(counter, data), true, 100); 
    counter++; 
} 
+0

Должен признать, что ваша идея выглядит так, как будто она может работать. Я добавил базовую логическую проверку, если разница между полученными данными составляет более 50 миллисекунд. Он отлично справился с моей победой. ** Но ** Я не знаю, почему выделенная память все еще растет. С runnable ничего подобного не происходит. С потоком он очень медленно увеличивается, но это так. У вас есть представление о том, что может быть причиной этого? – sebap123

+0

Похоже, если я добавлю эту логику проверки времени на мой «ProcessThread», все будет отлично работать, добавив его в конец onEventMainThread в увеличении распределения памяти. Если вы в состоянии, можете ли вы объяснить это? – sebap123

+0

@ sebap123 Я отредактировал свой ответ, проверьте свой ответ на ваши комментарии там и дайте мне знать! –

0

Я бы настроить что-то вроде этого:

public class MainActivity extends Activity { 

private class ProcessThread extends Thread{ 
     private float value = 0f; 
     private ReadingsUpdateData updater = new ReadingsUpdateData(values); 
     public void run() { 
      while(true) { 
       value = getRandom(); 
       updater.setData(value); 
       EventBus.getDefault().post(updater); 
      } 
     } 
    }  

    @Override 
    protected void onResume() { 
     super.onResume(); 
     testThread.start(); 
    } 

} 



public class GraphFragment extends Fragment { 

    private Handler mHandler; 
    private Queue<ReadingsUpdateData> queue; 

    @Override 
    public void onActivityCreated(Bundle state) { 
    super.onActivityCreated(state); 
    mHandler = new Handler(Looper.getMainLooper()); 
    } 

    public void onEvent(ReadingsUpdateData data){ 
    synchronized(queue){ 
     queue.add(data); 
    } 

    if (mHandler != null) { 
     mHandler.post(processPlots); 
    } 
    } 

    //implement pause/resume to register/unregister from event bus 


private Runnable processPlots = new Runnable { 

     @Override 
     public void run() { 
      synchronized(queue) { 
       if (queue.size()>0) { 
       mSeries1.appendData(new DataPoint(counter, queue.poll()), true, 100); 
       counter++; 
       } 
      } 
     } 
    }   

} 
+0

К сожалению, ваш ответ вообще не работает. Прежде всего, это блокирует все формы UI - я могу сделать что-то без ANR (я не уверен, почему), но каждый прессу занимает много времени. Во-вторых, график не обновляется в реальном времени, но он больше похож на скриншоты данных. – sebap123

1

Ваши PlotsRun на самом деле слишком быстр: как только он завершает свое исполнение, он req выдает на выполнение основного цикла потока, вызывая mHandler.post(processPlots);.

Прежде всего, вам необходимо сделать свой буфер данных независимым от сборщика данных и визуализатора данных: создать объект, который может получать (из коллектора) и передавать (визуализатору) данные. Таким образом, каждый компонент может работать совершенно независимо. И ваш объект данных не зависит от какого-либо потока. Ваш сборщик данных может подталкивать данные к вашему объекту данных, когда это необходимо, и ваш основной поток может запрашивать ваш объект данных на основе обычного таймера.

Затем поместите блокировку в этот буфер, чтобы ни один из двух других объектов, которые нуждаются в доступе к буферу данных, может делать это одновременно (что приведет к сбою). Эта блокировка может быть простой synchronized в объявлении метода.

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

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

0

Попробуйте использовать AsyncTask, который может быть выполнен из вашего фрагмента или действия. Вот ссылка на Android-документы для AsyncTask

public class SomeAsyncTask extends AsyncTask<Object,Void, Object>{ 
     @Override 
     protected void onPreExecute(){ 

     } 
     @Override 
     protected Object doInBackground(Object… params) { 
     //make your request for any data here 
      return getData(); 


     } 
     @Override 
     protected void onPostExecute(Object object){ 
     //update your UI elements here 
     mSeries1. appendData(object);   
     } 
    } 
Смежные вопросы