您的位置:首页 > 移动开发 > Android开发

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 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就不会变化了。

其中一种解决方法

很简单,加上这句就行了。



这时屏幕怎么切换都没事



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