美文网首页学习区Android/Java学习日记Android开发经验谈
Android学习笔记16 多线程编程之AsyncTask剖析

Android学习笔记16 多线程编程之AsyncTask剖析

作者: 尘语凡心 | 来源:发表于2016-07-26 16:15 被阅读343次

    在之前的博客中,已经详细地介绍了Android中异步消息处理机制的原理和整个流程,Handler、Message、MessageQueue、Looper各自的作用相信大家都已经很熟悉了。今天介绍的是多线程编程方面又一个重要的角色——AsyncTask。

    一、概述
    二、AsyncTask基本使用
    三、AsyncTask源码
    四、拓展
    

    一、AsyncTask是什么

    在Handler异步消息处理机制中,通过Handler发送消息处理消息,我们可以在非UI线程里对UI界面进行更新。但是,在实际使用中,因为要操作线程和Handler,过程可能会稍微有点复杂,重复的代码还是比较多的。

    AsyncTask,翻译为异步任务,它封装了Thread和Handler,可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。通过AsyncTask,我们可以很方便地完成一些需要异步执行的任务。当然,需要注意的一点是,AsyncTask不适合特别耗时的后台任务,所以对于特别耗时的任务来说,建议使用线程池等。

    二、AsyncTask怎么用

    简介

    1. AsyncTask是一个抽象类,一般在使用过程中我们会创建一个任务类,继承自AsyncTask,AsyncTask有三个泛型参数,分别是Params, Progress,Result,其中Params是参数的类型,Progress是后台任务执行进度的类型,Result是后台任务返回结果的类型,如果没有具体的类型就用Void代替。

    2. AsyncTask提供了4个重要的方法,分别是onPreExecute(),doInBackground(),onProgressUpdate(),onPostExecute(),其中doInBackground主要是执行耗时操作,onPreExecute主要是做一些准备工作,它是在UI线程里执行的,onProgressUpdate用于进度更新,onPostExecute则是用于发布结果。

    3. 主线程里实例化AsyncTask并调用execute方法来启动即可。

    实例

    下面我们通过一个实例来了解AsyncTask的基本用法,实例主要是通过网络请求获取服务器的数据,并把数据展示在界面上。

    /**
     * Created by JackalTsc on 2016/7/25.
     */
    public class AsyncTaskActivity extends Activity {
    
        private TextView tvData;
        private String dataUrl = "http://115.159.149.87:8080/testssm/user/usertest";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.layout_activity_asynctask);
            initView();
        }
    
        private void initView() {
            tvData = (TextView) findViewById(R.id.tv_data);
            Button btnStartAsync = (Button) findViewById(R.id.btn_start_async);
            btnStartAsync.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //执行异步任务
                    new taskGetData().execute(dataUrl);
                }
            });
        }
    
        private class taskGetData extends AsyncTask<String, Void, StringBuffer> {
    
            //在方法doInBackground()之前UI线程里调用.
            @Override
            protected void onPreExecute() {
                super.onPreExecute();
                tvData.setText("正在获取数据...");
            }
    
            //这里是执行耗时操作
            @Override
            protected StringBuffer doInBackground(String... params) {
    
                StringBuffer result = new StringBuffer("获取到的数据为:");
    
                //使用HttpURLConnection进行网络请求获取数据
                HttpURLConnection connection = null;
    
                try {
    
                    URL mURL = new URL(params[0]);
                    connection = (HttpURLConnection) mURL.openConnection();
                    connection.setConnectTimeout(5000);
    
                    if (connection.getResponseCode() == 200) {
    
                        InputStream is = connection.getInputStream();
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
                        String data;
                        try {
                            while ((data = bufferedReader.readLine()) != null) {
                                result.append(data);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
    
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (connection != null) {
                        connection.disconnect();
                    }
                }
                return result;
            }
    
            //获取进度更新
            @Override
            protected void onProgressUpdate(Void... values) {
                super.onProgressUpdate(values);
            }
    
            //doInBackground方法获取到数据后调用
            @Override
            protected void onPostExecute(StringBuffer result) {
                super.onPostExecute(result);
                tvData.setText(result);
            }
        }
    }
    

    布局文件很简单,layout_activity_asynctask.xml,代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/tv_data"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="12dp"
            android:text="Medium Text" />
    
        <Button
            android:id="@+id/btn_start_async"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="获取数据" />
    </LinearLayout>
    

    在上面的例子中,我们先创建继承自AsyncTask的异步任务类taskGetData,因为是直接根据url来获取数据,这里我们传入参数类型为String,返回类型为StringBuffer,任务类里重写了方法onPreExecute,让TextView先显示文字 “ 正在获取数据... ”,重写doInBackground方法进行网络请求获取数据,返回result,最后result会传递到onPostExecute方法里,我们在这里更新UI界面,显示数据。当然,创建好异步任务类后,我们通过new taskGetData().execute(dataUrl)来执行异步任务,这样就可以了。

    三、AsyncTask源码

    这部分我们主要是看下AsyncTask的源码,了解AsyncTask的实现原理。这里展示的只是关键的几步代码,要详细了解的话还是自己去看源码比较好。

    • 1. execute()方法

    当我们传入参数启动异步任务时,调用的是方法execute()。execute方法内部实现是调用executeOnExecutor方法,传入一个串行的线程池sDefaultExecutor和之前我们传入的参数。

    AsyncTask的execute()
    • 2. executeOnExecutor()方法

    接着看方法executeOnExecutor,先执行onPreExecute(),也就是我们可以重写来在执行前做初始化工作的地方,然后传入参数mFuture给SerialExecutor的execute方法,线程池开始执行。(mFuture是FutureTask实例,FutureTask是个并发类,充当Runnable的作用)

    方法executeOnExecutor
    • 3. SerialExecutor的execute方法

    查看SerialExecutor的execute方法。可以看到,方法中先是调用offer()方法,把传入的FutureTask实例插入到任务队列中,之后如果没有正在活动的任务,就调用scheduleNext()执行下个任务。同时当一个AsyncTask完成后,AsyncTask会继续执行其它任务直到都执行完。FutureTask的run方法会调用mWorker的call()方法。

    类SerialExecutor
    • 4. 线程池

    AsyncTask中有两个线程池和一个Handler,其中线程池SerialExecutor用于任务排队,线程池THREAD_POOL_EXECUTOR用于任务执行。在AsyncTask的构造方法中,有下面这段代码。可以看到,在mWorker的call()方法中会调用doInBackground方法,之后把得到的结果传递给postResult。

    mWorker的call()方法
    • 5. postResult()

    再看postResult()方法,不难发现,这里先获取Handler,然后发送消息。

    postResult()方法
    • 6. postResult()

    最后,我们查看AsyncTask内部的Handler,不难发现,接收到消息后,如果是MESSAGE_POST_RESULT标识,那么继续调用onPostExecute,这样整个过程就结束了。

    InternalHandler

    四、拓展

    在查看AsyncTask的源码时,我们可以看到其中用到了两个线程池。这里简单地对线程池作个介绍,算是初步接触,以后再作详细学习。

    进程是一个“执行中的程序”。通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。

    线程的引入可以减小开销,但是创建和销毁时难免有开销。引入线程池的好处的优点有三个,一是重用线程池中的线程,可以避免因为线程的创建和销毁而带来的性能开销。二是有效控制线程池的最大并发数,避免大量线程间因互相抢占系统资源而导致的阻塞现象。三是能够对线程进行简单的管理,提供定时执行及指定间隔循环执行的功能。

    在AsyncTask类里,我们可以看到其中有创建线程池THREAD_POOL_EXECUTOR:

    THREAD_POOL_EXECUTOR

    点击查看更多关于多线程编程的内容

    相关文章

      网友评论

        本文标题:Android学习笔记16 多线程编程之AsyncTask剖析

        本文链接:https://www.haomeiwen.com/subject/rggljttx.html