Difficulty understanding complex multithreading in an Android app

I have a big problem understanding multithreading in my application and find the error because of this. I have checked, I think, all possibilities, and yet I am getting various (sometimes unexpected) errors.

Perhaps someone here can advise me on what I should do.

In my project I am using two external libraries:

  • GraphView - Provides views for graphical drawing
  • EventBus - Provides an interface for easy communication between application components.

As for the application, it has the following structure:

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

      

The idea is that it ProcessThread

computes the data and provides a constant stream of values ​​until GraphFragment

throught EventBus

. As GraphFragment

I have one Series

required GraphView

.

To update the live charts according to the example , I need to create a new Runnable

one so that I can do this:

private class PlotsRun implements Runnable{

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

      

and when I start it with a fragment method onResume()

everything works like a charm.

Unfortunately, as I mentioned, I am using external data from another thread. To get it in GraphFragment

, I use (as per the documentation ) onEventMainThread()

.

And here, no matter what I do, I cannot pass data to update my graph in the object PlotsRun

. So far I have tried:

  • using Queue

    - add value to onEventMainThread

    and enter PlotsRun

    . It turned out that the runnable reads faster than the method is able to update the queue.
  • creating different buffers - the result is exactly the same as with Queue

    .
  • call mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100);

    directly from onEventMainThread

    - at some point it gets freez.
  • creating a onEvent()

    method inside my runnable and calling from there mHandler.post()

    - blocks the UI and updates look like snapshots.
  • using all mentioned with synchronized()

    or without block .

What is hard for me to understand is this is working correctly (at some point).

As the official Android blog said , you cannot update the UI from a non-UI thread. This is why I cannot use another thread internally GraphFragment

. But when I checked my runnable it works on the main thread (UI). That's why I can't create an infinite in while loop

there it needs to be called instead mHandler.post(this)

.

And yet it behaves like a different thread because it is faster (more often) and then onEventMainThread

.

What can I do to update my graphs (or where should I be looking) using the data from ProcessThread

?

EDIT1:

Answering @Matt Wolfe's request, I am including what I think is the most important piece of code for this problem, with all the required variable shown as they are declared. This is a very simplified example:

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 the runnable is added for protection because of this for a quick read problem. But it doesn't have to be, because there must always be something (at least that's what I expect).

Another thing to add is when I put a simple one Log.d

and counting the variable inside onEventMainThread

, it updated and displayed its value correctly, but unfortunately logcat is not the main user interface.

EDIT2:

This is basically the answer for @MattWolfe's comment

mHandler is just a variable declared and created in the GrapgFragment:

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

      

Yes, that's right, I use it mHandler.post()

without any delay. I'll try to use some delay to see if there is a difference.

What I didn't mention earlier is that it ProcessThread

provides data to other fragments as well - don't worry, they don't interfere with each other or share any resources. This is why I am using EventBus

.

EDIT3:

This code I used as my other idea with another thread in the method GraphFragment

and 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);
        }
    }

      

Unfortunately it doesn't work either.

+3


source to share


4 answers


First of all,

The executable part in the following example is only for animating real-time data refresh, you can call appendData()

without creating a new runnable. You have to call appendData()

from the main thread though.

Secondly,

You can call the function appendData()

directly from your function onEventMainThread

, but as you pointed out that this approach sometimes hangs in the UI, one possible reason for this behavior is that you are probably dispatching events too often... Updating the UI too often will eventually damage the UI. You can do the following to avoid this:

Updating the UI too often can also hang the UI, Here's the Solution:

Put some logic in ProcessThread

to store the last dispatched event time and compare it before sending a new one, and if the difference is less than 1 second than saving it to send later and when the next computation is done, compare the time again if it is more than 1 second now than dispatching events in an array or can only the last event be dispatched since the last computation can display the last chart state correctly?

Hope it helps!

Edit: (in response to comments 1 and 2)



I'm not sure if you tried to post the updated code to give a better idea. but i think you tried to implement time check function in onEventMainThread

or in PlotsRun

runnable, is that correct? If so, then I am afraid it will not do you much good. Instead, you need to do a timing check inside the ProcessThread and only post a new event if the threshold time is reached. The following reasons:

1- EventBus on the backend automatically creates a new runnable and calls in it onEventMainThread

. So checking the processing time internally ProcessThread

will result in fewer unwanted launches in memory, resulting in less memory consumption.

2- Also no need to maintain the queue and start new runnables, just update the data in onEventMainThread

.

Below is the minimal code to provide a proof of concept only. You need to update it according to your needs:

ProcessThread

class:

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++;
}

      

+3


source


Your PlotsRun is actually too fast: as soon as it finishes its execution, it requests the main thread's loop to run by calling mHandler.post(processPlots);

.

First, you need to make your data buffer independent of the data collector and data visualizer: create an object that can receive (from the collector) and send (to the visualizer) data. Thus, each component can work completely independently. And your data object is independent of any thread. Your data collector can pass data to your data object when needed, and your main thread can request your data object based on a regular timer.

Then place a lock on that buffer so that none of the other two objects that need to access the data buffer can do so at the same time (which will crash). This locking can be simple synchronized

in the method declaration.



This should ensure that your application does not crash due to concurrency access (this should be your main problem, I guess).

You can then start optimizing your data object by creating additional buffers to store temporary data if the main data collection is already in use when new data arrives, or make a copy of the actual data for it always available to the main thread even when new data is being added when requested by the main thread flow for values.

+1


source


I would install something like this:

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


source


Try using AsyncTask which can be executed from your Fragment or Activity. Here is a link to Android Docs for 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);           
        }
    }

      

0


source







All Articles