No se puede brindar en un hilo que no ha llamado a Looper.prepare()

12 minutos de lectura

Trato de ejecutar una prueba para mi aplicación de Android, pero obtengo este rastro. ¿Qué significa?

java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
at android.widget.Toast$TN.<init>(Toast.java:390)
at android.widget.Toast.<init>(Toast.java:114)
at android.widget.Toast.makeText(Toast.java:277)
at android.widget.Toast.makeText(Toast.java:267)
at dev.android.gamex.CatchGame.onDraw(MainActivity.java:317)
at dev.android.gamex.JamieTest.useAppContext(JamieTest.java:45)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:58)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:375)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2074)

Tests ran to completion.

mi clase de prueba

package dev.android.gamex;


import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.test.InstrumentationRegistry;
import android.widget.TextView;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class JamieTest {

    private static final String FAKE_STRING = "HELLO WORLD";

    private OnScoreListener onScoreListener = new OnScoreListener() {
        @Override
        public void onScore(int score) {
        }
    };

    @Mock
    Canvas can;

    @Test
    public void useAppContext() throws Exception {
        Context appContext = InstrumentationRegistry.getTargetContext();
        assertEquals("dev.android.gamex", appContext.getPackageName());
        CatchGame cg = new CatchGame(appContext, 5, "Jamie", onScoreListener);
        cg.initialize();
        assertTrue(! cg.gameOver);
        cg.onDraw(new Canvas());
        assertTrue(! cg.paused);

    }
}

El código que quiero probar está debajo.

package dev.android.gamex;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.view.MotionEventCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import java.util.Random;

public class MainActivity extends AppCompatActivity {
    CatchGame cg;
    public TextView textView;
    public LinearLayout mainLayout;
    String[] spinnerValue = {"Rookie", "Advanced", "Expert", "Master"};
    // start app
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainLayout = new LinearLayout(this);
        mainLayout.setOrientation(LinearLayout.VERTICAL);

        LinearLayout menuLayout = new LinearLayout(this);
        menuLayout.setBackgroundColor(Color.parseColor("#FFFFFF"));

        textView = new TextView(this);
        textView.setVisibility(View.VISIBLE);
        String str = "Score: 0";
        textView.setText(str);
        menuLayout.addView(textView);

        Button button = new Button(this);
        button.setText("Pause");
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                togglePausePlay();
            }
        });
        menuLayout.addView(button);


        Spinner spinner2 =new Spinner(this);
        ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, spinnerValue);
        spinner2.setAdapter(adapter);
        menuLayout.addView(spinner2);

        mainLayout.addView(menuLayout);

        cg = new CatchGame(this, 5, "Jamie", onScoreListener);
        cg.setBackground(getResources().getDrawable(R.drawable.bg_land_mdpi));
        mainLayout.addView(cg);
        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
        getSupportActionBar().hide();
        setContentView(mainLayout);
    }

    private void togglePausePlay() {
        if (cg.paused) {
            // play
            //  getSupportActionBar().hide();
            Toast.makeText(MainActivity.this, "Play", Toast.LENGTH_SHORT).show();
        } else {
            // pause
            //    getSupportActionBar().show();
            Toast.makeText(MainActivity.this, "Pause", Toast.LENGTH_SHORT).show();
        }

        cg.paused = !cg.paused;
    }

    private OnScoreListener onScoreListener = new OnScoreListener() {
        @Override
        public void onScore(int score) {
            textView.setText("Score: " + score);
        }
    };

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main_menu, menu);
        return true;
    }

    // method called when top right menu is tapped
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);
        int difficulty = cg.NBRSTEPS;
        String name = cg.heroName;

        switch (item.getItemId()) {
            case R.id.item11:
                cg = new CatchGame(this, 3, name, onScoreListener);
                setContentView(cg);
                mainLayout.addView(cg);
                getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
                getSupportActionBar().hide();
                setContentView(mainLayout);
                return true;
            case R.id.item12:
                cg = new CatchGame(this, 5, name, onScoreListener);
                setContentView(cg);
                mainLayout.addView(cg);
                getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
                getSupportActionBar().hide();
                setContentView(mainLayout);
                return true;
            case R.id.item13:
                cg = new CatchGame(this, 7, name, onScoreListener);
                setContentView(cg);
                mainLayout.addView(cg);
                getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
                getSupportActionBar().hide();
                setContentView(mainLayout);
                return true;
            case R.id.item14:
                cg = new CatchGame(this, 9, name, onScoreListener);
                setContentView(cg);
                mainLayout.addView(cg);
                getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
                getSupportActionBar().hide();
                setContentView(mainLayout);
                return true;
            case R.id.item15:
                cg = new CatchGame(this, 11, name, onScoreListener);
                setContentView(cg);
                mainLayout.addView(cg);
                getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
                getSupportActionBar().hide();
                setContentView(mainLayout);
                return true;
            case R.id.item21:
                cg = new CatchGame(this, difficulty, "Jamie", onScoreListener);
                setContentView(cg);
                mainLayout.addView(cg);
                getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
                getSupportActionBar().hide();
                setContentView(mainLayout);
                return true;
            case R.id.item22:
                cg = new CatchGame(this, difficulty, "Spaceship", onScoreListener);
                setContentView(cg);
                //mainLayout.addView(cg);
                //getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
                getSupportActionBar().hide();
                //setContentView(mainLayout);
                return true;
            default:
                cg.paused = true;
                return super.onOptionsItemSelected(item);
        }
    }

}

