前言
在安卓中我们的UI更新都是在主线程里面,如果主线程超过5s无响应(还是5s后操作无响应?这个需要待确认),就会导致ANR,那因此就需要启动线程进行耗时操作,然后再到主线程处理,正常我们是通过Handler+Thread实现的,其实这个就是AsyncTask
AsyncTask的优缺点
优点
1、操作简单
封装了Thread和Handler给用户使用,操作比较简单。用户需要使用时,仅需继承AsyncTask,并重写其中的doInBackground方法就可以,若是希望子线程的执行结果反馈到UI线程上,则将onPreExecute(告知UI线程,子线程开始执行)和onPostExectute(将子线程执行的结果反馈给UI线程)和onProgressUpdate(可以实时的将子线程的执行过程反馈给UI,一般是使用进度条)重写就可以。
2、过程可控
缺点
1、线程池容量不够会抛出异常
AsyncTask任务运行于线程池的。一般线程池的大小是固定的,这就导致AsyncTask可以并发的任务也是固定的。Android 1.5规定AsyncTask仅允许存放128个并发任务,同一时间只有10个任务可被同时处理(这10个任务以队列的形式存放)。也就是说如果你在完成138个任务之前排队,你的应用程序就会崩溃。大多数情况下,当人们使用AsyncTask从网络上加载Bitmaps时,会有这个问题。
2、内存泄露(所以最好任务执行时间少一点)
AsyncTask提供的cancell方法只是一个修改标志的方法,并没有真正的停止任务的执行,当人无论寻到此标志时才会停止。
持有外部引用,外部销毁后任务在运行,内存不会回收
3、在安卓3.0版本后AsyncTask默认是单线程的
所以当你添加任务的时候是按照顺序执行的,如果前一个任务卡住了,后面的就无法执行,不过系统提供了一个允许一定数量的线程池进行并行处理,后面也会讲到 简单点说就是调用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,params)方法
4、屏幕旋转
使用AsyncTask的时候,在屏幕切换也会出现问题。虽然屏幕切换后,任务也在执行,也在不停地调用更新进度条的方法,最后也执行了onPostExecute方法,但是界面上就是什么变化都没有。
因为在横竖屏切换的时候,Activity会销毁重建,所以AsyncTask所持有的引用就不是新建的Activity的控件了,新的Activity就不会变化了。
其中一种解决办法
AsyncTask的使用及注意事项
其实可以参考 AsyncTask的使用这篇文章,讲的蛮好
AsyncTask的使用
package com.xm.studyproject.java;
import android.os.AsyncTask;
import android.util.Log;
import static android.content.ContentValues.TAG;
public class UseAsynckTask {
public void test() {
IAsyncTask iAsyncTask = new IAsyncTask();
iAsyncTask.execute("url1", "url2", "url3");
//针对上一种利用 单线程存在的问题 提供了一种一定数量的线程池
iAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"url1", "url2", "url3");
}
//泛型参数1:其实这个地方是一个集合
//参数2:是进度条的进度 也是一个集合
//参数3:是结果值
private class IAsyncTask extends AsyncTask<String, Integer, String> {
//args1 就是上面的参数1
//这个方法就是在异步线程里面执行的 就是真实任务的执行
protected String doInBackground(String... args1) {
int length = args1.length;
Log.i(TAG, "doInBackground in:" + args1[0]);
Log.i(TAG, "doInBackground in:length:" + length);
int times = 0;
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(500);
times++;
//注意 非得调用这个方法
//不然onProgressUpdate方法不会被调用到
publishProgress(i);//提交之后,会执行onProcessUpdate方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.i(TAG, "doInBackground out");
return "over:" + times;
}
/**
* 在调用cancel方法后会执行到这里
*/
//也是运行在主线程上
protected void onCancelled() {
Log.i(TAG, "onCancelled");
}
/**
* 在doInbackground之后执行
*/
//这个方法是在主线程执行的
//args3值是doInBackground方法的返回值
protected void onPostExecute(String args3) {
Log.i(TAG, "onPostExecute:" + args3);
}
/**
* 在doInBackground之前执行
*/
//这个方法也是在主线程执行的
@Override
protected void onPreExecute() {
Log.i(TAG, "onPreExecute");
}
/**
* 特别赞一下这个多次参数的方法,特别方便
*
* @param args2
*/
//进度条更新
//args2[0]是doInBackground动态更新的值
@Override
protected void onProgressUpdate(Integer... args2) {
int length = args2.length;
Log.i(TAG, "args2.length:" + length);
Log.i(TAG, "onProgressUpdate:" + args2[0]);
}
}
}
注意
注意1: 记得在doInBackground()即时调用publishProgress()方法 不然onProgressUpdate()不执行。
注意2: 记得执行asyncTask.cancle方法 不然会存在内存泄漏等问题
源码分析
tip: AsyncTask源码还是相对来说比较简单的 但是针对细节的处理还是做得非常好的
1.创建实例IAsyncTask iAsyncTask = new IAsyncTask();
public AsyncTask() {
this((Looper) null);
}
public AsyncTask(@Nullable Looper callbackLooper) {
//默认是使用主线程的looper 创建了一个Hanlder
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
//这里采用的是创建线程方式3 不懂的可以去参考我之前多线程的文章
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
//任务的开始 这个任务一般就是在子线程执行的
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
//目标方法1 执行doInBackground方法
//这个方法此刻是不执行的 因为任务还没开始执行
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
//worker中run方法执行完后就走到这里了
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
//postResultIfNotInvoked方法
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
//发送到主线程
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
}
//方法mTask.finish
//mtask 就是之前new AsyncTaskResult<Result>(this, result));里面的mTask 其实就是AsyncTask
private void finish(Result result) {
if (isCancelled()) {
//如果是任务取消 则会走onCancelled方法
onCancelled(result);
} else {
//目标方法2 这个就是执行完任务后回调到主线程里面的
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
2、方法的执行 iAsyncTask.execute("url1", "url2", "url3");
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
//所以内部还是走的executeOnExecutor方法
//上面使用的时候我们发现除开通过execute方法执行 还有一种就是可多个线程的线程池执行
return executeOnExecutor(sDefaultExecutor, params);
}
//sDefaultExecutor是什么 其实只能说是对任务队列的一个包装 不知道是不是可以理解成一个静态代理类
//初始化的时候就对这个sDefaultExecutor进行了赋值操作
//同时使用了static 跟volatitle关键字保证sDefaultExecutor应用里只有一份
//volatitle后面会讲解
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
//SERIAL_EXECUTOR 变量
//注意1: 使用了static跟final关键字 staic去保证项目里面只有一个的同时通过final去保证 SERIAL_EXECUTOR不能被修改 不系统可以new 多个AsyncTask 去对SERIAL_EXECUTOR重新赋值
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
//SerialExecutor类
private static class SerialExecutor implements Executor {
//双端队列 双端队列的使用可参考文章 https://www.jianshu.com/p/2f633feda6fb
//主要是为了加速数据的生产跟消费
//是一个可变数组 提供了很多方法
//是线程不安全的 而我们这里采用了单线程的方式 所以使用这个并不担心
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
//1.将任务存放到队列中 默认添加到队尾
// offer是往队列中存放任务
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
//从队列中取出元素 默认是从队头拿数据
if ((mActive = mTasks.poll()) != null) {
//真正执行的方法
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
//THREAD_POOL_EXECUTOR就是一个在静态代码块创建的一个线程池
//THREAD_POOL_EXECUTOR 也是static final修饰了
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//核心线程数最大四个线程 或者cpu数-1 取小值
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
//最大线程数 cpu*2+1
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//最大时长30s
private static final int KEEP_ALIVE_SECONDS = 30;
上面的代码 我们知道了(比较关键的概念)
1、new 多个AsyncTask实例毫无意义 因为SERIAL_EXECUTOR、THREAD_POOL_EXECUTOR等等 都是被static final修饰了
2、AsyncTask存在1一个串口(只能一个个执行)形式的任务队列的一个代理类就是SERIAL_EXECUTOR 还存在一个执行任务的线程池THREAD_POOL_EXECUTOR
回到iAsyncTask.execute("url1", "url2", "url3");
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
sDefaultExecutor以及线程池的创建 以及里面核心方法我们讲解完了
真正方法的执行
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
//这里面存在一个知识点就是如果调用executeOnExecutor多次会抛出异常
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
//这个是任务执行之前调用的方法
onPreExecute();
mWorker.mParams = params;
//真正的执行
// exec是什么之前的代码也有讲解到
exec.execute(mFuture);
return this;
}
总结 其实也没什么好总结的
AsyncTask还是比较简单的 只是优缺点需要注意 以及使用上的注意即可