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:
在上述代码中我们发现了最终是把信息通过sendToTarget()方法发送了,我们来看他的源码:
sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。这个MessageQueue又是什么东西呢?其实从名字上就可以看出了,它是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。这个类是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。那么enqueueMessage()就是所谓的入队操作,入队已经完成接下来就需要进行出队获取消息。先看如下代码段:
二、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操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
由上面可以知道execute调用了executeOnExecutor()方法,我们来看executeOnExecutor()方法
b114
就是说此方法第一个运行;
在看如下代码:
因此上面二者是等同的,在 exec.execute(mFuture); 中也就相当于SerialExecutor类中的execute(),我们看它的源码:
我们发现了其中的run方法是执行的部分,在看run方法:
再找主要的执行函数call方法:
仔细看我们发现了熟悉的方法doInBackground()方法,而此时我们还是处于子线程当中,这也是doInBackground()方法能执行耗时操作的原因,我们发现最终的结果传给了postResult方法,我们来看它:
发现了什么,这里进行调用了onPregressUpdate方法和onPostExecute方法进行UI的操作,到这里我们理解了AsyncTask的内部机制。
三、二者的差异在什么地方
有些人会问这两种实现方式怎么灵活运用,什么地方改用哪一种呢,其实二者还是有且别的:
AsyncTask 提供了像onPreExecute, onProgressUpdate这类的快速调用方法,可以被UI线程方便的调用,Thread没有。
AsyncTask 不能重复运行, 一旦执行过了,你需要下次需要时重新创建调用。 thread 可以创建成在队列中获取workitem持续调用的模式,不停地执行。
AsyncTasks的执行优先级是3.0, 默认的执行模式是一次一个任务;thread的执行则与其它线程无关。
AsyncTask 写起来较快, Thread则需要多做一些工作。
AsyncTask和Thread都不会影响你的主线程的生命周期。
精益求精方可融会贯通!
一、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都不会影响你的主线程的生命周期。
精益求精方可融会贯通!
相关文章推荐
- Android中ViewPage的使用
- Android中分享功能的使用
- Android定时器的使用,
- 【转】Android 中的 Service 全面总结
- Android中用pull解析与保存xml文件
- 【转】Android上常见度量单位【xdpi、hdpi、mdpi、ldpi】解读
- 【转】Android编码规范
- 【转】Android技巧部分-优化
- Android中 sp,dp等
- 【转】Android中xml文件读写
- 【转】Android定位中的一些问题
- Android中多分辨率实现
- Android国际化快捷设置
- 转: 获取Android手机的一些信息
- 【转】Android学习总结
- Android深入浅出之Binder机制
- Android应用开发的一些规则
- Android中用 adb 命令操作数据库
- Android应用提升性能和用户体验
- Android NDK开发环境安装(OK版)