Трудность в понимании сложной многопоточности в приложении Android
У меня большая проблема с пониманием многопоточности в моем приложении, и из-за этого обнаружена ошибка. Я проверил, я думаю, все возможности, и все же я получаю различные (иногда неожиданные) ошибки.
Может быть, кто-то здесь сможет посоветовать мне, что мне делать.
В моем проекте я использую две внешние библиотеки:
- GraphView - предоставляет виды для рисования графика
- EventBus - предоставляет интерфейс для простого взаимодействия между компонентами приложения
Что касается приложения, оно имеет такую структуру:
MainActivity
/ \
/ \
Thread Fragment
(ProcessThread) (GraphFragment)
Идея в том, что ProcessThread
вычисляет данные и обеспечивает постоянный поток значений для GraphFragment
Повсеместно EventBus
, В GraphFragment
у меня есть такой Series
требуется GraphView
,
Для обновления графиков в реальном времени в соответствии с примером мне нужно сделать новый Runnable
поэтому я сделал один:
private class PlotsRun implements Runnable{
@Override
public void run() {
mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100);
counter++;
mHandler.post(this);
}
}
и когда я начинаю это из фрагмента onResume()
Метод все работает как шарм.
К сожалению, как я уже говорил, я использую внешние данные из другого потока. Чтобы получить это вGraphFragment
Я пользуюсь (согласно документации) onEventMainThread()
метод.
И здесь, независимо от того, что я буду делать, я не могу передать данные для обновления моего графика в PlotsRun
объект. Пока что я пробовал:
- с помощью
Queue
- добавить ценность вonEventMainThread
и войти вPlotsRun
, Оказалось, что runnable читает быстрее, чем метод способен обновить очередь. - создание различных буферов - результат такой же, как с
Queue
, - призвание
mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100);
прямо изonEventMainThread
- в какой-то момент он замерзает. - создание
onEvent()
метод внутри моего работоспособного и вызов оттудаmHandler.post()
- он блокирует пользовательский интерфейс и обновления выглядят как снимки. - используя все упомянутое с или без
synchronized()
блок.
Что мне довольно сложно понять, так это этот работающий корректно (в какой-то момент).
Как сказано в официальном блоге Android, вы не можете обновить пользовательский интерфейс из не-пользовательского потока. Вот почему я не могу использовать другой поток внутри GraphFragment
, Но когда я проверил свой runnable, он работает в основном потоке (UI). Вот почему я не могу создать бесконечное while loop
там вместо звонить mHandler.post(this)
,
И все же он ведет себя как другой поток, потому что он быстрее (вызывается чаще), чем onEventMainThread
метод.
Что я могу сделать, чтобы иметь возможность обновлять свои графики (или где я должен смотреть), используя данные из ProcessThread
?
EDIT1:
Отвечая на запрос @Matt Wolfe, я включил то, что я думаю, является наиболее важной частью кода для этой проблемы со всеми необходимыми переменными, показывающими, как они объявлены. Это очень упрощенный пример:
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);
}
}
If in runnable добавляется для защиты из-за этой проблемы с быстрым чтением. Но это не должно быть здесь, потому что всегда должно быть что-то (по крайней мере, я ожидаю этого).
Еще одна вещь, чтобы добавить - когда я ставлю простой Log.d
и подсчет переменной внутри onEventMainThread
он корректно обновлял и отображал его значение, но, к сожалению, logcat не является основным пользовательским интерфейсом.
EDIT2:
Это в основном ответ на комментарий@MattWolfe
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);
}
}
К сожалению, это не работает ни.
4 ответа
Прежде всего,
Запущенная часть в вашем следующем примере только для анимации обновления данных в реальном времени. Вы можете позвонить appendData()
без создания нового работоспособного. Вам нужно позвонить appendData()
из основного потока, хотя.
Во- вторых,
Вы можете позвонить appendData()
функционировать прямо с вашего onEventMainThread
функции, но, как вы указали, такой подход иногда приводит к зависанию пользовательского интерфейса. Одна из возможных причин такого поведения заключается в том, что вы, вероятно, публикуете события слишком часто, а слишком частое обновление пользовательского интерфейса в конечном итоге может привести к зависанию пользовательского интерфейса. Вы можете сделать следующее, чтобы избежать этого:
Слишком частое обновление пользовательского интерфейса может также привести к зависанию пользовательского интерфейса. Вот решение:
Положите немного логики в ProcessThread
чтобы сохранить время последнего отправленного события и сравнить его перед отправкой нового и, если разница меньше 1 секунды, чем сохранить его для отправки позже, и когда будет выполнено следующее вычисление, сравните время еще раз, если оно больше 1 секунды сейчас, чем отправлять события в массиве или может быть отправлено только самое последнее событие, так как последние вычисления могут представлять последнее состояние графика, верно?
Надеюсь, это поможет!
Изменить: (в ответ на комментарии 1 и 2)
Я не уверен, что то, что вы пытались опубликовать, - ваш обновленный код даст лучшую идею. но я думаю, что вы пытались реализовать функцию проверки времени в onEventMainThread
или в PlotsRun
выполнимо, это правильно? Если да, то я боюсь, что это не очень поможет вам. Вместо этого вам нужно выполнить проверку времени в ProcessThread и публиковать новое событие только при достижении порогового времени. По следующим причинам:
1- EventBus на бэкэнде автоматически создает новый runnable и вызывает onEventMainThread
в этом. Итак, проверка времени обработки внутри ProcessThread
будет порождать меньше ненужных runnables в памяти, что приведет к меньшему потреблению памяти.
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++;
}
Ваш PlotsRun на самом деле слишком быстрый: как только он заканчивает свое выполнение, он запрашивает себя при выполнении цикла основного потока, вызывая mHandler.post(processPlots);
,
Во-первых, вам нужно сделать свой буфер данных независимым от сборщика данных и визуализатора данных: создать объект, который может получать (от сборщика) и доставлять (визуализатору) данные. Итак, каждый компонент может работать совершенно независимо. И ваш объект данных не зависит от какого-либо потока. Ваш сборщик данных может передавать данные в ваш объект данных, когда это необходимо, и ваш основной поток может запрашивать ваш объект данных на основе обычного таймера.
Затем установите блокировку этого буфера, чтобы ни один из двух других объектов, которым требуется доступ к буферу данных, не мог сделать это одновременно (что приведет к сбою). Этот замок может быть простым synchronized
в объявлении метода.
Это должно гарантировать, что ваше приложение не падает из-за параллельного доступа (это должно быть вашей главной проблемой, я думаю).
Затем вы можете начать оптимизировать свой объект данных, создав дополнительные буферы для хранения временных данных, если сбор основных данных уже используется, когда поступают новые данные, или сделать копию фактических данных, чтобы они всегда были доступны для основного потока, даже когда новые данные в настоящее время добавляются, когда основной поток запрашивает значения.
Попробуйте использовать AsyncTask, который может быть выполнен из вашего фрагмента или действия. Вот ссылка на Android Docs для 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);
}
}
Я бы настроил что-то вроде этого:
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++;
}
}
}
}
}