Skip to content
Alejandro Troccoli edited this page Mar 31, 2015 · 12 revisions

Introduction to Android development

In this lecture we will cover the basics of Android development. We will:

  1. Create a new Android Application project, add an activity, launch the activity and debug.

  2. Create a basic UI with buttons and post messages to logcat.

  3. Create and write to a log file.

  4. Launch long tasks to run asynchronously.

Part 1: Create a new Android Application Project

To create a new Android Application Project, in Eclipse choose File → New → Android Applicatipn Project. Fill the wizard out with the following values for an application using Android 5.0 SDK APIs:

New Android App
New Android App

After the second screen, click Finish to have the wizard generate your skeleton code.

New Android Project

Take a look at the AndroidManifest.xml, it describes your application.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="edu.stanford.cs231m"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="21" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
    </application>

</manifest>

Create an Activity

An Activity contains your user interface components. We will now create a very simple Activity that we will launch on the device to show the text "Hello Android!". Go to File → New → Class and create new class that derives from android.app.Activity:

New Activity Class

Now, open the newly created java source file, and add the following code:

public class HelloAndroidActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {

		TextView txtView = new TextView(this);
		txtView.setText("Hello Android!");
		setContentView(txtView);
	}

}

Save the file, and launch the application.

New Activity Class

The Activity was compiled, but the Android Package Manager doesn’t know what the entry point of our application is. We define the entry point with an intent filter in the AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="edu.stanford.cs231m"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="21" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

        <activity android:name="HelloAndroidActivity" android:label="@string/app_name" android:screenOrientation="landscape">
            <intent-filter>
                <category android:name="android.intent.category.LAUNCHER"/>
                <action android:name="android.intent.action.MAIN"/>
            </intent-filter>
        </activity>

    </application>

</manifest>

Launch the application again, what happened? The Application is not responding, which means it has crashed. We will launch now with the debugger to see if we can get further insight on what is going on.

Debugger stack
Debugger variables

We forgot to call the parent class from the onCreate method. We shall add it and try again:

public class HelloAndroidActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {

    TextView txtView = new TextView(this);
    txtView.setText("Hello Android!");
    setContentView(txtView);

    super.onCreate(savedInstanceState);
  }

}

Part 2: Add UI elements using layouts

We will now create a set of layouts to arrange a pair of buttons and the text view.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <TextView
        android:id="@+id/txtMain"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:text="TextView" />

    <LinearLayout
        android:layout_width="200dp"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:orientation="vertical" >

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button 1" />

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button 2" />

    </LinearLayout>

</LinearLayout>

And modify the onCreate method to: 1. Add references to the elements in the layout 2. Add event handlers. 3. Print to the Android Log.

package edu.stanford.cs231m;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class HelloAndroidActivity extends Activity {

	protected static final String TAG = "HelloAndroid";
	private TextView mMainText;
	private Button mButton1;
	private Button mButton2;


	@Override
	public void onCreate(Bundle settings) {

		setContentView(R.layout.main_layout);

		mMainText = (TextView) findViewById(R.id.txtMain);
		mButton1 = (Button) findViewById(R.id.button1);
		mButton2 = (Button) findViewById(R.id.button2);

		mMainText.setText("Hello Android!\n");

		mButton1.setOnClickListener( new OnClickListener() {

			@Override
			public void onClick(View v) {
				mMainText.append("Button 1 was pressed!\n");
				Log.i(TAG, "Button 1 was pressed");
			}
		});

		mButton2.setOnClickListener( new OnClickListener() {

			@Override
			public void onClick(View v) {
				mMainText.append("Button 2 was pressed!\n");
				Log.i(TAG, "Button 2 was pressed");
			}
		});

		super.onCreate(settings);
	}
}

Part 3: Write to a log file

First, we are going to create a method to create a log file. Pay attention to how we find where to create the file, and then print to the log the file location.

private BufferedWriter openLogFile()
	{

		File appExternalDir = new File( Environment.getExternalStorageDirectory(),
				"HelloAndroid");

		if ( !appExternalDir.exists() )
		{
			if ( appExternalDir.mkdirs() )
			{
				Log.i(TAG, "External storage directory created: " + appExternalDir.toString() );
			}
			else
			{
				Log.e(TAG, "Failed to create directory " + appExternalDir.toString() );
				return null;
			}
		}

		File logFile = new File( appExternalDir, "log.txt");

		BufferedWriter writer = null;
		try {
			 writer = new BufferedWriter( new FileWriter(logFile));
       Log.i(TAG, "Created log file" + logFile);
		} catch (IOException e) {
			Log.e(TAG, "Failed to create file " + logFile.toString() );
			return null;
		}

		return writer;
	}

Now we are going to add a method to print to the log file

private void logMessage( String message )
{
  if ( mLogWriter != null )
  {
    try {
      mLogWriter.write(message);
      mLogWriter.newLine();
      mLogWriter.flush();
    } catch (IOException e) {
      Log.e(TAG, "Failed to write to log file");
    }

  }
}

