【声明】

欢迎转载,但请保留文章原始出处→_→

生命壹号:http://www.cnblogs.com/smyhvae/

文章来源:http://www.cnblogs.com/smyhvae/p/3866570.html

【正文】

本文将讲解一下Android的多线程的知识,以及如何通过AsyncTask机制来实现线程之间的通信。

一、Android当中的多线程:

在Android当中,当一个应用程序的组件启动的时候,并且没有其他的应用程序组件在运行时,Android系统就会为该应用程序组件开辟一个新的线程来执行。默认的情况下,在一个相同Android应用程序当中,里面的组件都是运行在同一个线程里的,这个线程称之为Main线程。当我们通过某个组件来启动另一个组件的时候,这个时候默认都是在同一个线程当中完成的。当然,我们可以自己来管理我们的Android应用的线程,我们可以根据我们自己的需要来给应用程序创建额外的线程。

二、Main Thread 和 Worker Thread:

在Android当中,通常将线程分为两种,一种叫做Main Thread,除了Main Thread之外的线程都可称为Worker Thread。

当一个应用程序运行的时候,Android操作系统就会给该应用程序启动一个线程,这个线程就是我们的Main Thread,这个线程非常重要,它主要用来加载UI界面,完成系统和用户之间的交互,并将交互后的结果又展示给用户,所以Main Thread又被称为UI Thread。

Android系统默认不会给应用程序组件创建一个额外的线程,所有的这些组件默认都是在同一个线程中运行。然而,某些时候当我们的应用程序需要完成一个耗时操作的时候(如访问网络或者是对数据库进行查询),此时UI Thread就会被阻塞。例如,当我们点击一个Button,然后希望其从网络中获取一些数据,如果此操作在UI Thread当中完成的话,UI线程就会处于阻塞的状态,此时,我们的系统不会调度任何其它的事件,更糟糕的是,当我们的整个现场如果阻塞时间超过5秒钟(官方描述),这个时候就会出现 著名的ANR (Application Not Responding)的问题,此时,应用程序会弹出一个框,让用户选择是否退出该程序。对于Android开发来说,出现ANR的现象是绝对不能被允许的。

另外,由于Android UI控件不是线程安全的,所以我们不能在UI Thread之外的线程当中对UI控件进行操作。因此在Android的多线程编程当中,有两条非常重要的原则必须遵守:

  • 不能在UI Thread当中进行耗时操作,以免阻塞UI Thread
  • 不能在UI Thread之外的线程当中操纵UI元素

 三、如何处理UI Thread 和 Worker Thread之间的通信:

我们既不能在主线程当中处理耗时的操作,又不能在工作线程中来访问我们的UI控件,那么我们比如从网络中要下载一张图片,又怎么能将其更新到UI控件上呢?这就关系到了主线程和工作线程之间的通信问题了。在Android当中,提供了异步消息处理机制的两种方式来解决线程之间的通信问题,一种是通过Handler的机制(这种方式在后面的博客中将详细介绍),还有一种就是今天要详细讲解的 AsyncTask 机制。

四、AsyncTask:

AsyncTask:异步任务,从字面上来说,就是在UI主线程运行的时候,异步完成一些操作。AsyncTask允许我们在后台执行一个异步任务。我们可以将耗时的操作放在异步任务当中来执行,并随时将任务执行的结果返回给UI线程来更新UI控件。通过AsyncTask我们可以轻松解决多线程之间的通信问题。

怎么来理解AsyncTask呢?通俗来说,AsyncTask就相当于Android给我们提供了一个多线程编程的一个框架,其介于Thread和Handler之间,我们如果要定义一个AsyncTask,就需要定义一个类来继承AsyncTask这个抽象类,并实现其唯一的一个 doInBackgroud 抽象方法。

要掌握AsyncTask,我们就必须要一个概念,总结起来就是: 3个泛型,4个步骤。

