android多线程-AsyncTask之工作原理深入解析(上)

来源:互联网 发布:中银淘宝信用卡查询 编辑:程序博客网 时间:2024/06/05 15:14

关联文章: 
Android 多线程之HandlerThread 完全详解 
Android 多线程之IntentService 完全详解 
android多线程-AsyncTask之工作原理深入解析(上) 
android多线程-AsyncTask之工作原理深入解析(下)

  前两篇我们分析android的异步线程类HandlerThread与IntentService,它们都是android系统独有的线程类,而android中还有另一个比较重要的异步线程类,它就是AsyncTask。本篇我们将从以下3点深入分析AsyncTask。

  • AsyncTask的常规使用分析以及案例实现
  • AsyncTask在不同android版本的下的差异
  • AsyncTask的工作原理流程

一、AsyncTask的常规使用分析以及案例实现

  AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后会把执行的进度和最终结果传递给主线程并更新UI。AsyncTask本身是一个抽象类它提供了Params、Progress、Result 三个泛型参数,其类声明如下:

public abstract class AsyncTask<Params, Progress, Result> {
  • 1

  由类声明可以看出AsyncTask抽象类确实定义了三种泛型类型 Params,Progress和Result,它们分别含义如下:

  • Params :启动任务执行的输入参数,如HTTP请求的URL
  • Progress : 后台任务执行的百分比
  • Result :后台执行任务最终返回的结果类型

