Android AsyncTask 异步任务

Android 提供了一个轻量级的用于处理异步任务的类 AsyncTask

我们一般是继承 AsyncTask,然后在类中实现异步操作,再将异步执行的进度,反馈给 UI 主线程

在我们继续讲 AsyncTask 之前,我们先来讲讲什么是线程

什么是多线程

理解多线程之前,我们先来了解几个名词 应用程序,进程,线程,多线程

名词 说明
应用程序(Application) 为了完成特定任务,用某种语言编写的一组指令集合(一组静态代码)
进程(Process) 运行中的程序 ,系统调度与资源分配的一个 独立单位 ,操作系统会为每个进程分配一段内存空间,程序的依次动态执行,经历代码加载 -> 执行 -> 执行完毕的完整过程!
线程(Thread) 比进程更小的执行单元,每个进程可能有多条线程
线程需要放在一个进程中才能执行
线程是由程序负责管理的,而进程则是由系统进行调度的
多线程概念(Multithreading) 并行地执行多条指令,将CPU的 时间片 按照调度算法,分配给各个线程,实际上是 分时执行 的,只是这个切换的时间很短,用户感觉是同时而已

打个比方

我们挂着 QQ,突然想去听歌,那么我们需要把 QQ 关掉,然后再去启动 XX 播放器吗?

答案显然是否定的

我们直接打开播放器放歌就好,QQ 还在运行着,是吧

这就是简单的多线程

在实际开发中,也有这样的例子,比如应用正在运行, 发现新版本了,想后台更新,这个时候一般我们会开辟出一条后台线程,用于下载新版本的apk,但是这个时候 我们还可以使用应用中的其它功能

同步与异步

同步 :当我们执行某个功能时,在没有得到结果之前,这个调用就不能返回!简单点就是说必须 等前一件事做完才能做下一件事;举个简单的例子:比如你啪啪啪,为了避免弄出人命,肯定要先戴好套套, 然后再啪啪啪是吧~套套戴好 -> 然后啪啪啪,比如你没套套,那啪啪啪的操作就要等待了,直到你把 套套买回来带上,这个时候就可以开始啪啪啪了~一个形象地例子,

异步 :和同步则是相对的,当我们执行某个功能后,我们并不需要立即得到结果,我们额可以正常地 做其他操作,这个功能可以在完成后通知或者回调来告诉我们;还是上面那个后台下载的例子,后台下载, 我们执行下载功能后,我们就无需去关心它的下载过程,当下载完毕后通知我们就可以了~

Android 为很么要引入异步任务

Android 程序刚启动时,会同时启动一个对应的主线程 (Main Thread),但这个主线程主要负责处理 与 UI 相关的事件,有时我们也把他称作UI线程

而 Android App 必须遵守这个单线程模型的规则: Android UI操作并不是线程安全的并且这些操作都需要在UI线程中执行

假如我们在非 UI线程 中,比如在主线程中 new Thread() 另外开辟一个线程,然后直接在里面修改 UI控件的值,此时会抛出下述异常

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views

另外,如果我们把耗时的操作都放在 UI 线程中的话,UI 线程超过 5s 没有响应用于请求,那么 这个时候会引发 ANR(Application Not Responding) 异常,就是应用无响应

最后一个原因,就是现在的 Android 4.0+ 禁止在 UI 线程中执行网络操作,不然会报: android.os.NetworkOnMainThreadException

这就是为什么 Android 要引入异步任务的原因

实现异步有很多方法,也不一定就要使用 AsyncTask,还可以自己开辟一个线程,完成相关操作后,通过下述两种方法进行 UI 更新

  1. Handler, 在 Handler 里写好 UI 更新,然后通过 sendMessage() 等的方法通知 UI

  2. 利用 Activity.runOnUiThread(Runnable) 把更新 UI 的代码创建在 Runnable 中,更新 UI 时,把 Runnable 作为参数传递

AsyncTask

AsyncTask 是一个抽象类,一般我们都会定义一个类继承 AsyncTask 然后重写相关方法

比如下面的代码就定义了一个下载文件的 Task : DownloadFilesTask

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

AsyncTask<Params, Progress, Result> 三个参数说明

参数 说明
Params 启动任务执行的是如参数,比如一个网络请求的 URL
Progress 后台任务执行的百分比
Result 后台任务执行完毕后返回的结果

如果不需要一些参数,可以使用 void 代替

