Home Tutorials Hands on The Android Message Queue
The Android Message Queue
Monday, 03 November 2008 00:00
Article Index
The Android Message Queue
Lets get quacking
All Pages

Ducks in a rowAt their heart, computers are pretty simple beasts: they only do what you tell them to. Sometimes though, it looks like they are responding to events being fired from all directions at once. Well, thats because they've been told how to deal with those "random" events by interrupting the current flow. In Android a mechanism for this is called the Message Handling System. It's a way of writing software that can be interrupted at any time by predefined messages. These messages are generated by real world events like timers firing or the keyboard being opened, so here's an Android application to illustrate it.

 

Tutorial: Message Queue Skill: Medium Market install Download source

Keeping with the idea of queueing, our application first draws a duck pond with a bunch of randomly placed baby ducks present, then waits for a finger press. When that arrives, momma duck is drawn and all the baby ducks then move to line up behind her. The twist is every baby duck movement is in response to a timed message which is sent only when they need moving.

Heres how it looks when run:

 

     

 

The basics are very similar to the wash your hands application but this time we're performing more calculations and setting up the message handling system. The new object which does all the work is the Handler, and we create an instance in our View class called "handler". You can think of this as the listener for any messages aimed at our class, and in this case the messages are sent using the sendMessageDelayed() method. At any point, we can create our custom message, call sendMessageDelayed() to fire it at our class and the handler will receive, analyse, and act on it if its appropriate to do so. Notice the send method used is called sendMessageDelayed() - thats because it takes an extra argument which allows for a delay before the message is added to the queue. The message queue is an internal structure which the handler constantly loops over, pulling each message out in turn. We make use of the delay value to make sure our baby ducks don't swim too fast or too slow.

Its important to realise the messages are processed asyncronously, that is they don't wait for the current task to finish. You can see this if you keep moving momma duck: the babies don't stop until you stop moving her, they keep on moving towards her no matter where she is on the screen. The task of moving momma is interrupted by the messages fired telling the babies to update their positions.


The interesting stuff is in the handler definition within the CustomView inner class. When a message arrives instructing it to recalculate the babies positions, it walks through their position list and tests each one to see is it is already in position. If they all are, no further messages are sent. If even just one isn't, the message is sent to update again when all the babies have been processed.

One word of caution - this example is purely to show the message queueing system and is not the way you'd normally write an app to solve this particular ducks-in-a-row problem - that would be just quackers ;-)

/*
 * Copyright (c) 2009 OTAMate Technology Ltd. All Rights Reserved.
 * http://www.androidacademy.com
 */

package com.androidacademy.tutorials.messagequeue;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Region;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.MotionEvent;
import android.view.View;

/**
 * This shows how to update the UI in response to messages.
 * A duck pond is drawn, and on a finger press event momma duck is placed
 * along with a few smaller randomly positioned baby ducks. The baby ducks
 * will always try to line up behind momma duck. If they are not in position,
 * they know of it because momma duck sends them all a message each time
 * she moves. Every movement from a baby duck is therefore in response
 * to a message.
 */
public class MessageQueueActivity extends Activity {
    
    /**
     * Handle the onCreate event
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new DuckPondView(this));
    }

    /**
     * Create a DuckPondView which draws the duck pond and the ducks, and
     * handles the finger press events by sending and handling the appropriate
     * messages.
     */
    private static class DuckPondView extends View {
        private int mBabyDuckCount;
        private static final int MSG_RECALC_BABYDUCK_POSITIONS = 1;
        private static final int BABY_UPDATE_TICKTIME = 200;

        private final Paint mPaint = new Paint();
        private Bitmap mBmDuckPond;
        private Bitmap mBmMommaDuck;
        private Bitmap mBmBabyDuck;

        private int mMommaDuckXPos = -1;
        private int mMommaDuckYPos = -1;

        private List<Point> mBabyDuckPositions = new ArrayList<Point>();

        public DuckPondView(Context context) {
            super(context);
            setFocusable(true);

            // Create the bitmaps
            mBmDuckPond = BitmapFactory.decodeResource(getResources(),
                R.drawable.duckpond);
            mBmMommaDuck = BitmapFactory.decodeResource(getResources(),
                R.drawable.mommaduck);
            mBmBabyDuck = BitmapFactory.decodeResource(getResources(),
                R.drawable.babyduck);

            // Place the baby ducks randomly
            Random generator = new Random();
            mBabyDuckCount = 5 + generator.nextInt(3);
            for (int i = 0; i < mBabyDuckCount; i++) {
                Point position = new Point ( 50 + generator.nextInt(250),
                    50 + generator.nextInt(350) );
                mBabyDuckPositions.add(position);
            }
        }