  如果AsyncTask不需要传递具体参数,那么这三个泛型参数可以使用Void代替。好~,我们现在创建一个类继承自AsyncTask如下:

package com.zejian.handlerlooper;import android.graphics.Bitmap;import android.os.AsyncTask;/** * Created by zejian * Time 16/9/4. * Description: */public class DownLoadAsyncTask extends AsyncTask<String,Integer,Bitmap> {    /**     * onPreExecute是可以选择性覆写的方法     * 在主线程中执行,在异步任务执行之前,该方法将会被调用     * 一般用来在执行后台任务前对UI做一些标记和准备工作,     * 如在界面上显示一个进度条。     */    @Override    protected void onPreExecute() {        super.onPreExecute();    }    /**     * 抽象方法必须覆写,执行异步任务的方法     * @param params     * @return     */    @Override    protected Bitmap doInBackground(String... params) {        return null;    }    /**     * onProgressUpdate是可以选择性覆写的方法     * 在主线程中执行,当后台任务的执行进度发生改变时,     * 当然我们必须在doInBackground方法中调用publishProgress()     * 来设置进度变化的值     * @param values     */    @Override    protected void onProgressUpdate(Integer... values) {        super.onProgressUpdate(values);    }    /**     * onPostExecute是可以选择性覆写的方法     * 在主线程中执行,在异步任务执行完成后,此方法会被调用     * 一般用于更新UI或其他必须在主线程执行的操作,传递参数bitmap为     * doInBackground方法中的返回值     * @param bitmap     */    @Override    protected void onPostExecute(Bitmap bitmap) {        super.onPostExecute(bitmap);    }    /**     * onCancelled是可以选择性覆写的方法     * 在主线程中,当异步任务被取消时,该方法将被调用,     * 要注意的是这个时onPostExecute将不会被执行     */    @Override    protected void onCancelled() {        super.onCancelled();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

  如代码所示,我们创建一个继承自AsyncTask的异步线程类,在泛型参数方面,传递String类型(Url) , Integer类型(显示进度),Bitmap类型作为返回值。接着重写了抽象方法doInBackground(),以及覆写了onPreExecute()、onProgressUpdate()、onPostExecute()、onCancelled()等方法,它们的主要含义如下:

  • (1)onPreExecute(), 该方法在主线程中执行,将在execute(Params… params)被调用后执行,一般用来做一些UI的准备工作,如在界面上显示一个进度条。
  • (2)doInBackground(Params…params), 抽象方法,必须实现,该方法在线程池中执行,用于执行异步任务,将在onPreExecute方法执行后执行。其参数是一个可变类型,表示异步任务的输入参数,在该方法中还可通过publishProgress(Progress… values)来更新实时的任务进度,而publishProgress方法则会调用onProgressUpdate方法。此外doInBackground方法会将计算的返回结果传递给onPostExecute方法。 
  • (3)onProgressUpdate(Progress…),在主线程中执行,该方法在publishProgress(Progress… values)方法被调用后执行,一般用于更新UI进度,如更新进度条的当前进度。 
  • (4)onPostExecute(Result), 在主线程中执行,在doInBackground 执行完成后,onPostExecute 方法将被UI线程调用,doInBackground 方法的返回值将作为此方法的参数传递到UI线程中,并执行一些UI相关的操作,如更新UI视图。
  • (5)onCancelled(),在主线程中执行,当异步任务被取消时,该方法将被调用,要注意的是这个时onPostExecute将不会被执行。

  我们这里再强调一下它们的执行顺序,onPreExecute方法先执行,接着是doInBackground方法,在doInBackground中如果调用了publishProgress方法,那么onProgressUpdate方法将会被执行,最后doInBackground方法执行后完后,onPostExecute方法将被执行。说了这么多,我们还没说如何启动AsyncTask呢,其实可以通过execute方法启动异步线程,其方法声明如下:

public final AsyncTask<Params, Progress, Result> execute(Params... params)
  • 1

  该方法是一个final方法,参数类型是可变类型,实际上这里传递的参数和doInBackground(Params…params)方法中的参数是一样的,该方法最终返回一个AsyncTask的实例对象,可以使用该对象进行其他操作,比如结束线程之类的。启动范例如下:

new DownLoadAsyncTask().execute(url1,url2,url3);
  • 1

  当然除了以上介绍的内容外,我们在使用AsyncTask时还必须遵守一些规则,以避免不必要的麻烦。

  • (1) AsyncTask的实例必须在主线程(UI线程)中创建 ,execute方法也必须在主线程中调用 
  • (2) 不要在程序中直接的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法 
  • (3) 不能在doInBackground(Params… params)中更新UI
  • (5) 一个AsyncTask对象只能被执行一次,也就是execute方法只能调用一次,否则多次调用时将会抛出异常 

  到此,AsyncTask的常规方法说明和使用以及注意事项全部介绍完了,下面我们来看一个下载案例,该案例是去下载一张大图,并实现下载实时进度。先来看看AsynTaskActivity.java的实现:

package com.zejian.handlerlooper;import android.content.Context;import android.os.AsyncTask;import android.os.Environment;import android.os.PowerManager;import android.widget.Toast;import com.zejian.handlerlooper.util.LogUtils;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.URL;/** * Created by zejian * Time 16/9/4. * Description: */public class DownLoadAsyncTask extends AsyncTask<String, Integer, String> {    private PowerManager.WakeLock mWakeLock;    private int ValueProgress=100;    private Context context;    public DownLoadAsyncTask(Context context){        this.context=context;    }    /**     * sync method which download file     * @param params     * @return     */    @Override    protected String doInBackground(String... params) {        InputStream input = null;        OutputStream output = null;        HttpURLConnection connection = null;        try {            URL url = new URL(params[0]);            connection = (HttpURLConnection) url.openConnection();            connection.connect();            // expect HTTP 200 OK, so we don't mistakenly save error report            // instead of the file            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {                return "Server returned HTTP " + connection.getResponseCode()                        + " " + connection.getResponseMessage();            }            // this will be useful to display download percentage            // might be -1: server did not report the length            int fileLength = connection.getContentLength();            // download the file            input = connection.getInputStream();            //create output            output = new FileOutputStream(getSDCardDir());            byte data[] = new byte[4096];            long total = 0;            int count;            while ((count = input.read(data)) != -1) {                // allow canceling with back button                if (isCancelled()) {                    input.close();                    return null;                }                total += count;                // publishing the progress....                if (fileLength > 0) // only if total length is known                    publishProgress((int) (total * 100 / fileLength));                //                Thread.sleep(100);                output.write(data, 0, count);            }        } catch (Exception e) {            return e.toString();        } finally {            try {                if (output != null)                    output.close();                if (input != null)                    input.close();            } catch (IOException ignored) {            }            if (connection != null)                connection.disconnect();        }        return null;    }    @Override    protected void onPreExecute() {        super.onPreExecute();        // take CPU lock to prevent CPU from going off if the user        // presses the power button during download        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,                getClass().getName());        mWakeLock.acquire();        //Display progressBar//        progressBar.setVisibility(View.VISIBLE);    }    @Override    protected void onPostExecute(String values) {        super.onPostExecute(values);        mWakeLock.release();        if (values != null)            LogUtils.e("Download error: "+values);        else {            Toast.makeText(context, "File downloaded", Toast.LENGTH_SHORT).show();        }    }    /**     * set progressBar     * @param values     */    @Override    protected void onProgressUpdate(Integer... values) {        super.onProgressUpdate(values);//        progressBar.setmProgress(values[0]);        //update progressBar        if(updateUI!=null){            updateUI.UpdateProgressBar(values[0]);        }    }    /**     * get SD card path     * @return     */    public File getSDCardDir(){        if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){            // 创建一个文件夹对象,赋值为外部存储器的目录            String dirName = Environment.getExternalStorageDirectory()+"/MyDownload/";            File f = new File(dirName);            if(!f.exists()){                f.mkdir();            }            File downloadFile=new File(f,"new.jpg");            return downloadFile;        }        else{            LogUtils.e("NO SD Card!");            return null;        }    }    public UpdateUI updateUI;    public interface UpdateUI{        void UpdateProgressBar(Integer values);    }    public void setUpdateUIInterface(UpdateUI updateUI){        this.updateUI=updateUI;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166

  简单说明一下代码,在onPreExecute方法中,可以做了一些准备工作,如显示进度圈,这里为了演示方便,进度圈在常态下就是显示的,同时,我们还锁定了CPU,防止下载中断,而在doInBackground方法中,通过HttpURLConnection对象去下载图片,然后再通过int fileLength =connection.getContentLength();代码获取整个下载图片的大小并使用publishProgress((int) (total * 100 / fileLength));更新进度,进而调用onProgressUpdate方法更新进度条。最后在onPostExecute方法中释放CPU锁,并通知是否下载成功。接着看看Activity的实现: 
activity_download.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:customView="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent">    <com.zejian.handlerlooper.util.LoadProgressBarWithNum        android:id="@+id/progressbar"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        customView:progress_radius="100dp"        android:layout_centerInParent="true"        customView:progress_strokeWidth="40dp"        customView:progress_text_size="35sp"        customView:progress_text_visibility="visible"        customView:progress_value="0"        />    <Button        android:id="@+id/downloadBtn"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="start download"        android:layout_centerHorizontal="true"        android:layout_below="@id/progressbar"        android:layout_marginTop="40dp"        /></RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

AsynTaskActivity.java

package com.zejian.handlerlooper;import android.Manifest;import android.app.Activity;import android.content.pm.PackageManager;import android.os.Bundle;import android.support.v4.app.ActivityCompat;import android.support.v4.content.ContextCompat;import android.view.View;import android.widget.Button;import com.zejian.handlerlooper.util.LoadProgressBarWithNum;import com.zejian.handlerlooper.util.LogUtils;/** * Created by zejian * Time 16/9/4. * Description:AsynTaskActivity */public class AsynTaskActivity extends Activity implements DownLoadAsyncTask.UpdateUI {    private static int WRITE_EXTERNAL_STORAGE_REQUEST_CODE=0x11;    private static String DOWNLOAD_FILE_JPG_URL="http://img2.3lian.com/2014/f6/173/d/51.jpg";    private LoadProgressBarWithNum progressBar;    private Button downloadBtn;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_download);        progressBar= (LoadProgressBarWithNum) findViewById(R.id.progressbar);        downloadBtn= (Button) findViewById(R.id.downloadBtn);        //create DownLoadAsyncTask        final DownLoadAsyncTask  downLoadAsyncTask= new DownLoadAsyncTask(AsynTaskActivity.this);        //set Interface        downLoadAsyncTask.setUpdateUIInterface(this);        //start download        downloadBtn.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                //execute                downLoadAsyncTask.execute(DOWNLOAD_FILE_JPG_URL);            }        });        //android 6.0 权限申请        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)                != PackageManager.PERMISSION_GRANTED) {            //android 6.0 API 必须申请WRITE_EXTERNAL_STORAGE权限            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},                    WRITE_EXTERNAL_STORAGE_REQUEST_CODE);        }    }    @Override    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {        super.onRequestPermissionsResult(requestCode, permissions, grantResults);        doNext(requestCode,grantResults);    }    private void doNext(int requestCode, int[] grantResults) {        if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST_CODE) {            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {                // Permission Granted                LogUtils.e("Permission Granted");            } else {                // Permission Denied                LogUtils.e("Permission Denied");            }        }    }    /**     * update progressBar     * @param values     */    @Override    public void UpdateProgressBar(Integer values) {        progressBar.setmProgress(values);;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

  在AsynTaskActivity中实现了更新UI的接口DownLoadAsyncTask.UpdateUI,用于更新主线程的progressBar的进度,由于使用的测试版本是android6.0,涉及到外部SD卡读取权限的申请,所以在代码中对SD卡权限进行了特殊处理(这点不深究,不明白可以google一下),LoadProgressBarWithNum是一个自定义的进度条控件。ok~,最后看看我们的运行结果: 
 
  效果符合预期,通过这个案例,相信我们对AsyncTask的使用已相当清晰了。基本使用到此,然后再来聊聊AsyncTask在不同android版本中的差异。

二、AsyncTask在不同android版本的下的差异

  这里我们主要区分一下android3.0前后版本的差异,在android 3.0之前,AsyncTask处理任务时默认采用的是线程池里并行处理任务的方式,而在android 3.0之后 ,为了避免AsyncTask处理任务时所带来的并发错误,AsyncTask则采用了单线程串行执行任务。但是这并不意味着android 3.0之后只能执行串行任务,我们仍然可以采用AsyncTask的executeOnExecutor方法来并行执行任务。接下来,编写一个案例,分别在android 2.3.3 和 android 6.0上执行,然后打印输出日志。代码如下:

package com.zejian.handlerlooper;import android.app.Activity;import android.os.AsyncTask;import android.os.Bundle;import android.view.View;import android.widget.Button;import com.zejian.handlerlooper.util.LogUtils;import java.text.SimpleDateFormat;import java.util.Date;/** * Created by zejian * Time 16/9/5. * Description: */public class ActivityAsyncTaskDiff extends Activity {    private Button btn;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_async_diff);        btn= (Button) findViewById(R.id.btn);        btn.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                new AysnTaskDiff("AysnTaskDiff-1").execute("");                new AysnTaskDiff("AysnTaskDiff-2").execute("");                new AysnTaskDiff("AysnTaskDiff-3").execute("");                new AysnTaskDiff("AysnTaskDiff-4").execute("");                new AysnTaskDiff("AysnTaskDiff-5").execute("");            }        });    }    private static class AysnTaskDiff extends AsyncTask<String ,Integer ,String>{        private String name;        public AysnTaskDiff(String name){            super();            this.name=name;        }        @Override        protected String doInBackground(String... params) {            try {                Thread.sleep(2000);            }catch (Exception ex){                ex.printStackTrace();            }            return name;        }        @Override        protected void onPostExecute(String s) {            super.onPostExecute(s);            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");            LogUtils.e(s+" execute 执行完成时间:"+df.format(new Date()));        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

  案例代码比较简单,不过多分析,我们直接看在android 2.3.3 和 android 6.0上执行的结果,其中android 2.3.3上执行Log打印如下: 

在 android 6.0上执行Log打印如下: 

   从打印log可以看出AsyncTask在android 2.3.3上确实是并行执行任务的,而在 android 6.0上则是串行执行任务。那么了解这点有什么用呢?其实以前我也只是知道这回事而已,不过最近在SDK开发中遇到了AsyncTask的开发问题,产生问题的场景是这样的,我们团队在SDK中使用了AsyncTask作为网络请求类,因为现在大部分系统都是在Android 3.0以上的系统运行的,所以默认就是串行运行,一开始SDK在海外版往外提供也没有出现什么问题,直到后面我们提供国内一个publisher海外版本时,问题就出现了,该publisher接入我们的SDK后,他们的应用网络加载速度变得十分慢,后来他们一直没排查出啥问题,我们这边也在懵逼中……直到我们双方都找到一个点,那就是publisher的应用和我们的SDK使用的都是AsyncTask作为网络请求,那么问题就来,我们SDK是在在Application启动时触发网络的,而他们的应用也是启动Activity时去访问网络,所以SDK比应用先加载网络数据,但是!!!AsyncTask默认是串行执行的,所以!!只有等我们的SDK网络加载完成后,他们应用才开始加载网络数据,这就造成应用的网络加载延迟十分严重了。后面我们SDK在内部把AsyncTask改为并行任务后问题也就解决了(当然这也是SDK的一个BUG,考虑欠佳)。在Android 3.0之后我们可以通过下面代码让AsyncTask执行并行任务,其AsyncTask.THREAD_POOL_EXECUTOR为AsyncTask的内部线程池。

new AysnTaskDiff("AysnTaskDiff-5").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
  • 1

   第一个参数传递是线程池,一般使用AsyncTask内部提供的线程池即可(也可以自己创建),第二个参数,就是最终会传递给doInBackground方法的可变参数,这里不传,所以直接给了空白符。执行效果就不再演示了,大家可以自行测试一下。 
   ok~,到此AsyncTask在不同android版本中的差异也分析完,感觉文章有点长了,那么AsyncTask工作原理分析就放到下篇吧,晚安。。

简易源码下载:源码地址

关联文章: 
Android 多线程之HandlerThread 完全详解 
Android 多线程之IntentService 完全详解 
android多线程-AsyncTask之工作原理深入解析(上) 
android多线程-AsyncTask之工作原理深入解析(下)



主要参考资料: 
https://developers.android.com 
《android开发艺术探索》


http://blog.csdn.net/javazejian/article/details/52462830 
出自【zejian的博客】