Android中AsyncTask(异步任务)和Handler(线程消息机制)的详解


转载请备注出自于:http://blog.csdn.net/qq_22118507/article/details/51441679



                               Android中Handler的简单用法


Android的消息机制(一)


 android 有一种叫消息队列的说法,这里我们可以这样理解:假如一个隧道就是一个消息队列,那么里面的每一部汽车就是一个一个消息,这里我们先忽略掉超车等种种因素,只那么先进隧道的车将会先出,这个机制跟我们android 的消息机制是一样的。

一、    角色描述

1.Looper:(相当于隧道) 一个线程可以产生一个Looper 对象,由它来管理此线程里的Message Queue( 车队,消息隧道) 。

2.Handler: 作用是:在子线程中发送Message或者Runnable对象到MessageQueue中;在UI线程中接收、处理从MessageQueue分发出来的Message或者Runnable对象。发送消息一般使用Handler的sendMessage()方法,而发出去的消息经过处理后最终会传递到Handler的handlerMessage()方法中。

3. Message Queue( 消息队列): 用于存放Message或Runnable对象的消息队列。它由对应的Looper对象创建,并由Looper对象管理。每个线程中都只会有一个MessageQueue 对象。

4.线程:UI thread 通常就是main thread ,而Android 启动程序时会替它建立一个Message Queue 。

5. Message :消息体,用于装载需要发送的对象。

Message

[java]  view plain  copy
  1. Message message = Message.obtain();  
  2.        message.arg1 = 1;  
  3. message.arg2 = 2;  
  4. message.obj = "Demo";  
  5. message.what = 3;  
  6. Bundle bundleData = new Bundle();  
  7. bundleData.putString("Name""Lucy");  
  8. message.setData(bundleData);  


Message 可以传递的参数有:

(1). arg1 arg2 整数类型,是setData的低成本替代品。传递简单类型

(2). Object 类型 obj

(3). what  用户自定义的消息代码,这样接受者可以了解这个消息的信息。每个handler各自包含自己的消息代码,所以不用担心自定义的消息跟其他handlers有冲突。

(4).其他的可以通过Bundle进行传递

     Message可以通过new Message构造来创建一个新的Message,但是这种方式很不好,不建议使用。最好使用Message.obtain()来获取Message实例,它创建了消息池来处理的。


每一个线程里可含有一个Looper 对象以及一个MessageQueue 数据结构。在你的应用程序里,可以定义Handler 的子类别来接收Looper 所送出的消息。

 

在你的Android 程序里,新诞生一个线程,或执行 (Thread) 时,并不会自动建立其Message Loop 。

Android 里并没有Global 的Message Queue 数据结构,例如,不同APK 里的对象不能透过Massage Queue 来交换讯息(Message) 。

例如:线程A 的Handler 对象可以传递消息给别的线程,让别的线程B 或C 等能送消息来给线程A( 存于A 的Message Queue 里) 。

线程A 的Message Queue 里的讯息,只有线程A 所属的对象可以处理。

使用Looper.myLooper 可以取得当前线程的Looper 对象。

使用mHandler = new EevntHandler(Looper.myLooper()); 可用来构造当前线程的Handler 对象;其中,EevntHandler 是自已实现的Handler的子类别。

使用mHandler = new EevntHandler(Looper.getMainLooper()); 可诞生用来处理main 线程的Handler 对象;其中,EevntHandler 是自已实现的Handler 的子类别。

 

Android官网总结的关于Handler类的两个主要用途:
 
(1)线程间的通信:
 
在执行较为耗时的操作时,Handler负责将子线程中执行的操作的结果传递到UI线程,然后UI线程再根据传递过来的结果进行相关UI元素的更新。(上面已有说明)
 
(2)执行定时任务:
 
指定任务时间,在某个具体时间或某个时间段后执行特定的任务操作,例如使用Handler提供的postDelayed(Runnable r,long delayMillis)方法指定在多久后执行某项操作,比如当当、淘宝、京东和微信等手机客户端的开启界面功能,都是通过Handler定时任务来完成的。

