Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
408 views
in Technique[技术] by (71.8m points)

android - Can't toast on a thread that has not called Looper.prepare()

I try to run a test for my android app but I get this trace. What does it mean?

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.

My test class

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

    }
}

The code I want to test is below.

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 resourceI

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You CANNOT show a Toast on non-UI thread. You need to call Toast.makeText() (and most other functions dealing with the UI) from within the main thread.


You could use Activity#runOnUiThread():

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

If you want execute a instrumentation test on main thread, add @UiThreadTest annotation:

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

P.s: There are also many other ways with explain (using Handler, Looper, Observable..) in these posts: Android: Toast in a thread and Can't create handler inside thread that has not called Looper.prepare()


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...