美文网首页
Android异步任务机制之AsyncTask

Android异步任务机制之AsyncTask

作者: 巫师Android | 来源:发表于2020-10-19 15:51 被阅读0次

    特别说明:在Android 10.0+中,AsyncTask已经标记为弃用了。

    一、什么是AsyncTask?

    AsyncTask是Android提供的轻量级(只是代码上轻量一些,而实际上要比handler更耗资源)异步任务类。它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。

    AsyncTask 是一个抽象的泛型类,它提供了 Params、Progress 和 Result 这三个泛型参数,其中 Params 表示参数的类型,Progress 表示后台任务的执行进度和类型,而 Result 则表示后台任务的返回结果的类型,如果 AsyncTask 不需要传递具体的参数,那么这三个泛型参数可以用 Void 来代替。

    它的出现是为了降低异步通信的开发难度。使用AsyncTask可以忽略Looper、MessageQueue、Handler 等复杂对象,更便捷的完成异步耗时操作。

    问题:什么是异步通信?

    二、消息机制与AsyncTask

    要弄懂Android中的消息通信机制,需要掌握主线程的刷新机制,Thread,Handler,Looper,MessageQueue,Message以及AsyncTask等知识点。

    AsyncTask适用于执行较为简单的耗时操作,如倒计时、下载等。我们只需掌握几个方法就可以完成消息通信,降低了消息通信难度。

    三、如何使用AsyncTask

    掌握AsyncTask的基本概念后,就是学习它的使用了。

    它的使用有以下步骤:

    1.新建内部类继承AsyncTask 。
    2.定义AsyncTask的三种泛型参数。
    3.重写方法。
    4.在需要启动的地方调用execute方法。

    AsyncTask的泛型参数说明:

    Params:启动任务执行的输入参数
    Progress:后台任务执行的百分比
    Result:后台执行任务最终返回的结果

    AsyncTask常用方法介绍

    AsyncTask常用方法 调用时机 方法说明
    onPreExecute 异步任务开始执行时,系统最先调用此方法。 此方法运行在主线程中,可以对控件进行初始化等操作。
    dolnBackground 执行完onPreExecute方法后,系统执行此方法 此方法运行在子线程中,比较耗时的操作放在此方法中执行。
    onProgressUpdate 触发此方法,需要在dolnBackground中使用publishProgress方法。 此方法运行在主线程中,可以修改控件状态,例如: 显示当前进度,适用于下载或扫描这类需要实时显示进度的需求。
    onPostExecute 当异步任务执行完成后,系统会调用此方法。 此方法运行在主线程中,可以修改控件状态,例如: 下载完成。
    publishProgress 在doInBackground中使用。 用于触发onProgressUpdate方法。

    注:onPreExecute()、onProgressUpdate(Integer... values)、
    onPostExecute(String s)这三个方法都是运行在主线程的,因此可以在里面进行UI更新。

    下面看一个 倒计时 Demo:

    public class MainActivity extends AppCompatActivity {
    
    private EditText et;
    private TextView tv;
    private Button bt;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        et = findViewById(R.id.et);
        tv = findViewById(R.id.tv);
        bt = findViewById(R.id.bt);
    
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int time = 0;
                try {
                    time = Integer.parseInt(et.getText().toString());
                }catch (Exception e){
                    Toast.makeText(MainActivity.this,"请输入正确的数字",Toast.LENGTH_SHORT).show();
                    return;
                }
                new MyTask().execute(time);
            }
        });
    }
    
    class MyTask extends AsyncTask<Integer,Integer,String>{
    
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }
    
        @Override
        protected String doInBackground(Integer... integers) {
            for (int i = integers[0]; i > 0; i--){
                try {
                    publishProgress(i);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return "计时结束";
        }
    
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            tv.setText(values[0]+"");
        }
    
        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            tv.setText(s);
        }
    }
    }
    

    new MyTask().execute(time);

    最终会把time作为参数传给doInBackground(Integer... integers)
    doInBackground方法的返回值会作为参数传给onPostExecute。

    ok,再使用AsyncTask做一个进度条:

    public class SecondActivity extends AppCompatActivity implements View.OnClickListener {
    
    private Button btn1,btn2;
    private ProgressBar pro1,pro2;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
    
        btn1 = findViewById(R.id.bt1);
        btn2 = findViewById(R.id.bt2);
        pro1 = findViewById(R.id.pr1);
        pro2 = findViewById(R.id.pr2);
        btn1 .setOnClickListener(this);
        btn2 .setOnClickListener(this);
    }
    
    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.bt1:
                DownloadTask d1= new DownloadTask();
                d1.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,1);
                btn1.setText("正在下载");
                btn1.setEnabled(false);
                break;
            case R.id.bt2:
                DownloadTask d2= new DownloadTask();
                d2.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,2);
                btn2.setText("正在下载");
                btn2.setEnabled(false);
                break;
            default:
                break;
        }
    }
    
    private class DownloadTask extends AsyncTask<Integer,Integer,Integer> {
        @Override
        protected Integer doInBackground(Integer... integers) {
            for (int i = 1; i <= 10; i++){
                try {
                    Thread.sleep(1000);
                    publishProgress(i * 10,integers[0]);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return integers[0];
        }
    
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            switch (values[1]){
                case 1:
                    pro1.setProgress(values[0]);
                    break;
                case 2:
                    pro2.setProgress(values[0]);
                    break;
                default:
                    break;
            }
        }
    
        @Override
        protected void onPostExecute(Integer integer) {
            super.onPostExecute(integer);
            switch (integer){
                case 1:
                    btn1.setText("下载完成");
                    btn1.setEnabled(false);
                    break;
                case 2:
                    btn2.setText("下载完成");
                    btn2.setEnabled(false);
                    break;
                default:
                    break;
            }
        }
    }
    }
    

    五、AsyncTask和Handle的简单对比

    1、Handler和AsyncTask的执行过程

    Handler和AsyncTask的执行过程

    和Handle相比,AsyncTask的执行过程更加简单。

    2、AsyncTask和Handle的优缺点

    AsyncTask使用的优点是简单,快捷,过程可控。使用的缺点是在使用多个异步操作和并需要进行UI变更时,就会变得复杂起来。
    Handler使用的优点是结构清晰,功能定义明确,对于多个后台任务时,简单,清晰。使用的缺点是在单个后台异步处理时,显得代码过多,结构过于复杂。
    所以在数据简单时使用AsyncTask,数据量多且复杂就使用handler。

    六、线程池

    关于线程池

    AsyncTask 对应的线程池 ThreadPoolExecutor 都是进程范围内共享的,且都是 static 的,所以是 Asynctask 控制着进程范围内所有的子类实例。由于这个限制的存在,当使用默认线程池时,如果线程数超过线程池的最大容量,线程池就会爆掉(3.0 后默认串行执行,不会出现个问题)。针对这种情况,可以尝试自定义线程池,配合 Asynctask 使用。

    关于默认线程池

    AsyncTask里面线程池是一个核心线程数为CPU + 1,最大线程数为CPU * 2 + 1, 工作队列长度为 128 的线程池,线程等待队列的最大等待数为 28,但是可以自定义线程池。线程池是由 AsyncTask 来处理的,线程池允许 tasks 并行运行,需要注意的是并发情况下数据的一致性问题,新数据可能会被老数据覆盖掉。所以希望 tasks 能够串行运行的话,使用 SERIAL_EXECUTOR。

    七、AsyncTask在不同的SDK版本中的区别

    调用 AsyncTask 的 execute 方法不能立即执行程序的原因及改善方案通过查阅官方文档发现,AsyncTask 首次引入时,异步任务是在一个独立的线程中顺序的执 行,也就是说一次只执行一个任务,不能并行的执行,从 1.6 开始,AsyncTask 引入了线程池,支持同时执行 5 个异步任务,也就是说只能有 5 个线程运行,超过的线程只能等待,等待前面的线程直到某个执行完了才被调度和运行。换句话说, 如果进程中的 AsyncTask 实例个数超过 5 个,那么假如前 5 都运行很长时间的话,那么第 6 个只能等待机会了。这是 AsyncTask 的一个限制,而且对于 2.3 以前的版本无法解决。如果你的应用需要大量的后台线程去执行任务,那么只能放弃使用 AsyncTask,自己创建线程池来管理 Thread。不得不说,虽然 AsyncTask 较 Thread 使用起来方便,但是它最多只能同时运行 5 个线程,这也大大局限了它的作用,你必须要小心设计你的应用,错开使用 AsyncTask 时间,尽力做到分时,或者保证数量不会大于 5 个,否在就会遇到上面提到的问题。可能是 Google 意识到了 AsynTask 的局限性了,从 Android 3.0 开始对 AsyncTask 的 API 做出了一些调整:每次只启动一个线程执行一个任务,完了之后再执行第二个任务, 也就是相当于只有一个后台线程在执行所提交的任务。

    八、一些问题

    1)AsyncTask不与任何组件绑定生命周期

    很多开发者会认为一个在 Activity 中创建的 AsyncTask 会随着 Activity 的销毁而销毁。然而事实并非如此。AsynTask 会一直执行,直到 doInBackground()方法执行完毕,然后,如果 cancel(boolean)被调用,那么 onCancelled(Result result) 方法会被执行;否则,执行 onPostExecute(Result result)方法。如果我们的 Activity 销毁之前,没有取消 AsyncTask,这有可能让我们的应用崩溃(crash)。因为它想要处理的 view 已经不存在了。所以,我们是必须确保在销毁活动之前取消任务。 总之,我们使用 AsyncTask 需要确保 AsyncTask 正确的取消。

    2)串行或者并行的执行异步任务

    在 Android1.6 之前的版本,AsyncTask 是串行的,在 1.6 之后的版本,采用线程池处理并行任务,但是从 Android 3.0 开始,为了避免 AsyncTask 所带来的并发错误,又采用一个线程来串行执行任务。可以使用 executeOnExecutor()方法来并行地执行任务。

    串行:Execute(),使用execute()方法启动的进度条一个结束后才能启动另一个进度条
    并行:ExecuteOnExecuter(),同一时间只能启动五个线程,使用executeOnExecuter()启动的进度条同时下载
    可以在第二个demo中,取消注释观察区别。

    3)内存泄漏

    如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

    4)结果丢失

    屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

    九、AsyncTask原理

    • AsyncTask 中有两个线程池( SerialExecutor 和 THREAD_POOL_EXECUTOR)和一个 Handler(InternalHandler),其中线程池 SerialExecutor 用于任务 的排队, 而线程池 THREAD_POOL_EXECUTOR 用于真正地执行任务,InternalHandler 用于将执行环境从线程池切换到主线程。
    • sHandler 是一个静态的 Handler 对象,为了能够将执行环境切换到主线程,这就要求 sHandler 这个对象必须在主线程创建。由于静态成员会在加载类的时候进行初始化,因此这就变相要求 AsyncTask 的类必须在主线程中加载,否则同一个进程中的 AsyncTask 都将无法正常工作。

    参考:
    1、https://blog.csdn.net/yixingling/article/details/79517048,demo和带水印图片均来源于此,在他的demo上,做了一些自己的改动和思考。
    2、https://blog.csdn.net/qq_37321098/article/details/81625580

    相关文章

      网友评论

          本文标题:Android异步任务机制之AsyncTask

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