• AsyncTask源码分析


    在Android中,主线程是UI线程,当需要根据其他数据进行更新UI时,如果获取数据的操作比较耗时的话,会触发ANR,所以我们应该讲耗时的操作进行异步操作,尤其是请求网络数据的操作应该放在后台线程进行,避免ANR。

    而AsyncTask是Android里很常用的异步任务请求方法,AsyncTaks基本用法都会用,网上也要好多教程,就不写例子了。

    以前用的时候分析过源码,发现学问还是挺多的,今天总结一下,错误之处的请诸位指正。

     分析一个AsyncTask的源码,需要了解的知识:线程池,future模式,无锁操作,handler、looper和Message之间的关系。

    1.先看一下AsyncTask的基本成员们:

    public abstract class AsyncTask<Params, Progress, Result> {
        private static final String LOG_TAG = "AsyncTask";
    
        private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
        private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
        private static final int KEEP_ALIVE = 1;
    
        private static final ThreadFactory sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);
    
            public Thread newThread(Runnable r) {
                return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
            }
        };
    
        private static final BlockingQueue<Runnable> sPoolWorkQueue =
                new LinkedBlockingQueue<Runnable>(128);
    
        /**
         * An {@link Executor} that can be used to execute tasks in parallel.
         */
        public static final Executor THREAD_POOL_EXECUTOR
                = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                        TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
    
        /**
         * An {@link Executor} that executes tasks one at a time in serial
         * order.  This serialization is global to a particular process.
         */
        public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    
        private static final int MESSAGE_POST_RESULT = 0x1;
        private static final int MESSAGE_POST_PROGRESS = 0x2;
    
        private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
        private static InternalHandler sHandler;
    
        private final WorkerRunnable<Params, Result> mWorker;
        private final FutureTask<Result> mFuture;
    
        private volatile Status mStatus = Status.PENDING;
        
        private final AtomicBoolean mCancelled = new AtomicBoolean();
        private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
        //后续省略
    }

    首先我们看到了AsyncTask必备的3大参数Params,Progress,Result。后续我们会再见到它们。

    CPU_COUNT是java虚拟机可用的处理器个数,可以通过Runtime类的availableProcessors()方法得到。

    看到刚开始的几个静态常量CORE_POOL_SIZE和MAXMUM_POOL_SIZE,就知道AsyncTask实现了一个线程池的机制。

    定义了一个线程工场,以便于自定义线程

    任务阻塞队列是一个定义了一个大小为128的无界任务队列。

    ThreadPoolExecutor定义了一个线程池,核心池大小是CORE_POOL_SIZE,线程池最大容量是MAXIMUM_POOL_SIZE,多余空闲线程的存活时间是KEEP_ALIVE,单位是秒,使用一个大小为128的LinkedBlockingQueue作为任务队列,用一个无锁整型计数的线程工场产生线程。
    定义了两个常量MESSAGE_POST_RESULT和MESSAGE_POST_PROGRESS,用于与主线程通信的标签,表示当获取到与主线程交互的数据类型,是进度信息还是结果信息。

    定义了两个无锁布尔值作为任务激活或者取消的标志位,无锁操作即CAS,使用于并发情况下数据的更新。

    然后看一下这里自定义的SeiralExcuter,实现了Executor接口,用于串行执行任务,并且设置为默认的Executor.

    AsyncTask定义了两个线程池:异步线程池THREAD_POOL_EXECUTOR,同步线程池SeiralExcuter

    随着Android版本的不同,AsyncTask处理任务默认是串行还是并行几经更迭,至今又改回串行,因为如果有一个UI的状态信息需要不同任务下反复更改,那么并行处理显然会出现更新顺序的问题,很麻烦,所以尽量使用串行的方法。

    2.执行任务的方式。

    一个自定义WorkerRunnable,实现了Callable接口

    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
            Params[] mParams;
        }

    再看一下AsyncTask的初始化方法:

    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
    
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };
    
        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 occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

    随后用这个WorkerRunnable实例构造了一个FutureTask实例,这个FutureTask则是JDK中对于future模式的一种实现。future模式是多线程开发中常用的一种设计模式,主要思想是异步调用。当Client想Server请求数据时,如果这个请求操作耗时比较长,那么Server会先立即返回一个FutureData,这个数据是一份虚拟的数据,此时Client就可以去做其他的事情了,过一段时间在调用get方法,如果这个时候Server已经准备好返回数据了,就会返回我们真正需要的数据RealData,否则Client会进入等待状态,直至返回RealData。简单的说,可以把FutureData理解成一份契约,可以用来装配RealData的契约。

    这样Client就不用一直等待数据的返回,拿到FutureData后就可以先去做别的事情了,到需要的时候调用get方法获得RealData.

    实现一个Callable接口,它的call方法会构造我们需要的真实数据并返回。

    构造futuretask的时候,使用Callable接口,告诉futuretask我们需要的数据应该如何产生,使用future.get()来获取实际的数据。

    在重复一遍,WorkerRunnable实现了Callable接口,然后用这个实例去构造一个FuntureTask实例。

    当准备好future模式后,我们需要决定让任务串行执行还是并行执行,Android官方认为大多数情况下尽量选择串行执行,直接调用execute方法即默认使用SeiralExcuter。

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

    如果一定要并行执行,则调用executeOnExecutor方法,并且设置Executor为前文定义的线程池THREAD_POOL_EXECTOR:

    execute方法也是调用executeOnExecutor方法,只是将executor直接设置为默认的SeiralExecutor。

    再强调一遍,必须在UI线程中调用执行方法。

    3.执行的过程

    直接看一下executeOnExector方法:

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

    可以看到,在执行前先检查任务状态,如果正在执行或者已经完成,则抛出异常。处于挂起状态则设置为RUNNING状态,随后执行onPreExecute方法,此方法需要重写,用于执行任务前对UI的动作。

    随后将执行任务所需参数赋给我们自定义的WorkerRunnable实例mWorker构造future实例mFuture,执行futureTask,处理任务。最后返回this,是为了保持UI线程对这个AsyncTask的引用。

    到这里,我们回头看一下AsyncTask初始化的方法了。 (上文已引用,此处隐藏)

    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
    
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };
    
        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 occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }
    View Code

    WorkerRunnable中的Call方法是继承自Callable的方法,这个方法会返回需要构造的实际数据。任务线程被激活后就将其优先级设置为后台线程,将我们任务需要的Param参数传递给doInBackground方法,去在后台执行耗时任务,整个耗时任务包括网络请求和信息处理的过程,在执行该方法的时候,可以激活publishProcess方法来进行主线程进度更新的显示,当处理完数据后返回Result

    在mFuture中重写了done方法,即当mFuture的mWorker处理完数据后,mFuture调用了get方法和postResultIfNotInvoked方法,get方法如下:

    public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
            ExecutionException, TimeoutException {
        return mFuture.get(timeout, unit);
    }

    调用future模式的get方法,获取到了我们想要的结果数据,之后通过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;
    }

    终于到这一步了,可以看到数据处理完毕后,我们把在FutureTask中处理后的数据最终传给了一个message,用来与UI线程进行通信,有了Message,Handler还远么?通过getHandler方法得到一个Handler,这个Handler是自定义的InternalHandler

    private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }
        @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;
    }

    此处Looper.getMainLooper () 获得UI线程的Lopper,准备更新主线程的UI啦!

    根据请求的标签信息判定所需行为,当需要更新进度信息时,将数据分发给onProgressUpdate方法去主线程显示进度的更新,当需要传递结果时调用finish方法,此时如果请求已被取消则不发送,如果没被取消则发送至UI线程进行UI信息的更新。

    至此,AsyncTask的基本流程就走了一遍了。

    总结一下:

    利用FutureTask进行异步请求,通常情况下串行处理请求,如果有并行需求则使用线程池应对并发请求,线程池的配置取决于当前虚拟机所能使用的CPU数量。

    三大参数Params任务后台执行所需的参数类型,Progress是如果需要前台进度更新的更新值类型,Result则是任务返回值的类型。

    使用execute方法激发异步任务执行,在发出请求之前,使用onPreExecute可以在执行任务前对UI进行标记,随后调用了doInBackground方法,进行后台耗时任务的操作,在这个过程中可以调用publishProgress方法将信息放入message,通过InternalHandler传递,调用onProgressUpdate方法将进度更新信息发送至UI线程,展示进度情况,处理完毕后,InternalHandler将结果数据的message通过onPostExecute发送至UI线程,UI线程根据结果数据进行UI的更新。

     所以,在使用AsyncTask时,我们需要重写上面标红的四个方法。最少得重写一个doInBackground方法,一般情况下还得重写一个onPostExecute方法。

    这种大的流程框架已经被写好(注意executrOnExecutor方法是final的),我么只需要自己定制流程一部分具体实现的方式,也就是设计模式之中的模板方法模式。

    public class MainActivity extends Activity {
        
        private static final String TAG = "ASYNC_TASK";
        
        private Button execute;
        private Button cancel;
        private ProgressBar progressBar;
        private TextView textView;
        
        private MyTask mTask;
        
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            
            execute = (Button) findViewById(R.id.execute);
            execute.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //注意每次需new一个实例,新建的任务只能执行一次,否则会出现异常
                    mTask = new MyTask();
                    mTask.execute("http://www.baidu.com");
                    
                    execute.setEnabled(false);
                    cancel.setEnabled(true);
                }
            });
            cancel = (Button) findViewById(R.id.cancel);
            cancel.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //取消一个正在执行的任务,onCancelled方法将会被调用
                    mTask.cancel(true);
                }
            });
            progressBar = (ProgressBar) findViewById(R.id.progress_bar);
            textView = (TextView) findViewById(R.id.text_view);
            
        }
        
        private class MyTask extends AsyncTask<String, Integer, String> {
            //onPreExecute方法用于在执行后台任务前做一些UI操作
            @Override
            protected void onPreExecute() {
                Log.i(TAG, "onPreExecute() called");
                textView.setText("loading...");
            }
            
            //doInBackground方法内部执行后台任务,不可在此方法内修改UI
            @Override
            protected String doInBackground(String... params) {
                Log.i(TAG, "doInBackground(Params... params) called");
                try {
                    HttpClient client = new DefaultHttpClient();
                    HttpGet get = new HttpGet(params[0]);
                    HttpResponse response = client.execute(get);
                    if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                        HttpEntity entity = response.getEntity();
                        InputStream is = entity.getContent();
                        long total = entity.getContentLength();
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        byte[] buf = new byte[1024];
                        int count = 0;
                        int length = -1;
                        while ((length = is.read(buf)) != -1) {
                            baos.write(buf, 0, length);
                            count += length;
                            //调用publishProgress公布进度,最后onProgressUpdate方法将被执行
                            publishProgress((int) ((count / (float) total) * 100));
                            //为了演示进度,休眠500毫秒
                            Thread.sleep(500);
                        }
                        return new String(baos.toByteArray(), "gb2312");
                    }
                } catch (Exception e) {
                    Log.e(TAG, e.getMessage());
                }
                return null;
            }
            
            //onProgressUpdate方法用于更新进度信息
            @Override
            protected void onProgressUpdate(Integer... progresses) {
                Log.i(TAG, "onProgressUpdate(Progress... progresses) called");
                progressBar.setProgress(progresses[0]);
                textView.setText("loading..." + progresses[0] + "%");
            }
            
            //onPostExecute方法用于在执行完后台任务后更新UI,显示结果
            @Override
            protected void onPostExecute(String result) {
                Log.i(TAG, "onPostExecute(Result result) called");
                textView.setText(result);
                
                execute.setEnabled(true);
                cancel.setEnabled(false);
            }
            
            //onCancelled方法用于在取消执行中的任务时更改UI
            @Override
            protected void onCancelled() {
                Log.i(TAG, "onCancelled() called");
                textView.setText("cancelled");
                progressBar.setProgress(0);
                
                execute.setEnabled(true);
                cancel.setEnabled(false);
            }
        }
    }

    源码地址:https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/AsyncTask.java

  • 相关阅读:
    ios程序中传值的几种方式
    iOS系统、设备信息获取方式
    关于如何使自定义的Button和系统的UIBarButtonItem保持一致的两种方法
    关于iOS中音视频播放的几种方式介绍
    关于IOS数据操作方式详解(三)— Sqlite数据解析
    关于iOS数据操作方式详解(二)— Jason数据解析
    关于IOS数据操作方式详解(一)— XML数据解析
    git常用命令
    pip及npm换源
    win10安装Docker并换国内源
  • 原文地址:https://www.cnblogs.com/yueyanglou/p/5285780.html
Copyright © 2020-2023  润新知