Android异步线程之AsyncTask

       AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后会把执行的进度和最终结果传递给主线程并更新UI。这里Android已经帮我们封装好了AsyncTask(AsyncTask是对Thread+Handlerde 的封装),即使你对异步消息处理机制完全不了解,也可以简单的从子线程切换到主线程。

       AsyncTask是一个抽象类。那么要使用AsyncTask,我们需要写一个类去继承它;在继承时我们可以为AsyncTask类指定3个泛型参数,分别为Params(开始时),Progress(执行时)和Result(结束后)。

Params:启动任务执行的输入参数,如HTTP请求的URL。

Progress:后台任务执行的百分比。后台任务执行时,如果需要对在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。

Result:后台执行任务最终返回的结果类型。

例如:如果AsyncTask不需要传递具体参数,那么这三个泛型参数可以使用Void代替

class MyAsyncTask extends AsyncTask<Params, Progress, Result>{...}

我们来一个完整的简单例子:

//创建一个类继承AsyncTask并设置好泛型参数
class demoAsyncTask extends AsyncTask<Void,Intent,Boolean>{
    /**
     * 该方法在主线程中执行,将在execute(Params… params)被调用后
     * 执行,一般用来做一些UI的准备工作,如在界面上显示一个进度条。
     */
    @Override
    protected void onPreExecute() {
        progressDialog.show();//显示进度条对话框
    }
    /**
     * 抽象方法,必须实现,该方法在线程池中执行,用于执行异步任务,
     * 将在onPreExecute方法执行后执行。其参数是一个可变类型,
     * 表示异步任务的输入参数,在该方法中还可通过
     * publishProgress(Progress… values)来更新实时的任务进度,
     * publishProgress方法则会调用onProgressUpdate方法。
     * 此外doInBackground方法会将计算的返回结果传递给
     * onPostExecute方法。
     * */
    @Override
    protected Boolean doInBackground(Void... params) {
        try {
            while (true){
                int downloadPercent=doDownload();
                publishProgress(downloadPercent);
                if (downloadPercent>=100){
                    break;
                }
            }
        }catch (Exception e){
            return false;
        }
        return null;
    }

    /**
     *在主线程中执行,该方法在publishProgress(Progress… values)
     *方法被调用后执行,一般用于更新UI进度,如更新进度条的当前进度。
     * @param values
     */
    @Override
    protected void onProgressUpdate(Intent... values) {
        //在这里更新下载进度
        progressDialog.setMessage("Download"+values[0]+"%");
    }

    /**
     *doInBackground 执行完成后,onPostExecute 方法将被UI线程
     *调用,doInBackground 方法的返回值将作为此方法的参数传递到
     *UI线程中,并执行一些UI相关的操作,如提醒任务执行的结果,
     *以及关闭进度条对话框等等。
     * @param aBoolean
     */
    @Override
    protected void onPostExecute(Boolean aBoolean) {
        progressDialog.dismiss();//关闭进度对话框
        //在这里提示下载结果
        if (aBoolean){
            Toast.makeText(MyAsyncTask.this, "aBoolean",
                    Toast.LENGTH_SHORT).show();
        }else {
            Toast.makeText(MyAsyncTask.this,"failed",
                    Toast.LENGTH_SHORT).show();
        }
    }
}

启动任务只需编写:new demoAsyncTask().execute();

//使用AsyncTask的诀窍:在doInBackground方法中执行具体的耗时操作,
//onProgressUpdate方法中进行UI操作,
//onPostExecute方法中执行一些任务的收尾工作

注意:老司机们提醒。为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
1) Task的实例必须在UI thread中创建
2) execute方法必须在UI thread中调用
3) 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法
4) 该task只能被执行一次,否则多次调用时将会出现异常
doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground 接受的参数,第二个为显示进度的参数,第三个为doInBackground返回和onPostExecute传入的参数。


         现在,我们来思考一些问题,Android异步线程AsyncTask我们学得差不多了,想想,AsyncTask+Okhttp(当然,AsyncTask还可以和其他网络技术合作,比如AsyncTask+HttpURLConnection等等,这里强烈推荐使用AsyncTask+OkHttp)实现异步下载图片。首先要考虑AsyncTask的三个泛型参数是什么数据类型?AsyncTask单独使用的时候需要传入url,Okhttp单独使用也是需要传入url,那么它俩合作,url作为参数传给谁去完成任务呢?还有具体的实现细节,它俩之间的数据通过什么方式方法来传递?先来个简单的例子:

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="AsyncTask+OkHttp网络下载图片"
        android:id="@+id/button" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/imageView" />

