Skip to content
This repository was archived by the owner on Dec 17, 2020. It is now read-only.

Commit 4a5de01

Browse files
committed
- Fixed a critical bug in the BaseTaskManager which could lead to a ConcurrentModificationException;
- Added execute(Task, Callback, Executor) method to the TaskManager class for executing Tasks with custom Executors; - Added experimental support for Android 2.2 (API 8);
1 parent fddab52 commit 4a5de01

File tree

9 files changed

+177
-45
lines changed

9 files changed

+177
-45
lines changed

demo/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ android {
66

77
defaultConfig {
88
applicationId "org.neotech.app.retainabletasksdemo"
9-
minSdkVersion 9
9+
minSdkVersion 8
1010
targetSdkVersion 23
1111
versionCode 1
1212
versionName "1.0"

demo/src/main/java/org/neotech/app/retainabletasksdemo/App.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import com.squareup.leakcanary.LeakCanary;
66

7+
import org.neotech.library.retainabletasks.TaskManager;
8+
79
/**
810
* Created by Rolf on 29-2-2016.
911
*/
@@ -13,5 +15,6 @@ public class App extends Application {
1315
public void onCreate() {
1416
super.onCreate();
1517
LeakCanary.install(this);
18+
TaskManager.setStrictDebugMode(true);
1619
}
1720
}

demo/src/main/java/org/neotech/app/retainabletasksdemo/Main.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import android.support.design.widget.Snackbar;
88
import android.support.v4.app.DialogFragment;
99
import android.support.v7.widget.Toolbar;
10+
import android.text.Html;
1011
import android.view.View;
1112
import android.widget.Button;
13+
import android.widget.TextView;
1214
import android.widget.Toast;
1315

1416
import org.neotech.app.retainabletasksdemo.tasks.CountDownTask;
@@ -22,6 +24,12 @@ public class Main extends TaskActivityCompat implements View.OnClickListener, Ta
2224

2325
private static final String TASK_RETAIN_UI_STATE = "retain-ui-state";
2426
private static final String TASK_PROGRESS = "progress-dialog";
27+
private static final String TASK_SERIAL = "serial-";
28+
29+
private static final int[] serialTaskText = new int[]{R.string.task_serial_1, R.string.task_serial_2, R.string.task_serial_2};
30+
private static final Button[] serialTaskButton = new Button[3];
31+
32+
2533
private static final String DIALOG_PROGRESS = "progress-dialog";
2634

2735
private ProgressDialog progressDialog;
@@ -41,6 +49,16 @@ protected void onCreate(Bundle savedInstanceState) {
4149
findViewById(R.id.button_no_ui_task).setOnClickListener(this);
4250
findViewById(R.id.button_progress_task).setOnClickListener(this);
4351

52+
53+
((TextView) findViewById(R.id.text_serial_tasks)).setText(Html.fromHtml(getString(R.string.task_serial)));
54+
serialTaskButton[0] = (Button) findViewById(R.id.button_serial_task_1);
55+
serialTaskButton[0].setOnClickListener(this);
56+
serialTaskButton[1] = (Button) findViewById(R.id.button_serial_task_2);
57+
serialTaskButton[1].setOnClickListener(this);
58+
serialTaskButton[2] = (Button) findViewById(R.id.button_serial_task_3);
59+
serialTaskButton[2].setOnClickListener(this);
60+
61+
4462
retainUserInterfaceButton = (Button) findViewById(R.id.button_retain_ui_state_task);
4563
retainUserInterfaceButton.setOnClickListener(this);
4664
}
@@ -60,6 +78,8 @@ public Task.Callback onPreAttach(@NonNull Task<?, ?> task) {
6078
}
6179
} else if(task.getTag().equals(TASK_PROGRESS)){
6280
progressDialog = ProgressDialog.getExistingInstance(getSupportFragmentManager(), DIALOG_PROGRESS);
81+
} else if(task.getTag().startsWith(TASK_SERIAL)){
82+
onPreAttachSerialTask(task);
6383
}
6484
return this;
6585
}
@@ -87,6 +107,15 @@ public void onClick(View v) {
87107
startActivity(new Intent(this, ActivityWithFragments.class));
88108
} else if(id == R.id.button_open_v11_activity){
89109
startActivity(new Intent(this, ActivityV11.class));
110+
} else if(id == R.id.button_serial_task_1){
111+
getTaskManager().execute(new CountDownTask(TASK_SERIAL + 1, 10), this, TaskExecutor.SERIAL_EXECUTOR);
112+
v.setEnabled(false);
113+
} else if(id == R.id.button_serial_task_2){
114+
getTaskManager().execute(new CountDownTask(TASK_SERIAL + 2, 10), this, TaskExecutor.SERIAL_EXECUTOR);
115+
v.setEnabled(false);
116+
} else if(id == R.id.button_serial_task_3){
117+
getTaskManager().execute(new CountDownTask(TASK_SERIAL + 3, 10), this, TaskExecutor.SERIAL_EXECUTOR);
118+
v.setEnabled(false);
90119
}
91120
}
92121

