ps:这一篇也是很久之前写在其它blog上的,搬家好几次都没有成功,因此手动搬过来了,很多人都探究过AsyncTask,文章都写的很好!
1:android在新版本中不允许UI线程访问网络,但是如果需要访问网络又改怎么办呐?这里有很多解决方案,比如新开一个线程,在新线程中进行访问,然后访问数据,返回后可能会更新界面也可能不更新界面,这个就涉及了怎么与UI线程通信的问题。
2:在android中UI线程中不能执行耗时太长的任务,否则会引发ANR,又怎么解决这个问题呐,其实也可以新开一个线程进行执行,执行完成后,在返回到UI线程,这也涉及了通信的问题。
一般的解决方案都是采用Hander来进行线程间的通信,但是今天不谈论Handler,而是谈论在android1.5之后推出AsyncTask,该类可以非常方便的从子线程切换到UI线程。
AsyncTask是一个抽象类含有三个参数, AsyncTask
sample:
AsyncTask<Integer, Void, Integer> task = new AsyncTask<Integer, Void, Integer>() {
@Override
protected Integer doInBackground(Integer... params) {
return params[0] * params[0];
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
@Override
protected void onCancelled() {
super.onCancelled();
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onPostExecute(Integer result) {
Toast.makeText(AsyncTaskActivity.this, "Current Value = " + result, Toast.LENGTH_LONG).show();
setTextValue("Current Value =" + result);
}
};
if (Build.VERSION.SDK_INT >= 11) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,value);
} else {
task.execute(value);
}
从上面的例子中可以看出,一般重写上面四个函数,其他还有cancel函数等。
1: protected Integer doInBackground(Integer… params)是必须要重写的,因为它是在后台执行的,你的耗时逻辑和网络访问都可以放到这个里面,其中返回类型就是AsyncTask中的第三个参数的类型,输入参数是可变参数类型,与AsyncTask第一个参数保持一致,如果没有参数可以采用Void代替,V大写。
2:void onPreExecute() 这个表示在后台线程执行之前,UI可以执行的操作,一般用来开始进度条,或者进行提醒。
3:void onProgressUpdate(Void… values) ,根据名字也可以看出是调整进度条的,输入的参数与AsyncTask第二个参数一致,用来在UI界面更新进度条。
4: void onPostExecute(Integer result) ,该函数表示执行完毕后,返回的内容,已经在UI线程执行了,可以用来更新界面。
最后来看执行的部分加入了一个判断,这是因为android刚推出AsyncTask的时候,采用的单线程方式,前一个执行完了后再执行下一个,后来在2.x版本中改为了多个线程,到3.0版本时又改为了一个线程。由于现在很多程序已经不在支持1.X版本但是2.x版本还是支持的,为了防止阻塞,在3.0以后的版本中指定执行的Executor。
上面的例子中,就可以方便的在其它线程与UI线程进行通信,但是他究竟是怎么实现这样的功能呐?那么我们就来看看AsyncTask的源码,我这儿查看的是4.4.2的源码,其它版本的没有看过,应该也差不多。这儿大致做一下分析:
1:在代码中我们见到了熟悉的Handler,可以发现AsyncTask的内部通信依然是采用的Handler,只是封装起来了,对使用者就不用管这些内容了。
private static class InternalHandler extends Handler {
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
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;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
handleMessage内部有两个分支语句,MESSAGE_POST_RESULT,可以看出这个是处理结果的 , result.mTask.finish(result.mData[0])调用了
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
MESSAGE_POST_PROGRESS这个是处理进度条的,result.mTask.onProgressUpdate(result.mData),这个对应了AsyncTask中的进度函数。
2:在上面我只是看到了Handler处理的地方,但是在上面地方发出的message呐?分析MESSAGE_POST_RESULT:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
在此处我们看到发送了消息,而调用这个的地方有两处:
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
可以发现第一处有一个熟悉的地方,doInBackground(mParams),这个就是我们执行后台程序的地方,另一处在postResultIfNotInvoked中调用,你可能疑惑了,这两个地方又是在什么地方进行调用的呐?
在FutureTask中的run函数中:
public void run() {
if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;//Callable
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
可以看到有一个Callable,调用了callable 的call函数,这个Callable函数就是AsyncTask构造函数中的mWorker ,call函数就是mWorker 中的call函数,在此调用了doInBackground()函数, 其它一处使在cancel中掉用的,cancel调用了done,所以就算你取消了任务执行也会调用到onPostExecute()
你可能还有疑问,那FutureTask又是什么时候调用的呐?我们继续分析,看看调用FutureTask的地方:
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
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.execute(mFuture);
return this;
}
在这个地方我们看到了exec.execute(mFuture),这个mFuture就是构造函数中的FutureTask。而executeOnExecutor被 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,value)与 task.execute(value)调用,而这就是页面上开始调用的地方。到此为止分析了返回结果整个的调用流程,而进度条流程要简单很多就不进行分析了。
3: 接下来我们分析一下不指定Executor的情况下,AsyncTask是怎么顺序执行的。
我们在task.execute的内部发现实际调用的是executeOnExecutor(sDefaultExecutor, params),这里面的sDefaultExecutor为SERIAL_EXECUTOR,而SERIAL_EXECUTOR为:
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
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);
}
}
}
当调用execute的时候,首先将Runnable添加到了一个队列的尾部,第一次执行的时候mActive肯定为null,因此调用scheduleNext,获取到对头的Runnable执行,当执行完毕后再finnally中获取下一个执行对象。这样就能串行进行执行了,因此当前一个任务阻塞后,也会导致后面的所有任务阻塞,因此在3.0后版本执行时指定Executor。
最后我们来整理一下调用流程,execute调用FutureTask中的run函数,在run函数中调用callable的call函数,在call函数中调用postResult函数,在此发送message到handler,在handler处理返回的结果。
在上面我们探究了AsyncTask的执行流程,那这一篇里面就写一个小的demo,来尝试一下AsyncTask的调用。
1:布局文件:
<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"
tools:context="com.example.toolbox.suspend.SuspendActivity" >
<TextView
android:id="@+id/async_task"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dip"
android:background="@drawable/g_green_btn_selector"
android:gravity="center"
android:padding="10dip"
android:text="@string/async_task"
android:textColor="@color/white_color"
android:textSize="16sp" />
<TextView
android:id="@+id/async_task_thread"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dip"
android:background="@drawable/g_green_btn_selector"
android:gravity="center"
android:padding="10dip"
android:text="@string/async_task_thread"
android:textColor="@color/white_color"
android:textSize="16sp" />
<TextView
android:id="@+id/async_task_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dip"
android:background="@drawable/g_green_btn_selector"
android:gravity="center"
android:padding="10dip"
android:text="@string/async_task_result"
android:textColor="@color/white_color"
android:textSize="16sp" />
</LinearLayout>
2:页面代码
public class AsyncTaskActivity extends ActionBarActivity implements OnClickListener {
TextView result;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.async_task_activity);
findViews();
setViewListener();
}
private void findViews() {
result = (TextView) findViewById(R.id.async_task_result);
}
private void setViewListener() {
findViewById(R.id.async_task).setOnClickListener(this);
findViewById(R.id.async_task_thread).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.async_task:
startAsyncTask(3);
break;
case R.id.async_task_thread:
startAsyncTask2();
default:
break;
}
}
private void startAsyncTask2() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
startAsyncTask(5);
}
});
thread.start();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB) private void startAsyncTask(int value) {
AsyncTask<Integer, Void, Integer> task = new AsyncTask<Integer, Void, Integer>() {
@Override
protected Integer doInBackground(Integer... params) {
return params[0] * params[0];
}
@Override
protected void onPostExecute(Integer result) {
Toast.makeText(AsyncTaskActivity.this, "Current Value = " + result, Toast.LENGTH_LONG).show();
setTextValue("Current Value =" + result);
}
};
if (Build.VERSION.SDK_INT >= 11) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,value);
} else {
task.execute(value);
}
}
private void setTextValue(String text) {
result.setText(text);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
代码中有两处调用AsyncTask的地方:
一处是在UI线程中进行调用的,另一处是新开了一个线程,在新的线程中进行调用的,第一种显而易见执行完毕可以更新UI界面,但是第二种呐?这就引出了今天的话题,在另外一个线程中调用AsyncTask会出现什么情况?
这儿主要是有两个问题:
1,能否在其他线程中调用AsyncTask,2:如果可以调用,它能更新界面吗?如果能,为什么在新开的线程中可以更新界面,下面逐步解答者两个问题:
A1:可以在其他线程中调用AsyncTask
A2:它能更新UI界面,下面我们详细说说为什么能更新UI界面,正常情况下,一个线程都对应了一个Hander,每个Hander对应一个Looper,我们上面新开了一个线程,如要使用Handler,就要调用Looper.prepare()函数,因为UI线程在ActivityThread起来的时候已经调用了prepare函数,所以在程序中我们不需要再次调用,但是AsyncTask函数中并没有任何地方调用Looper.prepare(),同时它还能更新界面就说明了这个Looper就是使用的MainLooper。
我们看到AsyncTask中有一个init函数
public static void init() {
sHandler.getLooper();
}
这个Looper就是MainLooper,但是init这个函数是在什么地方调用的呐?如果是MainLooper那应该就是在UI线程中调用的,那又在UI线程的什么地方进行调用的呐?
public static void main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
我们查看ActivityThread的main函数中调用了AsyncTask.init(),由此可以知道确实是在UI线程中进行调用的,因此在其他线程中调用AsyncTask,最终是与UI线程进行通信的。因此也能对界面进行更改。