如何在 Android Studio 中对动作运行 SoundPool

Posted

技术标签:

【中文标题】如何在 Android Studio 中对动作运行 SoundPool【英文标题】:How to run SoundPool in Android Studio on actions 【发布时间】:2017-08-08 19:27:20 【问题描述】:

我是在 android Studio 工作的新手,到目前为止,我只使用 java。我正在为学校做作业,但在让 SoundPool 正常工作而不使应用程序崩溃时遇到问题。我知道应用程序在没有我添加的声音的情况下运行,但我不确定我的错误在哪里,因为似乎有很多不同的方法可以解决这个问题。

我在调试时遇到的错误是:

java.lang.NullPointerException:尝试调用虚拟方法'int android.media.SoundPool.play(int, float, float, int, int, float)' 在一个 空对象引用

我的代码如下。 公共类 Snake 扩展 Activity

private SnakeView mSnakeView;

private static String ICICLE_KEY = "snake-view";


/**
 * Called when Activity is first created. Turns off the title bar, sets up
 * the content views, and fires up the SnakeView.
 * 
 */
@Override
public void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);

    //set the layout
    setContentView(R.layout.snake_layout);



    mSnakeView = (SnakeView) findViewById(R.id.snake);
    mSnakeView.setTextView((TextView) findViewById(R.id.text));

    if (savedInstanceState == null) 
        // We were just launched -- set up a new game
        mSnakeView.setMode(SnakeView.READY);
     else 
        // We are being restored
        Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
        if (map != null) 
            mSnakeView.restoreState(map);
         else 
            mSnakeView.setMode(SnakeView.PAUSE);
        
    


@Override
protected void onPause() 
    super.onPause();
    // Pause the game along with the activity
    mSnakeView.setMode(SnakeView.PAUSE);


@Override
public void onSaveInstanceState(Bundle outState) 
    //Store the game state
    outState.putBundle(ICICLE_KEY, mSnakeView.saveState());

公共类 SnakeView 扩展 TileView

private static final String TAG = "SnakeView";

private Context m_context;

//Sound
//initialize sound variables
private SoundPool sounds;
private int ulose = -1;
private int apple = -1;
private int moving = -1;

/**
 * Current mode of application: READY to run, RUNNING, or you have already
 * lost. static final ints are used instead of an enum for performance
 * reasons.
 */
private int mMode = READY;
public static final int PAUSE = 0;
public static final int READY = 1;
public static final int RUNNING = 2;
public static final int LOSE = 3;

/**
 * Current direction the snake is headed.
 */
private int mDirection = NORTH;
private int mNextDirection = NORTH;
private static final int NORTH = 1;
private static final int SOUTH = 2;
private static final int EAST = 3;
private static final int WEST = 4;

/**
 * Labels for the drawables that will be loaded into the TileView class
 */
private static final int RED_STAR = 1;
private static final int YELLOW_STAR = 2;
private static final int GREEN_STAR = 3;

/**
 * mScore: used to track the number of apples captured mMoveDelay: number of
 * milliseconds between snake movements. This will decrease as apples are
 * captured.
 */
private long mScore = 0;
private long mMoveDelay = 600;
/**
 * mLastMove: tracks the absolute time when the snake last moved, and is used
 * to determine if a move should be made based on mMoveDelay.
 */
private long mLastMove;

/**
 * mStatusText: text shows to the user in some run states
 */
private TextView mStatusText;

/**
 * mSnakeTrail: a list of Coordinates that make up the snake's body
 * mAppleList: the secret location of the juicy apples the snake craves.
 */
private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();
private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();

/**
 * Everyone needs a little randomness in their life
 */
private static final Random RNG = new Random();

/**
 * Create a simple handler that we can use to cause animation to happen.  We
 * set ourselves as a target and we can use the sleep()
 * function to cause an update/invalidate to occur at a later date.
 */
private RefreshHandler mRedrawHandler = new RefreshHandler();

class RefreshHandler extends Handler 

    @Override
    public void handleMessage(Message msg) 
        SnakeView.this.update();
        SnakeView.this.invalidate();
    

    public void sleep(long delayMillis) 
        this.removeMessages(0);
        sendMessageDelayed(obtainMessage(0), delayMillis);
    
;


/**
 * Constructs a SnakeView based on inflation from XML
 * 
 * @param context
 * @param attrs
 */
public SnakeView(Context context, AttributeSet attrs) 
    super(context, attrs);
    initSnakeView();

public SnakeView(Context context, AttributeSet attrs, int defStyle) 
    super(context, attrs, defStyle);
    initSnakeView();


