Commits

machristie  committed f1b49e3

initial import; created demos of two ways to deal with AsyncTask during Activity restarts

  • Participants

Comments (0)

Files changed (20)

+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>
+
+syntax: regexp
+^bin$
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>AsyncTaskTemplate</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

File AndroidManifest.xml

+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.fuelmyroute.android.asynctasktemplate"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="17" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.fuelmyroute.android.asynctasktemplate.AsyncTaskActivity"
+            android:label="@string/app_name" >
+        </activity>
+        <activity
+            android:name="com.fuelmyroute.android.asynctasktemplate.RetainedAsyncTaskActivity"
+            android:label="@string/activity_name_retained_at" >
+        </activity>
+        <activity
+            android:name="com.fuelmyroute.android.asynctasktemplate.MainActivity"
+            android:label="@string/title_activity_main" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

File proguard-project.txt

+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

File project.properties

+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-17

File res/drawable-hdpi/ic_launcher.png

Added
New image

File res/drawable-mdpi/ic_launcher.png

Added
New image

File res/drawable-xhdpi/ic_launcher.png

Added
New image

File res/layout/activity_async_task.xml

+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".AsyncTaskActivity" >
+
+    <ProgressBar
+        android:id="@+id/progressBar"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:visibility="gone" />
+
+    <Button
+        android:id="@+id/button_load"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="48dp"
+        android:text="@string/button_label_load"
+        android:onClick="onLoadClick" />
+
+    <TextView
+        android:id="@+id/textview_total"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/button_load"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="22dp"
+        android:textAppearance="?android:attr/textAppearanceLarge" />
+
+</RelativeLayout>

File res/layout/activity_main.xml

+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity" >
+
+    <Button
+        android:id="@+id/button_restarting_async"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginTop="23dp"
+        android:text="@string/restarting_asynctask"
+        android:onClick="startRestartingAsyncActivity" />
+
+    <Button
+        android:id="@+id/button_retained_async"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_below="@+id/button_restarting_async"
+        android:text="@string/retained_asynctask" 
+        android:onClick="startRetainedAsyncActivity"/>
+
+</RelativeLayout>

File res/menu/activity_async_task.xml

+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@+id/menu_settings"
+        android:orderInCategory="100"
+        android:showAsAction="never"
+        android:title="@string/menu_settings"/>
+
+</menu>

File res/menu/activity_main.xml

+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@+id/menu_settings"
+        android:orderInCategory="100"
+        android:showAsAction="never"
+        android:title="@string/menu_settings"/>
+
+</menu>

File res/values-v11/styles.xml

+<resources>
+
+    <!--
+        Base application theme for API 11+. This theme completely replaces
+        AppBaseTheme from res/values/styles.xml on API 11+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
+        <!-- API 11 theme customizations can go here. -->
+    </style>
+
+</resources>

File res/values-v14/styles.xml

+<resources>
+
+    <!--
+        Base application theme for API 14+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>

File res/values/strings.xml

+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">Restarted AsyncTask</string>
+    <string name="activity_name_retained_at">Retained AsyncTask</string>
+    <string name="hello_world">Hello world!</string>
+    <string name="menu_settings">Settings</string>
+    <string name="button_label_load">Load</string>
+    <string name="title_activity_main">MainActivity</string>
+    <string name="restarting_asynctask">Restarting AsyncTask</string>
+    <string name="retained_asynctask">Retained AsyncTask</string>
+
+</resources>

File res/values/styles.xml

+<resources>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>

File src/com/fuelmyroute/android/asynctasktemplate/AsyncTaskActivity.java