我们来看看AsyncTask这个抽象类的定义,当我们定义一个类来继承AsyncTask这个类的时候,需要为其指定3个泛型参数:

AsyncTask <Params, Progress, Result>
  • Params: 指定的是我们传递给异步任务执行时的参数的类型
  • Progress: 指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型
  • Result: 指定的是异步任务执行完后返回给UI线程的结果的类型

我们在定义一个类继承AsyncTask类的时候,必须指定好这三个泛型的类型,如果都不指定的话,则都将其写成Void,例如:

AsyncTask <Void, Void, Void>

4个步骤:当我们执行一个异步任务时,需要按照下面的4个步骤分别执行:

  • onPreExecute(): 这个方法是在执行异步任务之前的时候执行,并且是在UI Thread当中执行的,通常我们在这个方法里做一些UI控件的初始化的操作,例如弹出ProgressDialog
  • doInBackground(Params... params): 在onPreExecute()方法执行完后,会马上执行这个方法,这个方法就是来处理异步任务的方法,Android操作系统会在后台的线程池当中开启一个worker thread来执行这个方法(即在worker thread当中执行),执行完后将执行结果发送给最后一个 onPostExecute 方法,在这个方法里,我们可以从网络当中获取数据等一些耗时的操作
  • onProgressUpdate(Progess... values): 这个方法也是在UI Thread当中执行的,在异步任务执行的时候,有时需要将执行的进度返回给UI界面,例如下载一张网络图片,我们需要时刻显示其下载的进度,就可以使用这个方法来更新进度。这个方法在调用之前,我们需要在 doInBackground 方法中调用一个 publishProgress(Progress) 的方法来将进度时时刻刻传递给 onProgressUpdate 方法来更新
  • onPostExecute(Result... result): 当异步任务执行完之后,就会将结果返回给这个方法,这个方法也是在UI Thread当中调用的,我们可以将返回的结果显示在UI控件上

为什么AsyncTask抽象类只有一个 doInBackground 的抽象方法呢??原因是,我们如果要做一个异步任务,我们必须要为其开辟一个新的Thread,让其完成一些操作,而在完成这个异步任务时,我可能并不需要弹出ProgressDialog,并不需要随时更新ProgressDialog的进度条,也并不需要将结果更新给UI界面,所以除了 doInBackground 方法之外的三个方法,都不是必须有的,因此必须要实现的方法是 doInBackground 方法。

4个步骤简洁版描述如下:

  第一步:表示任务执行前的操作

  第二步:主要完成耗时操作

  第三步:主要是更新UI操作

  第四步:产生最终结果

以下实例中代表的含义为:

  第一步:显示进度条

  第二步:(此任务必不可少)在后台执行任务,将进度值传给第三步,将结果传给第四步;

  第三步:进度值更新

  第四步:产生最终结果

五、【实例】通过AsyncTask来从网络上下载一张图片:

下面通过两个代码示例,来看看如何通过AsyncTask来从网络上下载一张图片,并更新到ImageView控件上。

在这之前,必须要执行的操作是:添加网络授权。下面的例子将有详细的操作描述。

【实例一】从网络下载图片,弹出一个ProgressDialog,但不显示实时进度:

1、添加网络授权:

因为手机默认不能访问网络,所以首先要在清单文件 AndroidManifest.xml中添加网络授权。

方法如下:

打开AndroidManifest.xml文件,点击"Permissions"按钮,然后点击“Add”:

弹出对话框后,选择最后一项:Users Permission:

接着选择其中的Internet选项:

按Ctrl+S保存即可。

紧接着,我们在清单文件中就能看到对应自动生成的代码:

其实,直接在AndroidManifest.xml文件中添加上这一行代码也是可以的。

2、完整版代码如下:

activity_main.xml的代码:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="136dp"
android:text="下载网络图片" />
</RelativeLayout>

MainActivity.java的代码:

package com.example.downloadimage01;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
public class MainActivity extends Activity {
private ImageView imageView ;
private Button button ;
private ProgressDialog dialog ;
//来自网络的图片
private String image_path = "http://imgsrc.baidu.com/forum/pic/item/7c1ed21b0ef41bd51a5ac36451da81cb39db3d10.jpg" ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); //添加弹出的对话框
dialog = new ProgressDialog(this) ;
dialog.setTitle("提示") ;
dialog.setMessage("正在下载图片,请稍后···") ; imageView = (ImageView)findViewById(R.id.imageView1) ;
button = (Button)findViewById(R.id.button1) ;
button.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
//点击按钮时,执行异步任务的操作
new DownTask().execute(image_path) ;
}
}) ; //注意,这个地方的分号容易遗忘
}
/*
* 异步任务执行网络下载图片
* */
public class DownTask extends AsyncTask<String, Void, Bitmap> {
//上面的方法中,第一个参数:网络图片的路径,第二个参数的包装类:进度的刻度,第三个参数:任务执行的返回结果
@Override
//在界面上显示进度条
protected void onPreExecute() {
dialog.show() ;
};
protected Bitmap doInBackground(String... params) { //三个点,代表可变参数
//使用网络链接类HttpClient类完成对网络数据的提取
HttpClient httpClient = new DefaultHttpClient() ;
HttpGet httpget = new HttpGet(params[0]) ;
Bitmap bitmap = null ;
try {
HttpResponse httpResponse = httpClient.execute(httpget) ;
if(httpResponse.getStatusLine().getStatusCode()==200){
HttpEntity httpEntity = httpResponse.getEntity() ;
byte[] data = EntityUtils.toByteArray(httpEntity);
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return bitmap;
}
//主要是更新UI
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
imageView.setImageBitmap(result) ;//更新UI
dialog.dismiss() ;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
} }

运行后,点击按钮,显示结果如下:

【实例二】从网络下载图片,弹出能显示进度值的对话框:

注:既然要显示进度值,所以此处的进度条风格要设置为水平。

1、添加网络授权(同上)

2、完整版代码如下:

activity_main.xml的代码和【实例一】中的一样;

MainActivity.java的代码:

 package com.example.smyh001downloadimage01;

