Android进阶:网络与数据存储—步骤1:Android网络与通信(第3小节:AsyncTask)

AsyncTask(异步任务)

同步任务:

我现在吃饭——>吃完饭刷碗——>刷完碗去看电视

一步一步的在同步进行进行

异步任务:

我一边看电视、一边吃饭(互不干扰)(多个任务同时进行——多线程)

内容概括:

1、多线程相关

2、AsyncTask

  • 详解
  • 实践
  • 应用

二、异步任务简介

2-1多线程那些事

1、何为线程、多线程?

  • 一个线程,相当于一个车间的一条流水线,一个流水线生产手机屏幕,另外一个流水线生产手机壳
  • 多线程:就像有n条流水线在我们车间里跑,程序自发的行为

2、为什么要使用多线程?

  • 像上面那个例子,车间里使用多条流水线,就能够产生更大的效能
  • 流水线和流水线之间是没有关系的,他们之间互不干扰,提高效率、提高产能

3、ANR(Appliacation Not  Responding)(应用程序没有反应)

为什么会产生ANR?

而造成这种现象的主要原因有:

Activity响应时间超过5s

Broadcast在处理时间超过10s

Service处理时间超过20s

这大部分的原因是主线程进行过于耗时的操作,因为Activity,Broadcast,Serivce本身都是通过主线程进行承载的。

 

android中定义了一个UI线程(有一个车间),这个车间主要和我们的控件界面相关的

当这个UI线程在执行耗时的过程(卡住了),比如下载一个音乐?,要两分钟下完

10秒钟没有下完ANR就当做你是卡住了

为什么?下载的过程中,我什么事情都不能干,点这个按钮也没有反应,说明界面卡死,这是就ANR了

怎么解决:

再开一个线程(流水线)专门下载,UI线程该干嘛就干嘛。那么这个界面就不会卡住了

4、什么时候使用多线程?

只要你不想ANR,进行耗时的操作的时候就要使用多线程

5、Android当中的多线程

和Windows他们差不多,android中有主线程无限循环来处理UI事件,可以用其他线程来处理其他的事情

处理完后,来主界面展示,那么我们的UI线程(主线程)界面就不会被卡死

6、Main/UI Thread 和Worker Thread

主线程(UI线程)和工作线程

7、Main/UI Thread 和Worker Thread之间的通信

Handler来处理通信

8、Thread/Runnable的线程安全

线程不安全:当一个数据可能同时被N个线程修改,使它的值被修改了

线程安全:这个数据被N个线程修改它依然是正确的值

  • A流水线使用先给它加一把锁,只有A流水线操作完了,锁才打开
  • 交给下一个流水线操作(加锁)

8、普通的主线程更新

UI更新只能在主线程,耗时操作要在子线程,子线程要想操作UI,需要借助下面方法,就该更新UI操作

放到主线程进行

1、Actiivty.runOnUiThread(Runnable)
2、View.post(Runnable)
3、View.postDelayed(Runnable,long)

9、Handler和AsyncTask

Handler更新UI的操作:

子线程进行耗时操作,将更新UI操作发送给主线程处理....主线程接接收到这个消息再更新。。好麻烦

所以官方给准备这样的一个工具

AsyncTask:

  • 目的:方便后台线程中操作后更新UI
  • 实现:Thread和Handler进行封装
  • 实质:Handler异步消息处理机制

揭开AsyncTask的面纱

  • onPreExecute();         操作前的准备工作(主线程)
  • doInBackground();     耗时操作(后台任务子线程)
  • onProgressUpdate();  更新进度(主线程)
  • onPostExcute();          执行完后台任务后要更新UI显示结果

2-5AsyncTask的常用方法详解

    //自定义一个AsyncTask
    //AsyncTask<Params(入参), Progress(进度), Result(出参)>
    public class DownloadAsyncTask extends AsyncTask<String,Integer,Boolean>{


}
  • 在MainActivity主线程中新建这个类的对象执行异步任务
  //execute里面的参数传到doInBackground()中的参数
        new DownloadAsyncTask().execute("imooc","good");//执行异步任务