interface OnScoreListener {
    void onScore(int score);
}

class CatchGame extends View {
    int NBRSTEPS; // number of discrete positions in the x-dimension; must be uneven
    String heroName;
    int screenW;
    int screenH;
    int[] x; // x-coordinates for falling objects
    int[] y; // y-coordinates for falling objects
    int[] hero_positions; // x-coordinates for hero
    Random random = new Random();
    int ballW; // width of each falling object
    int ballH; // height of ditto
    float dY; //vertical speed
    Bitmap falling, hero, jamie2, jamieleft, jamieright;
    int heroXCoord;
    int heroYCoord;
    int xsteps;
    int score;
    int offset;
    boolean gameOver; // default value is false
    boolean toastDisplayed;
    boolean paused = false;

    OnScoreListener onScoreListener;

    // constructor, load images and get sizes
    public CatchGame(Context context, int difficulty, String name, OnScoreListener onScoreListener) {
        super(context);
        NBRSTEPS = difficulty;
        heroName = name;
        this.onScoreListener = onScoreListener;

        x = new int[NBRSTEPS];
        y = new int[NBRSTEPS];
        hero_positions = new int[NBRSTEPS];
        int resourceIdFalling = 0;
        int resourceIdHero = 0;
        if (heroName.equals("Jamie")) {
            resourceIdFalling = R.mipmap.falling_object2;
            resourceIdHero = R.drawable.left_side_hdpi;
            setBackground(getResources().getDrawable(R.mipmap.background));
        }
        if (heroName.equals("Spaceship")) {
            resourceIdFalling = R.mipmap.falling_object;
            resourceIdHero = R.mipmap.ufo;
            setBackground(getResources().getDrawable(R.mipmap.space));
        }
        falling = BitmapFactory.decodeResource(getResources(), resourceIdFalling); //load a falling image
        hero = BitmapFactory.decodeResource(getResources(), resourceIdHero); //load a hero image
        jamieleft = BitmapFactory.decodeResource(getResources(), R.drawable.left_side_hdpi); //load a hero image
        jamieright = BitmapFactory.decodeResource(getResources(), R.drawable.right_side_hdpi); //load a hero image

        ballW = falling.getWidth();
        ballH = falling.getHeight();
    }

    public CatchGame(Context context, int difficulty, String name, OnScoreListener onScoreListener, Drawable background) {
        this(context, difficulty, name, onScoreListener);
        this.setBackground(background);
    }

    // set coordinates, etc.
    void initialize() {
        if (!gameOver) { // run only once, when the game is first started
            int maxOffset = (NBRSTEPS - 1) / 2;
            for (int i = 0; i < x.length; i++) {
                int origin = (screenW / 2) + xsteps * (i - maxOffset);
                x[i] = origin - (ballW / 2);
                hero_positions[i] = origin - hero.getWidth();
            }
            int heroWidth = hero.getWidth();
            int heroHeight = hero.getHeight();

            hero = Bitmap.createScaledBitmap(hero, heroWidth * 2, heroHeight * 2, true);
            hero = Bitmap.createScaledBitmap(hero, heroWidth * 2, heroHeight * 2, true);
            jamieleft = Bitmap.createScaledBitmap(jamieleft, jamieleft.getWidth()* 2, jamieright.getWidth() * 2, true);
            jamieright = Bitmap.createScaledBitmap(jamieright, jamieright.getWidth()* 2, jamieright.getWidth() * 2, true);

            heroYCoord = screenH - 2 * heroHeight; // bottom of screen

        }
        for (int i = 0; i < y.length; i++) {
            y[i] = -random.nextInt(1000); // place items randomly in vertical direction
        }

        offset = (NBRSTEPS - 1) / 2; // place hero at centre of the screen
        heroXCoord = hero_positions[offset];

        // initialize or reset global attributes
        dY = 2.0f;
        score = 0;
        gameOver = false;
        toastDisplayed = false;
    }

