Fork me on GitHub
AsyncTask Missteps

What's the Problem?

QA raise an incident, through the stack trace I know it's Dialog.show() called after Activity being destoryed. Specific to the code, we new a LoginTask in LoginActivity, if failed, we will show a dialog in onPostExecute(), crash happens when the Activity is destoryed but we still call the dialog.show() function.

So I solve this incident by surround the dialog.show() with an if block:

1 if(!isFinishing()) {
2     dialog.show();
3 }

Everything seems to be fine, problem solved and minimum code changed. But ** point out two issues after review the submission:

1.. "If isFinishing is true, we probably don’t want to do any UI work." Except for dialog.show(), we also do many other UI work in onPostExcute().

2.. There may be same issue in other activities where an AsyncTask is used.

For the first question, indeed, the UI work is useless if Activity isFinishing, but as it also do no harm, so we can let them go. But for the second question, I do find a similar question in AccountSignupActivity.java  , in SignupTask's onPostExecute() we also call dialog.show().

So the question is, how to use AsyncTask properly?

Is AsyncTask Really Entirely Flawed?

There is an interesting question in StackOverflow  which pinpoint the problem:

1.. AsyncTask "allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.";

2.. But when we call dialog.show() in onPostExecute() it may cause a crash.

3.. Solve this problem we can use a Handler.

4.. But AsyncTask's role is to help us get rid of handler.

There are many answers under this question, but I'm not satisfied or not understood until I found this blog  .

AsyncTask Missteps

This blog(http://www.shanekirk.com/2012/04/asynctask‑missteps/ ) is really worth reading, it draws the problems and the solutions step by step. I'll quote the "One For All" solution here, but firstly let's summarize the problem:

1.. Dialog.show() crash happens when we reference a destroyed Activity, so maybe we can't reference the Activity in AsyncTask;

2.. But if we can't reference to an Activity, how can we update the UI?

3.. What will happen if the Activity destroyed but re‑create again? Further more, what will happen if the AsyncTask is running or after the AsyncTask has completed?

4.. There is concurrent AsyncTask limitations  .

Here goes the solution:

复制代码
 1 public class MyActivity extends Activity
 2 {
 3     @Override
 4     public void onCreate(Bundle savedInstanceState)
 5     {
 6         super.onCreate(savedInstanceState);
 7         setContentView(R.layout.main);
 8  
 9         // Find views and assign them to member variables.
10  
11         m_task = (MyTask) getLastNonConfigurationInstance();
12         if (m_task != null)
13         {
14             m_task.m_activity = this;
15             if (m_task.m_isFinished)
16                 m_task.updateUI();
17         }
18         else
19         {
20             m_task = new MyTask();
21             m_task.m_activity = this;
22             m_task.execute();
23         }
24     }
25  
26     @Override
27     public void onDestroy()
28     {
29         super.onDestroy();
30  
31         m_task.m_activity = null;
32  
33         if (this.isFinishing())
34             m_task.cancel(false);
35     }
36  
37     @Override
38     public Object onRetainNonConfigurationInstance()
39     {
40         return m_task;
41     }
42  
43  
44     static class MyTask extends AsyncTask<Void, Void, String>
45     {
46         @Override
47         protected String doInBackground(Void... params)
48         {
49             // Do some long running task. We need to make sure
50             // we peridically check the return value of isCancelled().
51             return result;
52         }
53  
54         @Override
55         protected void onPostExecute(String result)
56         {
57             m_result = result;
58             m_isFinished = true;
59             updateUI();
60         }
61  
62         public void updateUI()
63         {
64             if (m_activity != null)
65             {
66                 // Update UI using m_result
67             }           
68         }
69  
70         // These should never be accessed from within doInBackground()
71         MyActivity m_activity = null;
72         boolean m_isFinished  = false;
73         String m_result = null;
74     }
75  
76     private MyTask m_task = null;
77  
78     // Various View member variables.
79 
80 }
复制代码

The problem solved by:

1.. Remove the implicit pointer by make the AsyncTask static

2.. Give an explicit Activity reference back to AsyncTask to direct control their relationships.

3.. Use a so called "connect/disconnect" pattern to handle the destroyed then re‑create problem.

4.. Cancel the AsyncTask in onDestory().

Keep in Mind

Maybe there is no "One For All" solution, but there is one thing we must keep in mind: Never ever call dialog.show() or that kind of function without a check.
 I still don't know the full function list that will cause a crash except for dialog.show(), maybe we need not do any UI work if the Activity isFinishing, as **suggested.

Reference

• Incident: https://engtools.engba.symantec.com/Etrack/readonly_inc.php?incident=3028953

• AsyncTask: https://developer.android.com/reference/android/os/AsyncTask.html

• Is AsyncTask really conceptually flawed or am I just missing something?: http://stackoverflow.com/q/3357477/1203241

• Android AsyncTask threads limits?: http://stackoverflow.com/a/9654445/1203241

• AsyncTask Missteps: http://www.shanekirk.com/2012/04/asynctask‑missteps/

• Proper use of AsyncTask: https://blogactivity.wordpress.com/2011/09/01/proper‑use‑of‑asynctask/

 
 
分类: Android
posted on 2013-01-08 22:40  HackerVirus  阅读(256)  评论(0编辑  收藏  举报