深入解析AsyncTask

释放双眼,带上耳机,听听看~!

AsyncTask是Android 1.5 Cubake加入的用于实现异步操作的一个类,在此之前只能用Java SE库中的Thread来实现多线程异步,AsyncTask是Android平台自己的异步工具,融入了Android平台的特性,让异步操作更加的安全,方便和实用。实质上它也是对Java SE库中Thread的一个封装,加上了平台相关的特性,所以对于所有的多线程异步都强烈推荐使用AsyncTask,因为它考虑,也融入了Android平台的特性,更加的安全和高效。

AsyncTask可以方便的执行异步操作(doInBackground),又能方便的与主线程进行通信,它本身又有良好的封装性,可以进行取消操作(cancel())。关于AsyncTask的使用,文档说的很明白,下面直接上实例。

实例

这个实例用AsyncTask到网络上下载图片,同时显示进度,下载完图片更新UI。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
1package daimajiaoliu.concurrent;  
2   
3import java.io.IOException;
4import java.io.InputStream;
5import java.io.OutputStream;
6import java.net.HttpURLConnection;
7import java.net.MalformedURLException;
8import java.net.URL;
9
10import android.app.Activity;
11import android.content.Context;
12import android.graphics.Bitmap;
13import android.graphics.BitmapFactory;
14import android.os.AsyncTask;
15import android.os.Bundle;
16import android.os.SystemClock;
17import android.view.View;
18import android.widget.Button;
19import android.widget.ImageView;
20import android.widget.ProgressBar;
21
22import daimajiaoliu.R;
23
24/*
25 * AsyncTask cannot be reused, i.e. if you have executed one AsyncTask, you must discard it, you cannot execute it again.
26 * If you try to execute an executed AsyncTask, you will get "java.lang.IllegalStateException: Cannot execute task: the task is already running"
27 * In this demo, if you click "get the image" button twice at any time, you will receive "IllegalStateException".
28 * About cancellation:
29 * You can call AsyncTask\#cancel() at any time during AsyncTask executing, but the result is onPostExecute() is not called after
30 * doInBackground() finishes, which means doInBackground() is not stopped. AsyncTask\#isCancelled() returns true after cancel() getting
31 * called, so if you want to really cancel the task, i.e. stop doInBackground(), you must check the return value of isCancelled() in
32 * doInBackground, when there are loops in doInBackground in particular.
33 * This is the same to Java threading, in which is no effective way to stop a running thread, only way to do is set a flag to thread, and check
34 * the flag every time in Thread\#run(), if flag is set, run() aborts.
35 */
36
37public class AsyncTaskDemoActivity extends Activity {
38
39    private static final String ImageUrl = "http://i1.cqnews.net/sports/attachement/jpg/site82/2011-10-01/2960950278670008721.jpg";
40
41    private ProgressBar mProgressBar;
42
43    private ImageView mImageView;
44
45    private Button mGetImage;
46
47    private Button mAbort;
48
49
50    @Override
51
52
53    public void
54    onCreate(Bundle icicle) {
55
56        super
57                .onCreate(icicle);
58        setContentView(R.layout.async_task_demo_activity);
59        mProgressBar = (ProgressBar) findViewById(R.id.async_task_progress);
60        mImageView = (ImageView) findViewById(R.id.async_task_displayer);
61
62        final ImageLoader loader =
63                new
64                        ImageLoader();
65        mGetImage = (Button) findViewById(R.id.async_task_get_image);
66        mGetImage.setOnClickListener(
67                new
68                        View.OnClickListener() {
69
70                            public void
71                            onClick(View v) {
72                                loader.execute(ImageUrl);
73                            }
74                        });
75        mAbort = (Button) findViewById(R.id.asyc_task_abort);
76        mAbort.setOnClickListener(
77                new
78                        View.OnClickListener() {
79
80                            public void
81                            onClick(View v) {
82                                loader.cancel(
83                                        true
84                                );
85                            }
86                        });
87        mAbort.setEnabled(
88                false
89        );
90    }
91
92
93    private class ImageLoader extends AsyncTask<String, Integer, Bitmap> {
94
95        private static final String TAG = "ImageLoader";
96
97        @Override
98        protected void onPreExecute() {
99
100            // Initialize progress and image
101            mGetImage.setEnabled(false);
102            mAbort.setEnabled(true);
103            mProgressBar.setVisibility(View.VISIBLE);
104            mProgressBar.setProgress(0);
105            mImageView.setImageResource(R.drawable.icon);
106        }
107
108
109        @Override
110        protected Bitmap doInBackground(String... url) {
111
112            /*
113             * Fucking ridiculous thing happened here, to use any Internet connections, either via HttpURLConnection
114             * or HttpClient, you must declare INTERNET permission in AndroidManifest.xml. Otherwise you will get
115             * "UnknownHostException" when connecting or other tcp/ip/http exceptions rather than "SecurityException"
116             * which tells you need to declare INTERNET permission.
117             */
118
119
120            try {
121                URL u;
122                HttpURLConnection conn = null;
123                InputStream in = null;
124                OutputStream out = null;
125
126                final String filename = "local_temp_image";
127
128                try {
129                    u = new URL(url[0]);
130                    conn = (HttpURLConnection) u.openConnection();
131                    conn.setDoInput(true);
132                    conn.setDoOutput(false);
133                    conn.setConnectTimeout(20 * 1000);
134                    in = conn.getInputStream();
135                    out = openFileOutput(filename, Context.MODE_PRIVATE);
136
137                    byte[] buf = new byte[8196];
138
139                    int seg = 0;
140
141                    final long total = conn.getContentLength();
142
143                    long current = 0;
144
145                    /*
146                     * Without checking isCancelled(), the loop continues until reading whole image done, i.e. the progress
147                     * continues go up to 100. But onPostExecute() will not be called.
148                     * By checking isCancelled(), we can stop immediately, i.e. progress stops immediately when cancel() is called.
149                     */
150
151
152                    while (!isCancelled() && (seg = in.read(buf)) != -1) {
153                        out.write(buf, 0, seg);
154                        current += seg;
155                        int progress = (int) ((float) current / (float) total * 100f);
156                        publishProgress(progress);
157                        SystemClock.sleep(1000);
158                    }
159                } finally {
160
161                    if (conn != null) {
162                        conn.disconnect();
163                    }
164
165                    if (in != null) {
166                        in.close();
167                    }
168
169                    if (out != null
170                    ) {
171                        out.close();
172                    }
173                }
174
175                return BitmapFactory.decodeFile(getFileStreamPath(filename).getAbsolutePath());
176            } catch (MalformedURLException e) {
177                e.printStackTrace();
178            } catch (IOException e) {
179                e.printStackTrace();
180            }
181
182            return null;
183        }
184
185
186        @Override
187        protected void onProgressUpdate(Integer... progress) {
188            mProgressBar.setProgress(progress[0]);
189        }
190
191
192        @Override
193        protected void onPostExecute(Bitmap image) {
194            if (image != null) {
195                mImageView.setImageBitmap(image);
196            }
197            mProgressBar.setProgress(100);
198            mProgressBar.setVisibility(View.GONE);
199            mAbort.setEnabled(false);
200        }
201    }
202}  
203
204