在DownloadAsyncTask类中重写它的方法(Command+N):

  • onPreExecute();
  • 在执行异步任务之前,在主线程中
  /**
         * 在执行异步任务之前,在主线程中
         */
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //可操作UI

        }
  • doInBackground(String... strings);
  • 在另外一个线程中处理事件(异步任务子线程开始) 
 /**
         * command+N
         * 重写方法1:在另外一个线程中处理事件(异步任务子线程开始)
         *
         * @param strings 入参
         * @return 结果
         */
        @Override
        protected Boolean doInBackground(String... strings) {
            // /String..代表入参个数可变
            //doInBackground("a","b","c"...)传多少个都可以
            //执行1000遍
            for (int i = 0; i < 1000; i++) {
                Log.i(TAG, "doInBackground:" + strings[0]);
                //抛出进度,在onProgressUpdate()方法中接受
                publishProgress();
            }
            return true;
        }
  • onPostExecute();
  • 异步任务执行完之后在主线程 
  /**
         *当异步任务执行完之后在主线程
         * @param aBoolean 这个参数是doInBackground返回的参数
         */
        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            //也是在主线程中,可执行结果、处理
        }
  • onProgressUpdate(Integer... values)
  • 当我们的进度在变化的时候 (主线程中)
/**
         * 当我们的进度在变化的时候,
         * @param values DownloadAsyncTask<String,Integer,Boolean>
         *                  下面参数Integer(进度条),就是那个泛型里面的
         */
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            //接受进度,然后处理:也是在UI线程中

        }
  • onCancelled(Boolean aBoolean)
  • 取消的方法只能取消onPostExecute()异步执行之后,不再处理
    /**取消的方法只能取消onPostExecute()异步执行之后,不再处理
         *AsyncTask<Params(入参), Progress(进度), Result(出参)>
         * @param aBoolean 参数是 Result(出参)
         */
        @Override
        protected void onCancelled(Boolean aBoolean) {
            super.onCancelled(aBoolean);
        }

 3-1AsyncTask实现下载文件之前的准备

/**
 * 1、网络请求数据:申请网络权限 ,读写存储权限
 * 2、设置布局layout
 * 3、下载之前我我们要做什么?UI
 * 4、下载中我们要做什么? 数据
 * 5、下载后我们要做什么?UI
 */

第一步:申请网络权限 ,读写存储权限

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

android6.0(包括本身)之后要动态申请权限:

   //android6.0之后要动态获取权限
        private void checkPermission(Activity activity) {
            // Storage Permissions
            final int REQUEST_EXTERNAL_STORAGE = 1;
            String[] PERMISSIONS_STORAGE = {
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE};

            try {
                //检测是否有写的权限
                int permission = ActivityCompat.checkSelfPermission(MainActivity.this,
                        "android.permission.WRITE_EXTERNAL_STORAGE");
                if (permission != PackageManager.PERMISSION_GRANTED) {
                    // 没有写的权限,去申请写的权限,会弹出对话框
                    ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }


        }

第二步:设置布局layout

        checkPermission(MainActivity.this);//动态获取权限     
       //初始化视图
        initView();
        //设置点击监听
        setListener();
        //初始化UI数据
        setData();
    /**
     * 1初始化视图
     */
    private void initView() {
        mProgressBar=findViewById(R.id.progressBar);
        mResultTextView=findViewById(R.id.text_view);
        mDownlodaButton=findViewById(R.id.button);
    }

    /**
     * 2设置监听
     */

    private void setListener() {
        mDownlodaButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /做下载的事情
                //TODO 下载任务
                DownloadAsyncTask downloadAsyncTask = new DownloadAsyncTask();
                //execute(Params... params)
                downloadAsyncTask.execute(APK_URL);//执行异步操作(传入URL)
            }
        });
    }

    /**
     * 3设置数据
     */
    private void setData() {
        mProgressBar.setProgress(Init_Progress);
        mDownlodaButton.setText(R.string.click_download);
        mResultTextView.setText(R.string.ready_down);
    }

第三步:下载之前我我们要做什么?UI

(把这三步看做煮饭的过程:第一步:洗米)

 /**
         * 在异步任务之前,在主线程中
         */
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //可操作UI  (煮饭之前的准备:淘米)
            mDownlodaButton.setText(R.string.downloading);
            mResultTextView.setText(R.string.downloading);
            mProgressBar.setProgress(Init_Progress);
        }

