Search This Blog

Tuesday, July 16, 2013

The Agony of Android Development...

A long time ago in a galaxy far, far away I used Eclipse for software development.

I can't even remember why.

Its awful.  Mostly its not precisely deterministic.  Things that you do once don't work unless you restart it or fool around in some way. 

It hasn't changed.

Three or four sub-menus off of "File" to open an existing Android project.

The emulator comm goes off into the weeds randomly and you have to reset things.  The debug window spews crap from all the local app garbage collections running the emulator; really?

This as opposed to XCode or VisualStudio; of course these have their problems as well but they are deterministic relative to Eclipse.

So I am working on a cross-platform app: Windows, OS X, Android, iOS.  I know how to write the others so I figured I'd an Android version up and running.

To do this I decided to write a basic app with the following properties: threads, some images that can detect touches, actions based on touches, dynamic transparency and color, background stuff, basic stuff to build my app.

(The idea is to get everything you need in one place...)

So, after agonizing hours here is the core of what you need all in one place (I will incrementally add to this as I uncover more nasties, like dynamic image transparency, etc.)

Create a new Eclipse project using defaults and use this code.  You also need this image:





There is transparency.


Put the image in res/drawable-mdpi.  Paste the code into the editor.   You will get some log output in the "LogCat" window - good luck finding this if its not already visible.

Activities appear to be like iOS Views, but we'll see...
package com.foobar.basicsuface1;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.view.WindowManager;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Bitmap;

public class BasicSufaceActivity extends Activity {

    private static final String TAG = BasicSufaceActivity.class.getSimpleName();

    public class mainSurface extends SurfaceView implements SurfaceHolder.Callback {

        private mainThread thread;        // This thread does all the display updating.

        public class mainThread extends Thread {
           
            Bitmap bmp;

            // Surface holder that can access the physical surface
            private SurfaceHolder surfaceHolder;
            // The actual view that handles inputs
            // and draws to the surface

            // flag to hold game state
            private boolean running;
           
            public void setRunning(boolean running) {
                this.running = running;
            }

            public mainThread(SurfaceHolder surfaceHolder) {
                super();
                this.surfaceHolder = surfaceHolder;
               
                // You have to use Finder to "copy" android1test.png
and then "paste" it into
                // res/drawable-mdpi using Eclipse for Eclipse to actually find it.
                // Don't use spaces in the name or Eclipse/ADT creates problems.
                //
                bmp = BitmapFactory.decodeResource(getResources(), R.drawable.android1test);
            }

            public void display(Canvas canvas) {
                canvas.drawBitmap(bmp, 10, 10, null);
            }

            @Override
            public void run() {
                Canvas canvas;
               
                Log.d(TAG, "Starting display loop...");
               
                while (running) {
                    canvas = null;
                    try {
                        canvas = this.surfaceHolder.lockCanvas();
                        if (canvas != null)
                        {
                            synchronized (surfaceHolder) {
                                display(canvas);
                            }
                        }
                    } finally {
                        if (canvas != null)
                        {
                            surfaceHolder.unlockCanvasAndPost(canvas);
                        }
                    }
                }
                Log.d(TAG, "Display loop terminates...");
            }
        }

        public mainSurface(Context context) {
            super(context);
           
            // adding the callback (this) to the surface holder to intercept events
            getHolder().addCallback(this);

            // create the display loop thread
            thread = new mainThread(getHolder());

            // make this surface focusable so it can handle events
            setFocusable(true);
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // at this point the surface is created and
            // we can safely start the display loop
            //
            thread.setRunning(true);
            thread.start();
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            Log.d(TAG, "Surface is being destroyed");
            // tell the thread to shut down and wait for it to finish
            // this is a clean shutdown
            boolean retry = true;
            thread.running = false;
            while (retry) {
                try {
                    thread.join();
                    retry = false;
                } catch (InterruptedException e) {
                    // try again shutting down the thread
                }
            }
            Log.d(TAG, "Thread was shut down cleanly");
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                if (event.getY() > getHeight() - 50) {
                    thread.setRunning(false);
                    ((Activity)getContext()).finish();
                } else {
                    Log.d(TAG, "Coords: x=" + event.getX() + ",y=" + event.getY());
                }
            }
            return super.onTouchEvent(event);
        }

    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // requesting to turn the title OFF
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // making it full screen
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        // set our MainGamePanel as the View
        setContentView(new mainSurface(this));
        Log.d(TAG, "View added");
    }

    @Override
    protected void onDestroy() {
        Log.d(TAG, "Destroying...");
        super.onDestroy();
    }

    @Override
    protected void onStop() {
        Log.d(TAG, "Stopping...");
        super.onStop();
    }

}