    // method called when the screen opens
    @Override
    public void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        screenW = w;
        screenH = h;
        xsteps = w / NBRSTEPS;
        initialize();
    }

    // method called when the "game over" toast has finished displaying
    void restart(Canvas canvas) {

        toastDisplayed = true;
        initialize();
        draw(canvas);
    }

    // update the canvas in order to display the game action
    @Override
    public void onDraw(Canvas canvas) {
        if (toastDisplayed) {
            restart(canvas);
            return;
        }
        super.onDraw(canvas);

        int heroHeight = hero.getHeight();
        int heroWidth = hero.getWidth();
        int heroCentre = heroXCoord + heroWidth / 2;

        Context context = this.getContext();

        // compute locations of falling objects
        for (int i = 0; i < y.length; i++) {
            if (!paused) {
                y[i] += (int) dY;
            }
            // if falling object hits bottom of screen
            if (y[i] > (screenH - ballH) && !gameOver) {
                dY = 0;
                gameOver = true;
                paused = true;
                int duration = Toast.LENGTH_SHORT;

                final Toast toast = Toast.makeText(context, "GAME OVER!\nScore: " + score, duration);
                toast.show();
                Handler handler = new Handler();
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        toast.cancel();
                        toastDisplayed = true;
                    }
                }, 3000);
                //Vibrator v = (Vibrator) context.getSystemService(context.VIBRATOR_SERVICE);
                // Vibrate for 3000 milliseconds
                //v.vibrate(3000);

            }
            // if the hero catches a falling object
            if (x[i] < heroCentre && x[i] + ballW > heroCentre &&
                    y[i] > screenH - ballH - heroHeight) {

                y[i] = -random.nextInt(1000); // reset to new vertical position
                score += 1;
                onScoreListener.onScore(score);
            }

        }

        canvas.save(); //Save the position of the canvas.

        for (int i = 0; i < y.length; i++) {
            canvas.drawBitmap(falling, x[i], y[i], null); //Draw the falling on the canvas.
        }
        canvas.drawBitmap(hero, heroXCoord, heroYCoord, null); //Draw the hero on the canvas.

        canvas.restore();
        //Call the next frame.
        invalidate();
    }

    // event listener for when the user touches the screen
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (paused) {
            paused = false;
        }
        int action = MotionEventCompat.getActionMasked(event);
        if (action != MotionEvent.ACTION_DOWN || gameOver) { // non-touchdown event or gameover
            return true; // do nothing
        }
        int coordX = (int) event.getX();
        int xCentre = (screenW / 2) - (hero.getWidth() / 2);
        int maxOffset = hero_positions.length - 1; // can't move outside right edge of screen
        int minOffset = 0; // ditto left edge of screen

        if (coordX < xCentre && offset > minOffset) { // touch event left of the centre of screen
            offset--; // move hero to the left

            if(coordX < heroXCoord)// + heroWidth / 2)
                hero = Bitmap.createScaledBitmap(jamieleft, jamieleft.getWidth() , jamieleft.getHeight() , true);

        }
        if (coordX > xCentre && offset < maxOffset) { // touch event right of the centre of screen
            offset++; // move hero to the right

            if(coordX > heroXCoord)
                hero  = Bitmap.createScaledBitmap(jamieright, jamieright.getWidth() , jamieright.getHeight() , true);

        }
        heroXCoord = hero_positions[offset];

        return true;
    }
}

el repositorio es disponible en linea.

  • mira esto: stackoverflow.com/a/35189629/4031815

    – Código de sentido común

    28 de noviembre de 2017 a las 16:12

  • ¿Responde esto a tu pregunta? No se puede crear un controlador dentro del hilo que no haya llamado a Looper.prepare()

    – Bink

    15 de septiembre a las 21:16