 import java.io.ByteArrayOutputStream;
import java.io.InputStream; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient; import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView; public class MainActivity extends Activity { private ImageView imageView ;
private Button button ;
private ProgressDialog dialog ;
//来自网络的图片
private String image_path = "http://imgsrc.baidu.com/forum/pic/item/7c1ed21b0ef41bd51a5ac36451da81cb39db3d10.jpg" ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); //添加弹出的对话框
dialog = new ProgressDialog(this) ;
dialog.setTitle("提示") ;
dialog.setMessage("正在下载图片,请稍后···") ;
//将进度条设置为水平风格,让其能够显示具体的进度值
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) ;
dialog.setCancelable(false) ; //用了这个方法之后,直到图片下载完成,进度条才会消失(即使在这之前点击了屏幕) imageView = (ImageView)findViewById(R.id.imageView1) ;
button = (Button)findViewById(R.id.button1) ;
button.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
//点击按钮时,执行异步任务的操作
new DownTask().execute(image_path) ;
}
}) ; //注意,这个地方的分号容易遗忘 } /*
* 异步任务执行网络下载图片
* */
public class DownTask extends AsyncTask<String, Integer, byte[]> {
//上面的方法中,第一个参数:网络图片的路径,第二个参数的包装类:进度的刻度,第三个参数:任务执行的返回结果
@Override
//在界面上显示进度条
protected void onPreExecute() {
dialog.show() ;
}; protected byte[] doInBackground(String... params) { //三个点,代表可变参数
//使用网络链接类HttpClient类完成对网络数据的提取,即完成对图片的下载功能
HttpClient httpClient = new DefaultHttpClient() ;
HttpGet httpget = new HttpGet(params[0]) ;
byte[] result = null ;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream() ;
InputStream inputStream = null ; try {
HttpResponse httpResponse = httpClient.execute(httpget) ;
if(httpResponse.getStatusLine().getStatusCode()==200){ HttpEntity httpEntiry = httpResponse.getEntity();
inputStream = httpEntiry.getContent();
// 先要获得文件的总长度
long file_length = httpResponse.getEntity().getContentLength() ;
int len = 0 ;
// 每次读取1024个字节
byte[] data = new byte[1024] ;
// 每次读取后累加的长度
int total_length = 0 ;
while ((len = inputStream.read(data))!=-1) {
// 每读一次,就将total_length累加起来
total_length+=len ;
// 得到当前图片下载的进度
int progress_value = (int) ((total_length / (float)file_length)*100);
// 时刻将当前进度更新给onProgressUpdate方法
publishProgress(progress_value) ;
outputStream.write(data, 0, len);
}
// 边读边写到ByteArrayOutputStream当中
result = outputStream.toByteArray();
//bitmap = BitmapFactory.decodeByteArray(result, 0, result.length) ;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
httpClient.getConnectionManager().shutdown();
}
return result;
} @Override
protected void onProgressUpdate(Integer... values) {
// TODO Auto-generated method stub
super.onProgressUpdate(values);
// 更新ProgressDialog的进度条
dialog.setProgress(values[0]);
} //主要是更新UI
@Override
protected void onPostExecute(byte[] result) {
super.onPostExecute(result);
// 将doInBackground方法返回的byte[]解码成要给Bitmap
Bitmap bitmap = BitmapFactory.decodeByteArray(result, 0, result.length) ;
// 更新我们的ImageView控件
imageView.setImageBitmap(bitmap) ;//更新UI
// 使ProgressDialog框消失
dialog.dismiss() ;
}
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
} }

运行后,显示结果如下:

【工程文件】

链接:http://pan.baidu.com/s/1dDvhXkt

密码:47ce

六、AsyncTask的重要知识点:

明白了AsyncTask的工作原理后,继续补充一下AsyncTask的一些其他知识点:

1、Cancelling a Task

我们可以在任何时刻来取消异步任务的执行,通过调用 cancel(boolean)方法,调用完这个方法后系统会随后调用 isCancelled() 方法并且返回true。如果调用了这个方法,那么在 doInBackgroud() 方法执行完之后,就不会调用 onPostExecute() 方法了,取而代之的是调用 onCancelled() 方法。如果有必要的话,为了确保Task已经被取消了,我们需要经常调用 isCancelled() 方法来判断。

2、在使用AsyncTask做异步任务的时候必须要遵循的原则:

  • AsyncTask类必须在UI Thread当中加载,在Android Jelly_Bean版本后这些都是自动完成的
  • AsyncTask的对象必须在UI Thread当中实例化
  • execute方法必须在UI Thread当中调用
  • 不要手动的去调用AsyncTask的四个方法,这些都是由Android系统自动调用的
  • AsyncTask任务只能被执行一次

【总结】

到此,有关AsyncTask的总结就到此为止了,本文主要讲解了Android中的多线程知识,并且详细地讲解了 AsyncTask 异步任务的概念和实现机制,并通过实例来了解 AsyncTask 的执行过程,最后还补充了 AsyncTask 的一些重要知识点,包括如何取消一个 AsyncTask 以及在使用 AsyncTask 时所必须遵循的规则。建议初学者(包括我)在理解这方面的问题时可以多参考官方的API文档。

我的公众号

想学习代码之外的软技能?不妨关注我的微信公众号:生命团队(id:vitateam)。

扫一扫,你将发现另一个全新的世界,而这将是一场美丽的意外:

Android 多线程----AsyncTask异步任务详解的更多相关文章

  1. Android应用AsyncTask处理机制详解及源码分析

    1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点.前面我们分析了Handler异步机制原理(不了解的可以阅读我的<Android异步消息处理机 ...

  2. 【转载】Android应用AsyncTask处理机制详解及源码分析

    [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个 ...

  3. Android App优化之ANR详解

    引言 背景:Android App优化, 要怎么做? Android App优化之性能分析工具 Android App优化之提升你的App启动速度之理论基础 Android App优化之提升你的App ...

  4. android多线程-AsyncTask之工作原理深入解析(下)

    关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) ...

  5. android多线程-AsyncTask之工作原理深入解析(上)

    关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) ...

  6. Android高效率编码-第三方SDK详解系列(一)——百度地图,绘制,覆盖物,导航,定位,细腻分解!

    Android高效率编码-第三方SDK详解系列(一)--百度地图,绘制,覆盖物,导航,定位,细腻分解! 这是一个系列,但是我也不确定具体会更新多少期,最近很忙,主要还是效率的问题,所以一些有效的东西还 ...

  7. Android Telephony分析(三) ---- RILJ详解

    前言 本文主要讲解RILJ工作原理,以便更好地分析代码,分析业务的流程.这里说的RILJ指的是RIL.java (frameworks\opt\telephony\src\java\com\andro ...

  8. Android注解支持Support Annotations详解

    ###注解支持(Support Annotations)Android support library从19.1版本开始引入了一个新的注解库,它包含很多有用的元注解,你能用它们修饰你的代码,帮助你发现 ...

  9. Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)

    Android XML shape 标签使用详解   一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...

随机推荐

  1. [翻译]:SQL死锁-锁与事务级别

    其实这一篇呢与解决我项目中遇到的问题也是必不可少的.上一篇讲到了各种锁之间的兼容性,里面有一项就是共享锁会引起死锁,如何避免呢,将我们的查询都设置中read uncommitted是否可行呢?其结果显 ...

  2. 实验12:Problem D: 判断两个圆之间的关系

    Home Web Board ProblemSet Standing Status Statistics   Problem D: 判断两个圆之间的关系 Problem D: 判断两个圆之间的关系 T ...

  3. 微信支付redirect_uri参数错误

    在做微信支付的时候,点击提交,出现“redirect_uri参数错误”.经过查找,需要在后台正确设置授权域名.大致步骤如下:1.首先登录微信公众号管理后台2.点击开发者中心3.找到 网页账号—> ...

  4. JAVA基础学习day25--Socket基础二-多线程

    一.上传图片 1.1.示例 /* 上传图片 */ import java.net.*; import java.io.*; import java.util.*; import java.text.* ...

  5. JSON取值前判断

    public static void main(String[] args)throws Exception{ String jsonStr1="{\"access_token\& ...

  6. C++中const用法总结

    1修饰变量/指针 注意以下几种修饰的区别: (1)const int * a; (2)int const *a; (3)int * const b; (4)int const* const c; 其中 ...

  7. 一步步学敏捷开发:5. Scrum的4种会议

    在Scrum会议中包括:计划会议.每日站会.评审会议和回顾会议. 1.Sprint计划会(Sprint Planning) 在Scrum中,Sprint计划会议有两部分:1. 决定需要完成哪些工作?2 ...

  8. oracel数据泵的使用

    1.查看目录,用下面任意一条查询语句即可. select * from dba_directories;         select * from ALL_DIRECTORIES; 2.一般安装好数 ...

  9. Visual Studio发布Web项目报错:Unable to add 'xxx' to the Web site. Unable to add file 'xxx'. The specified file could not be encrypted.

    背景 Visual Studio下的Web项目 现象 发布时遇到Unable to add 'xxx' to the Web site.  Unable to add file 'xxx'. The ...

  10. 第八章 了解tempdb数据库

    1.一个sqlserver数据库实例上只能有一个tempdb数据库,这个实例上所有的用户都共享这个数据库.2.tempdb数据库在每次sqlserver重启后都会重新创建,所以数据会丢失.3.因为te ...