+package com.fuelmyroute.android.asynctasktemplate;
+
+import android.app.Activity;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.View;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class AsyncTaskActivity extends Activity {
+
+	private static final String TAG = AsyncTaskActivity.class.getSimpleName();
+
+	private static final String STATE_LOAD_IN_PROGRESS = "state.load.in_progress";
+
+	private static final String STATE_LOAD_MAX = "state.load.max";
+
+	private LoadTask loadTask = null;
+	private TextView textViewTotal = null;
+	private ProgressBar progressBar = null;
+	private int max = 10;
+
+	@Override
+	protected void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_async_task);
+
+		textViewTotal = (TextView) findViewById(R.id.textview_total);
+		progressBar = (ProgressBar) findViewById(R.id.progressBar);
+	}
+
+	@Override
+	public boolean onCreateOptionsMenu(Menu menu) {
+		// Inflate the menu; this adds items to the action bar if it is present.
+		getMenuInflater().inflate(R.menu.activity_async_task, menu);
+		return true;
+	}
+
+	public void onLoadClick(View loadButton) {
+		
+		Toast.makeText(getApplicationContext(), "Clicked!", Toast.LENGTH_SHORT)
+				.show();
+		loadTask = new LoadTask();
+		// max could be based on user input, but for now is just a hard coded
+		// instance variable in this activity
+		loadTask.execute(max);
+	}
+
+	@Override
+	protected void onDestroy() {
+		Log.d(TAG, "onDestroy");
+		super.onDestroy();
+
+		// We need to cancel the task even in onDestroy because
+		// onSaveInstanceState is only called when an activity instance may need
+		// to be restored
+		onCancelLoad();
+	}
+
+	private void onCancelLoad() {
+
+		if (loadTask != null
+				&& loadTask.getStatus() == AsyncTask.Status.RUNNING) {
+			loadTask.cancel(true);
+			loadTask = null;
+		}
+	}
+
+	@Override
+	protected void onRestoreInstanceState(Bundle savedInstanceState) {
+
+		Log.d(TAG, "onRestoreInstanceState");
+		super.onRestoreInstanceState(savedInstanceState);
+
+		restoreLoadTask(savedInstanceState);
+	}
+
+	private void restoreLoadTask(Bundle savedInstanceState) {
+
+		Log.d(TAG, "onRestoreInstanceState, " + STATE_LOAD_IN_PROGRESS + ": " + savedInstanceState.getBoolean(STATE_LOAD_IN_PROGRESS));
+		Log.d(TAG, "onRestoreInstanceState, " + STATE_LOAD_MAX + ": " + savedInstanceState.getInt(STATE_LOAD_MAX));
+		if (savedInstanceState.getBoolean(STATE_LOAD_IN_PROGRESS)) {
+			max = savedInstanceState.getInt(STATE_LOAD_MAX);
+			loadTask = new LoadTask();
+			loadTask.execute(max);
+		}
+	}
+
+	@Override
+	protected void onSaveInstanceState(Bundle outState) {
+		Log.d(TAG, "onSaveInstanceState: isFinishing=" + isFinishing());
+		super.onSaveInstanceState(outState);
+
+		// TODO: maybe we don't need to do this if isFinishing?
+		saveLoadTask(outState);
+	}
+
+	private void saveLoadTask(Bundle outState) {
+
+		if (loadTask != null
+				&& loadTask.getStatus() != AsyncTask.Status.FINISHED) {
+			loadTask.cancel(true);
+
+			if (max != 0) {
+				outState.putBoolean(STATE_LOAD_IN_PROGRESS, true);
+				outState.putInt(STATE_LOAD_MAX, max);
+			}
+
+			loadTask = null;
+		}
+	}
+
+
+	private class LoadTask extends AsyncTask<Integer, Integer, String> {
+
+		int i = 0;
+		int sum = 0;
+
+		@Override
+		protected void onPreExecute() {
+			super.onPreExecute();
+
+			textViewTotal.setText("Starting...");
+			progressBar.setVisibility(View.VISIBLE);
+		}
+
+		@Override
+		protected void onPostExecute(String result) {
+			super.onPostExecute(result);
+
+			textViewTotal.setText(result);
+			progressBar.setVisibility(View.GONE);
+		}
+
+		// first value is the running total, the second value is the iteration
+		// third value is the max
+		@Override
+		protected void onProgressUpdate(Integer... values) {
+			super.onProgressUpdate(values);
+			int runningTotal = values[0];
+			int iteration = values[1];
+			int maxIterations = values[2];
+			textViewTotal.setText("Current total: " + runningTotal);
+			progressBar.setProgress(Math.round(iteration * 100 / maxIterations));
+		}
+
+		@Override
+		protected void onCancelled() {
+			super.onCancelled();
+			textViewTotal.setText("Cancelled!");
+			Log.d(TAG, "Cancelled the task!");
+			progressBar.setVisibility(View.GONE);
+		}
+
+		@Override
+		protected String doInBackground(Integer... params) {
+
+			Log.d(TAG, "doInBackground");
+			int max = params[0];
+
+			while (i < max) {
+
+				i++;
+				sum += i;
+				publishProgress(sum, i, max);
+
+				try {
+					Thread.sleep(1000);
+				} catch (InterruptedException e) {
+					Log.d(TAG, "interrupted!", e);
+				}
+			}
+
+			return "The sum is: " + sum;
+		}
+
+	}
+}