private void initSnakeView() 
    setFocusable(true);

    Resources r = this.getContext().getResources();

    resetTiles(4);
    loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));
    loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));
    loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));




private void initNewGame() 
    mSnakeTrail.clear();
    mAppleList.clear();

    // For now we're just going to load up a short default eastbound snake
    // that's just turned north


    mSnakeTrail.add(new Coordinate(7, 7));
    mSnakeTrail.add(new Coordinate(6, 7));
    mSnakeTrail.add(new Coordinate(5, 7));
    mSnakeTrail.add(new Coordinate(4, 7));
    mSnakeTrail.add(new Coordinate(3, 7));
    mSnakeTrail.add(new Coordinate(2, 7));
    mNextDirection = NORTH;

    // Two apples to start with
    addRandomApple();
    addRandomApple();

    mMoveDelay = 600;
    mScore = 0;



/**
 * Given a ArrayList of coordinates, we need to flatten them into an array of
 * ints before we can stuff them into a map for flattening and storage.
 * 
 * @param cvec : a ArrayList of Coordinate objects
 * @return : a simple array containing the x/y values of the coordinates
 * as [x1,y1,x2,y2,x3,y3...]
 */
private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) 
    int count = cvec.size();
    int[] rawArray = new int[count * 2];
    for (int index = 0; index < count; index++) 
        Coordinate c = cvec.get(index);
        rawArray[2 * index] = c.x;
        rawArray[2 * index + 1] = c.y;
    
    return rawArray;


/**
 * Save game state so that the user does not lose anything
 * if the game process is killed while we are in the 
 * background.
 * 
 * @return a Bundle with this view's state
 */
public Bundle saveState() 
    Bundle map = new Bundle();

    map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
    map.putInt("mDirection", Integer.valueOf(mDirection));
    map.putInt("mNextDirection", Integer.valueOf(mNextDirection));
    map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));
    map.putLong("mScore", Long.valueOf(mScore));
    map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));

    return map;


/**
 * Given a flattened array of ordinate pairs, we reconstitute them into a
 * ArrayList of Coordinate objects
 * 
 * @param rawArray : [x1,y1,x2,y2,...]
 * @return a ArrayList of Coordinates
 */
private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) 
    ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();

    int coordCount = rawArray.length;
    for (int index = 0; index < coordCount; index += 2) 
        Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
        coordArrayList.add(c);
    
    return coordArrayList;


/**
 * Restore game state if our process is being relaunched
 * 
 * @param icicle a Bundle containing the game state
 */
public void restoreState(Bundle icicle) 
    setMode(PAUSE);

    mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
    mDirection = icicle.getInt("mDirection");
    mNextDirection = icicle.getInt("mNextDirection");
    mMoveDelay = icicle.getLong("mMoveDelay");
    mScore = icicle.getLong("mScore");
    mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));


/*
 * handles key events in the game. Update the direction our snake is traveling
 * based on the DPAD. Ignore events that would cause the snake to immediately
 * turn back on itself.
 * 
 * (non-Javadoc)
 * 
 * @see android.view.View#onKeyDown(int, android.os.KeyEvent)
 */
@Override
public boolean onKeyDown(int keyCode, KeyEvent msg) 

    if (keyCode == KeyEvent.KEYCODE_DPAD_UP) 
        if (mMode == READY | mMode == LOSE) 
            /*
             * At the beginning of the game, or the end of a previous one,
             * we should start a new game.
             */
            initNewGame();
            setMode(RUNNING);
            update();
            return (true);
        

        if (mMode == PAUSE) 
            /*
             * If the game is merely paused, we should just continue where
             * we left off.
             */
            setMode(RUNNING);
            update();
            return (true);
        

        if (mDirection != SOUTH) 
            mNextDirection = NORTH;
        
        return (true);
    

    if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) 
        if (mDirection != NORTH) 
            mNextDirection = SOUTH;
        
        return (true);
    

    if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) 
        if (mDirection != EAST) 
            mNextDirection = WEST;
        
        return (true);
    

    if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) 
        if (mDirection != WEST) 
            mNextDirection = EAST;
        
        return (true);
    

    return super.onKeyDown(keyCode, msg);


public void loadSound()
    sounds = new SoundPool(3, AudioManager.STREAM_MUSIC, 0);

    try
        //Create objects for the required classes
        AssetManager assetManager = m_context.getAssets();
        AssetFileDescriptor descriptor;

        //Create the three sound effects in memory
                descriptor = assetManager.openFd("wall.ogg");
        ulose = sounds.load(descriptor, 0);

        descriptor = assetManager.openFd("apple.ogg");
        apple = sounds.load(descriptor, 0);

        descriptor = assetManager.openFd("tiles.ogg");
        moving = sounds.load(descriptor, 0);
    
    catch (IOException e)
        Log.e("error", "failed to load sound files");
    


