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、注意防止内存泄漏(使用弱引用解决)