Android入门项目(四)AsyncTask异步加载图片和模拟进度条

一,最终效果

在这里插入图片描述

二,关于AsyncTask

1,异步消息处理机制
咱们先来了解一下Handler相关概念,这里先上图(还在绘制中。。。)

Handler负责发消息,Looper是一个消息队列,主要负责接收Handler发送的消息,并处理之后回传给Handler。而MessageQueue是一个存储消息的容器,Looper会不断的从MessageQueue中取出消息。

联想:我们可以把Handler看成是小明同学,Looper是老师。小明同学向老师请假,得向老师发消息,老师收到消息之后回复小明。当然实际中不仅仅只有请假一个消息,可能小明还要上厕所,还有想和女神坐同桌等等。那么我们需要一个容器(MessageQueue)把消息装在里面,这就是消息队列,装好之后肯定要从容器取消息啊。老师就是取消息的,他能保证上一个消息成功回复给小明同学之后再取下一个消息。当然这里发消息和处理消息都是小明同学,比如小明同学想和女神坐同桌,如果老师同意了,他就可以很开心的搬过去了,如果老师不同意,他可能会有点伤心。

2,为什么需要Asynctask?
AsyncTask为我们提供了Handler和多线程的封装,让我们不需要懂异步也能很轻易的更新UI。底层大家可以去看源码,这里我们知道它的原理就行了。
AsyncTask的作用:
1,执行耗时任务,子线程中更新UI;
2,实现工作线程和UI线程之间的通信,简化了异步消息处理机制

3,Asynctask参数和常用方法介绍
三个参数AsyncTask<Params, Progress, Result>
Params:启动任务时输入参数;
Progress:后台任务执行中返回进度值;
Result:后台执行任务完成后返回的结果。
doInBackground(Params…params):执行后台具体的逻辑,执行耗时操作
onProgressUpdate(Progress values):在主线程中更新当前下载进度
onPostExecute(Result result):通知最终的下载结果,回调操作
execute(Parames…params)::触发执行异步线程任务

三,异步加载网络图片的核心代码

布局文件,一个ImageView和ProgressBar,ProgressBar控件是实现加载的效果

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="300dp" />
    
    <ProgressBar
        android:id="@+id/progress_bar"
        android:visibility="gone"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

主活动

package com.yaninfo;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.URL;
import java.io.InputStream;
import java.net.URLConnection;

/**
 * @Author: zhangyan
 * @Date: 2019/4/4 11:04
 * @Description: 图片异步加载
 * @Version: 1.0
 */
public class ImageActivity extends Activity {

    private ImageView mImageView;
    private ProgressBar mProgressBar;
    private static String URL = "http://img.mp.itc.cn/upload/20170221/579c7d2769fd4ee2b6d4c460cd1c4b9c_th.jpg";
    private MyTask mTask = new MyTask();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_layout);

        // 初始化
        init();
        // 执行线程
        mTask.execute(URL);

    }

    /**
     * 初始化控件
     */
    private void init() {
        mImageView = findViewById(R.id.image);
        mProgressBar = findViewById(R.id.progress_bar);
    }

    /**
     * 开启异步线程
     */
    private class MyTask extends AsyncTask<String, Void, Bitmap> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            mProgressBar.setVisibility(View.VISIBLE);
        }

        /**
         * 更新UI
         *
         * @param bitmap
         */
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            mImageView.setImageBitmap(bitmap);
            mProgressBar.setVisibility(View.GONE);

        }

        /**
         * 执行耗时操作
         *
         * @param params
         * @return
         */
        @Override
        protected Bitmap doInBackground(String... params) {
            // 从params可变长数组中获取传递进来的url参数
            String url = params[0];
            Bitmap bitmap = null;
            URLConnection connection;
            InputStream in = null;
            BufferedInputStream buffer = null;

            try {
                connection = new URL(url).openConnection();
                in = connection.getInputStream();
                buffer = new BufferedInputStream(in);
                // 输入流转化为bitmap对象,利用decodeStream方法来解析输入流
                Thread.sleep(2000);
                bitmap = BitmapFactory.decodeStream(buffer);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                try {
                    in.close();
                    buffer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // 返回bitmap
            return bitmap;
        }
    }
}

这里我们在doInBackground方法中定义了一个Bitmap 对象来存图片,最后Bitmap对象会传给onPostExecute方法用来更新UI。这里怎么传参? 想深入了解的小老弟,可以看一下源码很简单的!

四,模拟进度条的核心代码

布局文件,这里定义了两个Button,一个TextView控件和ProgressBar。采用的是RelativeLayout 布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    tools:context=".MainActivity">

    <Button
        android:layout_centerInParent="true"
        android:id="@+id/loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="点我加载"/>

    <TextView
        android:id="@+id/text"
        android:layout_below="@+id/loading"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="还没开始加载!" />

    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_below="@+id/text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:progress="0"
        android:max="100"
        style="?android:attr/progressBarStyleHorizontal"/>

    <Button
        android:layout_below="@+id/progress_bar"
        android:layout_centerInParent="true"
        android:id="@+id/cancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="取消加载"/>
</RelativeLayout>

线程类

package com.yaninfo;

import android.os.AsyncTask;
import android.widget.ProgressBar;
import android.widget.TextView;

/**
 * @Author: zhangyan
 * @Date: 2019/4/4 9:58
 * @Description: AsyncTask线程任务进度条加载
 * @Version: 1.0
 */
public class MyTask extends AsyncTask<String, Integer, String> {


    private TextView text;
    private ProgressBar progressBar;

    /**
     * 构造方法
     *
     * @param text
     * @param progressBar
     */
    public MyTask(TextView text, ProgressBar progressBar) {

        this.text = text;
        this.progressBar = progressBar;
    }