运行结果

先后顺序分别是下载前,下载中和下载后

总结

关于怎么使用看文档和这个例子就够了,下面说下,使用时的注意事项:

  1. AsyncTask对象不可重复使用,也就是说一个AsyncTask对象只能execute()一次,否则会有异常抛出"java.lang.IllegalStateException: Cannot execute task: the task is already running"

  2. 在doInBackground()中要检查isCancelled()的返回值,如果你的异步任务是可以取消的话。

cancel()仅仅是给AsyncTask对象设置了一个标识位,当调用了cancel()后,发生的事情只有:AsyncTask对象的标识位变了,和doInBackground()执行完成后,onPostExecute()不会被回调了,而doInBackground()和onProgressUpdate()还是会继续执行直到doInBackground()结束。所以要在doInBackground()中不断的检查isCancellled()的返回值,当其返回true时就停止执行,特别是有循环的时候。如上面的例子,如果把读取数据的isCancelled()检查去掉,图片还是会下载,进度也一直会走,只是最后图片不会放到UI上(因为onPostExecute()没被回调)!
这里的原因其实很好理解,想想Java SE的Thread吧,是没有方法将其直接Cacncel掉的,那些线程取消也无非就是给线程设置标识位,然后在run()方法中不断的检查标识而已。

  1. 如果要在应用程序中使用网络,一定不要忘记在AndroidManifest中声明INTERNET权限,否则会报出很诡异的异常信息,比如上面的例子,如果把INTERNET权限拿掉会抛出"UnknownHostException"。刚开始很疑惑,因为模拟器是可以正常上网的,后来Google了下才发现原来是没权限,但是疑问还是没有消除,既然没有声明网络权限,为什么不直接提示无网络权限呢?

对比Java SE的Thread

Thread是非常原始的类,它只有一个run()方法,一旦开始,无法停止,它仅适合于一个非常独立的异步任务,也即不需要与主线程交互,对于其他情况,比如需要取消或与主线程交互,都需添加额外的代码来实现,并且还要注意同步的问题。

而AsyncTask是封装好了的,可以直接拿来用,如果你仅执行独立的异步任务,可以仅实现doInBackground()。

所以,当有一个非常独立的任务时,可以考虑使用Thread,其他时候,尽可能的用AsyncTask。

给TA打赏
共{{data.count}}人
人已打赏
安全经验

Google Adsense(Google网站联盟)广告申请指南

2021-10-11 16:36:11

安全经验

安全咨询服务

2022-1-12 14:11:49

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索