        /**
         * The main duck pond draw handler
         */
        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawBitmap(mBmDuckPond, 0, 0, mPaint);

            // Draw Momma duck if she's present
            if (!(mMommaDuckXPos < 0 && mMommaDuckYPos < 0)) {
                canvas.drawBitmap(mBmMommaDuck, mMommaDuckXPos, mMommaDuckYPos, mPaint);
            }

            // Draw the baby ducks
            for (Point position: mBabyDuckPositions) {
                canvas.drawBitmap(mBmBabyDuck, position.x, position.y, mPaint);
            }
        }

        /**
         * Handle the finger press events
         */
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    if (mMommaDuckXPos < 0 && mMommaDuckYPos < 0) {

                        // No Momma duck present so set it
                        mMommaDuckXPos = (int) event.getX() -
                            (mBmMommaDuck.getWidth() / 2);
                        mMommaDuckYPos = (int) event.getY() -
                            (mBmMommaDuck.getHeight() / 2);
                    }
                    invalidate();
                    break;
                }

                case MotionEvent.ACTION_MOVE: {

                    // Was this a press within the Momma Duck Region?
                    if (!(mMommaDuckXPos < 0 && mMommaDuckYPos < 0)) {
                        Region r = new Region(mMommaDuckXPos, mMommaDuckYPos,
                            mMommaDuckXPos + mBmMommaDuck.getWidth(),
                            mMommaDuckYPos + mBmMommaDuck.getHeight());
                        if (r.contains((int) event.getX(), (int) event.getY())) {
                            mMommaDuckXPos = (int) event.getX() -
                                (mBmMommaDuck.getWidth() / 2);
                            mMommaDuckYPos = (int) event.getY() -
                                (mBmMommaDuck.getHeight() / 2);
                        }
                        Message msg = Message.obtain();
                        msg.arg1 = MSG_RECALC_BABYDUCK_POSITIONS;
                        handler.sendMessageDelayed(msg, BABY_UPDATE_TICKTIME);
                    }
                    invalidate();
                    break;
                }
            }
            return true;
        }

        /**
         * Handle incoming messages
         */
        private Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                switch (msg.arg1) {
                    case MSG_RECALC_BABYDUCK_POSITIONS: {

                        // Iterate through the duck position List checking
                        // each one is in position. If any are not, recalculate
                        // their positions. If they are all in position, set
                        // the allInPosition flag because it means no more
                        // recalc messages are needed.
                        boolean allInPosition = true;
                        boolean thisDuckHasMoved;
                        int duckCount = 0;
                        int finalDuckXPos;
                        int finalDuckYPos;
                        int newDuckXPos;
                        int newDuckYPos;

                        for (Point position: mBabyDuckPositions) {
                            thisDuckHasMoved = false;

                            // Calc where duck should be
                            finalDuckXPos = mMommaDuckXPos
                                + mBmMommaDuck.getWidth()
                                + (duckCount * mBmBabyDuck.getWidth());
                            finalDuckYPos = mMommaDuckYPos
                                + (mBmMommaDuck.getHeight() / 2);

                            // Is this duck in position?
                            // Do we need to move this duck in the X axis?
                            if (finalDuckXPos != position.x) {
                                thisDuckHasMoved = true;
                                newDuckXPos = position.x;
                                if (finalDuckXPos < position.x) {
                                    newDuckXPos--;
                                } else {
                                    newDuckXPos++;
                                }
                                position.x = newDuckXPos;
                            }

                            // Do we need to move this duck in the Y axis?
                            if (finalDuckYPos != position.y) {
                                thisDuckHasMoved = true;
                                newDuckYPos = position.y;
                                if (finalDuckYPos < position.y) {
                                    newDuckYPos--;
                                } else {
                                    newDuckYPos++;
                                }
                                position.y = newDuckYPos;
                            }
                            if (thisDuckHasMoved) {
                                mBabyDuckPositions.set(duckCount, position);
                                allInPosition = false;
                            }
                            duckCount++;
                        }

                        if (!allInPosition) {
                            Message msgUpdate = Message.obtain();
                            msgUpdate.arg1 = MSG_RECALC_BABYDUCK_POSITIONS;
                            handler.sendMessageDelayed(msgUpdate,
                                BABY_UPDATE_TICKTIME);
                            invalidate();
                        }
                        break;
                    }
                }
            }
        };
    }
}
Joomla Templates and Joomla Extensions by JoomlaVision.Com
 

Add comment


Security code
Refresh

Portions are modifications based on work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License. Android Academy is independent from Google. All trademarks acknowledged.
 
Glossary
We have 22 guests online