这样描述可能太抽像,下面举几个实际的例子来说明:

二、    举例

1.  同线程内不同组件间的消息传递

Looper 类用来管理特定线程内对象之间的消息交换(Message Exchange) 。你的应用程序可以产生许多个线程。而一个线程可以有许多个组件,这些组件之间常常需要互相交换讯息。如果有这种需要,您可以替线程构造一个Looper 对象,来担任讯息交换的管理工作。Looper 对象会建立一个MessageQueue 数据结构来存放各对象传来的消息( 包括UI 事件或System 事件等) 。如下图:

 

每一个线程里可含有一个Looper 对象以及一个MessageQueue 数据结构。在你的应用程序里,可以定义Handler 的子类别来接收Looper 所送出的消息。

同线程不同组件之间的消息传递:

public class Activity1 extends Activity implements OnClickListener{

       Button button null ;

       TextView text null ;

       @Override

       protected void onCreate(Bundle savedInstanceState) {

              super .onCreate(savedInstanceState);

              setContentView(R.layout. activity1 );        

              button = (Button)findViewById(R.id. btn );

              button .setOnClickListener( this );

              text = (TextView)findViewById(R.id. content );

       }

       public void onClick(View v) {

              switch (v.getId()) {

              case R.id. btn :

                     Looper looper = Looper.myLooper (); // 取得当前线程里的looper

                     MyHandler mHandler = new MyHandler(looper); // 构造一个handler 使之可与looper 通信

                     //buton 等组件可以由mHandler 将消息传给looper 后, 再放入messageQueue 中, 同时mHandler 也可以接受来自looper 消息

                     mHandler.removeMessages(0);

                     String msgStr = " 主线程不同组件通信: 消息来自button" ;

                     Message m = mHandler.obtainMessage(1, 1, 1, msgStr); // 构造要传递的消息

                     mHandler.sendMessage(m); // 发送消息: 系统会自动调用handleMessage 方法来处理消息

                     break ;

                }            

       }     

       private class MyHandler extends Handler{             

              public MyHandler(Looper looper){

                     super (looper);

              }

              @Override

              public void handleMessage(Message msg) { // 处理消息

                     text .setText(msg. obj .toString());

              }            

       }

}

 

说明:

此程序启动时,当前线程( 即主线程, main thread) 已诞生了一个Looper 对象,并且有了一个MessageQueue 数据结构。

        looper = Looper.myLooper (); 调用Looper 类别的静态myLooper() 函数,以取得目前线程里的Looper 对象.

mHandler = new MyHandler (looper);构造一个MyHandler 对象来与Looper 沟通。Activity 等对象可以藉由MyHandler 对象来将消息传给Looper ,然后放入MessageQueue里;MyHandler 对象也扮演Listener 的角色,可接收Looper 对象所送来的消息。

Message m = mHandler.obtainMessage(1, 1, 1, obj);先构造一个Message 对象,并将数据存入对象里。

mHandler.sendMessage(m);就透过mHandler 对象而将消息m 传给Looper ,然后放入MessageQueue 里。


Android消息处理机制(二)


角色综述(回顾):

   (1)UI thread 通常就是main thread ,而Android 启动程序时会替它建立一个MessageQueue 。

(2) 当然需要一个Looper 对象,来管理该MessageQueue 。

(3) 我们可以构造Handler 对象来push 新消息到Message Queue 里;或者接收Looper( 从Message Queue 取出) 所送来的消息。

(4) 线程A 的Handler 对象可以传递给别的线程,让别的线程B 或C 等能送讯息来给线程A( 存于A 的Message Queue 里) 。

(5) 线程A 的Message Queue 里的消息,只有线程A 所属的对象可以处理。

 

子线程传递消息给主线程

public class Activity2 extends Activity implements OnClickListener{

       Button button null ;

       TextView text null ;

       MyHandler mHandler null ;

       Thread thread ;

       @Override