</LinearLayout>
Java代码:
public class MainActivity extends AppCompatActivity {
    private String url="http://photocdn.sohu.com/20110927/Img320705637.jpg";
    private Button mButton;
    private ImageView mImageView;
    private Bitmap bitmap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mImageView=(ImageView) findViewById(R.id.imageView);
        mButton=(Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //UI Thread当中实例化AsyncTask对象,并调用execute方法
                new MyAsyncTask().execute(url);
            }
        });
    }
    public class MyAsyncTask extends AsyncTask<String,Void,Bitmap>{
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            try {
                OkHttpClient client=new OkHttpClient();
                Request request=new Request.Builder()
                        .url(url).build();
                Response response=client.newCall(request).execute();
                InputStream is=response.body().byteStream();
                bitmap= BitmapFactory.decodeStream(is);
                is.close();
            }catch (Exception e){
                e.printStackTrace();
            }
            return bitmap;
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            mImageView.setImageBitmap(bitmap);
        }
    }
}
效果图:

差点忘了说:记得申请权限

<!-- 授权手机能够访问网络 -->
<uses-permission android:name="android.permission.INTERNET" />

还有使用OkHttp需要下载它的两个依赖包:在build.gradle(Module:app) 中的dependencies添加{

compile 'com.squareup.okhttp3:okhttp:3.4.1'

}


栗子中我们只用到了两个方法,doInBackground和onPostExecute,还有两个方法没有用到,还得继续优化栗子,增加一个下载提示框提示用户图片正在下载中。布局不变,下面贴出Java代码:

public class MainActivity extends AppCompatActivity {
    private String url="http://photocdn.sohu.com/20110927/Img320705637.jpg";
    private Button mButton;
    private ImageView mImageView;
    private Bitmap bitmap;
    private ProgressDialog progressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mImageView=(ImageView) findViewById(R.id.imageView);
        //弹出要给ProgressDialog
        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setTitle("提示信息");
        progressDialog.setMessage("正在下载中,请稍后......");
        //设置setCancelable(false); 表示我们不能取消这个弹出框,等下载完成之后再让弹出框消失
        progressDialog.setCancelable(false);
        //设置ProgressDialog样式为圆圈的形式
        progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);

        mButton=(Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //UI Thread当中实例化AsyncTask对象,并调用execute方法
                new MyAsyncTask().execute(url);
            }
        });
    }
    public class MyAsyncTask extends AsyncTask<String,Void,Bitmap>{
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //onPreExecute()中我们让ProgressDialog显示出来
            progressDialog.show();
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            try {
                OkHttpClient client=new OkHttpClient();
                Request request=new Request.Builder()
                        .url(url).build();
                Response response=client.newCall(request).execute();
                InputStream is=response.body().byteStream();
                bitmap= BitmapFactory.decodeStream(is);
                is.close();
            }catch (Exception e){
                e.printStackTrace();
            }
            return bitmap;
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            //更新我们的ImageView控件
            mImageView.setImageBitmap(bitmap);
            //使ProgressDialog框消失
            progressDialog.dismiss();
        }
    }
}
效果图:

再继续优化栗子:这次我们用到更新UI的功能,一样的布局没变,下面贴Java代码:

public class MainActivity extends AppCompatActivity {
    private String url="http://photocdn.sohu.com/20110927/Img320705637.jpg";
    private Button mButton;
    private ImageView mImageView;
    private Bitmap bitmap;
    private ProgressDialog progressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mImageView=(ImageView) findViewById(R.id.imageView);
        //弹出要给ProgressDialog
        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setTitle("提示信息");
        progressDialog.setMessage("正在下载中,请稍后......");
        //设置setCancelable(false);表示我们不能取消这个弹出框,
        // 等下载完成之后再让弹出框消失
        progressDialog.setCancelable(false);
//        //设置ProgressDialog样式为圆圈的形式
//        progressDialog.setProgressStyle(ProgressDialog
//                .STYLE_SPINNER);
        //设置ProgressDialog样式为水平的样式
        progressDialog.setProgressStyle(ProgressDialog
                .STYLE_HORIZONTAL);
        