File src/com/fuelmyroute/android/asynctasktemplate/MainActivity.java

+package com.fuelmyroute.android.asynctasktemplate;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.View;
+
+public class MainActivity extends Activity {
+
+	@Override
+	protected void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_main);
+	}
+
+	@Override
+	public boolean onCreateOptionsMenu(Menu menu) {
+		// Inflate the menu; this adds items to the action bar if it is present.
+		getMenuInflater().inflate(R.menu.activity_main, menu);
+		return true;
+	}
+
+	public void startRestartingAsyncActivity(View button) {
+		startActivity(new Intent(this, AsyncTaskActivity.class));
+	}
+
+	public void startRetainedAsyncActivity(View button) {
+		startActivity(new Intent(this, RetainedAsyncTaskActivity.class));
+	}
+}

File src/com/fuelmyroute/android/asynctasktemplate/RetainedAsyncTaskActivity.java

+package com.fuelmyroute.android.asynctasktemplate;
+
+import android.app.Activity;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class RetainedAsyncTaskActivity extends Activity {
+
+	private static final String TEXT_VIEW_TOTAL_TEXT = "textViewTotal.text";
+
+	private static final String PROGRESS_BAR_PROGRESS = "progressBar.progress";
+
+	private static final String PROGRESS_BAR_VISIBILITY = "progressBar.visibility";
+
+	private static final String TAG = RetainedAsyncTaskActivity.class.getSimpleName();
+
+	private LoadTask loadTask = null;
+	private TextView textViewTotal = null;
+	private ProgressBar progressBar = null;
+	private int max = 30;
+
+	@Override
+	protected void onCreate(Bundle savedInstanceState) {
+
+
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_async_task);
+
+		textViewTotal = (TextView) findViewById(R.id.textview_total);
+		progressBar = (ProgressBar) findViewById(R.id.progressBar);
+
+		// AsyncTask will not call UI thread methods from the point of it being
+		// retained until onCreate is called, but I'm not quite sure of the
+		// timing. I'm updating the task after I've grabbed references to the UI
+		// elements the task needs to update.
+
+		// TODO: how exactly does this mechanism work, is it guaranteed?
+		// Not quite sure how it works, but it does. Uncomment the following
+		// lines to convince yourself. You'll see that onCreate sleeps even
+		// while LoadTask is "publishing" progress updates.
+
+		// try {
+		// Log.d(TAG, "onCreate sleeping...");
+		// Thread.sleep(2000);
+		// Log.d(TAG, "onCreate awake");
+		// } catch (InterruptedException e) {
+		// // TODO Auto-generated catch block
+		// e.printStackTrace();
+		// }
+
+		if (getLastNonConfigurationInstance() != null) {
+			loadTask = (LoadTask) getLastNonConfigurationInstance();
+			loadTask.updateActivity(this);
+		}
+
+		if (savedInstanceState != null) {
+			textViewTotal.setText(savedInstanceState
+					.getString(TEXT_VIEW_TOTAL_TEXT));
+			progressBar.setVisibility(savedInstanceState
+					.getInt(PROGRESS_BAR_VISIBILITY));
+			progressBar.setProgress(savedInstanceState
+					.getInt(PROGRESS_BAR_PROGRESS));
+		}
+	}
+
+	public void onLoadClick(View loadButton) {
+		
+		Toast.makeText(getApplicationContext(), "Clicked!", Toast.LENGTH_SHORT)
+				.show();
+		loadTask = new LoadTask(this);
+		// max could be based on user input, but for now is just a hard coded
+		// instance variable in this activity
+		loadTask.execute(max);
+	}
+
+	@Override
+	public Object onRetainNonConfigurationInstance() {
+
+		if (loadTask != null
+				&& loadTask.getStatus() != AsyncTask.Status.FINISHED) {
+			// Just to prove to myself that AsyncTask won't call UI thread
+			// methods when activity goes away
+			loadTask.updateActivity(null);
+			return loadTask;
+		}
+
+		return null;
+	}
+
+	@Override
+	protected void onSaveInstanceState(Bundle outState) {
+		super.onSaveInstanceState(outState);
+
+		// Save progress bar state
+		outState.putInt(PROGRESS_BAR_VISIBILITY, progressBar.getVisibility());
+		outState.putInt(PROGRESS_BAR_PROGRESS, progressBar.getProgress());
+
+		// Save status text view state
+		outState.putString(TEXT_VIEW_TOTAL_TEXT,
+				textViewTotal.getText() != null ? textViewTotal.getText()
+						.toString() : null);
+	}
+
+	@Override
+	protected void onDestroy() {
+		Log.d(TAG, "onDestroy");
+		super.onDestroy();
+
+		// If we have a running task and we are finished, then go ahead and
+		// cancel it
+		if (isFinishing()) {
+			onCancelLoad();
+		}
+	}
+
+	private void onCancelLoad() {
+
+		if (loadTask != null
+				&& loadTask.getStatus() == AsyncTask.Status.RUNNING) {
+			loadTask.cancel(true);
+			loadTask = null;
+		}
+	}
+
+	private class LoadTask extends AsyncTask<Integer, Integer, String> {
+
+		// Important to only access this field on the UI thread or methods that
+		// run in the UI thread
+		private RetainedAsyncTaskActivity activity = null;
+		int i = 0;
+		int sum = 0;
+
+		LoadTask(RetainedAsyncTaskActivity activity) {
+			this.activity = activity;
+		}
+
+		public void updateActivity(RetainedAsyncTaskActivity activity) {
+			this.activity = activity;
+		}
+
+		@Override
+		protected void onPreExecute() {
+			super.onPreExecute();
+
+			activity.textViewTotal.setText("Starting...");
+			activity.progressBar.setVisibility(View.VISIBLE);
+		}
+
+		@Override
+		protected void onPostExecute(String result) {
+			super.onPostExecute(result);
+
+			activity.textViewTotal.setText(result);
+			activity.progressBar.setVisibility(View.GONE);
+		}
+
+		@Override
+		protected void onProgressUpdate(Integer... values) {
+			super.onProgressUpdate(values);
+			int runningTotal = values[0];
+			int iteration = values[1];
+			int maxIterations = values[2];
+			activity.textViewTotal.setText("Current total: " + runningTotal);
+			activity.progressBar.setProgress(Math.round(iteration * 100
+					/ maxIterations));
+		}
+
+		@Override
+		protected void onCancelled() {
+			super.onCancelled();
+			activity.textViewTotal.setText("Cancelled!");
+			Log.d(TAG, "Cancelled the task!");
+			activity.progressBar.setVisibility(View.GONE);
+		}
+
+		@Override
+		protected String doInBackground(Integer... params) {
+
+			Log.d(TAG, "doInBackground");
+			int max = params[0];
+
+			while (i < max) {
+
+				i++;
+				sum += i;
+				Log.d(TAG, "publishing progress: " + sum);
+				publishProgress(sum, i, max);
+
+				try {
+					Thread.sleep(1000);
+				} catch (InterruptedException e) {
+					Log.d(TAG, "interrupted!", e);
+				}
+			}
+
+			return "The sum is: " + sum;
+		}
+
+	}
+}