    /**
     * 执行线程任务前的操作
     */
    @Override
    protected void onPreExecute() {

        text.setText("加载中");
    }

    /**
     * 接收输入参数、执行任务中的耗时操作、返回线程任务执行的结果
     *
     * @param params
     * @return
     */
    @Override
    protected String doInBackground(String... params) {

        try {
            for (int count = 0; count < 100; count++) {
            
                // 改变count,之后传给onProgressUpdate中的progresses用于更新进度条
                publishProgress(count);
                // 休眠100毫秒
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 在主线程 显示线程任务执行的进度
     *
     * @param progresses
     */
    @Override
    protected void onProgressUpdate(Integer... progresses) {

        int progress = progresses[0];
        progressBar.setProgress(progress);
        text.setText("loading..." + progress + "%");

    }

    /**
     * 接收线程任务执行结果、将执行结果显示到UI组件
     *
     * @param result
     */
    @Override
    protected void onPostExecute(String result) {

        text.setText("加载完毕");
    }

    /**
     * 将异步任务设置为:取消状态
     */
    @Override
    protected void onCancelled() {

        text.setText("已取消");
        progressBar.setProgress(0);

    }
}

这里在doInBackground方法中用for循环模拟了更新进度条的逻辑,然后在执行相关UI操作,很简单有没有!!
主活动

主活动

package com.yaninfo;

import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

/**
 * @Author: zhangyan
 * @Date: 2019/4/4 11:04
 * @Description: 进度条更新活动
 * @Version: 1.0
 */
public class ProgressBarActivity extends AppCompatActivity {

    private MyTask mTask;

    private Button loading;
    private Button cancel;
    private TextView text;
    private android.widget.ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.progressbar_layout);

        init();

        // 实例化AsyncTask对象
        mTask = new MyTask(text, progressBar);
    }

    /**
     * 初始化控件
     */
    private void init() {
        loading = findViewById(R.id.loading);
        cancel = findViewById(R.id.cancel);
        text = findViewById(R.id.text);
        progressBar = findViewById(R.id.progress_bar);

        loading.setOnClickListener(new MyListener());
        cancel.setOnClickListener(new MyListener());
    }

    private class MyListener implements View.OnClickListener {

        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.loading:
                    mTask.execute();
                    Log.e("loading", "############");
                    break;
                case R.id.cancel:
                    mTask.cancel(true);
                    Log.e("cancel", "############");
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * 设置取消AsyncTask线程
     */
    @Override
        protected void onPause() {
            super.onPause();
            if (mTask != null && mTask.getStatus() == AsyncTask.Status.RUNNING) {
                // cancel()只是将对应的AsyncTask标记为cancel状态为true
                mTask.cancel(true);
            }
        }
}

这里我们初始化控件之后,就实例化上面定义的MyTask,最后实现按钮监听。
调用execute()方法来启动线程,这里想当然是start()方法;

问题:
这里我们会有一个bug,我们先点击加载按钮之后,加载一部分就退出回到主页面。然后再次点击加载按钮,进度条会先继续加载之前的线程,然后再加载现在的。这里是因为AsyncTask内部封装了线程池的原因,我们可以这样处理来保证取消当前Activity的时候,AsyncTask线程也跟着终止:

1,ProgressBarActivity类的onPause()方法中添加

 if (mTask != null && mTask.getStatus() == AsyncTask.Status.RUNNING) {
                // cancel()只是将对应的AsyncTask标记为cancel状态为true
                mTask.cancel(true);
            }

这里的意思是活动取消时,将AsyncTask线程的状态也变成终止。
2,myTask类中doInBackground()方法中的循环内加判断

// 判断是否为cancel状态,如果是就终止
                if (isCancelled()) {
                    break;
                }

3,myTask类中onProgressUpdate()方法中的循环内加判断

        if (isCancelled()) {
            return;
        }

最后贴上MainActivity,java代码:

package com.yaninfo;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

/**
 * @Author: zhangyan
 * @Date: 2019/4/4 11:04
 * @Description: 主活动
 * @Version: 1.0
 */
public class MainActivity extends AppCompatActivity {

    private Button button1;
    private Button button2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();
    }

    private void init() {
        button1 = findViewById(R.id.button1);
        button2 = findViewById(R.id.button2);

        button1.setOnClickListener(new MyListener());
        button2.setOnClickListener(new MyListener());
    }

    /**
     * 按钮监听
     */
    private class MyListener implements View.OnClickListener {

        @Override
        public void onClick(View v) {

            switch (v.getId()) {
                case R.id.button1:
                    Intent intent1 = new Intent(MainActivity.this, ImageActivity.class);
                    startActivity(intent1);
                    break;
                case R.id.button2:
                    Intent intent2 = new Intent(MainActivity.this, ProgressBarActivity.class);
                    startActivity(intent2);
                    break;
                default:
                    break;
            }
        }
    }
}

需要加载网络图片,权限别忘了:

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

五,AsyncTask的总结

1. AsyncTask底层是线程池,一定要等到上一个线程执行完才执行当前的操作,这是线程池的原理。可以设置AsyncTask生命周期和Activity保持一致,以避免Activity已经取消之后,AsyncTask还在运行;
2. 只有doInBackground在主线程运行,所以doInBackground不能用来更新UI;
3. 线程池中的工作线程执行doInBackground(mParams)方法执行异步任务;
4. 当任务状态改变之后,工作线程会向UI线程发送消息,AsyncTask内部的InternalHandler响应这些消息,并调用相关的回调函数。

写在最后:原创不易,觉得对自己有帮助的小老弟,欢迎点赞评论~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章