【Android 开发】: AsyncTask 详解

  在Androidy应用开发中,整个架构的性能好坏很大一部分都体现在线程的操作中,所以这是Android开发中是一块很重要的内容,对于线程,多线程这一部分的基础内容,我们可以参考本博客前期中Java多线程的内容:JavaSE第九十七讲:线程与进程 在这一讲中我们主要学习Android中关于AsyncTask的学习以及相关Demo的实现。

一. 使用 AsyncTask 目的

  官网位置:Android ---> API Guides ---> Processes and Threads --->  Using AsyncTask

  异步任务允许你在用户接口上执行异步工作。它会在自己的工作线程上执行可能阻塞的操作然后将结果推送到UI主线程,而不会让你自己去处理线程的操作。(在Android3.0以上的版本中,为了使得Android UI能够更加流畅,它不允许用户在UI的主线程中访问网络,所以会强制用户开辟一个子线程,在这个线程中完成下载耗时的操作(比如从网络中下载一张图片),此时UI的主线程与下载的子线程是存在一个异步的过程,一旦子线程中下载图片完成,就要把结果推送到UI上.)
  使用方法:继承AsyncTask类,实现 doInBackground() 回调接口,这样就运行在后台的线程池中,如果你想更新你的UI,你可以实现onPostExecute()放,它可以讲doInBackground()方法中的结果运行在 UI主线程中,所以你可以安全的更新你的UI.你可以在UI主线程中通过execute()方法来执行你的异步任务。
  如下代码片段:

【Android 开发】: AsyncTask 详解_第1张图片

二. AsyncTask 介绍

  AsyncTask其实就是一个线程操作的框架,它也是android围绕这 Thread 和 Handler来设计的。它可以直接的被一些短时间的操作使用(通常是几秒),如果你想要保持线程运行很长时间,强烈建议你使用java.util.concurrent包下的Executor, ThreadPoolExecutor 和 FutureTask 这些类来实现.
  一个异步任务是在跑在后台线程中进行计算然后把结果推送到UI主线程中的。它是由三个泛型类型和四个回调方法来实现的。

1 三个泛型类型

1)Params: 启动任务执行的输入参数,比如HTTP请求的URL。
2) Progress: 后台任务执行的百分比会发布到UI主线程中。
3) Result: 后台执行任务最终返回的结果,比如String,Integer等

【注意】如果三个泛型都没有类型,我们就用void代替,如下所所示(注意类型是Void是大写的)

 private class MyTask extends AsyncTask<Void, Void, Void> {}

2 四个回调方法

1) onPreExecute(): 在任务执行之前在UI主线程中被调用的。这个通常是用来做任务的准备,比如获得一个显示进度条的实例等。

2) doInBackground(Params...): onPreExecute()执行完成后马上被后台的进程中调用,用来处理耗时的操作,异步任务的输入参数也会传递到这里。计算得到结果会通过后面的执行方法(onPostExecute()方法)推送到UI主线程中。这个步骤还可以使用使用 publishProgress(Progress...) 来显示进度刻度。这些刻度会在UI主线程中实时显示通过onProgressUpdate(Progress...)方法.

3) onProgressUpdate(Progress...): publishProgress(Progress...)方法执行之后会被UI主线程调用,用来在UI主线程中实时显示计算刻度。

4) onPostExecute(Result): 在后台计算完成之后被UI主线程调用。doInBackground()方法返回的结果会作为它的一个参数来推送到UI主线程中。

3. AsyncTask的取消

  异步任务可以在任意时间调用cancel(boolean)来取消,调用这个方法之后会造成后续的isCancelled()方法都是返回true,取消之后在执行完doInBackground(Object[])后onCancelled(Object)方法会代替onPostExecute(Object)方法被执行。为了确保能够尽快的取消一个任务,我们应该在doInBackground(Object[])里面周期性的检查isCancelled()的返回值(例如在一个循环里面)。

4. AsyncTask的注意事项