/**
 * Sets the TextView that will be used to give information (such as "Game
 * Over" to the user.
 * 
 * @param newView
 */
public void setTextView(TextView newView) 
    mStatusText = newView;


/**
 * Updates the current mode of the application (RUNNING or PAUSED or the like)
 * as well as sets the visibility of textview for notification
 * 
 * @param newMode
 */
public void setMode(int newMode) 
    int oldMode = mMode;
    mMode = newMode;

    if (newMode == RUNNING & oldMode != RUNNING) 
        mStatusText.setVisibility(View.INVISIBLE);
        update();
        return;
    

    Resources res = getContext().getResources();
    CharSequence str = "";
    if (newMode == PAUSE) 
        str = res.getText(R.string.mode_pause);
    
    if (newMode == READY) 
        str = res.getText(R.string.mode_ready);
    
    if (newMode == LOSE) 
        str = res.getString(R.string.mode_lose_prefix) + mScore
              + res.getString(R.string.mode_lose_suffix);
    

    mStatusText.setText(str);
    mStatusText.setVisibility(View.VISIBLE);


/**
 * Selects a random location within the garden that is not currently covered
 * by the snake. Currently _could_ go into an infinite loop if the snake
 * currently fills the garden, but we'll leave discovery of this prize to a
 * truly excellent snake-player.
 * 
 */
private void addRandomApple() 
    Coordinate newCoord = null;
    boolean found = false;
    while (!found) 
        // Choose a new location for our apple
        int newX = 1 + RNG.nextInt(mXTileCount - 2);
        int newY = 1 + RNG.nextInt(mYTileCount - 2);
        newCoord = new Coordinate(newX, newY);

        // Make sure it's not already under the snake
        boolean collision = false;
        int snakelength = mSnakeTrail.size();
        for (int index = 0; index < snakelength; index++) 
            if (mSnakeTrail.get(index).equals(newCoord)) 
                collision = true;
            
        
        // if we're here and there's been no collision, then we have
        // a good location for an apple. Otherwise, we'll circle back
        // and try again
        found = !collision;
    
    if (newCoord == null) 
        Log.e(TAG, "Somehow ended up with a null newCoord!");
    
    mAppleList.add(newCoord);



/**
 * Handles the basic update loop, checking to see if we are in the running
 * state, determining if a move should be made, updating the snake's location.
 */
public void update() 
    if (mMode == RUNNING) 
        long now = System.currentTimeMillis();

        if (now - mLastMove > mMoveDelay) 
            clearTiles();
            updateWalls();
            updateSnake();
            updateApples();
            mLastMove = now;
        
        mRedrawHandler.sleep(mMoveDelay);
    



/**
 * Draws some walls.
 * 
 */
private void updateWalls() 
    for (int x = 0; x < mXTileCount; x++) 
        setTile(GREEN_STAR, x, 0);
        setTile(GREEN_STAR, x, mYTileCount - 1);
    
    for (int y = 1; y < mYTileCount - 1; y++) 
        setTile(GREEN_STAR, 0, y);
        setTile(GREEN_STAR, mXTileCount - 1, y);
    


/**
 * Draws some apples.
 * 
 */
private void updateApples() 
    for (Coordinate c : mAppleList) 
        setTile(YELLOW_STAR, c.x, c.y);
    




/**
 * Figure out which way the snake is going, see if he's run into anything (the
 * walls, himself, or an apple). If he's not going to die, we then add to the
 * front and subtract from the rear in order to simulate motion. If we want to
 * grow him, we don't subtract from the rear.
 * 
 */
