Android探索之旅 | 用AsyncTask实现多线程+实例

-- 作者 谢恩铭 转载请注明出处

用AsyncTask实现多线程


在Android应用开发中,有时我们需要实现任务的同步。

Android里的AsyncTask类可以帮我们更好地管理线程同步(异步方式),就像Thread类能做的,不过用法比Thread更简单。

AsyncTask算是帮我们做了一层封装吧,使我们可以不用操心那么多,如果阅读AsyncTask的源码就可以了解。

具体AsyncTask的使用方法,最好参看Google Android的官方文档:

https://developer.android.com/reference/android/os/AsyncTask.html

在你开发Android应用程序时,如果有一个耗时任务(通常是一个子线程),并且这个任务调用了主线程,应用就会抛出著名的“ANR” (Application Not Responding,"应用无响应")错误。

ANR

AsyncTask类可以帮我们解围,使用AsyncTask能让我们正确及简便地使用主线程,即使此时另有一个异步线程被创建。

AsyncTask是asynchronous(英语“异步的”的意思)和task(英语“任务”的意思)的缩写,表示“异步任务”。

它使得耗时任务可以在后台执行,并在前台(UI线程,或称主线程)把执行结果展现出来,不必用到Thread类或Handler类。线程间通信也随之变得更简单,优雅。

主线程(User Interface Thread,UI线程)是在Android里负责和用户界面进行交互的线程。

AsyncTask是一个抽象类(abstract class),必须被继承才能实例化。有三个泛型参数,分别是:

  • Params : 传递给执行的任务的参数,也就是doInBackground方法的参数。

  • Progress : 后台任务执行过程中在主线程展现更新时传入的参数,也就是onProgressUpdate方法的参数。

  • Result : 后台执行的任务返回的结果,也就是onPostExecute方法的参数。

除此之外,继承AsyncTask类时,一般需要实现四个方法。

当然应用程序不需要调用这些方法,这些方法会在任务执行过程中被自动调用: onPreExecute, doInBackground, onProgressUpdate 和 onPostExecute:

  • onPreExecute : 此方法在主线程中执行,用于初始化任务。

  • doInBackground : 此方法在后台执行,是一个抽象方法,必须要被子类重写。此方法在onPreExecute方法执行完后启动。这个方法中执行的操作可以是耗时的,并不会阻塞主线程。通过调用publishProgress方法来在主线程显示后台任务执行的结果更新。

  • onProgressUpdate : 此方法也在主线程中执行,每当publishProgress方法被调用时,此方法就被执行,此方法只在doInBackground执行过程中才能被调用。

  • onPostExecute : 在doInBackground方法执行完之后启动的方法,在后台任务结束后才调用此方法,也在主线程执行。

实例


为了更好地理解AsyncTask的使用,我们来实现一个计时器的小应用。

首先我们创建一个Android项目,就命名为AsyncTaskActivity好了(名字无所谓),修改 res->layout 里的定义主用户界面的 xml 文件(比如是main.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:padding="15dp" >
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:padding="5dp"
        android:text="Time in min"
        android:textSize="22sp"
        android:textStyle="bold" />
                                                                       
    <EditText
        android:id="@+id/chronoValue"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_marginBottom="15dp"
        android:layout_gravity="center"
        android:hint="minutes"
        android:inputType="number"
        android:maxLines="1"
        android:text="1"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/chronoText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="0:0"
        android:textSize="80sp" />

    <Button
        android:id="@+id/start"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:text="Start" />
</LinearLayout>

在以上的main.xml文件中,我们主要定义了:

  • 一个EditText,用于输入需要计数的时间

  • 一个TextView,用于显示计数的变化

  • 一个Button,用于启动计数任务。

在我们的类AsyncTaskActivity中,我们首先声明三个private变量,对应以上三个元素。

private EditText chronoValue;
private TextView chronoText;
private Button start;

然后创建一个内部类,继承AsyncTask类,命名为“Chronograph”,就是英语“秒表,计时器”的意思。

private class Chronograph extends AsyncTask<Integer, Integer, Void> {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        // 在计时开始前,先使按钮和EditText不能用
        chronoValue.setEnabled(false);
        start.setEnabled(false);
        chronoText.setText("0:0");
    }
    @Override
    protected Void doInBackground(Integer... params) {
        // 计时
        for (int i = 0; i <= params[0]; i++) {
            for (int j = 0; j < 60; j++) {
                try {
                    // 发布增量
                    publishProgress(i, j);
                    if (i == params[0]) {
                        return null;
                    }
                    // 暂停一秒
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        if (isCancelled()) {
            return null;
        }
        return null;
    }
                                                                                     
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        // 更新UI界面
        chronoText.setText(values[0] + ":" + values[1]);
    }

    @Override 
    protected void onPostExecute(Void result) {
        super.onPostExecute(result);
        // 重新使按钮和EditText可以使用
        chronoValue.setEnabled(true);
        start.setEnabled(true);
    }
}

以上,我们重写了我们需要的四个方法。最后我们再完成我们AsyncTaskActivity类的onCreate方法:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
                                                               
    // 获取三个UI组件
    start = (Button)findViewById(R.id.start);
    chronoText = (TextView)findViewById(R.id.chronoText);
    chronoValue = (EditText)findViewById(R.id.chronoValue);
                                                               
    start.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // 获取EditText里的数值
            int value = Integer.parseInt(String.valueOf(chronoValue.getText()));
            // 验证数值是否大于零
            if (value > 0) {
                new Chronograph().execute(value);
            }
            else {
                Toast.makeText(AsyncTaskActivity.this, "请输入一个大于零的整数值 !", Toast.LENGTH_LONG).show();
            }
        }
    });
}

如果我们在继承AsyncTask类时,对于三个参数中有不需要的,可以定义为Void类型(注意,与小写的 void 不同),例如:

private class Chronograph extends AsyncTask<Integer, Integer, Void> {...}

运行我们的项目,可以得到如下面三张图所示的结果:

按下Start按钮前

计数中

计数结束后

怎么样,AsyncTask不难使用吧~

这个例子项目我发在我的Github上了,请参看:

https://github.com/frogoscar/AsyncTaskExample

总结


  1. 今后,当有异步任务需要执行时,可以使用AsyncTask类,可以根据自己的需要来定制。

  2. AsyncTask使用了线程池(Thread Pool)的机制,使得同时执行多个AsyncTask成为可能。但是要注意的是,这个线程池的容量是5个线程同时执行,如果超过了这个数量,多余的线程必须等待线程池里的线程执行完才能启动。

  3. 使用AsyncTask,最好在明确知道任务会有一个确定和合理的结束的情况下。否则,还是使用传统的Thread类为好。

  4. 在doInBackground方法中的耗时操作最好是能保证在几秒钟之内完成的,不要做特别久的耗时操作。


我是谢恩铭,在巴黎奋斗的软件工程师。
热爱生活,喜欢游泳,略懂烹饪。
人生格言:「向着标杆直跑」

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容