Droidman
Droidman

Reputation: 11608

CalledFromWrongThreadException occurs accidentally

A brief explanation of what I'm doing: my FragmentActivity hosts 4 Fragments via a ViewPager, those are created in my class which extends Fragment.

    @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View view = null;
    switch (mPage) {

    case 1:

        view = inflater.inflate(R.layout.frag1,
                container, false);
        initFrag1(view);
        break;

    // ....... and so on

    }

    return view;
}

In the onCreateView() method I initialize the data depending on the page (Fragment). Each Fragment has quite a complex Layout with a LOT OF TextViews. That FragmentActivity gets started when the user clicks a ListItem in the previous Activity, and there's a delay of about 0.5-1 sec until the FragmentActivity starts. My consideration was that the delay is caused by initializing and setting tons of TextViews, so I tried to do all that stuff in an AsyncTask:

    @Override
protected Void doInBackground(Void... params) {

    DecimalFormat df = new DecimalFormat("#0.00");

    for (int i = 0; i < FirstTextViewArray.length; i++) {
        tmp = (TextView) v.findViewById(FirstTextViewArray[i]);
        tmp.setText("test " + String.valueOf(i));
    }
    for (int i = 0; i < SecondTextViewArray.length; i++) {
        tmp = (TextView) v.findViewById(SecondTextViewArray[i]);
        tmp.setText(df.format((SomeClass
                .getSomeFloatArray(Constants.tmpObject.getSomeInt())[i]));


    }
    return null;

}

So basically I'm just passing TextViews as Arrays and the inflated View to the AsyncTask in its constructor, then initialize and set them inside a loop.

It works BUT sometimes (like 1 out of 10) when I swipe between Fragments the app crashes with an Exception:

CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views

I know what that Exception means, but the question is: why does it occur accidentally?

2nd question: I guess init and set Views in the background is not the way to go. How can I then avoid delay at Activity's start?

Upvotes: 0

Views: 106

Answers (2)

athom
athom

Reputation: 313

Only the main thread can work with the views, that's why you get this error. You should use this thread :

getActivity().runOnUiThread(new Runnable() {
     public void run() {
          //stuff that updates your views
          DecimalFormat df = new DecimalFormat("#0.00");

          for (int i = 0; i < FirstTextViewArray.length; i++) {
              tmp = (TextView) v.findViewById(FirstTextViewArray[i]);
              tmp.setText("test " + String.valueOf(i));
          }
          for (int i = 0; i < SecondTextViewArray.length; i++) {
              tmp = (TextView) v.findViewById(SecondTextViewArray[i]);
              tmp.setText(df.format((SomeClass.getSomeFloatArray(Constants.tmpObject.getSomeInt())[i]));

          }
    }
});

So

Upvotes: 0

Krylez
Krylez

Reputation: 17820

Use the AsyncTask.onProgressUpdate method to safely update the UI on the UI thread. Here's a rough outline:

new AsyncTask<Void, Object, Void>() {

    DecimalFormat df = new DecimalFormat("#0.00");

    @Override
    protected void onProgressUpdate(Object... values) {
        String label = (String) values[0];
        int viewId = (Integer) values[1];
        TextView tv = (TextView) v.findViewById(viewId);
        tv.setText(label);
    }

    @Override
    protected Void doInBackground(Void... params) {

        for (int i = 0; i < FirstTextViewArray.length; i++) {
            publishProgress("test " + String.valueOf(i), FirstTextViewArray[i]);
        }
        for (int i = 0; i < SecondTextViewArray.length; i++) {
            publishProgress(
                    df.format((SomeClass.getSomeFloatArray(Constants.tmpObject.getSomeInt())[i])),
                    SecondTextViewArray[i]);


        }
        return null;
    }
}.execute();

Upvotes: 1

Related Questions