< CS193A Android Programming

Example projects linked at the end of the page.

View Animation Bonus Techniques

First today, I'll add some more advanced techniques from last time's monkey view example. You do not need to use these techniques on your view homework.

1. Invalidate-Rect Optimization

			// ACTION_MOVE = during a finger drag
			// Compute delta of gesture, apply that to the model.
			else if (action == MotionEvent.ACTION_MOVE && mDragging) {
				// Compute delta
				float deltaX = x - mLastX;
				float deltaY = y - mLastY;

				// Not-optimized strategy:
				// invalidate();

				// Invalidate-rect optimization. Rather than invalidate the entire view,
				// compute just the area of pixels to redraw. For a move, want to invalidate the pre-move
				// rect and the post-move rect. This can be a significant speedup, as the number of pixels
				// to redraw could easily be 10x less.

				// 1. Compute rect of the monkey pre-move. Cast float down to int, and add 1 to width/height to avoid
				// rounding problems. Invalidate just this rect.
				Rect rect = new Rect((int)mX, (int)mY, (int)mX + mMonkey.getWidth() + 1, (int)mY + mMonkey.getHeight() + 1);
				invalidate(rect);

				// 2. Now move the rect by the delta for this, case, and invalidate again. The two invalidate
				// rects are added together by the system, forming the overall area to draw.
				rect.offset((int)deltaX, (int)deltaY);
				invalidate(rect);

				// Apply that delta to the monkey
				mX += deltaX;
				mY += deltaY;

				// Store lastX/lastY for next time.
				mLastX = x;
				mLastY = y;
			}

2. Pause/Resume

3. kill/recreate cycle

	/**
	 * View save-code for kill/recreate case. Note that this is similar but different
	 * from the activity onSaveInstaneState() code.
	 * This method and the resume below are sent automatically by the system for the
	 * kill/recreate case. We need to save any mXXX variables we want to preserve.
	 * Result of this code is: monkey could be motoring, but when it goes through
	 * a kill/recreate cycle, it keeps the same position and speed and keeps motoring.
	 */
	@Override
	protected Parcelable onSaveInstanceState() {
		System.err.println("save");

		// 1. Make Bundle, save mXXX vars we want to preserve.
		Bundle bundle = new Bundle();
		bundle.putFloat("x", mX);
		bundle.putFloat("y", mY);
		bundle.putFloat("dx", mDX);
		bundle.putFloat("dy", mDY);
		bundle.putBoolean("motoring", mMotoring);
		// not saving "clicked"

		// 2. Also should call super class, and save whatever
		// it returns.
		Parcelable s = super.onSaveInstanceState();
		bundle.putParcelable("super", s);

		return bundle;
	}


	/** Restore from kill/recreate cycle. */
	@Override
	protected void onRestoreInstanceState(Parcelable state) {
		System.err.println("restore");

		// This gets passed whatever we make above, so it should be a Bundle.
		Bundle bundle = (Bundle) state;
		mX = bundle.getFloat("x");
		mY = bundle.getFloat("y");
		mDX = bundle.getFloat("dx");
		mDY = bundle.getFloat("dy");
		mMotoring = bundle.getBoolean("motoring");
		// mClicked we'll leave as false on resume

		// Restore for our superclass too.
		// todo: should this be first or last?
		super.onRestoreInstanceState(bundle.getParcelable("super"));
	}

New Topic: Doing Work In The Background

First a demo: start the download on a background thread. Notice how start/stop buttons enable. Notice the status text updating and two progress bars, all updating as the work goes along. Notice how the seeker bar still works as the work goes on (i.e. the work is not on the UI thread). This is ideal: we get visual feedback that that work is happening, we can stop it if we want. The UI is still responsive to any clicks etc.

AsyncTask Strategy

Start/Stop Methods

package edu.stanford.nick;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;



/**
 * This example demonstrates doing networking in the background
 * as an AsyncTask. Handles correctly button enabling, progress bars, stop button,
 * pausing.
 */
public class NetworkFetchActivity extends Activity {
	// Pointers to ui elements, set up in onCreate()
	private EditText mUrlText;
	private TextView mStatusText;
	private Button mDownloadButton;
	private Button mStopButton;
	private ProgressBar mProgress;
	private CheckBox mSlowMode;