1) AsyncTask 类必须在UI主线程中被加载,这点在 Android4.1 上已经帮我们自动完成
2) 任务类的实例(即new出一个继承AsyncTask类,作为匿名内部类的实例)必须在UI主线程中创建.
3) execute(Params...) 必须在UI主线程中被调用.
4) 不要手动的去调用 onPreExecute(), onPostExecute(Result), doInBackground(Params...),  onProgressUpdate(Progress...)这些方法.
5) 任务只能被执行一次(如果有第二次执行会抛出一个异常)

三、 程序Demo

1. Manifest.xml文件中添加网络授权

 <uses-permission android:name="android.permission.INTERNET"/>

2. 布局文件,主要是定义一个Button和ImageView,这里就不贴出来,读者可以自己下载源码查看

3. MainActivity.java

package com.android.asynctasktest;

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

/**
 * 使用异步任务从服务器上下载网络图片
 * @author AHuier
 *
 */
public class MainActivity extends Activity {

    private Button btn;
    private ImageView img;
    private String imgPath = "http://f.hiphotos.baidu.com/image/w%3D2048/sign=05793c21bba1cd1105b675208d2ac9fc/43a7d933c895d14350ee3c3272f082025aaf0703.jpg";
    private ProgressDialog dialog;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initComponent();
        dialog = new ProgressDialog(this);
        dialog.setTitle("提示信息");
        dialog.setMessage("正在下载,请稍后...");
        btn.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // 执行异步任务的操作,这个必须写在UI主线程中,由UI主线程去操作
                new MyTask().execute(imgPath);
            }
        });
    }

    /**
     * 使用异步任务的规则:
     * 1. 声明一个类继承AsyncTask, 指定好三个泛型的参数
     * 2. 第一个参数:启动任务执行的输入参数,比如HTTP请求的URL
     *    第二个参数:后台任务执行的百分比会发布到UI主线程中
     *    第三个参数:后台执行任务最终返回的结果,比如String,Integer等
     * 3. 小技巧
     *    这边写异步任务的时候先指定后三个参数在去实现对应的方法,这样Eclipse会自动生成与我们参数类型相匹配的返回类型的方法。
     * @author AHuier
     *
     */
    public class MyTask extends AsyncTask<String, Void, Bitmap>{

        // 任务执行之前的准备工作
        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();
            dialog.show();
        }
        
        // 完成耗时操作,将结果推送到onPostExecute()方法中
        // String... params : 表示可以传递多个String类型的参数,我们只取一个所以用params[0]
        @Override
        protected Bitmap doInBackground(String... params) {
            // TODO Auto-generated method stub
            // 使用网络链接类 HttpClient 类完成对网络数据的提取
            HttpClient httpClient = new DefaultHttpClient();
            HttpGet httpGet = new HttpGet(params[0]);
            Bitmap bitmap = null;
            try {
                HttpResponse httpResponse = httpClient.execute(httpGet);
                if(httpResponse.getStatusLine().getStatusCode() == 200){
                    HttpEntity httpEntity = httpResponse.getEntity(); // 取出Http协议实体
                    byte[] data = EntityUtils.toByteArray(httpEntity); //转换成字节数组
                    // 字节数组转换成Bitmap对象
                    bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                }
            } catch (ClientProtocolException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 返回bitmap对象,最终会作为参数到onPostExecute()方法中,用这个方法将其推送到UI主线程中。
            return bitmap;
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            // TODO Auto-generated method stub
            super.onProgressUpdate(values);
        }
        
        // 更新UI线程
        @Override
        protected void onPostExecute(Bitmap result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            img.setImageBitmap(result);
            dialog.dismiss();
        }
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    
    private void initComponent(){
        btn = (Button)findViewById(R.id.button1);
        img = (ImageView)findViewById(R.id.imageView1);
    }

}

4. 程序执行结果

   点击Button从网络中下载图片

【Android 开发】: AsyncTask 详解_第2张图片   【Android 开发】: AsyncTask 详解_第3张图片

四、使用带有刻度的进度条来完善上述功能

  在上一讲中我们实现了点击按钮在不阻塞UI主线程的情况下从网络中获取一张图片,并且显示出来。我们主要的操作是使用延时框,这一讲我们讲一下如何使用异步任务给它添加一下带有下载进度的刻度条。