avatar de usuario de nhoxbypass
nhoxbypass

NO PUEDE mostrar un Toast en subprocesos que no son de interfaz de usuario. tienes que llamar Toast.makeText() (y la mayoría de las otras funciones relacionadas con la interfaz de usuario) de dentro del hilo principal.


podrías usar Actividad#runOnUiThread():

runOnUiThread(new Runnable() {
      public void run() {
         final Toast toast = Toast.makeText(context, "GAME OVER!\nScore: " + score, duration);
         toast.show();
      }
 });

Si desea ejecutar una prueba de instrumentación en el hilo principal, agregue @UiThreadTest anotación:

@Test
@UiThreadTest
public void useAppContext() {
    // ...
}

PD: También hay muchas otras formas de explicar (usando Handler, Looper, Observable…) en estas publicaciones: Android: Toast en un hilo y no se puede crear un controlador dentro del hilo que no haya llamado a Looper.prepare()

  • No entiendo. ¿Se supone que el hilo está en mi clase principal o en mi clase de prueba?

    – Niklas Rosencrantz

    28 de noviembre de 2017 a las 16:23

  • @DacSaunders ¡sí! cuando quieras llamar Toast.makeText().show()debe ser hilo principal

    – nhoxbypass

    28/11/2017 a las 16:30

  • Pero, ¿cómo se puede probar desde otro hilo que no sea el hilo principal? La llamada es implícita, si miras mi código de prueba. Todavía no entiendo cómo probarlo.

    – Niklas Rosencrantz

    28 de noviembre de 2017 a las 16:43

  • Entonces, ¿su CatchGame funciona bien cuando se ejecuta pero falla en la prueba? @DacSaunders. Intenta agregar @UiThreadTest a tu prueba.

    – nhoxbypass

    28 de noviembre de 2017 a las 16:44


  • Usé tu código en la clase de prueba. Parece que funcionó porque mi cobertura de prueba aumentó: coveralls.io/github/montao/gamex

    – Niklas Rosencrantz

    28 de noviembre de 2017 a las 17:13


No se puede mostrar un brindis desde un subproceso que no sea de interfaz de usuario. Entonces puede hacer lo siguiente desde el subproceso de trabajo y no requiere Actividad o Contexto

JAVA

 new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
    Toast toast = Toast.makeText(mContext, "Toast", Toast.LENGTH_SHORT);
    toast.show();
}
});

KOTLIN

Handler(Looper.getMainLooper()).post {
    Toast.makeText(mContext, "Toast", Toast.LENGTH_SHORT).show()
}

  • Usted dice “no requiere Contexto”, entonces, ¿qué es mContext??

    – usuario2342558

    2 de junio de 2021 a las 7:32

  • @usuario2342558 mContext es el contexto de la aplicación.

    – Akhil Nair

    7 de junio de 2021 a las 13:22

  • Entonces, “no requiere actividad o contexto” no es cierto

    – usuario2342558

    7 jun 2021 a las 20:32

  • @ user2342558 El controlador permite ejecutar el código en una interfaz de usuario/subproceso principal y para eso no necesita tener una actividad o contexto. El código puede ser cualquier cosa que deba ejecutarse en UI Thread. En este código de muestra, mostramos un Toast y un Toast requiere un Contexto. Por lo tanto, estamos usando un mContext.

    – Akhil Nair

    8 de junio de 2021 a las 7:31

  • ¡Gracias! FUNCIONA

    – Teekam Suthar

    22 de septiembre de 2021 a las 16:53

Avatar de usuario de Marlon
Marlon

En kotlin pon tu código dentro de esto:

runOnUiThread { 
    Log.i(TAG, "runOnUiThread")
    Toast.makeText(MainActivity.this, "Play", Toast.LENGTH_SHORT).show()
}

Ejecute su componente de interfaz de usuario con runOnUiThread, esto es lo que hice – KOTLIN

inner class yourThread : Thread() {
    override fun run() {
    // this is for fragment, you can use 'this' for Activity
        requireActivity().runOnUiThread { 
       // Your Toast
             Toast.makeText(requireContext(), "Toasted",Toast.LENGTH_LONG).show()
    }
}

¿Ha sido útil esta solución?