Permalink
Browse files

Added CrossConfigSurvival project

  • Loading branch information...
1 parent bd9546d commit d11e02a5d9659b7c59ebc7d3c03e925666ad7293 @inazaruk committed Jul 17, 2011
View
7 CrossConfigSurvival/.classpath
@@ -0,0 +1,7 @@
+<?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="output" path="bin"/>
+</classpath>
View
33 CrossConfigSurvival/.project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>CrossConfigSurvival</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>
View
18 CrossConfigSurvival/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.inazaruk.configchanges"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <uses-sdk android:minSdkVersion="8" />
+
+ <application android:icon="@drawable/icon" android:label="@string/app_name">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+</manifest>
View
11 CrossConfigSurvival/default.properties
@@ -0,0 +1,11 @@
+# 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 use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-8
View
36 CrossConfigSurvival/proguard.cfg
@@ -0,0 +1,36 @@
+-optimizationpasses 5
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-dontpreverify
+-verbose
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+-keep public class * extends android.app.backup.BackupAgentHelper
+-keep public class * extends android.preference.Preference
+-keep public class com.android.vending.licensing.ILicensingService
+
+-keepclasseswithmembernames class * {
+ native <methods>;
+}
+
+-keepclasseswithmembernames class * {
+ public <init>(android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembernames class * {
+ public <init>(android.content.Context, android.util.AttributeSet, int);
+}
+
+-keepclassmembers enum * {
+ public static **[] values();
+ public static ** valueOf(java.lang.String);
+}
+
+-keep class * implements android.os.Parcelable {
+ public static final android.os.Parcelable$Creator *;
+}
View
BIN CrossConfigSurvival/res/drawable-hdpi/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN CrossConfigSurvival/res/drawable-ldpi/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN CrossConfigSurvival/res/drawable-mdpi/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
27 CrossConfigSurvival/res/layout/main.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+
+ <TextView
+ android:id="@+id/progress"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="not started" />
+
+ <Button
+ android:id="@+id/start"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="start"
+ android:onClick="start" />
+
+ <Button
+ android:id="@+id/cancel"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="cancel"
+ android:onClick="cancel" />
+</LinearLayout>
View
5 CrossConfigSurvival/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="hello">Hello World, MainActivity!</string>
+ <string name="app_name">CrossConfigSurvival</string>
+</resources>
View
305 CrossConfigSurvival/src/com/inazaruk/configchanges/MainActivity.java
@@ -0,0 +1,305 @@
+package com.inazaruk.configchanges;
+
+import java.lang.ref.WeakReference;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+public class MainActivity extends Activity
+{
+ private final String TAG = this.toString();
+
+ public interface TaskListener
+ {
+ void onProgress(int max, int cur);
+ void onCompleted(Object result);
+ void onCancelled();
+ }
+
+ /**
+ * If configuration change is detected this varibale is set to true.
+ * It will be checked in onDestroy() to decide whether to cancel
+ * async task or not.
+ */
+ boolean mConfigurationChange = false;
+
+ /**
+ * This variable is set to true just before onDestroy returns.
+ * This is used to decide whether UI can be update or not.
+ */
+ boolean mDestroyed = false;
+
+ TextView mProgressText;
+ CrossConfigAsyncTask mAsyncTask;
+
+ /**
+ * Only update UI if activity is not destroyed.
+ */
+ TaskListener mTaskListener = new TaskListener()
+ {
+ @Override
+ public void onProgress(int max, int cur)
+ {
+ Log.e(TAG, "TaskListener.onProgress: "+cur+"/"+max);
+ if(!mDestroyed)
+ {
+ mProgressText.setText(cur+"/"+max);
+ }
+ }
+
+ @Override
+ public void onCompleted(Object result)
+ {
+ Log.e(TAG, "TaskListener.onCompleted: "+result);
+ if(!mDestroyed)
+ {
+ mProgressText.setText("Completed "+result);
+ }
+ }
+
+ @Override
+ public void onCancelled()
+ {
+ Log.e(TAG, "TaskListener.onCancelled");
+ if(!mDestroyed)
+ {
+ mProgressText.setText("Cancelled");
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ Log.e(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mProgressText = (TextView)findViewById(R.id.progress);
+
+ mAsyncTask = (CrossConfigAsyncTask)getLastNonConfigurationInstance();
+ if(mAsyncTask != null)
+ {
+ Log.e(TAG, "onCreate - mAsyncTask from previos activity is picked up. "+mAsyncTask);
+ mAsyncTask.setListener(mTaskListener);
+ }
+
+ System.gc();//force previous object of Activity to be garbage collect, if possible
+ }
+
+ @Override
+ public Object onRetainNonConfigurationInstance()
+ {
+ Log.e(TAG, "onRetainNonConfigurationInstance - mAsyncTask is saved. "+mAsyncTask);
+ mConfigurationChange = true;
+ return mAsyncTask;
+ }
+
+ @Override
+ protected void onDestroy()
+ {
+ Log.e(TAG, "onDestroy");
+ super.onDestroy();
+
+ if(!mConfigurationChange)
+ {
+ Log.e(TAG, "onDestroy - non configuration change onDestroy.");
+ if(mAsyncTask != null)
+ {
+ Log.e(TAG, "onDestroy - cancelling task as it was running.");
+ mAsyncTask.cancel(true);
+ }
+ }
+ mAsyncTask.setListener(null);
+ mDestroyed = true;
+ }
+
+ /**
+ * This function is called if button "start" is pressed.
+ * It starts new long running background task.
+ */
+ public void start(View v)
+ {
+ Log.e(TAG, "start - start new task.");
+
+ if(mAsyncTask != null)
+ {
+ Log.e(TAG, "start - cancel old task.");
+ mAsyncTask.cancel(true);
+ }
+ mAsyncTask = new CrossConfigAsyncTask(getApplicationContext(), 3000);
+ mAsyncTask.setListener(mTaskListener);
+ mAsyncTask.execute();
+ }
+
+ /**
+ * This function is called if button "cancel" is pressed.
+ * It cancels long running background task.
+ */
+ public void cancel(View v)
+ {
+ Log.e(TAG, "cancel");
+ if(mAsyncTask != null)
+ {
+ Log.e(TAG, "cancel - old task is cancelled");
+ mAsyncTask.cancel(true);
+ }
+ }
+
+
+ @Override
+ protected void finalize() throws Throwable
+ {
+ Log.e(TAG, "garbage collected");
+ super.finalize();
+ }
+
+ /**
+ * This class must reference anything from Activity, as it :
+ * 1) May live longer then any activity (that is destroyed due to config changes)
+ * 2) It may complete its task after onDestroy() is called on old activity,
+ * but before onCreate() is called on new activity due to configuration change.
+ *
+ */
+ private static class CrossConfigAsyncTask extends AsyncTask<Void, Integer, Object>
+ {
+ private final String TAG = this.toString();
+
+ private final Context mContext;
+ private final int mDuration;
+ private int mProgress;
+ private Object mResult = null;
+
+ /**
+ * Use weak reference so Activity that registers listener is not
+ * held in memory because of this listener.
+ */
+ private WeakReference<TaskListener> mListener;
+
+
+ /**
+ * Do not pass Activity as context to this constructor. This should
+ * be Application context. If Activity is passed here memory leaks
+ * are possible, because thread may live much longer then Activity that crated
+ * this thread.
+ */
+ public CrossConfigAsyncTask(Context ctx, int duration)
+ {
+ mContext = ctx;
+ mDuration = duration;
+ mListener = null;
+ }
+
+ /**
+ * setListener checks weather result is already available, and
+ * if so immediately reports it on listener's callback function.
+ * This is done in case task is completed just between
+ * onDestroy() and onCreate().
+ */
+ public void setListener(TaskListener listener)
+ {
+ synchronized (this)
+ {
+ if(listener == null)
+ {
+ mListener = null;
+ }
+ else
+ {
+ mListener = new WeakReference<TaskListener>(listener);
+ if(getStatus() == Status.FINISHED)
+ {
+ listener.onCompleted(mResult);
+ }
+ else
+ {
+ listener.onProgress(mDuration, mProgress);
+ }
+ }
+ }
+ }
+
+ public TaskListener getListener()
+ {
+ synchronized (this)
+ {
+ if(mListener != null)
+ {
+ return mListener.get();
+ }
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPreExecute()
+ {
+ Log.e(TAG, "onPreExecute");
+ onProgressUpdate(0);
+ }
+
+ @Override
+ protected Object doInBackground(Void... params)
+ {
+ Log.e(TAG, "doInBackground");
+
+ //emulate long running non-interruptable work
+ for(int i = 0; i < mDuration; i++)
+ {
+ if(isCancelled()) return null;
+ try
+ {
+ Thread.sleep(10);
+ }
+ catch(InterruptedException ex)
+ {
+ return null;
+ }
+
+ publishProgress(i);
+ Log.e(TAG, "doInBackground: "+mProgress+"/"+mDuration);
+ }
+ return new Object();
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values)
+ {
+ TaskListener listener = getListener();
+ if(listener != null)
+ {
+ mProgress = values[0];
+ listener.onProgress(mDuration, values[0]);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Object result)
+ {
+ Log.e(TAG, "onPostExecute: "+result);
+ mResult = result;
+ TaskListener listener = getListener();
+ if(listener != null)
+ {
+ listener.onCompleted(result);
+ }
+ }
+
+ @Override
+ protected void onCancelled()
+ {
+ Log.e(TAG, "onCancelled.");
+ TaskListener listener = getListener();
+ if(listener != null)
+ {
+ listener.onCancelled();
+ }
+ }
+ }
+
+}

0 comments on commit d11e02a

Please sign in to comment.