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

Android异步机制的几种实现方式剖析

2016-03-15 11:39 369 查看
今天来谈一谈android中异步处理机制,众所周知在android中由于UI主线程是不安全的,因此不能直接在子线程中操作UI,一般我们会用到异步机制来解决这种问题,下面会介绍两种常用的异步机制Thread+Handler与Async Task机制;

一、Thread+Handler

提起Thread'很多人都不会陌生,做过Android手机开发的人都知道,手机UI是一个单独的线程在运行,并且该线程最好不会因为用户的操作而阻塞。换句话说,如果用户进行的操作需要耗时几十秒甚至几十分钟,那么在这段时间内占用UI线程是一个非常不明智的做法。它会阻塞掉UI线程,导致手机不再显示或者接受用户新的操作,给用户一种死机的感觉,因此我们开辟出子线程用来执行耗时操作。我们可以通过Thread+Handler来实现具体UI与子线程的协同;

下面是常用的handler:

private Handler mHandler = new Handler() {
public void handleMessage (Message msg) {//此方法在ui线程运行
switch(msg.what) {
case MSG_SUCCESS:

break;

case MSG_FAILURE:
break;
}
}
};
我们通过thread中的发送消息操作将消息传递给handler

Runnable runnable = new Runnable() {

@Override
public void run() {//run()在新的线程中运行 
<span style="white-space:pre">		</span>//执行耗时操作

mHandler.obtainMessage(MSG_SUCCESS,bm).sendToTarget();//获取图片成功,向ui线程发送MSG_SUCCESS标识和bitmap对象

}
};

}
由以上步骤我们便完成了一个最基本的异步机制,但是这种方式内部到底是如何传递消息的呢?就让我们在源码中找到它们的身影。

在上述代码中我们发现了最终是把信息通过sendToTarget()方法发送了,我们来看他的源码:

/**
* Sends this Message to the Handler specified by {@link #getTarget}.
* Throws a null pointer exception if this field has not been set.
*/
public void sendToTarget() {
<span style="background-color:#ff0000"> target.sendMessage(this);</span>
}
我们发现最终还是调用了sendMessage方法,继续看它的源码:

/**
* Pushes a message onto the end of the message queue after all pending messages
* before the current time. It will be received in {@link #handleMessage},
* in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
*         message queue.  Returns false on failure, usually because the
*         looper processing the message queue is exiting.
*/
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
它是调用了sendMessageDelayed方法,下面我摘出了几个片段我们分析一下:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
调用sendMessageAtTime();

public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}


sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。这个MessageQueue又是什么东西呢?其实从名字上就可以看出了,它是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。这个类是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。那么enqueueMessage()就是所谓的入队操作,入队已经完成接下来就需要进行出队获取消息。先看如下代码段:
public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
return;
}
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
msg.target.dispatchMessage(msg);
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to    " + msg.target + " "
+ msg.callback);
msg.recycle();
}
}
}
在while的死循环里不断地去进行出队操作,出队的方法就是next();出队后消息去了哪里呢?我们发现他调用了msg.target的dispatchMessage(),这里msg.targe就是handler,也就是说它调用的handler的dispatchMessage()
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
我们发现了一个熟悉的方法handleMessage(),这不正是我们写在handler中的方法吗?并且这里还传递了message过去,到这里一切豁然开朗,经过一圈,消息最终由子线程传递到了handler的handleMessage()中进行UI处理。

二、Async Task

Android从很早就引入了一个AsyncTask类,我们利用它可以非常灵活方便地从子线程切换到UI线程,由于Async Task是一个抽象类,因此我们必须去用子类去继承他才能去使用,在继承时我们可以为AsyncTask类指定三个泛型参数,这三个参数的用途如下:

1. Params

在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。

2. Progress

后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。

3. Result

当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
在子类中我们需要重写以下几个常用的方法来执行相应操作:

1. onPreExecute():

在后台任务开始执行之前调用,我们可以用来进行控件的初始化操作。

2. doInBackground(Params...)

这个方法就是我们执行耗时操作的地方,完毕后返回子类中的第三个参数类型,若为void则不进行返回。并且在此方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。

3. onProgressUpdate(Progress...)

当在doInBackground(Params...)中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。

4. onPostExecute(Result)

当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
package com.example.asynctask;
import android.os.AsyncTask;
import android.widget.ProgressBar;
import android.widget.TextView;

