当android应用程序启动的时候,android为应用程序开启了一个线程,称为主线程也称UI线程.这个线程负责分发用户响应并与用户进行交互.
如果在应用程序中只有主线程一个线程运行,在有些情况下会出现问题:比如请求网络数据,与数据库进行交互等一些比较耗时的操作进行时,此时,主线程被阻塞,任何消息都不能发送出去.从用户的角度上来看,应用程序被挂起了.更糟糕的是,如果UI线程被阻塞5秒以上就会出现ANR现象.
在这种情况下,我们可以考虑开启额外的线程,在线程里面处理耗时操作.考虑下面的代码:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork();
mImageView.setImageBitmap(b);
}
}).start();
}
在onClick方法里面下载网络图片,并设置到本地的ImageView.
这看起来是一个不错的处理方案,并没有阻塞UI线程,但是由于在UI线程里面修改主线程里面的资源,这是线程不安全的.
android提供了以下几种方法在其他线程中访问主线程:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
Handler
以上代码可以修改成:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap b = loadImageFromNetwork();
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(b);
}
});
}
}).start();
}
但不幸的是,这些方法和类会降低代码的可读性.并且当我们需要进行复杂的操作和频繁进行UI更新时,这样的方法会变得更糟糕.
为了弥补这样的缺陷,android1.5版本之后,提供了AsyncTask类,这个类提供了一个长时间运行的任务并,其中提供了与用户进行交互的接口.
AsyncTask 可以替你进行线程管理.我们可以把上面的例子进行以下改写:
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
AsyncTask 必须要通过继承后才能使用.以下几点需要注意
1.一个AsyncTask实例必须要创建在UI线程上,并仅仅只执行一次,
2.doInBackground方法是自动在用户线程被执行的,而onPreExecute(), onPostExecute()和 onProgressUpdate() 回调方法被 UI 线程调用.
3.doInBackground()的返回值被发送到 onPostExecute()
4.任意时刻在doInBackground()里,我们可以通过调用所有的publishProgress() 使UI线程执行onProgressUpdate()方法
5.你可以在任意时间任意线程结束任务
一个小例子:
首先定义类DownloadFilesTask 继承自AsyncTask,AsyncTask中传入的三个参数类型URL, Integer, Long分别与三个回调方法的参数类型对应.
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
在主线程中执行任务(必须在主线程中执行):
new DownloadFilesTask().execute(url1, url2, url3);
此时传入的三个参数被doInBackground获取到,它会调用publishProgress,这个方法里面可以传多个参数,publishProgress会触发onProgressUpdate,该方法取出了第一个参数.doInBackground方法会返回一个Long 类型的totalSize,这个作为参数传入onPostExecute.
1.任务执行后,onPreExecute()方法马上被主线程唤醒,
2.onPreExecute()方法执行完后,doInBackground(Params...)方法马上被其所在的线程唤醒,
3.在后台线程计算结束后,onPostExecute(Result)方法立即被主线程唤醒.
4.
cancel(boolean)方法可以在任意时间结束任务,该方法可以导致
isCancelled()
返回true.此时,doInBackground(Object[])调用结束后会调用onCancelled(Object)方法,为了保证任务尽快的结束,需要周期性的在doInBackground内部检查isCancelled()的返回值,如果可能的话,使用looper