       protected void onCreate(Bundle savedInstanceState) {

              super .onCreate(savedInstanceState);

              setContentView(R.layout. activity1 );        

              button = (Button)findViewById(R.id. btn );

              button .setOnClickListener( this );

              text = (TextView)findViewById(R.id. content );

       }

       public void onClick(View v) {

              switch (v.getId()) {

              case R.id. btn :

                     thread new MyThread();

                     thread .start();

                     break ;

              }            

       }     

       private class MyHandler extends Handler{             

              public MyHandler(Looper looper){

                     super (looper);

              }

              @Override

              public void handleMessage(Message msg) { // 处理消息

                     text .setText(msg. obj .toString());

              }            

       }

       private class MyThread extends Thread{

              @Override

              public void run() {

                     Looper curLooper = Looper.myLooper ();

                     Looper mainLooper = Looper.getMainLooper ();

                     String msg ;

                     if (curLooper== null ){

                            mHandler new MyHandler(mainLooper);

                            msg = "curLooper is null" ;

                     } else {

                            mHandler new MyHandler(curLooper);

                            msg = "This is curLooper" ;

                     }

                     mHandler .removeMessages(0);

                     Message m = mHandler .obtainMessage(1, 1, 1, msg);

                     mHandler .sendMessage(m);

              }            

       }

}

说明:

Android 会自动替主线程建立Message Queue 。在这个子线程里并没有建立Message Queue 。所以,myLooper 值为null ,而mainLooper则指向主线程里的Looper 。于是,执行到:mHandler = new MyHandler (mainLooper);mHandler 属于主线程。  mHandler.sendMessage(m);就将m 消息存入到主线程的Message Queue 里。mainLooper 看到Message Queue 里有讯息,就会作出处理,于是由主线程执行到mHandler 的handleMessage() 来处理消息。


Handler中,与Message发送消息相关的方法有:

 
Message obtainMessage():获取一个Message对象。
boolean sendMessage():发送一个Message对象到消息队列中,并在UI线程取到消息后,立即执行。
boolean sendMessageDelayed():发送一个Message对象到消息队列中,在UI线程取到消息后,延迟执行。
boolean sendEmptyMessage(int what):发送一个空的Message对象到队列中,并在UI线程取到消息后,立即执行。
boolean sendEmptyMessageDelayed(int what,long delayMillis):发送一个空Message对象到消息队列中,在UI线程取到消息后,延迟执行。
void removeMessage():从消息队列中移除一个未响应的消息。

post的有关方法:
 
对于Handler的Post方式来说,它会传递一个Runnable对象到消息队列中,在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作。
 
Post允许把一个Runnable对象入队到消息队列中。它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)。详细解释如下:
 
boolean post(Runnable r):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,立即执行。
boolean postAtTime(Runnable r,long uptimeMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,在特定的时间执行。
boolean postDelayed(Runnable r,long delayMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,延迟delayMills秒执行
void removeCallbacks(Runnable r):从消息队列中移除一个Runnable对象。

  在开发Android移动客户端的时候往往要使用多线程来进行操作,我们通常会将耗时的操作放在单独的线程执行,避免其占用主线程而给用户带来不好的用户体验。但是在子线程中无法去操作主线程(UI 线程),在子线程中操作UI线程会出现错误。因此android提供了一个类Handler来在子线程中来更新UI线程,用发消息的机制更新UI界面,呈现给用户。这样就解决了子线程更新UI的问题。但是费时的任务操作总会启动一些匿名的子线程,太多的子线程给系统带来巨大的负担,随之带来一些性能问题。因此android提供了一个工具类AsyncTask,顾名思义异步执行任务。这个AsyncTask生来就是处理一些后台的比较耗时的任务,给用户带来良好用户体验的,从编程的语法上显得优雅了许多,不再需要子线程和Handler就可以完成异步操作并且刷新用户界面。

        先大概认识下Android.os.AsyncTask类:

       * android的类AsyncTask对线程间通讯进行了包装,提供了简易的编程方式来使后台线程和UI线程进行通讯:后台线程执行异步任务,并把操作结果通知UI线程。

       * AsyncTask是抽象类.AsyncTask定义了三种泛型类型 Params,Progress和Result。 
       * Params 启动任务执行的输入参数,比如HTTP请求的URL。 
       * Progress 后台任务执行的百分比。 
       * Result 后台执行任务最终返回的结果,比如String,Integer等。

       * AsyncTask的执行分为四个步骤,每一步都对应一个回调方法,开发者需要实现这些方法。

    * 1) 继承AsyncTask 
    * 2) 实现AsyncTask中定义的下面一个或几个方法 
       * onPreExecute(), 该方法将在执行实际的后台操作前被UI 线程调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条,或者一些控件的实例化,这个方法可以不用实现。 
       * doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台处理工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。 
      * onProgressUpdate(Progress...),在publishProgress方法被调用后,UI 线程将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。 
      * onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI 线程调用,后台的计算结果将通过该方法传递到UI 线程,并且在界面上展示给用户.

      * onCancelled(),在用户取消线程操作的时候调用。在主线程中调用onCancelled()的时候调用。

