在Android中实现异步任务机制有两种方式,Handler和AsyncTask。
Handler模式需要为每一个任务创建一个新的线程,任务完成后通过Handler实例向UI线程发送消息,完成界面的更新,这种方式对于整个过程的控制比较精细,但也是有缺点的,例如代码相对臃肿,在多个任务同时执行时,不易对线程进行精确的控制,为了简化操作,Android1.5提供了工具类android.os.AsyncTask,它使创建异步任务变得更加简单,不再需要编写任务线程和Handler实例即可完成相同的任务。
AsyncTask的定义
public abstract class AsyncTask<Params, Progress, Result>
三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用java.lang.Void类型代替。不能使用基本数据类型例如int,double等
AsyncTask的使用方法
使用AsyncTask需要自定义一个类继承自AsyncTask,里面有这样几个回调函数
- doInBackground : 必须重写,异步操作中,后台线程需要完成的任务
- onPreExecute : 在上一个函数也就是异步操作开始前调用,通常用来完成一些初始化工作
- onPostExecute : 当异步操作执行完毕后系统调用这个函数,通常用来显示doInBackground函数的结果
- onProgressUpdata:在doInBackground()方法中调动publishProgress()方法更新任务进度的时候,就会调用改方法
- onCancelled : 调用AsyncTask.cancel()方法取消线程,当线程停止掉以后回调这个函数而不回调onPostExecute ()。
上面几个回调方法中,只有doInBackgound()方法运行的新线程中,其他的方法均运行在主线程里面,在这些方法里面进行耗时操作可能会产生ANR
使用AsyncTask的注意事项
- 异步任务的实例必须在UI线程中创建。
- execute(Params... params)方法(执行一个异步线程)必须在UI线程中调用。
- 不要手动调用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。
- 不能在doInBackground(Params... params)中更改UI组件的信息。因为doInBackground运行在新线程
- 一个任务实例只能执行一次,如果执行第二次将会抛出异常。因为线程的生命周期,消亡后不能重生,Java中线程
- 调用AsyncTask.cancel()方法并不会直接停止线程,而是等待当前线程运行完毕去回调onCancelled()方法而不调用onPostExecute ()。
上面两行红色字体是由线程的特性决定的,线程的生命周期是不能回头的,线程不能被强行结束,一般的做法是设置标志位,然后在run方法中判断标志位,Thread.interrupted()并不能中断线程。
使用AsyncTask的demo
我们先来看看效果图,这是加载一张网络上的图片
Demo下载:360云盘下载 访问密码 3b6c 环境为:Android Stdio
布局文件非常简单,两个Button以及一个ProgressBar和一个ImageView,默认的情况下ProgressBar和ImageView是不可见的。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:orientation="vertical"> <Button android:id="@+id/ioadimg" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="加载图片" /> <Button android:id="@+id/stopload" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="停止加载" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleLarge" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:visibility="gone" /> <ImageView android:id="@+id/img" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@mipmap/ic_launcher" android:visibility="gone" /> </RelativeLayout> </LinearLayout>
然后就是我们继承自AsyncTask的类,MyAsyncTask extends AsyncTask<String, Void, Bitmap>,三个泛型参数前面已经说过了,一个是传入的参数,这是为String类型的,是一个url,第二个为执行的进度,没有使用到,使用Void,第三个是Bitmap,是在doInBackground中加载的网络图片。
class MyAsyncTask extends AsyncTask<String, Void, Bitmap> { /** * 在任务提交之前被调用,可以用来执行一些初始化任务 * 运行再主线程 */ @Override protected void onPreExecute() { super.onPreExecute(); mProgressBar.setVisibility(View.VISIBLE); } /** * 异步任务完成后调用,运行在主线程 * * @param bitmap */ @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); mProgressBar.setVisibility(View.GONE); mImagel.setVisibility(View.VISIBLE); mImagel.setImageBitmap(bitmap); } /** * 执行异步任务,运行在新线程 */ @Override protected Bitmap doInBackground(String... params) { String str = params[0]; Bitmap bitmap = null; try { URL url = new URL(str); InputStream inputStream = url.openStream(); bitmap = BitmapFactory.decodeStream(inputStream); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //下面注释的代码是用来测试cancel是否能真的停止线程 /*long timeMillis = System.currentTimeMillis(); while(System.currentTimeMillis() - timeMillis <= 1000 * 10) { }*/ return bitmap; } @Override protected void onCancelled() { Log.v("x", "取消了"); super.onCancelled(); } @Override protected void onProgressUpdate(Void... values) { super.onProgressUpdate(values); } }
下面这段代码是给加载按钮添加点击监听消息,点击的时候加载网络图片
mLoadimg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { myAsyncTask = new MyAsyncTask(); myAsyncTask.execute("http://27house.cn/wp-content/uploads/2016/01/android.jpg", ""); } });
测试AsyncTask.cancel()方法
关于这个方法,并不是真的取消了线程的执行,而是给正在执行的线程发送了一个中断消息,也就是Thread.interrupted();
还是上面那个demo,取消MyAsyncTask中的注释,也就是在doInBackground()返回的时候让这个函数停止10秒。
//下面注释的代码是用来测试cancel是否能真的停止线程 long timeMillis = System.currentTimeMillis(); while (System.currentTimeMillis() - timeMillis <= 1000 * 10) { }
然后我们点击取消加载按钮,执行AsyncTask.cancel()方法。
mStopLoad.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.v("x", "点击了取消"); myAsyncTask.cancel(true); } });
在logcat中打印如下语句,我们可以很清楚的看到,并不是调用cancel()方法以后就直接停止了,而是继续等待后台线程的完成。
02-02 03:21:23.630 29914-30002/com.jay.myasynctask D/dalvikvm: GC_FOR_ALLOC freed 184K, 7% free 3543K/3780K, paused 3ms, total 3ms
02-02 03:21:24.294 29914-29914/com.jay.myasynctask V/x: 点击了取消
02-02 03:21:33.750 29914-29914/com.jay.myasynctask V/x: 取消了
AsyncTask提交的任务的执行顺序
AsyncTask默认的执行的顺序是按照任务提交的顺序,除非调用AsyncTask.executeOnExecutor才是并发执行,不过此方法可能造成数据混乱。下面一个demo是验证这一点的。(源码类似,为了篇幅,可以自行下载)
Demo下载:360云盘下载 访问密码 cc43 环境为:Android Stdio
如图,当点击同时开始的时候,会同时启动两个AsyncTask去更新ProgressBar,我们可以很清楚的看到,只有第一个在更新,只有当第一个任务被取消或者完成以后第二个进度条才会开始更新,这就验证我上面说的,AsyncTask默认是按照任务提交的顺序来执行的。你也可以采用这个系统提供的线程池来处理你的任务AsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),默认这个线程池是并发处理任务的,也就是不按顺序来.核心为5条,最大128条,不过这种方法使用不当可能照成数据异常,比如在这个demo中,我们使用AsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),当多次点击开始按钮,也就是启动多个任务同时执行去刷新进度,这样当每一个任务进度不同的时候,进度条的显示就会异常。
AsyncTask适合处理短时间的操作,长时间的操作,比如下载一个很大的视频,这就需要你使用自己的线程来下载,不管是断点下载还是其它的.从google官方文档你也可以看到,AsyncTasks should ideally be used for short operations (a few seconds at the most.)
参考文章: liuhe688 AsyncTask执行顺序