1. 修改上面 MainActivity.java 中 onCreate()方法:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initComponent();
        dialog = new ProgressDialog(this);
        dialog.setTitle("提示信息");
        dialog.setMessage("正在下载,请稍后...");
        // 设置进度条对话框的样式
        dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        btn.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // 执行异步任务的操作,这个必须写在UI主线程中,由UI主线程去操作
                new MyTask().execute(imgPath);
            }
        });
    }

    public class MyTask extends AsyncTask<String, Integer, Bitmap>{

        // 任务执行之前的准备工作
        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();
            dialog.show();
        }
        
        // 完成耗时操作,将结果推送到onPostExecute()方法中
        // String... params : 表示可以传递多个String类型的参数,我们只取一个所以用params[0]
        @Override
        protected Bitmap doInBackground(String... params) {
            // 完成图片的下载功能
            Bitmap bitmap = null;
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            InputStream inputStream = null;
            try {
                HttpClient httpClient = new DefaultHttpClient();
                HttpGet httpGet = new HttpGet(params[0]);
                HttpResponse httpResponse = httpClient.execute(httpGet);
                if(200 == httpResponse.getStatusLine().getStatusCode()){
                    inputStream = httpResponse.getEntity().getContent();
                    // 先要获得文件的总长度
                    long file_length = httpResponse.getEntity().getContentLength();
                    // 每次读取字节的长度
                    int len = 0;
                    // 读取字节长度的总和
                    int total_length = 0;
                    byte[] data = new byte[1024];
                    while(-1 != (len = inputStream.read(data))){
                        total_length += len; //每次下载的长度进行叠加
                        /*
                         * 计算机每次下载完的部分占全部文件长度的百分比。 
                         * 计算公式如下:(int)((i/(float)count) * 100) 
                         * 得到的结果就是它的刻度值了
                         */
                        int value = (int)((total_length / (float)file_length) * 100);
                        // 使用 publishProgress(value)方法把刻度发布出去,它会发布到 onProgressUpdate()方法中
                        publishProgress(value);
                        outputStream.write(data, 0, len);
                    }
                    // outputStream 有一个特性,它可以将流里面的数据转换成一个字节数组,通过字节数据我们可以转换成Bitmap图像
                    byte[] result = outputStream.toByteArray();
                    // 将字节数组流转换成Bitmap的图片格式
                    bitmap = BitmapFactory.decodeByteArray(result, 0, result.length);  
                }
            } catch (Exception e) {
                // TODO: handle exception
            } finally{
                // outputStream 是特殊的流,它作用在内存中可以不用关闭,这一点我们之前有讲过.
                if(inputStream != null){
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
            return bitmap;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            // TODO Auto-generated method stub
            super.onProgressUpdate(values);
            dialog.setProgress(values[0]);
        }
        
        // 更新UI线程
        @Override
        protected void onPostExecute(Bitmap result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            img.setImageBitmap(result);
            dialog.dismiss();
        }
    }

2. 程序执行结果如下所示:

【Android 开发】: AsyncTask 详解_第4张图片   【Android 开发】: AsyncTask 详解_第5张图片


【备注】:使用异步任务的好处是用户不需要再去开启线程,去做一些线程里面方法的顺序操作。而 AsyncTask 其实就是一个线程框架,它已经为我们封装好了。


程序Demo源码:http://download.csdn.net/my 

[2013.11.27更新]-------------------------------------------------

1. 查看上述两种通过Http协议获取网络数据的方式。

1) 通过输入流来转换成byte[] 然后在利用字节数组流转换成Bitmap. 这也是比较笨的方式,如上述中的第二个Demo。

2) 直接通过 org.apache.http.util.EntityUtils方式获得byte[]然后转换成字节数组流,如上诉第一个Demo

HttpEntity httpEntity = httpResponse.getEntity(); // 取出Http协议实体
byte[] data = EntityUtils.toByteArray(httpEntity); //转换成字节数组
这一点读者可以注意一下。


你可能感兴趣的:(多线程,android,线程池,AsyncTask,异步任务)