为了正确的使用AsyncTask类,以下是几条必须遵守的准则:

    1) Task的实例必须在UI 线程中创建

    2) execute方法必须在UI 线程中调用

    3) 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法,需要在UI线程中实例化这个task来调用。

    4) 该task只能被执行一次,否则多次调用时将会出现异常

      doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground接受的参数,第二个为显示进度的参数,第第三个为doInBackground返回和onPostExecute传入的参数。

下面通过一个Demo来说明如何使用Android.os.AsyncTask类,通过进度条来显示进行的进度,然后用TextView来显示进度值。程序结构图如下:

0_13132466438x1I

 

 

 

 

 

 

  [1] \layout\main.xml 布局文件源码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView  
       android:layout_width="fill_parent" 
       android:layout_height="wrap_content" 
       android:text="Hello , Welcome to Andy's Blog!"/>
    <Button
       android:id="@+id/download"
       android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       android:text="Download"/>
    <TextView  
       android:id="@+id/tv"
       android:layout_width="fill_parent" 
       android:layout_height="wrap_content" 
       android:text="当前进度显示"/>
    <ProgressBar
       android:id="@+id/pb"
       android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       style="?android:attr/progressBarStyleHorizontal"/>
</LinearLayout>

[2] /src中的MainActivity.java源码如下:

package com.andyidea.demo;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends Activity {
        
    Button download;
    ProgressBar pb;
    TextView tv;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        pb=(ProgressBar)findViewById(R.id.pb);
        tv=(TextView)findViewById(R.id.tv);
        
        download = (Button)findViewById(R.id.download);
        download.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DownloadTask dTask = new DownloadTask();
                dTask.execute(100);
            }
        });
    }
    
    class DownloadTask extends AsyncTask<Integer, Integer, String>{
        //后面尖括号内分别是参数(例子里是线程休息时间),进度(publishProgress用到),返回值 类型
        
        @Override
        protected void onPreExecute() {
            //第一个执行方法
            super.onPreExecute();
        }
        
        @Override
        protected String doInBackground(Integer... params) {
            //第二个执行方法,onPreExecute()执行完后执行
            for(int i=0;i<=100;i  ){
                pb.setProgress(i);
                publishProgress(i);
                try {
                    Thread.sleep(params[0]);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return "执行完毕";
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            //这个函数在doInBackground调用publishProgress时触发,虽然调用时只有一个参数
            //但是这里取到的是一个数组,所以要用progesss[0]来取值
            //第n个参数就用progress[n]来取值
            tv.setText(progress[0]+"%");
            super.onProgressUpdate(progress);
        }

        @Override
        protected void onPostExecute(String result) {
            //doInBackground返回时触发,换句话说,就是doInBackground执行完后触发
            //这里的result就是上面doInBackground执行后的返回值,所以这里是"执行完毕"
            setTitle(result);
            super.onPostExecute(result);
        }
        
    }
}

[3] 下面看下程序的运行结果截图:

0_1313247228OaoO

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值