Android 异步任务:AsyncTask 学习解析及基本使用 (Android单线程模式)
2016-10-09 18:39
791 查看
(在此声明:以下部分内容来自于慕课网中“AsyncTask概述”讲解视频中个人总结及书籍总结)
提示:如果对AsyncTask机制熟练于心,想要了解Android中源码底层实现来拔高,这里有源码详细分析:http://blog.csdn.net/itermeng/article/details/52159831
大部分时间, 一个Android进程中仅进行一个线程,称为主线程。各个组件之间共享此主线程的影响,可是Android不可能只有单一的主线程,将所有的逻辑放在主线程中实现。类似于一些耗时的操作:请求网络、读取文件……若全部放在主线程中执行,会造成后面任务的阻塞。一旦阻塞的时间过长,Android系统检测到就会ANR(Application Not Responding 应用程序无响应)。
所以这些耗时操作应该放到非主线程中执行,比如请求网络时出现问题,也不会影响到主线程。这样即保证了Android的单线程模型,又避免了系统出现ANR。
为了解决子线程不能更新UI组件的问题,Android提供了如下几种解决方案:
(1)使用Hanlder实现线程之间的通信
(2) Activity.runOnUiThread(Runnable)
(3) View.Post(Runnable)
(4) View.PostDelayed(Runnable, long )
暂不考虑Hanlder的实例,后三种方式略显繁琐,而异步任务(AsyncTask)可进一步简化这种操作,更轻量,适用于简单异步处理,不需要借助线程和Handler
提到异步任务,最先想到的是线程,而Android也为我们提供了主线程与其它线程的通信机制,更赞的是Android已经封装好了AsyncTask 类,它可以方便的实现异步任务。
(1)Params:启动任务执行的输入参数的类型。
(2)Progress:后台任务完成的进度值的类型。
(3)Result:后台执行任务完成后返回结果的类型。
(2)doInBackground(Params…):【重点】该方法规定必须重写,在子线程中执行,所以重写该方法就是为了后台线程将要完成的任务(例如耗时操作:请求网络……)。方法中可以调用onProgressUpdate()来更新任务的执行进度(如进度条),该方法执行完后,会回调onPostExecute(),并传入结果result。
(3)onPostExecute(Result result):当doInBackground()完成后,系统会回调此方法,并将doInBackground()方法的返回值传过来。该方法运行在主线程,一般用来更新UI界面。
(4)onProgressUpdate(Progress… values):在doInBackground()方法中调用此方法,在主线程执行,一般用来更新任务的执行进度(如进度条)。
(1)必须在UI线程(主线程)中创建AsyncTask的实例。
(2)必须在UI线程(主线程)中调用AsyncTask的execute()方法。
(3)每个AsyncTask只能被执行一次,多次调用execute()方法会抛异常。
(4)以上介绍实现AsyncTask类的四个方法,方法之间存在互相调用联系,无需我们手动调用,Android系统已封装好!我们只需重写实现需要的方法即可。
以上的规则必须遵循,如若有疑惑的话,可查看Android中AsyncTask源码部分,即可明白以上道理。
要求:实现一个进度条,人工在doInBackground()方法中模拟进度更新,调用onProgressUpdate()方法显示加载过程中的进度更新。
布局代码:
逻辑代码:
显示情况:
分析: 其实AsyncTask机制底层实现是通过一个线程池,当一个线程没有执行完毕,后面的线程是无法进行的。所以退出来后第二次启动异步任务时会等待第一次执行完毕(即我们模拟的进度更新 ——for循环 执行完毕),才会开始加载进度条更新。
解决: 令AsyncTask的生命周期与对应Activity的保持一致!
在ProgressBarTest.java文件中添加onPause()方法,方法体中if判断AsyncTask的状态部位空且等于“任务正在执行”的状态,满足条件就需要cancel掉该AsyncTask。
问题:可是加上以上方法也不可行,并没有解决问题,为什么cancel()方法不起作用?
分析:因为cancel()方法并没有将AsyncTask中的线程停止,只是给AsyncTask发送了一个cancel的请求,将该任务标记为cancel状态。而且在java中也无法粗暴地去停止线程,必须要等一个线程执行完后,再进行下一个任务。所以我们还需要一个步骤:
解决:在具体的异步线程中监视状态!一旦当前AsyncTask的状态等于cancel,就结束掉整个线程的剩余逻辑(即跳出for循环)。
在doInBackground()方法的for循环中 和 onProgressUpdate()中判断状态,若为cancel状态,程序就可以快速终结掉线程中的剩余操作,尽快的将权利留给下一个异步任务。
逻辑代码:
显示结果:
如上GIF动图所示,第一次启动AsyncTask异步任务,执行到一半时退出,再次启动异步任务,程序成功判断到cancel状态,结束掉上次线程中剩余的逻辑,进度条重新从0开始显示!
总而言之,正确的做法是确认活动的生命周期状态,相应的取消异步任务。异步任务需要充分意识到活动的生命周期状态。因此,必须真正实现任务的生命周期方法,并且由活动调用这些生命周期方法,这样异步任务才会像活动的一部分那样运行
看似AsyncTask 机制使用简单,可是大部分还是坚持使用HandlerThread。浅析原因:
技术层面的原因:在Android 3.2系统版本中,AsyncTask的内部实现发生了变化,自3.2版本起,AsyncTask不再为每一个AsyncTask实例单独创建一个线程,相反,它使用一个Executor在单一的后台线程上运行所有的AsyncTask的后台任务。结合上面实例的讲解,这意味着每个AsyncTask都需要排队逐个运行,显然,长时间运行的AsyncTask会阻塞其他的AsyncTask异步任务。
虽然使用一个线程池Executor可以安全地并发多个AsyncTask,但是更好的办法是:自己处理线程相关工作,必要时使用Handler与主线程通信!
(以上只是根据书籍内容浅显分析,
https://www.zhihu.com/question/30804052/answer/49562693这里知乎大神分析的更加全!)
以上内容为AsyncTask 学习解析及基本使用,基本掌握应该没问题,但还是推荐大家要真正理解还是要分析源代码!http://blog.csdn.net/itermeng/article/details/52159831这是自己分析的,可能有不足的地方,但十分详细,而且网上也有其它大神的分析,学无止境~
希望对你们有帮助 :)
提示:如果对AsyncTask机制熟练于心,想要了解Android中源码底层实现来拔高,这里有源码详细分析:http://blog.csdn.net/itermeng/article/details/52159831
一 .Android单线程模式
Android应用程序中大部分代码在一个组件(比如活动或服务)的上下文中运行。大部分时间, 一个Android进程中仅进行一个线程,称为主线程。各个组件之间共享此主线程的影响,可是Android不可能只有单一的主线程,将所有的逻辑放在主线程中实现。类似于一些耗时的操作:请求网络、读取文件……若全部放在主线程中执行,会造成后面任务的阻塞。一旦阻塞的时间过长,Android系统检测到就会ANR(Application Not Responding 应用程序无响应)。
二. 初识AsyncTask
1. 为什么要异步任务?
(1)Android单线程模型
(2)耗时操作应该放到非主线程中执行
如上所示,Android中只有主线程才能对UI线程进行操作,这样做的好处是保证了UI的准确性和稳定性,避免多线程同时对UI操作,造成UI混乱。Android的UI线程主要负责处理用户的按键事件、用户触屏事件及屏幕绘图事件等,因此开发者的其它操作不应该、也不能阻塞 UI线程,否则会引发ANR。所以这些耗时操作应该放到非主线程中执行,比如请求网络时出现问题,也不会影响到主线程。这样即保证了Android的单线程模型,又避免了系统出现ANR。
为了解决子线程不能更新UI组件的问题,Android提供了如下几种解决方案:
(1)使用Hanlder实现线程之间的通信
(2) Activity.runOnUiThread(Runnable)
(3) View.Post(Runnable)
(4) View.PostDelayed(Runnable, long )
暂不考虑Hanlder的实例,后三种方式略显繁琐,而异步任务(AsyncTask)可进一步简化这种操作,更轻量,适用于简单异步处理,不需要借助线程和Handler
提到异步任务,最先想到的是线程,而Android也为我们提供了主线程与其它线程的通信机制,更赞的是Android已经封装好了AsyncTask 类,它可以方便的实现异步任务。
2. AsyncTask作用
(1)子线程中更新UI
(2)封装、简化异步操作
我们要实现异步任务 通常使用 线程和线程池,这里同时涉及到了线程的同步和管理,当线程结束时,还需要一个handler通知主线程进行UI更新,这不免增加了代码量,使逻辑复杂,而AsyncTask已封装好,只需要简单重写它的方法,在对应的方法中进行UI更新。三. AsyncTask解析
1. AsyncTask的参数
AsyncTask< Params ,Progress,Result>是一个抽象类,通常用于被继承,继承AsyncTask需要指定以下三个泛型参数:(1)Params:启动任务执行的输入参数的类型。
(2)Progress:后台任务完成的进度值的类型。
(3)Result:后台执行任务完成后返回结果的类型。
2. AsyncTask的实现方法
2.1. 继承AsyncTask,并为三个泛型参数指定类型,若某个参数无需求则可写void。
2.2. 根据需要,实现父类AsyncTask的方法:
(1)onPreExecute():该方法最先被调用,执行在主线程,其次是doInBackground()方法(执行后台耗时操作)。通常用于完成一些初始化的准备工作,比如界面上进度条显示正在加载。(2)doInBackground(Params…):【重点】该方法规定必须重写,在子线程中执行,所以重写该方法就是为了后台线程将要完成的任务(例如耗时操作:请求网络……)。方法中可以调用onProgressUpdate()来更新任务的执行进度(如进度条),该方法执行完后,会回调onPostExecute(),并传入结果result。
(3)onPostExecute(Result result):当doInBackground()完成后,系统会回调此方法,并将doInBackground()方法的返回值传过来。该方法运行在主线程,一般用来更新UI界面。
(4)onProgressUpdate(Progress… values):在doInBackground()方法中调用此方法,在主线程执行,一般用来更新任务的执行进度(如进度条)。
2.3 遵循规则
调用AsyncTask子类实例的 execute(Params… params)开始执行耗时任务,需遵循以下规则:(1)必须在UI线程(主线程)中创建AsyncTask的实例。
(2)必须在UI线程(主线程)中调用AsyncTask的execute()方法。
(3)每个AsyncTask只能被执行一次,多次调用execute()方法会抛异常。
(4)以上介绍实现AsyncTask类的四个方法,方法之间存在互相调用联系,无需我们手动调用,Android系统已封装好!我们只需重写实现需要的方法即可。
以上的规则必须遵循,如若有疑惑的话,可查看Android中AsyncTask源码部分,即可明白以上道理。
四. 通过实例(进度条)进一步解析AsyncTask
一般写的例子就是在doInBackground()方法中进行网络请求,在被回调的onPostExecute()方法中更新UI界面(也可以在onProgressUpdate()方法更新加载中的进度条)。这里为了继续挖掘AsyncTask的性质,就不举这个例子了,网上也有许多,这里举另外一个:进度条的更新。要求:实现一个进度条,人工在doInBackground()方法中模拟进度更新,调用onProgressUpdate()方法显示加载过程中的进度更新。
布局代码:
activity_progress_bar.xml <?xml version="1.0" encoding="utf-8"?> <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:padding="10dp" tools:context="com.gym.progressbarasunctask.ProgressBarTest"> <ProgressBar style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/progressBar" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> </RelativeLayout>
逻辑代码:
ProgressBarTest.java package com.gym.progressbarasunctask; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.ProgressBar; public class ProgressBarTest extends AppCompatActivity { private ProgressBar mProgressBar; private MyAsyncTask myAsyncTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_progress_bar); mProgressBar = (ProgressBar) findViewById(R.id.progressBar); //启动异步任务 myAsyncTask = new MyAsyncTask(); myAsyncTask.execute(); } class MyAsyncTask extends AsyncTask<Void,Integer,Void>{ @Override protected Void doInBackground(Void... voids) { //模拟进度更新 for (int i=0; i<100; i++){ publishProgress(i);//传入进度,方便进度条更新时取值 try { Thread.sleep(100);//延缓更新进度 } catch (InterruptedException e) { e.printStackTrace(); } } return null; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); //获取进度更新值,UI线程中获取子线程更新进度 mProgressBar.setProgress(values[0]); } } }
显示情况:
1. 生命周期方法与AsyncTask
以上程序问题:第一次启动异步任务,进度条进行更新过程中退出,第二次再进入时需要待会一会,进度条才会重新开始加载,为什么?分析: 其实AsyncTask机制底层实现是通过一个线程池,当一个线程没有执行完毕,后面的线程是无法进行的。所以退出来后第二次启动异步任务时会等待第一次执行完毕(即我们模拟的进度更新 ——for循环 执行完毕),才会开始加载进度条更新。
解决: 令AsyncTask的生命周期与对应Activity的保持一致!
在ProgressBarTest.java文件中添加onPause()方法,方法体中if判断AsyncTask的状态部位空且等于“任务正在执行”的状态,满足条件就需要cancel掉该AsyncTask。
@Override protected void onPause() { super.onPause(); if(myAsyncTask != null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) { myAsyncTask.cancel(true); } }
问题:可是加上以上方法也不可行,并没有解决问题,为什么cancel()方法不起作用?
分析:因为cancel()方法并没有将AsyncTask中的线程停止,只是给AsyncTask发送了一个cancel的请求,将该任务标记为cancel状态。而且在java中也无法粗暴地去停止线程,必须要等一个线程执行完后,再进行下一个任务。所以我们还需要一个步骤:
解决:在具体的异步线程中监视状态!一旦当前AsyncTask的状态等于cancel,就结束掉整个线程的剩余逻辑(即跳出for循环)。
在doInBackground()方法的for循环中 和 onProgressUpdate()中判断状态,若为cancel状态,程序就可以快速终结掉线程中的剩余操作,尽快的将权利留给下一个异步任务。
逻辑代码:
ProgressBarTest.java package com.gym.progressbarasunctask; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.ProgressBar; public class ProgressBarTest extends AppCompatActivity { private ProgressBar mProgressBar; private MyAsyncTask myAsyncTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_progress_bar); mProgressBar = (ProgressBar) findViewById(R.id.progressBar); //启动异步任务 myAsyncTask = new MyAsyncTask(); myAsyncTask.execute(); } @Override protected void onPause() { super.onPause(); if(myAsyncTask != null && myAsyncTask.getStatus() == AsyncTask.Status.RUNNING) { //cancel方法只是将对应的AsyncTask标记为cancle状态,并不是真正取消线程的逻辑 myAsyncTask.cancel(true); } } class MyAsyncTask extends AsyncTask<Void,Integer,Void>{ @Override protected Void doInBackground(Void... voids) { //模拟进度更新 for (int i=0; i<100; i++){ //判断状态,若为cancel则终结掉该线程剩余操作 if(isCancelled()){ break; } publishProgress(i);//传入进度,方便进度条更新时取值 try { Thread.sleep(100);//延缓更新进度 } catch (InterruptedException e) { e.printStackTrace(); } } return null; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); //判断状态,若为cancel则终结掉该线程剩余操作 if(isCancelled()){ return; } //获取进度更新值,UI线程中获取子线程更新进度 mProgressBar.setProgress(values[0]); } } }
显示结果:
如上GIF动图所示,第一次启动AsyncTask异步任务,执行到一半时退出,再次启动异步任务,程序成功判断到cancel状态,结束掉上次线程中剩余的逻辑,进度条重新从0开始显示!
2. Cancel()方法总结:
Cancel()方法只是给当前AsyncTask传递了一个信号量,标记了AsyncTask的状态,并没有真正cancle掉当前AsyncTask,需要将AsyncTask的生命周期和Activity或Fragment的进行绑定,同时在AsyncTask中判断状态,停止线程中剩余逻辑!总而言之,正确的做法是确认活动的生命周期状态,相应的取消异步任务。异步任务需要充分意识到活动的生命周期状态。因此,必须真正实现任务的生命周期方法,并且由活动调用这些生命周期方法,这样异步任务才会像活动的一部分那样运行
五. AsyncTask 与 Thread
阅读完以上内容后,相信你已经可以熟练掌握AsyncTask 机制了,可是应该何时使用呢?包括之前介绍“子线程不能更新UI组件”的解决办法中,还有一个不容忽视的Thread。看似AsyncTask 机制使用简单,可是大部分还是坚持使用HandlerThread。浅析原因:
比较:
基本原因: AsyncTask 主要应用于短暂且较少重复的任务,若长时间的运行AsyncTask,而且同一时间规定最多只能有5个线程同时运行,否则会出现异常。技术层面的原因:在Android 3.2系统版本中,AsyncTask的内部实现发生了变化,自3.2版本起,AsyncTask不再为每一个AsyncTask实例单独创建一个线程,相反,它使用一个Executor在单一的后台线程上运行所有的AsyncTask的后台任务。结合上面实例的讲解,这意味着每个AsyncTask都需要排队逐个运行,显然,长时间运行的AsyncTask会阻塞其他的AsyncTask异步任务。
虽然使用一个线程池Executor可以安全地并发多个AsyncTask,但是更好的办法是:自己处理线程相关工作,必要时使用Handler与主线程通信!
(以上只是根据书籍内容浅显分析,
https://www.zhihu.com/question/30804052/answer/49562693这里知乎大神分析的更加全!)
以上内容为AsyncTask 学习解析及基本使用,基本掌握应该没问题,但还是推荐大家要真正理解还是要分析源代码!http://blog.csdn.net/itermeng/article/details/52159831这是自己分析的,可能有不足的地方,但十分详细,而且网上也有其它大神的分析,学无止境~
希望对你们有帮助 :)
相关文章推荐
- [Android]异步任务AsyncTask使用解析
- Android面试题——异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- Android异步任务AsyncTask使用解析 获取网络图片
- [Android]异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- [Android]异步任务AsyncTask使用解析
- 【Android开源项目解析】QQ“一键下班”功能实现解析——学习Path及贝塞尔曲线的基本使用
- Android异步消息处理机制(3)AsyncTask基本使用
- Android异步加载全解析之使用AsyncTask
- Android 异步任务AsyncTask的使用