-
Notifications
You must be signed in to change notification settings - Fork 10
Lecture 2
In this lecture we will cover the basics of Android development. We will:
-
Create a new Android Application project, add an activity, launch the activity and debug.
-
Create a basic UI with buttons and post messages to logcat.
-
Create and write to a log file.
-
Launch long tasks to run asynchronously.
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:
After the second screen, click Finish to have the wizard generate your skeleton code.
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>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:
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.
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.
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);
}
}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);
}
}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>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