@@ -105,6 +134,8 @@ public void onPostExecute(Task<?, ?> task) {
105134
} else if(task.getTag().equals(TASK_RETAIN_UI_STATE)){
106135
retainUserInterfaceButton.setEnabled(true);
107136
retainUserInterfaceButton.setText(R.string.task_retain_ui_state);
137+
} else if(task.getTag().startsWith(TASK_SERIAL)) {
138+
onPostExecuteSerialTask(task);
108139
}
109140
}
110141

@@ -122,11 +153,40 @@ public void onProgressUpdate(Task<?, ?> task, Object progress) {
122153
progressDialog.setProgress((int) progress);
123154
} else if(task.getTag().equals(TASK_RETAIN_UI_STATE)){
124155
retainUserInterfaceButton.setText("" + (int) progress);
156+
} else if(task.getTag().startsWith(TASK_SERIAL)){
157+
onProgressUpdateSerialTask(task, (Integer) progress);
125158
}
126159
}
127160

128161
@Override
129162
public void onDialogFragmentClick(DialogFragment fragment, int which) {
130163
getTaskManager().cancel(TASK_PROGRESS);
131164
}
165+
166+
167+
168+
private int getSerialTaskIndex(String tag){
169+
return Integer.parseInt(tag.substring(TASK_SERIAL.length())) - 1;
170+
}
171+
172+
private void onPreAttachSerialTask(Task<?, ?> task) {
173+
final int index = getSerialTaskIndex(task.getTag());
174+
serialTaskButton[index].setEnabled(false);
175+
final Integer progress = (Integer) task.getLastKnownProgress();
176+
if (progress != null) {
177+
serialTaskButton[index].setText("" + progress);
178+
}
179+
}
180+
181+
private void onPostExecuteSerialTask(Task<?, ?> task) {
182+
final int index = getSerialTaskIndex(task.getTag());
183+
serialTaskButton[index].setEnabled(false);
184+
serialTaskButton[index].setText(serialTaskText[index]);
185+
serialTaskButton[index].setEnabled(true);
186+
}
187+
188+
private void onProgressUpdateSerialTask(Task<?, ?> task, int progress){
189+
final int index = getSerialTaskIndex(task.getTag());
190+
serialTaskButton[index].setText("" + progress);
191+
}
132192
}

demo/src/main/res/layout/content_main.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,31 @@
4949
android:layout_width="wrap_content"
5050
android:text="@string/task_retain_ui_state" />
5151

52+
<TextView
53+
android:layout_width="wrap_content"
54+
android:layout_height="wrap_content"
55+
android:textAppearance="?android:attr/textAppearanceSmall"
56+
android:id="@+id/text_serial_tasks"
57+
android:gravity="center_horizontal"
58+
android:layout_gravity="center_horizontal" />
59+
60+
<Button
61+
android:id="@+id/button_serial_task_1"
62+
android:layout_height="wrap_content"
63+
android:layout_width="wrap_content"
64+
android:text="@string/task_serial_1" />
65+
66+
<Button
67+
android:id="@+id/button_serial_task_2"
68+
android:layout_height="wrap_content"
69+
android:layout_width="wrap_content"
70+
android:text="@string/task_serial_2" />
71+
72+
<Button
73+
android:id="@+id/button_serial_task_3"
74+
android:layout_height="wrap_content"
75+
android:layout_width="wrap_content"
76+
android:text="@string/task_serial_3" />
77+
5278
</LinearLayout>
5379
</ScrollView>

demo/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
<string name="toast_task_finished">\'%1$s\' finished.</string>
66
<string name="toast_task_canceled">\'%1$s\' canceled.</string>
77

