Cj1m
Cj1m

Reputation: 815

Called From Wrong Thread Exception Android

I am trying to create an app that when you touch the screen, a snowflake appears at the top of the screen and slowly falls. I am adding each snowflake to an arraylist so that I can make each snowflake fall. Here is my code:

Runnable runable = new Runnable(){
    @Override
    public void run(){
        while(true){
            letTheSnowFall();
        }
    }
};

public void letTheSnowFall(){
    for(int i = 0; i < snowArray.size(); i++){
        snowArray.get(i).setY(snowArray.get(i).getY() + 0.01f);
    }
}

and the I start the thread in the onCreate() method:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    layout = (RelativeLayout) findViewById(R.id.activity_main);

    setContentView(layout);
    layout.setOnClickListener(listener);

    Thread myThread = new Thread(runable);
    myThread.start();
}

Upvotes: 0

Views: 4168

Answers (4)

Bijay Koirala
Bijay Koirala

Reputation: 535

To update the view, you need to update it from the main thread. Clearly, you are doing it wrong. You can throw the information to the main thread and update it. Perhaps, you could try updating the view using Android built threads (AsyncTasks), compute the results on doInBackGround(), and publish the results on onPostExecute() method.

Upvotes: 1

Mattias Buelens
Mattias Buelens

Reputation: 20159

I'm guessing your snowArray contains a bunch of instances of View (or one of its many subclasses)? In that case, you're trying to modify a GUI component from a non-GUI thread, which is not allowed and thus throws a CalledFromWrongThreadException.

The "easy" solution would be to call Activity.runOnUiThread wherever you need to modify the UI. This is useful if you've done some processing on a separate thread and are now ready to show the results on the GUI.

However, you want to do a lot of GUI updates. It's even worse, since you're doing updates in an infinite loop: while(true) { letTheSnowFall(); }. This thing is truly infinite: it will never stop and your snow will just keep falling forever, with the Y-coordinate rapidly incrementing and eventually overflowing. You won't see that happen though since as soon as the GUI has updated, you're immediately updating it again: there is no delay.

What you're trying to do is much simpler though: you just want to let the snow fall. You want to animate your snow flake views starting from their current position and ending some distance downward. Android has a whole package for animations along with some very good guides.

Upvotes: 0

GareginSargsyan
GareginSargsyan

Reputation: 1895

If I understand correctly the snowflakes are views. So the snowArray has the type ArrayList<SubtypeOfView>. If so then the exception is throw because you call View.setY() method from a thread other than the main thred. Almost all of the methods related to the Android GUI framework have to be called from the main thread(the thread that executes callbacks such as Activity.onCreate()).

Upvotes: 0

CommonsWare
CommonsWare

Reputation: 1006584

First, you cannot update your UI from a background thread.

Second, an infinite background thread with no delays is exceedingly bad code.

Third, please do not call setContentView() twice in onCreate(). You do not need the second one (setContentView(layout)).

The lightest-weight way to accomplish this is to use postDelayed(), available on any View (such as layout), to arrange to get control in the future after a delay. You can cancel the postDelayed() work by calling removeCallbacks(), passing in the same Runnable as you used with postDelayed():

/***
  Copyright (c) 2012 CommonsWare, LLC
  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  use this file except in compliance with the License. You may obtain a copy
  of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
  by applicable law or agreed to in writing, software distributed under the
  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
  OF ANY KIND, either express or implied. See the License for the specific
  language governing permissions and limitations under the License.

  From _The Busy Coder's Guide to Android Development_
    http://commonsware.com/Android
 */

package com.commonsware.android.post;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class PostDelayedDemo extends Activity implements Runnable {
  private static final int PERIOD=5000;
  private View root=null;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    root=findViewById(android.R.id.content);
  }

  @Override
  public void onResume() {
    super.onResume();

    run();
  }

  @Override
  public void onPause() {
    root.removeCallbacks(this);

    super.onPause();
  }

  @Override
  public void run() {
    Toast.makeText(PostDelayedDemo.this, "Who-hoo!", Toast.LENGTH_SHORT)
         .show();
    root.postDelayed(this, PERIOD);
  }
}

(code from this sample project)

In your case, run() would update your snowflakes instead of showing a Toast.

Upvotes: 1

Related Questions