/**
* 生成该类的对象,并调用execute方法之后
* 首先执行的是onProExecute方法
* 其次执行doInBackgroup方法
*
*/
public class ProgressBarAsyncTask extends AsyncTask<Integer, Integer, String> {

private TextView textView;
private ProgressBar progressBar;

public ProgressBarAsyncTask(TextView textView, ProgressBar progressBar) {
super();
this.textView = textView;
this.progressBar = progressBar;
}

/**
* 这里的Integer参数对应AsyncTask中的第一个参数
* 这里的String返回值对应AsyncTask的第三个参数
* 该方法并不运行在UI线程当中,主要用于异步操作,所有在该方法中不能对UI当中的空间进行设置和修改
* 但是可以调用publishProgress方法触发onProgressUpdate对UI进行操作
*/
@Override
protected String doInBackground(Integer... params) {
NetOperator netOperator = new NetOperator();
int i = 0;
for (i = 10; i <= 100; i+=10) {
netOperator.operator();
publishProgress(i);
}
return i + params[0].intValue() + "";
}

/**
* 这里的String参数对应AsyncTask中的第三个参数(也就是接收doInBackground的返回值)
* 在doInBackground方法执行结束之后在运行,并且运行在UI线程当中 可以对UI空间进行设置
*/
@Override
protected void onPostExecute(String result) {
textView.setText("异步操作执行结束" + result);
}

//该方法运行在UI线程当中,并且运行在UI线程当中 可以对UI空间进行设置
@Override
protected void onPreExecute() {
textView.setText("开始执行异步线程");
}

/**
* 这里的Intege参数对应AsyncTask中的第二个参数
* 在doInBackground方法当中,,每次调用publishProgress方法都会触发onProgressUpdate执行
* onProgressUpdate是在UI线程中执行,所有可以对UI空间进行操作
*/
@Override
protected void onProgressUpdate(Integer... values) {
int vlaue = values[0];
progressBar.setProgress(vlaue);
}

}
在调用子类时我们只需这样:

new ProgressBarAsyncTask().execute();
到这里AsyncTask的使用就完成了,下面我们对AsyncTask进行分析;

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}


由上面可以知道execute调用了executeOnExecutor()方法,我们来看executeOnExecutor()方法
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;
}
发现了什么,我们找到了子类中的onPreExecute这个方法,这也
b114
就是说此方法第一个运行;

在看如下代码:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();


因此上面二者是等同的,在 exec.execute(mFuture); 中也就相当于SerialExecutor类中的execute(),我们看它的源码:

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);
}
}
}


我们发现了其中的run方法是执行的部分,在看run方法:

void innerRun() {
if (!compareAndSetState(READY, RUNNING))
return;
runner = Thread.currentThread();
if (getState() == RUNNING) { // recheck after setting thread
V result;
try {
result = callable.call();
} catch (Throwable ex) {
setException(ex);
return;
}
set(result);
} else {
releaseShared(0); // cancel
}
}


再找主要的执行函数call方法:

public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return postResult(doInBackground(mParams));
}


仔细看我们发现了熟悉的方法doInBackground()方法,而此时我们还是处于子线程当中,这也是doInBackground()方法能执行耗时操作的原因,我们发现最终的结果传给了postResult方法,我们来看它:

private Result postResult(Result result) {
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
到这里你发现了什么,又回到了我们熟悉的handler消息处理机制,那我们看看他的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 void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}


发现了什么,这里进行调用了onPregressUpdate方法和onPostExecute方法进行UI的操作,到这里我们理解了AsyncTask的内部机制。

三、二者的差异在什么地方

有些人会问这两种实现方式怎么灵活运用,什么地方改用哪一种呢,其实二者还是有且别的:

AsyncTask 提供了像onPreExecute, onProgressUpdate这类的快速调用方法,可以被UI线程方便的调用,Thread没有。

AsyncTask 不能重复运行, 一旦执行过了,你需要下次需要时重新创建调用。 thread 可以创建成在队列中获取workitem持续调用的模式,不停地执行。

AsyncTasks的执行优先级是3.0, 默认的执行模式是一次一个任务;thread的执行则与其它线程无关。

AsyncTask 写起来较快, Thread则需要多做一些工作。

AsyncTask和Thread都不会影响你的主线程的生命周期。

精益求精方可融会贯通!

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