8+
<string name="task_serial">"<![CDATA[<strong>Serial tasks</strong><br />These tasks won't run in parallel like the other tasks, they wait for each other to finish.]]>"</string>
9+
<string name="task_serial_1">Serial task #1</string>
10+
<string name="task_serial_2">Serial task #2</string>
11+
<string name="task_serial_3">Serial task #3</string>
812

913
<string name="task_fragment_based">Start fragment based task</string>
1014
<string name="task_no_ui_callback">Task without UI callback</string>

library/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ ext {
1616
artifact = 'android-retainable-tasks'
1717

1818
libraryDescription = 'Android-Retainable-Tasks is an easy to use mini-library for easy asynchronous background tasking with callback support to the UI. This library is based on the Android AsyncTask implementation but with support for retaining tasks and therefore surviving configuration changes (orientation).'
19-
libraryVersion = '0.2.0'
19+
libraryVersion = '0.2.1'
2020

2121
siteUrl = 'https://github.com/NeoTech-Software/Android-Retainable-Tasks'
2222
gitUrl = 'https://github.com/NeoTech-Software/Android-Retainable-Tasks.git'
@@ -40,7 +40,7 @@ android {
4040
buildToolsVersion "23.0.2"
4141

4242
defaultConfig {
43-
minSdkVersion 9
43+
minSdkVersion 8
4444
targetSdkVersion 23
4545
versionCode 1
4646
versionName = libraryVersion
Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package org.neotech.library.retainabletasks;
22

3+
import android.os.Build;
34
import android.support.annotation.MainThread;
5+
import android.support.annotation.NonNull;
46

57
import java.util.ArrayDeque;
8+
import java.util.LinkedList;
9+
import java.util.Queue;
610
import java.util.concurrent.BlockingQueue;
711
import java.util.concurrent.Executor;
812
import java.util.concurrent.LinkedBlockingQueue;
@@ -21,32 +25,35 @@ public class TaskExecutor {
2125
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
2226
private static final int KEEP_ALIVE = 1;
2327

24-
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
25-
private final AtomicInteger mCount = new AtomicInteger(1);
28+
private static final ThreadFactory threadFactory = new ThreadFactory() {
29+
private final AtomicInteger threadCount = new AtomicInteger(1);
2630

27-
public Thread newThread(Runnable r) {
28-
return new Thread(r, "Task #" + mCount.getAndIncrement());
31+
public Thread newThread(@NonNull Runnable runnable) {
32+
return new Thread(runnable, "Task #" + threadCount.getAndIncrement());
2933
}
3034
};
3135

32-
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128);
33-
3436
/**
3537
* An {@link Executor} that can be used to execute tasks in parallel.
3638
*/
37-
public static final Executor THREAD_POOL_EXECUTOR
38-
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
39-
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
40-
39+
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
40+
CORE_POOL_SIZE,
41+
MAXIMUM_POOL_SIZE,
42+
KEEP_ALIVE, TimeUnit.SECONDS,
43+
new LinkedBlockingQueue<Runnable>(128),
44+
threadFactory);
4145

46+
/**
47+
* An {@link Executor} that can be used to execute tasks in serial. This Executor internally uses the {@link TaskExecutor#THREAD_POOL_EXECUTOR} for executing it's tasks.
48+
*/
4249
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
4350

4451
private static class SerialExecutor implements Executor {
45-
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
46-
Runnable mActive;
52+
private final Queue<Runnable> taskQueue = (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD?new LinkedList<Runnable>():new ArrayDeque<Runnable>());
53+
private Runnable activeRunnable;
4754

4855
public synchronized void execute(final Runnable r) {
49-
mTasks.offer(new Runnable() {
56+
taskQueue.offer(new Runnable() {
5057
public void run() {
5158
try {
5259
r.run();
@@ -55,37 +62,42 @@ public void run() {
5562
}
5663
}
5764
});
58-
if (mActive == null) {
65+
if (activeRunnable == null) {
5966
scheduleNext();
6067
}
6168
}
6269

63-
protected synchronized void scheduleNext() {
64-
if ((mActive = mTasks.poll()) != null) {
65-
THREAD_POOL_EXECUTOR.execute(mActive);
70+
private synchronized void scheduleNext() {
71+
if ((activeRunnable = taskQueue.poll()) != null) {
72+
THREAD_POOL_EXECUTOR.execute(activeRunnable);
6673
}
6774
}
6875
}
6976

70-
private static volatile Executor sDefaultExecutor = THREAD_POOL_EXECUTOR;
77+
private static volatile Executor defaultExecutor = THREAD_POOL_EXECUTOR;
7178

72-
private static TaskExecutor instance;
79+
private TaskExecutor() {
7380

74-
public static void setDefaultExecutor(Executor exec) {
75-
sDefaultExecutor = exec;
7681
}
7782

78-
public static TaskExecutor getInstance(){
79-
synchronized (TaskExecutor.class) {
80-
if (instance == null) {
81-
instance = new TaskExecutor();
82-
}
83-
return instance;
84-
}
83+
/**
84+
* Sets the default {@link Executor} to use when executing {@link Task Tasks} using this class.
85+
* <strong>Important:</strong> The {@link TaskManager} uses this class to execute Tasks,
86+
* changing the default Executor changes the executing behaviour of all TaskMangers.
87+
* @param executor The {@link Executor} to use as default.
88+
*/
89+
public static void setDefaultExecutor(@NonNull Executor executor) {
90+
defaultExecutor = executor;
8591
}
8692

87-
private TaskExecutor() {
88-
93+
/**
94+
* Returns the currently set default {@link Executor}. This is by default, if not changed using the
95+
* {@link TaskExecutor#setDefaultExecutor(Executor)} method, the
96+
* {@link TaskExecutor#THREAD_POOL_EXECUTOR}.
97+
* @return The default {@link Executor}.
98+
*/
99+
public static @NonNull Executor getDefaultExecutor(){
100+
return defaultExecutor;
89101
}
90102

91103
/**
@@ -114,23 +126,22 @@ private TaskExecutor() {
114126
* @see Task#executeOnExecutor(java.util.concurrent.Executor)
115127
*/
116128
@MainThread
117-
public static <Progress, Result> Task<Progress, Result> executeOnExecutor(Task<Progress, Result> task, Executor executor) {
129+
public static <Progress, Result> Task<Progress, Result> executeOnExecutor(@NonNull Task<Progress, Result> task, @NonNull Executor executor) {
118130
return task.executeOnExecutor(executor);
119131
}
120132

121133
@MainThread
122-
public static <Progress, Result> Task<Progress, Result> execute(Task<Progress, Result> task) {
123-
return executeOnExecutor(task, sDefaultExecutor);
134+
public static <Progress, Result> Task<Progress, Result> execute(@NonNull Task<Progress, Result> task) {
135+
return executeOnExecutor(task, defaultExecutor);
124136
}
125137

126138
@MainThread
127-
public static <Progress, Result> Task<Progress, Result> executeSerial(Task<Progress, Result> task) {
139+
public static <Progress, Result> Task<Progress, Result> executeSerial(@NonNull Task<Progress, Result> task) {
128140
return executeOnExecutor(task, SERIAL_EXECUTOR);
129141
}
130142

131-
132143
@MainThread
133-
public static <Progress, Result> Task<Progress, Result> executeParallel(Task<Progress, Result> task) {
144+
public static <Progress, Result> Task<Progress, Result> executeParallel(@NonNull Task<Progress, Result> task) {
134145
return executeOnExecutor(task, THREAD_POOL_EXECUTOR);
135146
}
136147
}

library/src/main/java/org/neotech/library/retainabletasks/TaskManager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import org.neotech.library.retainabletasks.internal.BaseTaskManager;
1212
import org.neotech.library.retainabletasks.internal.TaskRetainingFragment;
1313

14+
import java.util.concurrent.Executor;
15+
1416
/**
1517
* Created by Rolf on 29-2-2016.
1618
*/
@@ -72,6 +74,9 @@ public interface TaskAttachListener {
7274
@MainThread
7375
public abstract <Progress, Result> void execute(@NonNull Task<Progress, Result> task, @NonNull Task.Callback callback);
7476

77+
@MainThread
78+
public abstract <Progress, Result> void execute(@NonNull Task<Progress, Result> task, @NonNull Task.Callback callback, @NonNull Executor executor);
79+
7580
@MainThread
7681
public abstract boolean isResultDelivered(@NonNull String tag);
7782

0 commit comments

Comments
 (0)