Android多线程-AsyncTask的使用和问题(取消,并行和串行,屏幕切换)
2017-05-27 15:45
726 查看
AsyncTask是Android提供的一个执行异步工作的类,内部其实是运用了线程池和Handler来进行异步任务的执行和与主线程的交互。AsyncTask只是一个辅助类,适合执行时间短的异步任务。
本文基于Android7.0的代码来说的。
原文地址 http://blog.csdn.net/qq_25806863/article/details/72782050
布局是这样的:
里面有一个进度条
然后实现一个AsyncTask,通过构造方法接收一个ProgressBar和Button进行操作:
然后给两个按钮添加点击事件:
在源码中定义是:
参数含义在下面的方法中会具体用到,先大致了解一下:
Params 启动任务的时候输入的参数类型,一般都是String类型,如填个网址啥的。 上面示例中就是String类型。
Progress 用来更新进度的类型。表示任务执行的进度的类型。 示例用的进度条,所以选择Integer类型。
Result 后台任务执行完后返回的结果, 示例中返回的也是String类型。
这个方法是在后台线程执行的。可以看到使用的参数类型Params就是在构造时定义的第一个类型。而返回的类型Result就是定义的第三个类型。
初次之外一般为了对任务流程进行控制还会重写下面几个方法
下面几个方法在AsyncTask中是空的,而且都要求在主线程中执行。
onPreExecute() 在异步任务开始前做的操作,
onPostExecute(Result result) 后台任务执行完后,通过这个方法能拿到任务返回的结果,进行处理。
onProgressUpdate(Progress… values) 这个表示进度变化,参数类型是构造时的第二个类型Progress。进度应该是随着任务的执行实时更新的,但是这个方法要在主线程中运行,而
execute(Params… params) 这个就是在示例中使用的开始任务的方式,传入指定的参数,参数类型要和构造时定义的第一个参数类型Params一样。参数可以为空的,那么在方法
executeOnExecutor(Executor exec,Params… params) 如果默认的线程池不能满足你的要求,可以用这个方法用指定线程池来执行任务。流程跟上面是一样的。
execute(Runnable runnable) 这个方法传进来的是一个Runnable类型,方法中只有一行代码
下面是停止的演示:
取消也有一个回调方法可以重写,这里加上,也是运行在主线程中:
然而实际用起来就不是那样的了,无论我们传的是
当调用了
AsyncTask设计成这样就是为了方便更新主线程界面的,所以对用户来说,在调用了cancle方法后,后台的任务就不会在影响到主线程的界面变化了,因为后续的跟主线程交互的方法都不会再执行了。,也可以说是取消了。
而真的要及时取消
cancle(true):
cancle(false):
可以看到区别是,当值为true的时候,后台线程也会跑完。但是会调用子线程的
而值为false的时候,没有任何变化,后台线程继续跑完。
这时调用cancle(false):
调用cancle(true)只是会多打印一个异常,一样会停止。
这时调用cancle(true):
何为串行,比如有下面的界面:
有两个task
在上面的MyAstncTask中,后台任务要执行10秒。
这里为了区分,打印开始按钮的名字来区分:
在点击第一个开始按钮之后点击第二个开始按钮,效果:
打印日志:
虽然点击了开始2,但是依然等第一个任务完成了才开始第二个任务。
想要让任务并行执行怎么办呢?其实他之所以会串行执行任务,是因为内部默认的线程池中将任务进行了排队,保证他们一个一个来。只要我们换个满足要求的线程池来执行任务就行了。AstncTask内部就有一个线程池
然后将开始任务的
效果:
日志:
画面是这样的:
日志是这样的,动图中也能看见:
虽然屏幕切换后,任务也在执行,也在不停地调用更新进度条的方法,最后也执行了
因为在横竖屏切换的时候,Activity会销毁重建,所以AsyncTask所持有的引用就不是新建的Activity的控件了,新的Activity就不会变化了。
这时屏幕怎么切换都没事
本文基于Android7.0的代码来说的。
原文地址 http://blog.csdn.net/qq_25806863/article/details/72782050
示例
AsyncTask的使用方法是很简单的。就做一个简单的进度条。布局是这样的:
里面有一个进度条
ProgressBar pb1,开始按钮
Button btn1,停止按钮
Button stop1
然后实现一个AsyncTask,通过构造方法接收一个ProgressBar和Button进行操作:
public class MyAsyncTask extends AsyncTask<String, Integer, String> { private String TAG = this.getClass().getSimpleName(); Button btn; ProgressBar pb; public MyAsyncTask(Button btn, ProgressBar pb) { this.btn = btn; this.pb = pb; } @Override protected String doInBackground(String... params) { String result = "完成"; for (int i = 1; i <= 10; i++) { try { Log.i(TAG, "doInBackground: "+i); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } publishProgress(i); } return result; } @Override protected void onPreExecute() { Log.i(TAG, "onPreExecute: 准备工作"); } @Override protected void onPostExecute(String s) { btn.setText(s); Log.i(TAG, "onPostExecute: 回调"); } @Override protected void onProgressUpdate(Integer... values) { pb.setProgress(values[0]); } }
然后给两个按钮添加点击事件:
MyAsyncTask task1
task1 = new MyAsyncTask(btn1, pb1);
btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i(TAG, "onClick: 开始1"); task1.execute(); } });
stop1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i(TAG, "onClick: 停止1"); task1.cancel(true); } });
1.构造参数
AsyncTask定义了三个泛型参数,在继承的时候必须填写。例如上面的AsyncTask<String, Integer, String>
在源码中定义是:
参数含义在下面的方法中会具体用到,先大致了解一下:
Params 启动任务的时候输入的参数类型,一般都是String类型,如填个网址啥的。 上面示例中就是String类型。
Progress 用来更新进度的类型。表示任务执行的进度的类型。 示例用的进度条,所以选择Integer类型。
Result 后台任务执行完后返回的结果, 示例中返回的也是String类型。
2.重写方法
要使用AsyncTask最少需要重写方法doInBackground。因为只有这个方法是抽象方法。
这个方法是在后台线程执行的。可以看到使用的参数类型Params就是在构造时定义的第一个类型。而返回的类型Result就是定义的第三个类型。
初次之外一般为了对任务流程进行控制还会重写下面几个方法
onPreExecute,
onPostExecute,
onProgressUpdate。
下面几个方法在AsyncTask中是空的,而且都要求在主线程中执行。
@MainThread protected void onPreExecute() { }
@MainThread protected void onPostExecute(Result result) { }
@MainThread protected void onProgressUpdate(Progress... values) { }
onPreExecute() 在异步任务开始前做的操作,
onPostExecute(Result result) 后台任务执行完后,通过这个方法能拿到任务返回的结果,进行处理。
onProgressUpdate(Progress… values) 这个表示进度变化,参数类型是构造时的第二个类型Progress。进度应该是随着任务的执行实时更新的,但是这个方法要在主线程中运行,而
doInBackground是在子线程中运行,所以不能直接在
doInBackground中调用
onProgressUpdate方法,而是通过调用
publishProgress(Progress... values)来间接调用这个方法。
3.开始任务
AsyncTask的开始有下面三种方法:execute(Params... params) executeOnExecutor(Executor exec,Params... params) execute(Runnable runnable)
execute(Params… params) 这个就是在示例中使用的开始任务的方式,传入指定的参数,参数类型要和构造时定义的第一个参数类型Params一样。参数可以为空的,那么在方法
doInBackground(Params... params)中的参数也是空的。使用默认的线程池执行任务,会按流程执行
onPreExecute,
doInBackground(Params... params),
onPostExecute(Result result)等方法。
executeOnExecutor(Executor exec,Params… params) 如果默认的线程池不能满足你的要求,可以用这个方法用指定线程池来执行任务。流程跟上面是一样的。
execute(Runnable runnable) 这个方法传进来的是一个Runnable类型,方法中只有一行代码
sDefaultExecutor.execute(runnable)就是用默认的线程池直接执行任务,就是使用线程池了,跟前面那些重写的方法没关系。
4.停止任务
要停止任务可以调用下面的方法:public final boolean cancel(boolean mayInterruptIfRunning) { mCancelled.set(true); return mFuture.cancel(mayInterruptIfRunning); }
下面是停止的演示:
取消也有一个回调方法可以重写,这里加上,也是运行在主线程中:
@Override protected void onCancelled() { Log.i(TAG, "onCancelled: 取消任务"); btn.setText("取消了"); }
取消的问题
当cancel方法被调用后,
onPostExecute和
onProgressUpdate方法都不会再调用了。而
doInBackground方法却会一直执行下去,也就是后台任务会继续执行。
cancel(boolean mayInterruptIfRunning)这个参数
mayInterruptIfRunning文档中表示是否应该立即终止
doInBackground中的任务。
然而实际用起来就不是那样的了,无论我们传的是
true还是
false,而AsyncTask的
cancle方法只是打上了一个取消的标记。并不是直接终止任务。如果是
true,则会调用一下后台线程的
interrupt方法。
当调用了
cancle方法后,调用
isCancelled方法会返回true。在
doInBackground中应该调用
isCancelled来检查当前任务是否被取消,以便及时终止任务。
AsyncTask设计成这样就是为了方便更新主线程界面的,所以对用户来说,在调用了cancle方法后,后台的任务就不会在影响到主线程的界面变化了,因为后续的跟主线程交互的方法都不会再执行了。,也可以说是取消了。
而真的要及时取消
doInBackground的继续运行则需要在这个方法中进行一些判断。
不做处理
不做处理也就是在doInBackground中不做判断,像下面这样。看一下输出的日志。当然界面的进度条都会停住,只要看
doInBackground有没有在点击停止按钮后停下来。
protected String doInBackground(String... params) { String result = "完成"; for (int i = 1; i <= 10; i++) { try { Log.i(TAG, "doInBackground: "+i); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } publishProgress(i); } return result; }
cancle(true):
cancle(false):
可以看到区别是,当值为true的时候,后台线程也会跑完。但是会调用子线程的
interrupt方法,而这个现在正在sleep,所以会引发
InterruptedException.
而值为false的时候,没有任何变化,后台线程继续跑完。
做处理
1. 判断isCancelled
在不同的运行节点判断这个方法的值:@Override protected String doInBackground(String... params) { String result = "完成"; for (int i = 1; i <= 10; i++) { try { if (isCancelled()){ Log.i(TAG, "doInBackground: 被标记停止了"); break; } Log.i(TAG, "doInBackground: "+i); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } publishProgress(i); } return result; }
这时调用cancle(false):
调用cancle(true)只是会多打印一个异常,一样会停止。
2.抓异常
因为调用cancle(true)的时候有可能会抛出异常,如这个例子中的InterruptedException,因此可以通过异常捕捉来实现。
@Override protected String doInBackground(String... params) { String result = "完成"; for (int i = 1; i <= 10; i++) { try { Log.i(TAG, "doInBackground: "+i); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); Log.i(TAG, "doInBackground: 捕捉到异常,退出"); break; } publishProgress(i); } return result; }
这时调用cancle(true):
并行和串行
据说AsyncTask的任务是并行还是串行执行在不同Android版本有所变化,但是从API13开始,AsyncTask的任务执行都是串行的。何为串行,比如有下面的界面:
有两个task
private ProgressBar pb1;
private ProgressBar pb2;
private Button btn1;
private Button btn2;
private Button stop1;
private Button stop2;
private MyAsyncTask task1, task2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
task1 = new MyAsyncTask(btn1, pb1);
task2 = new MyAsyncTask(btn2, pb2);java
btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i(TAG, "onClick: 开始1"); task1.execute(); } });
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "onClick: 开始2");
task2.execute();
}
});
stop1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "onClick: 停止1 ");
task1.cancel(true);
}
});
stop2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "onClick: 停止2");
task2.cancel(false);
}
});
}
在上面的MyAstncTask中,后台任务要执行10秒。
这里为了区分,打印开始按钮的名字来区分:
public class MyAsyncTask extends AsyncTask<String, Integer, String> { private String TAG = this.getClass().getSimpleName(); Button btn; ProgressBar pb; String name; public MyAsyncTask(Button btn, ProgressBar pb) { this.btn = btn; this.pb = pb; name = btn.getText().toString(); } @Override protected String doInBackground(String... params) { String result = "完成"; for (int i = 1; i <= 10; i++) { try { Log.i(TAG, "doInBackground: "+name+" "+i); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); Log.i(TAG, "doInBackground: 捕捉到异常,退出"); break; } publishProgress(i); } return result; } @Override protected void onPreExecute() { Log.i(TAG, "onPreExecute: 准备工作 "+name); } @Override protected void onPostExecute(String s) { btn.setText(s); Log.i(TAG, "onPostExecute: 回调 "+name); } @Override protected void onProgressUpdate(Integer... values) { pb.setProgress(values[0]); } @Override protected void onCancelled() { Log.i(TAG, "onCancelled: 取消任务 "+name); btn.setText("取消了"); } }
在点击第一个开始按钮之后点击第二个开始按钮,效果:
打印日志:
虽然点击了开始2,但是依然等第一个任务完成了才开始第二个任务。
想要让任务并行执行怎么办呢?其实他之所以会串行执行任务,是因为内部默认的线程池中将任务进行了排队,保证他们一个一个来。只要我们换个满足要求的线程池来执行任务就行了。AstncTask内部就有一个线程池
AsyncTask.THREAD_POOL_EXECUTOR可以使用。当然,用Executors来创建也行。
然后将开始任务的
execute(Params... params)方法改为
executeOnExecutor(Executor exec,Params... params).这里用
AsyncTask.THREAD_POOL_EXECUTOR.
task1.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); task2.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
效果:
日志:
屏幕横竖屏切换
使用AsyncTask的时候,在屏幕切换也会出现问题。画面是这样的:
日志是这样的,动图中也能看见:
虽然屏幕切换后,任务也在执行,也在不停地调用更新进度条的方法,最后也执行了
onPostExecute方法,但是界面上就是什么变化都没有。
因为在横竖屏切换的时候,Activity会销毁重建,所以AsyncTask所持有的引用就不是新建的Activity的控件了,新的Activity就不会变化了。
其中一种解决方法
很简单,加上这句就行了。这时屏幕怎么切换都没事
相关文章推荐
- Android中使用ViewFlipper实现屏幕页面切换(关于坐标轴的问题已补充更改)
- ANDROID中使用VIEWFLIPPER类实现屏幕切换(关于坐标轴的问题已补充更改)
- Android中解决手机屏幕横竖屏切换问题
- android 屏幕横竖切换问题
- Android入门(34)——第十一章 使用ViewFlipper实现屏幕切换动画效果
- Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会有解决很多问题]生产者消费者模型
- android:configuration 处理屏幕旋转和语言切换等问题
- Android开发:在AsyncTask中使用Dialog出现的问题
- 安卓笔记android fragment在viewpager中的使用和屏幕切换的状态保存等
- 关于android的webview 使用的问题 JS无法正常使用 界面切换出现白屏
- android 多线程 - 并行包线程池为例说说线程池的设计需求及使用
- Android使用AsyncTask实现多线程下载的方法
- Android多线程分析之五:使用AsyncTask异步下载图像
- Android解决Fragment中使用SurfaceView切换时闪一下黑屏的问题
- android屏幕大小,字体大小,横屏竖屏切换问题
- android程序中使用多线程的问题
- Android多线程操作sqlite(Sqlite解决database locked问题)(2)使用事务处理的效果
- android开发步步为营之47:使用viewflipper实现屏幕的切换
- 使用android support library中的tablayout实现页签切换效果出现的问题
- android 使用fragment切换,出现fragment重叠的问题