In the AndroidManifest.xml, you will need to request permission to access the external storage.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="edu.stanford.cs231m"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="21" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

        <activity android:name="HelloAndroidActivity" android:label="@string/app_name" android:screenOrientation="landscape">
            <intent-filter>
                <category android:name="android.intent.category.LAUNCHER"/>
                <action android:name="android.intent.action.MAIN"/>
            </intent-filter>
        </activity>

    </application>

</manifest>

Part 4: Long running tasks

In this part we will look at how to run long running tasks asynchronously. First, let’s write a function that will take a long time execute and see what happens if we run it on the main application thread.

private void longRunningTask( long taskDurationInMs )
{
  long startTime = System.currentTimeMillis();
  mMainText.append("Started long running task at" + startTime +"\n");

  long currentTime = startTime;
  do
  {
    try {
      Thread.sleep( taskDurationInMs );
    } catch (InterruptedException e) {
    }

    currentTime = System.currentTimeMillis();

  } while ( currentTime < startTime + taskDurationInMs );

  mMainText.append("Ended long running task at" + currentTime +"\n");
}

Modify the OnClickListener of Button 2, make it call this long running task with a duration of 6000 ms, and launch the app. You should notice that when you press Button 2 the UI becomes sluggish and doesn’t respond well.

Ideally we would like to run the long running tasks on a separate thread so that the UI keeps responsive. Let’s create a separate thread to run our long task. Add a variable of type Thread mWorkerThread; and then modify the OnClickListener to create and start the thread:

mButton2.setOnClickListener( new OnClickListener() {

  @Override
  public void onClick(View v) {
    mMainText.append("Button 2 was pressed!\n");
    Log.i(TAG, "Button 2 was pressed");
    logMessage("Button 2 was pressed");
    mWorkerThread = new Thread ( new Runnable() {

      @Override
      public void run() {
        longRunningTask(6000);
      };
    });
    mWorkerThread.start();
  }
});

Run the code. What happened?

The application crashed. The reason for the crash can be found by looking at the logcat output. Android is complaining that we are trying to update a View from a thread that is different from the one that created it. The culprit here is the line that calls mMainText.append inside long running task.

To solve this problem, we need to create a Handler that will receive messages from worker threads and execute actions on the main thread.

Let’s add a Handler class variable:

private Handler mHandler;

private final static int MSG_ASYNC_TASK_STARTED = 0;
private final static int MSG_ASYNC_TASK_COMPLETED = 1;

Now, let’s create a Handler.Callback that we will pass on creation to the Handler. Note that this function will modify the UI components.

private Handler.Callback mHandlerCallback = new Handler.Callback() {

		@Override
		public boolean handleMessage(Message msg) {

			long currentTime = System.currentTimeMillis();
			switch( msg.what )
			{
			case MSG_ASYNC_TASK_STARTED:
				mMainText.append("Async task started at " + currentTime + "\n");
				return true;
			case MSG_ASYNC_TASK_COMPLETED:
				mMainText.append("Async task ended at " + currentTime + "\n");
				return true;
			default:
				// The message was not handled, return false
				return false;
			}
		}
	};

We now create the Handler instance during onCreate:

@Override
public void onCreate(Bundle settings) {

  setContentView(R.layout.main_layout);

  mLogWriter = openLogFile();
  mHandler = new Handler(mHandlerCallback);

  ...

and change the long running task to post messages to the Handler:

private void longRunningTask( long taskDurationInMs )
{
  long startTime = System.currentTimeMillis();
  mHandler.sendEmptyMessage(MSG_ASYNC_TASK_STARTED);

  long currentTime = startTime;
  do
  {
    try {
      Thread.sleep( taskDurationInMs );
    } catch (InterruptedException e) {
    }

    currentTime = System.currentTimeMillis();

  } while ( currentTime < startTime + taskDurationInMs );

  mHandler.sendEmptyMessage(MSG_ASYNC_TASK_COMPLETED);
}

We are ready to launch. Run the app and see how responsive it is after pressing 'Button 2'. The final task for this part is to add a progress dialog to show a message on the screen. Add a new ProgressDialog variable and create an instance of a ProgressDialog object.

...
private Handler mHandler;
private ProgressDialog mProgress;


@Override
public void onCreate(Bundle settings) {

  setContentView(R.layout.main_layout);
  mLogWriter = openLogFile();
  mHandler = new Handler(mHandlerCallback);
  mProgress = new ProgressDialog(this);
...

Then modify the handler callback to show and dismiss the progress dialog:

private Handler.Callback mHandlerCallback = new Handler.Callback() {

		@Override
		public boolean handleMessage(Message msg) {

			long currentTime = System.currentTimeMillis();
			switch( msg.what )
			{
			case MSG_ASYNC_TASK_STARTED:
				mMainText.append("Async task started at " + currentTime + "\n");
				mProgress.setTitle("Running async task");
				mProgress.setMessage("Wait...");
				mProgress.show();
				return true;
			case MSG_ASYNC_TASK_COMPLETED:
				mMainText.append("Async task ended at " + currentTime + "\n");
				mProgress.dismiss();
				return true;
			default:
				// The message was not handled, return false
				return false;
			}
		}
	};

Run the app and see the dialog showing up when pressing Button 2

Clone this wiki locally