        mButton=(Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //UI Thread当中实例化AsyncTask对象,并调用execute方法
                new MyAsyncTask().execute(url);
            }
        });
    }/**
      * 定义一个类,让其继承AsyncTask这个类
 * Params: String类型,表示传递给异步任务的参数类型是String,通常指定的是URL路径
 * Progress: Integer类型,进度条的单位通常都是Integer类型
 * ResultBitmap类型,表示我们下载好的图片以Bitmap返回
 * @author xiaoluo
 *
 */
    public class MyAsyncTask extends AsyncTask<String,Integer
            ,Bitmap>{
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //onPreExecute()中我们让ProgressDialog显示出来
            progressDialog.show();
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            try {
                OkHttpClient client=new OkHttpClient();
                Request request=new Request.Builder()
                        .url(url).build();
                Response response=client.newCall(request).execute();
                InputStream is=response.body().byteStream();
                bitmap= BitmapFactory.decodeStream(is);
                //每次读取后累加的长度
                  long file_length = 0;
                int length = 0;
                //每次读取1024个字节
                  byte[] data = new byte[1024];
                while (-1!=(length=is.read(data))){
                    //每读一次,就将file_length累加起来
                    file_length += length;
                    //得到当前图片下载的进度
                    int mData=((int) (file_length/is.read())*100);
                    publishProgress(mData);
                }
                is.close();
            }catch (Exception e){
                e.printStackTrace();
            }
            return bitmap;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            //更新ProgressDialog的进度条
            progressDialog.setProgress(values[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            //更新我们的ImageView控件
            mImageView.setImageBitmap(bitmap);
            //使ProgressDialog框消失
            progressDialog.dismiss();
        }
    }
}
效果图:

在写这个例子的时候,还是学到了很多基础的知识,说实话,有时候你不动手打代码,你都不知道自己有些知识点依然懵懂。例子涉及的知识点,本人也去好好巩固,下面分享一些知识:

/**
以字节为单位读取文件,常用于读二进制文件,如图片、声音、影像等文件。
 * 所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件
 * 的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件
 *(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列
*/
/**  
BitmapByte  
 */
public static byte[] Bitmap2Bytes(Bitmap bm){    
        ByteArrayOutputStream baos = new ByteArrayOutputStream();    
        bm.compress(Bitmap.CompressFormat.PNG, 100, baos);    
        return baos.toByteArray();    
 }  
 
 
//android将图片内容解析成字节数组,将字节数组转换为
// ImageView可调用的Bitmap对象,
//图片缩放,把字节数组保存为一个文件,BitmapByte 
/**  
 * @param 图片缩放  
 * @param bitmap 对象  
 * @param w 要缩放的宽度  
 * @param h 要缩放的高度  
 * @return newBmp  Bitmap对象  
*/
public static Bitmap zoomBitmap(Bitmap bitmap, int w, int h){    
        int width = bitmap.getWidth();    
        int height = bitmap.getHeight();    
        Matrix matrix = new Matrix();    
        float scaleWidth = ((float) w / width);    
        float scaleHeight = ((float) h / height);    
        matrix.postScale(scaleWidth, scaleHeight);    
        Bitmap newBmp = Bitmap.createBitmap(bitmap, 0, 0, width, height,    
                 matrix, true);    
             return newBmp;    
} 
 
/**
 * 将字节数组转换为ImageView可调用的Bitmap对象  
 */
public static Bitmap getPicFromBytes(byte[] bytes,BitmapFactory.Options opts) {    
      if (bytes != null)    
          if (opts != null)    
               return BitmapFactory.decodeByteArray(bytes, 0, bytes.length,opts);    
          else    
              return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);    
      return null;    
 }  
/**  
 * @param 将图片内容解析成字节数组  
 * @param inStream  
 * @return byte[]  
 * @throws Exception  
 */
public static byte[] readStream(InputStream inStream) throws Exception {    
            byte[] buffer = new byte[1024];    
            int len = -1;    
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();    
                while ((len = inStream.read(buffer)) != -1) {    
                    outStream.write(buffer, 0, len);
                }    
                byte[] data = outStream.toByteArray();
                outStream.close();
                inStream.close();
                return data;
从资源中获取Bitmap
Java代码  
Resources res=getResources();  
Bitmap bmp=BitmapFactory.decodeResource(res, R.drawable.pic);  
 
Bitmap → byte[]
Java代码  
private byte[] Bitmap2Bytes(Bitmap bm){  
    ByteArrayOutputStream baos = new ByteArrayOutputStream();    
    bm.compress(Bitmap.CompressFormat.PNG, 100, baos);    
    return baos.toByteArray();  
   }  
 
byte[] → Bitmap
Java代码  
private Bitmap Bytes2Bimap(byte[] b){  
            if(b.length!=0){  
                return BitmapFactory.decodeByteArray(b, 0, b.length);  
            }  
            else {  
                return null;  
            }  
      }  
*/

希望这些知识点对大家有帮助!

如果大家对OkHttp还陌生的话,请看另一篇博客:Android网络技术之OkHttp框架

猜你喜欢

转载自blog.csdn.net/l_201607/article/details/70207235
今日推荐