第四步:下载中 子线程(煮饭)

  /**
         * 在另外一个线程中处理事件(异步任务子线程开始)
         *
         * @param strings 入参   煮饭的过程
         * @return 结果
         */
        @Override
        protected Boolean doInBackground(String... strings) {
            // /String..代表入参个数可变
            //doInBackground("a","b","c"...)传多少个都可以
            if (strings != null && strings.length > 0) {
                String apkUrl = strings[0];//拿到url
                try {
                    //构造URL
                    URL url = new URL(apkUrl);
                    //构造连接,并打开
                    URLConnection urlConnection = url.openConnection();
                    //把这个链接,连接的输入拿到(输入流读取数据)(输入管道读数据)
                    InputStream inputStream = urlConnection.getInputStream();
                    //获取下载内容的总长度
                    int contentLength = urlConnection.getContentLength();

                    //拼接下载地址(sd的路径)File.separator:是斜杆/
                    mfilePath = Environment.getExternalStorageDirectory() +
                            File.separator + FILE_NAME;
                    //对下载地址进行处理
                    File apkFile = new File(mfilePath);
                    if (apkFile.exists()) {//如果存在在删除原来的
                        boolean result = apkFile.delete();
                        if (!result) {//如果删除失败
                            return false;
                        }
                    }
                    //已下载的大小
                    int DownloadSize = 0;
                    //用来缓冲:我们要去挖土,需要车来装土运走,byte数组相对于这个车
                    byte[] bytes = new byte[1024];

                    int length;

                    //创建一个(输出管道)把写入到文件里
                    OutputStream outputStream = new FileOutputStream(mfilePath);
                    //每次挖完的土给这个输入管道读取,返回是一个长度
                    length = inputStream.read(bytes);//如果length为-1表示我们挖完了
                    //不断的一车一车的挖土。走到挖不动为止
                    while ((length = inputStream.read(bytes)) != -1) {
                        //把我们挖到的土写入到文件管道里
                        //从0写道length这个长度
                        outputStream.write(bytes, 0, length);
                        DownloadSize += length;//记录当前下载度
                        //抛出当前进度条
                        publishProgress(DownloadSize * 100 / contentLength);
                    }
                    inputStream.close();//关闭流
                    outputStream.close();//


                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
                return false;
            }
            return true;
        }

第五步:饭煮好了更新UI

      /**
         * 当异步任务执行完之后在主线程(煮饭煮好了)
         *
         * @param aBoolean 这个参数是doInBackground返回的参数(true表示下载成功)
         */
        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            //也是在主线程中,可执行结果、处理
            mDownlodaButton.setText(aBoolean ? getString(R.string.download_finish) : getString(R.string.download_fail));
            mResultTextView.setText(aBoolean ? getString(R.string.download_finish) + mfilePath : getString(R.string.download_fail));

        }

进度条更新

 /**
         * 当我们的进度在变化的时候,
         *
         * @param values DownloadAsyncTask<String,Integer,Boolean>
         *               下面参数Integer(进度条),就是那个泛型里面的
         */
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            //接受进度,然后处理:也是在UI线程中
            if (values != null && values.length > 0)
                mProgressBar.setProgress(values[0]);
            mResultTextView.setText(getString(R.string.downLoading)+":"+values[0]+"%");

        }

 

3-4自定义下载封装实现

上面一步一步写这么复杂?是不是每次实现下载操作都有写这么多呢?

将下载操作进行封装

你只需要传入一个URL和文件路径,就可以下载了,其他你什么都不用管!

DownLoadHelper.java 封装AsyncTask

import android.os.AsyncTask;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

/**
 * 自定义下载封装
 * 1、download的方法 url localPath listener
 * 2、Listener :start,success,fail,progress
 * 3、用AsyncTask封装的
 */
public class DownLoadHelper {
    public static DownloadAsyncTask task;

    public static void download(String localPath, String url, OnDownLoadListener listener) {
        task = new DownloadAsyncTask(localPath, url, listener);
    }

    public static void status(Boolean b) {
        if (b) {
            task.execute();//执行,不用传入参数了,上面已经通过构造方法传入了
        } else {
            task.cancel(true);
            task=null;//让它为空

        }


    }