private void updateSnake() 
    boolean growSnake = false;

    // grab the snake by the head
    Coordinate head = mSnakeTrail.get(0);
    Coordinate newHead = new Coordinate(1, 1);

    mDirection = mNextDirection;

    switch (mDirection) 
    case EAST: 
        newHead = new Coordinate(head.x + 1, head.y);
        sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
        break;
    
    case WEST: 
        newHead = new Coordinate(head.x - 1, head.y);
        sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
        break;
    
    case NORTH: 
        newHead = new Coordinate(head.x, head.y - 1);
        sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
        break;
    
    case SOUTH: 
        newHead = new Coordinate(head.x, head.y + 1);
        sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
        break;
    
    

    // Collision detection
    // For now we have a 1-square wall around the entire arena
    if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)
            || (newHead.y > mYTileCount - 2)) 
        sounds.play(ulose, 1.0f, 1.0f, 0, 0, 1.5f);
        setMode(LOSE);
        return;

    

    // Look for collisions with itself
    int snakelength = mSnakeTrail.size();
    for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) 
        Coordinate c = mSnakeTrail.get(snakeindex);
        if (c.equals(newHead)) 
            sounds.play(ulose, 1.0f, 1.0f, 0, 0, 1.5f);
            setMode(LOSE);
            return;
        
    

    // Look for apples
    int applecount = mAppleList.size();
    for (int appleindex = 0; appleindex < applecount; appleindex++) 
        Coordinate c = mAppleList.get(appleindex);
        if (c.equals(newHead)) 
            sounds.play(apple, 1.0f, 1.0f, 0, 0, 1.5f);
            mAppleList.remove(c);
            addRandomApple();

            mScore++;
            mMoveDelay *= 0.9;

            growSnake = true;
        
    

    // push a new head onto the ArrayList and pull off the tail
    mSnakeTrail.add(0, newHead);
    // except if we want the snake to grow
    if (!growSnake) 
        mSnakeTrail.remove(mSnakeTrail.size() - 1);
    

    int index = 0;
    for (Coordinate c : mSnakeTrail) 
        if (index == 0) 
            setTile(YELLOW_STAR, c.x, c.y);
         else 
            setTile(RED_STAR, c.x, c.y);
        
        index++;
    



/**
 * Simple class containing two integer values and a comparison function.
 * There's probably something I should use instead, but this was quick and
 * easy to build.
 * 
 */
private class Coordinate 
    public int x;
    public int y;

    public Coordinate(int newX, int newY) 
        x = newX;
        y = newY;
    

    public boolean equals(Coordinate other) 
        if (x == other.x && y == other.y) 
            return true;
        
        return false;
    

    @Override
    public String toString() 
        return "Coordinate: [" + x + "," + y + "]";
    

我不是在找人为我做我的工作,我只是希望能够了解我在哪里犯了错误以及纠正错误的正确方法,以便我可以按预期从中吸取教训。非常感谢任何建议。

【问题讨论】:

您似乎从未调用过 loadSound() 方法。您没有实例化 soundPool 对象,这就是您遇到的错误。 当我添加“mSnakeView.loadSound();”对于 OnCreate 方法,应用程序完全崩溃并且不会加载,因此调试器没有返回任何我可以看到的内容。 重新启动 Android Studio 并且能够从调试过程中得到一个错误:java.lang.RuntimeException: Unable to start activity ComponentInfocom.example.android.snake/com.example.android.snake. Snake:java.lang.NullPointerException:尝试在空对象引用上调用虚拟方法“void com.example.android.snake.SnakeView.loadSound()” 错误将出现在 logcat 中。(Logcat 是一个命令行工具,用于转储系统消息日志,包括设备抛出错误时的堆栈跟踪和您编写的消息)。你可以看到它Android Studio。 您没有将 m_context 设置为一个值。有一种更简单的方法可以从原始目录加载声音:- apple =sounds.load(getApplicationContext(),R.raw.apple,1); (另见 getContext()) 【参考方案1】:

(1) 您似乎从未调用过 loadSound() 方法。 您没有实例化 soundPool 对象,这是您遇到的错误。 您需要调用 loadSound() 方法。

(2) 您没有将 m_context 设置为一个值,请执行以下操作:

m_context = getApplicationContext();

(3) 有一种更简单的方法可以从原始目录加载声音:

apple = soundsload(getApplicationContext(),R.raw.apple,1);

【讨论】:

我将 loadsound() 方法添加到 public SnakeView 部分,更改了 m_context 值并更改了声音负载,但我在声音负载上得到“无法解析方法 getApplicationContext()”。跨度> Activity 理解 getApplicationContext(),在那里设置 m_context。并根据需要传递它。 那么我应该把整个 loadsound 方法放在 Activity 中吗?到目前为止,我所看到的所有内容都希望在 SnakeView 文件中,但 onCreate 在 Snake 中。 再次阅读我的评论。 非常感谢!你让我指出了正确的方向,所以我终于知道要查找什么以及如何正确实施它。先生,你太棒了!

以上是关于如何在 Android Studio 中对动作运行 SoundPool的主要内容,如果未能解决你的问题,请参考以下文章

求教下各位大虾,在Android Studio中如何实现通过一个动作按钮实现页面的跳转(求详细的每一个步骤)

Android Studio 3.1.2运行按钮不起作用

如何在 VueJS 中对 Vuex 动作进行单元测试

如何在 tvOS 中对按钮焦点执行动作事件

如何在android studio上面运行已经打包好的文件?

Android Studio新建一个HelloWorld 程序(App)