0

I'm developing an application where, in a certain part of its execution, the user will be able to see the data in a text file in the form of a graph. However, as the text file can have several columns (and it's intended to create a graph for each column), using only the UI thread can block the application for a few moments. So my question is: is there any class on Android that allows me to create these graphs (Views) in parallel and, when all the graphs are ready, add them to the parent layout?

My first thought was to use an ExecutorService as follows (for the sake of simplicity, using a TextView instead of GraphView, class from a library that I'm using in my project):

  • createTextsInParallel implementation:
public void createTextsInParallel(){
    ExecutorService executor = (ExecutorService) Executors.newFixedThreadPool(3);

    List<TextBuilderTask> taskList = new ArrayList<>();
    for (int i = 0; i < arrays.size(); i++){    // arrays is an ArrayList<ArrayList<Double>>
        if (arrays.get(i) == null) continue;
        taskList.add(new TextBuilderTask(this, arrays.get(i), i));
    }

    List<Future<TextView>> futureList = null;
    try {
        futureList = executor.invokeAll(taskList);

    } catch (InterruptedException e){
        e.printStackTrace();
    }

    executor.shutdown();

    if (futureList != null){
        for (int i = 0; i < futureList.size(); i++){
            TextView tv = futureList.get(i).get();
            parentLayout.addView(tv);           // parentLayout is a LinearLayout 
        }
    }
}
  • TextBuilderTask implementation:
class TextBuilderTask implements Callable<TextView> {
    protected MyActivity activity;
    protected ArrayList<Double> array;
    protected int pos;

    public TextBuilderTask(MyActivity activity, ArrayList<Double> array, int pos){
        this.activity = activity;
        this.array = array;
        this.pos = pos;
    }

    @Override
    public TextView call() throws Exception {
        TextView tv = new TextView(this.activity);
        tv.setText(String.format("%d: %s", this.pos, Arrays.toString(this.array)));
        return tv;
    }
}

But the above throws the following exception:

Caused by: java.lang.RuntimeException: Can't create handler inside thread Thread[pool-1-thread-1,5,main] that has not called Looper.prepare()

So where should I call Looper.prepare()? Based on previous SO questions, whenever I call Looper.prepare(), I should call Looper.loop() and Looper.quit() somewhere too. But since I'm using a ExecutorService to do the job, I'm not sure where should I call them. Any help is appreciated.

enzo
  • 9,861
  • 3
  • 15
  • 38

1 Answers1

1

I couldn't run your code, as it contains some errors and doesn't compile straight away, but I suspect that the problem is in the fact that you instantiate the views in a secondary thread (which is the one calling call()). I know this is exactly your goal, but it may be not a good idea because it depends on the implementation of the View you're instantiating. For example, if their constructor create some handlers, they will require the looper to be prepared. You could try doing that in the call() method, but there is still the fact that, at that point, their handlers may be running on an unexpected thread.

It seems also to be platform specific, so it may work on some phones and not on others, depending on the actual implementation of that particular View. I would suggest to take a look at this post here, and maybe you can try to follow some of those suggestions (e.g. use AsyncLayoutInflater): Inflate a view in a background thread

Bruno
  • 148
  • 8
  • Hi, thanks for your time trying to analyze the code! I searched a lot on the internet and I see that most people recommend not to inflate Views in background threads, so I think I'll have to find another way out... – enzo Feb 02 '20 at 23:47