    public static class DownloadAsyncTask extends AsyncTask<String, Integer, Boolean> {
        String mfilePath;
        String mUrl;
        OnDownLoadListener mListener;

        //初始化构造函数
        public DownloadAsyncTask(String mfilePath, String mUrl, OnDownLoadListener mListener) {
            this.mfilePath = mfilePath;
            this.mUrl = mUrl;
            this.mListener = mListener;
        }

        /**
         * 在异步任务之前,在主线程中
         */
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //可操作UI  (煮饭之前的准备:淘米)
            if (mListener != null) {
                mListener.onStart();//回调出去
            }
        }

        /**
         * 在另外一个线程中处理事件(异步任务子线程开始)
         *
         * @param strings 入参   煮饭的过程
         * @return 结果
         */
        @Override
        protected Boolean doInBackground(String... strings) {
            // /String..代表入参个数可变
            //doInBackground("a","b","c"...)传多少个都可以
            String apkUrl = mUrl;//拿到url
            try {
                //构造URL
                URL url = new URL(apkUrl);
                //构造连接,并打开
                URLConnection urlConnection = url.openConnection();
                //把这个链接,连接的输入拿到(输入流读取数据)(输入管道读数据)
                InputStream inputStream = urlConnection.getInputStream();
                //获取下载内容的总长度
                int contentLength = urlConnection.getContentLength();

                //对下载地址进行处理
                File apkFile = new File(mfilePath);
                if (apkFile.exists()) {//如果存在在删除原来的
                    boolean result = apkFile.delete();
                    if (!result) {//如果删除失败
                        if (mListener != null) {
                            mListener.onFail(-1, apkFile, "文件删除失败");
                        }
                        return false;
                    }
                }
                //已下载的大小
                int DownloadSize = 0;
                //用来缓冲:我们要去挖土,需要车来装土运走,byte数组相对于这个车
                byte[] bytes = new byte[1024];
                int length;

                //创建一个(输出管道)把写入到文件里
                OutputStream outputStream = new FileOutputStream(mfilePath);
                //每次挖完的土给这个输入管道读取,返回是一个长度
                length = inputStream.read(bytes);//如果length为-1表示我们挖完了
                //不断的一车一车的挖土。走到挖不动为止
                while ((length = inputStream.read(bytes)) != -1) {
                    //把我们挖到的土写入到文件管道里
                    //从0写道length这个长度
                    outputStream.write(bytes, 0, length);
                    DownloadSize += length;//记录当前下载度
                    //抛出当前进度条
                    publishProgress(DownloadSize * 100 / contentLength);
                }
                inputStream.close();//关闭流
                outputStream.close();//


            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (InterruptedIOException e) {
                e.printStackTrace();
                System.out.println("我被取消了");
            } catch (IOException e) {
                e.printStackTrace();
                if (mListener != null) {
                    mListener.onFail(-2, new File(mfilePath), e.getMessage());
                }
            }

            //下载成功提示
            return true;
        }

        /**
         * 当异步任务执行完之后在主线程(煮饭煮好了)
         *
         * @param aBoolean 这个参数是doInBackground返回的参数(true表示下载成功)
         */
        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            //也是在主线程中,可执行结果、处理
            if (mListener != null && aBoolean == true)
                mListener.onSuccess(0, new File(mfilePath));
        }

        /**
         * 当我们的进度在变化的时候,
         *
         * @param values DownloadAsyncTask<String,Integer,Boolean>
         *               下面参数Integer(进度条),就是那个泛型里面的
         */
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            //接受进度,然后处理:也是在UI线程中
            if (values != null && values.length > 0) {
                if (mListener != null)
                    mListener.onProgress(values[0]);
                System.out.println(isCancelled());

            } else {
                System.out.println("cancel1");
            }
        }

        /**
         * 取消的方法只能取消onPostExecute()异步执行之后,不再处理
         * AsyncTask<Params(入参), Progress(进度), Result(出参)>
         *
         * @param aBoolean 参数是 Result(出参)
         */
        @Override
        protected void onCancelled(Boolean aBoolean) {
            super.onCancelled(aBoolean);
        }
    }


    //建立接口
    public interface OnDownLoadListener {
        void onStart();

        void onSuccess(int code, File file);

        void onFail(int code, File file, String message);

        void onProgress(int progress);

        //简化版
        abstract class SimpleDownloadListener implements OnDownLoadListener {
            @Override
            public void onStart() {

            }

            @Override
            public void onProgress(int progress) {

            }

        }
    }
}