	/**
	 * Currently running download task or null.
	 */
	private DownloadTask mDownloadTask;    


	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// This is the simple progress bar which works in the window title.
		requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);

		setContentView(R.layout.main);

		// Get pointers to the UI elements in the main.xml layout
		mUrlText = (EditText)findViewById(R.id.url_text);
		mStatusText = (TextView)findViewById(R.id.status_text);
		mDownloadButton = (Button)findViewById(R.id.task_download_button);
		mStopButton = (Button)findViewById(R.id.stop_button);
		mProgress = (ProgressBar) findViewById(R.id.progress_bar);
		mSlowMode = (CheckBox) findViewById(R.id.slow_mode);

		mDownloadButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				startDownloadTask(mUrlText.getText());
			}
		});

		mStopButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				stopDownloadTask();
			}
		});        
		setUIRunning(false);
	}


	/** AsyncTask inner class download data from the given url -- basic AsyncTask example.
	 */
	private class DownloadTask extends AsyncTask<CharSequence, CharSequence, CharSequence> {
		private boolean mSlow;

		// Runs on UI thread first for setup. Here we grab the state of
		// the slow-down checkbox.
		// With this, notice how never have to touch the UI once we start running.
		protected void onPreExecute() {
			mSlow = mSlowMode.isChecked();
		}

		// Main run code, runs on a background thread.
		// This must not touch the UI directly AT ALL (!!!!!!!).
		// Various methods are available to allow us to indirectly.
		// work with the UI.
		// Can call publishProgress() periodically to send status updates to the UI.
		// When this is done, its return value is passed to onPostExecute()
		// to communicate the result to the UI.
		protected CharSequence doInBackground(CharSequence... urls) {
			String status = "";
			int totalSize = 0;
			InputStream in = null;
			long start = System.currentTimeMillis();
			long lastTime = start;
			try {
				// This code downloads the bytes at the given url, updates the status.
				URL url = new URL(urls[0].toString());
				URLConnection connection = url.openConnection();
				connection.setConnectTimeout(10000);

				connection.connect();
				in = connection.getInputStream();

				// Here we just read and discard the bytes to count them
				byte[] buff = new byte[1024];
				int len;
				while ((len = in.read(buff, 0, buff.length)) != -1) {
					totalSize += len;
					// With slow mode, limit download to around 10 kb/sec
					if (mSlow) {
						Thread.sleep(100);
					}

					// A few times a second, send an update to the UI.
					long now = System.currentTimeMillis();
					if (now - lastTime >= 250) {
						publishProgress(statusText(totalSize, now - start));
						lastTime = now;
					}
				}
				// Successful download if we get here
				status = "done";
			}
			catch (InterruptedException ex) {
				// nothing special required here ... just continue to below
			}
			catch (IOException ex) {
				status = "network error:" + ex.getMessage();
			}
			// close the input stream in any case.
			finally {
				try {
					if (in != null) in.close();
				}
				catch(IOException ignored) {}  // don't care about exception on close
			}

			if (status.equals("done")) return "done " + statusText(totalSize, System.currentTimeMillis() - start);
			else return status;  // will be some sort of error message
		}

		// Called with status update from publishProgress, push data to the UI.
		// (this runs on the UI thread, so it can touch the UI).
		protected void onProgressUpdate(CharSequence... status) {
			mStatusText.setText(status[0]);
		}

		// Called for final result, push it to the UI.
		protected void onPostExecute(CharSequence status) {
			setUIRunning(false);
			mStatusText.setText(status);
			mDownloadTask = null;
		}

		// Called for cancel case.. could update the UI here.
		@Override
		protected void onCancelled() {
			mDownloadTask = null;
		}
	}


	/**
	 * Starts the download process going with the given url,
	 */
	private void startDownloadTask(CharSequence url) {
		setUIRunning(true);
		mStatusText.setText("Downloading\u2026");
		mDownloadTask = new DownloadTask();
		mDownloadTask.execute(url);
	}

	/** Compute the "bytes read ..." status text given count of bytes
     read and elapsed ms.
	 */
	public static CharSequence statusText(int size, long elapsed) {
		if (elapsed == 0) elapsed = 1;
		return "bytes read:" + size + "   kb/sec:" + (size * 1000) / (elapsed * 1024);
	}

	/**
	 * Stops the download process and updates the UI.
	 */
	private void stopDownloadTask() {        
		if (mDownloadTask != null) {
			mDownloadTask.cancel(true);
		} 

		// The task stops running on its own. Here we update the UI.
		setUIRunning(false);
		mStatusText.setText(mStatusText.getText() + " -- stopped");
	}

	/**
	 * Sets the UI to reflect running or not (button enabling, progress bars running).
	 */
	private void setUIRunning(boolean running) {
		mDownloadButton.setEnabled(!running);
		mStopButton.setEnabled(running);

		// Here using the little progress widget in the window.
		setProgressBarIndeterminateVisibility(running);

		// This second progress bar in the main view is indeterminate,
		// so it always runs when visible.
		// Therefore, all we do is make it visible or not.
		mProgress.setVisibility(running?View.VISIBLE:View.INVISIBLE);
	}
	
	@Override
	public void onPause() {
		super.onPause();
		stopDownloadTask();  // In this case, we'll say leaving does a "stop"
	}
}

For the curious, today's complete example projects MonkeyImage.zip NetworkFetch.zip