AsyncTask 方法与流程

方法 说明
onPreExecute() 在执行后台耗时操作前调用,通常用于一些初始化操作,比如显示进度条
doInBackground(params...) onPreExecute() 方法执行完毕后立即执行,该方法运行于后台,主要负责执行耗时的后台处理工作,可调用 publishProgress(progress) 来更新时时的任务进度
onPostExecute(Result) doInBackground(params...) 执行完毕后,该方法会被 UI 线程调用,后台任务的运行结果将通过该方法传递到 UI 线程,然后展示给用户
onProgressUpdate(progress) publishProgress(progress...) 被调用后,UI 线程将调用该方法在界面上展示任务的进度,比如更新进度条
onCancelled() 用户取消线程操作的时候调用,也就是在主线程调用 onCancelled() 的时候调用

使用 AsyncTask 几点注意事项

  1. Task 的实例必须在 UI 线程中创建
  2. execute() 方法必须在 UI 线程中调用
  3. 不要手动调用 onPreExecute()doInBackground(params...)onPostExecuteonCancelled() 这几个方法
  4. Task 只能执行一次,运行多次会出现异常

范例

理论的东西真是枯燥无味,但又要讲清楚,还好已经完了,接下来我们写一个范例来演示下 AsyncTask

很简单,就是模拟下载更新进度条


  1. 创建一个 空的 Android 项目 cn.twle.android.AsyncTask

  2. 修改 activity_main.xml 添加一个进度条和一个按钮

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:orientation="vertical">  
        <TextView  
            android:id="@+id/txttitle"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content" />
    
        <!--设置一个进度条,并且设置为水平方向-->  
        <ProgressBar  
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content"  
            android:id="@+id/pgbar"  
            style="?android:attr/progressBarStyleHorizontal"/>  
        <Button  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:id="@+id/btn_update"  
            android:text="开始下载"/>  
    </LinearLayout>
    
  3. MainActivity.java 同一目录下创建 Download.java 用于模拟耗时操作

    package cn.twle.android.asynctask;
    
    public class Download {
    
        //延时操作,用来模拟下载  
        public void delay()  
        {  
            try {  
                Thread.sleep(1000);  
            }catch (InterruptedException e){  
                e.printStackTrace();;  
            }  
        }  
    }
    
  4. MainActivity.java 同一目录下创建 MsDownloadTask.java

    package cn.twle.android.asynctask;
    
    import android.os.AsyncTask;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    
    public class MsDownloadTask extends AsyncTask<Integer,Integer,String>  
    {  
        private TextView txt;  
        private ProgressBar pgbar;
    
        public MsDownloadTask(TextView txt,ProgressBar pgbar)  
        {  
            super();  
            this.txt = txt;  
            this.pgbar = pgbar;  
        }
    
        //该方法不运行在UI线程中,主要用于异步操作,通过调用publishProgress()方法  
        //触发onProgressUpdate对UI进行操作
    
        @Override  
        protected String doInBackground(Integer... params) {  
            Download dop = new Download();  
            int i = 0;  
            for (i = 10;i <= 100;i+=10)  
            {  
                dop.delay();  
                publishProgress(i);  
            }  
            return  i + params[0].intValue() + "";  
        }
    
        //该方法运行在UI线程中,可对UI控件进行设置  
        @Override  
        protected void onPreExecute() {  
            txt.setText("开始下载");  
        }
    
        //doBackground方法中,每次调用publishProgress方法都会触发该方法  
        //运行在UI线程中,可对UI控件进行操作
    
        @Override  
        protected void onProgressUpdate(Integer... values) {  
            int value = values[0];  
            pgbar.setProgress(value);  
        }  
    }
    
  5. 最后修改 MainActivity.java

    package cn.twle.android.asynctask;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    
    public class MainActivity extends AppCompatActivity {
    
        private TextView txttitle;
        private ProgressBar pgbar;
        private Button btnupdate;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            txttitle = (TextView)findViewById(R.id.txttitle);
            pgbar = (ProgressBar)findViewById(R.id.pgbar);
            btnupdate = (Button)findViewById(R.id.btn_update);
            btnupdate.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    MsDownloadTask myTask = new MsDownloadTask(txttitle,pgbar);
                    myTask.execute(1000);
                }
            });
        }
    }
    

是不是很简单

参考文档

  1. Android AsyncTask

Android 基础教程

关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.