实现:

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

import java.io.File;

/**
 * 1、网络请求数据:申请网络权限 ,读写存储权限
 * 2、设置布局layout
 * 3、下载之前我我们要做什么?UI
 * 4、下载中我们要做什么? 数据
 * 5、下载后我们要做什么?UI
 */

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "MainActivity";
    public static final String APK_URL = "http://download.sj.qq.com/upload/connAssitantDownload/upload/MobileAssistant_1.apk";
    public static  final String LOCAL_PATH=Environment.getExternalStorageDirectory()+File.separator+"imooc.apk";
    private ProgressBar mProgressBar;
    private TextView mResultTextView;
    private Button mDownlodaButton;
    private int Init_Progress = 0;
    private Button mCancelButton;
    DownLoadHelper.OnDownLoadListener downLoadListener;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        checkPermission(MainActivity.this);//动态获取权限
        //初始化视图
        initView();
        //初始化UI数据
        setData();
    }


    /**
     * 1初始化视图
     */
    private void initView() {
        mProgressBar = findViewById(R.id.progressBar);
        mResultTextView = findViewById(R.id.text_view);
        mDownlodaButton = findViewById(R.id.button);
        mCancelButton = findViewById(R.id.cancel_button);
        mCancelButton.setOnClickListener(this);//取消按钮设置监听
        mDownlodaButton.setOnClickListener(this);//设置监听
        downLoadListener=new DownLoadHelper.OnDownLoadListener.SimpleDownloadListener() {
            @Override
            public void onStart(){
                //可操作UI  (煮饭之前的准备:淘米)
                mDownlodaButton.setText(R.string.downloading);
                mResultTextView.setText(R.string.downloading);
                mProgressBar.setProgress(Init_Progress);
            }

            @Override
            public void onSuccess(int code, File file) {
                //下载成功显示路径
                mResultTextView.setText(file.getPath()+"");
                mDownlodaButton.setText(getString(R.string.download_finish));
                mDownlodaButton.setEnabled(false);//下载完后不可点击
            }

            @Override
            public void onFail(int code, File file, String message) {
                //下载失败显示失败消息
                mResultTextView.setText(message);

            }
            @Override
            public void onProgress(int progress){
                //接受进度,然后处理:也是在UI线程中
                mProgressBar.setProgress(progress);
                mResultTextView.setText(getString(R.string.downLoading)+":"+progress+"%");
            }
        };

    }

    /**
     * 2设置监听
     */

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button:
                //做下载的事情
                //TODO 下载任务
                DownLoadHelper.download(LOCAL_PATH, APK_URL,downLoadListener);
                DownLoadHelper.status(true);
                break;
            case R.id.cancel_button:
                //取消按钮
                DownLoadHelper.status(false);
                break;
        }
    }

    /**
     * 3设置数据
     */
    private void setData() {
        mProgressBar.setProgress(Init_Progress);
        mDownlodaButton.setText(getString(R.string.click_download));
        mResultTextView.setText(getString(R.string.ready_down));
    }

    /**
     * 在另外一个线程
     * Params(入参)
     * Progress(进度)
     * Result(出参,返回值)
     */
    //android6.0之后要动态获取权限
    private void checkPermission(Activity activity) {
        // Storage Permissions
        final int REQUEST_EXTERNAL_STORAGE = 1;
        String[] PERMISSIONS_STORAGE = {
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE};

        try {
            //检测是否有写的权限
            int permission = ActivityCompat.checkSelfPermission(MainActivity.this,
                    "android.permission.WRITE_EXTERNAL_STORAGE");
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 没有写的权限,去申请写的权限,会弹出对话框
                ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}

3-5、那些年AsyncTask的恩恩怨怨

  • 1、1.6之前——>AsyncTask是串行执行任务的
  • 2、1.6-2.3 ——>线程池异步任务并行运行
  • 3、3.0之后——>异步任务被顺序执行

AsyncTask使用注意事项:

  • 1、主线程中执行excute();
  • 2、Task的实例必须值Ui Thread中创建
  • 3、注意防止内存泄漏(使用弱引用解决)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值