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

今天来谈一谈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这个方法,这也就是说此方法第一个运行;

在看如下代码:

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都不会影响你的主线程的生命周期。

精益求精方可融会贯通!













 






  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值