什么是异步
首先我们要先知道什么是异步、什么是同步:
同步交互:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程,同步是阻塞模式。
异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待,异步是非阻塞模式。
为什么要异步任务
然后我们开始介绍为什么我们需要异步任务这个功能,也就是AsyncTask
因为Android是单线程模型,只有UI线程也就是主线程才能对UI进行操作,而其他workerThread(工作线程)是不能直接操作UI的,这样做的好处是保证了UI的稳定性和准确性,避免多线程对UI进行操作而造成混乱。
但是同时Android也是一个多线程的操作系统,我们不可能把所有的事情都放到主线程中去做,比如一些网络操作,和读取文件这些耗时的操作。如果你都放到主线程去执行,这就会造成后面任务的阻塞,Android系统会去检测这样一个阻塞,当阻塞时间太长的时候,系统就会抛出Application Not Responding的无反应提示框(ANR)
所以我们要将耗时操作放到非主线程去执行,这样既保证了android的单线程模型,也避免了程序的一个ANR。
提到异步任务,我们最直接想到的是用线程和线程池去实现,Android给我们提供了一个主线程与其他线程的通讯机制,同时Android也给我们提供了一个封装好了组件AsyncTask,利用她我们可以很方便的实行异步任务
AsyncTask为何而生
总结下来就是两条,第一、它可以在子线程中去更新UI。第二、它封装简化了异步操作。
上边我们提到,我们要是想异步任务,通常我们可以通过线程和线程池去实现。但这里就涉及到了一个线程同步、和一个线程的管理,同时当线程结束的时候,我们还需要用handler去通知主线程去更新UI。而AsyncTask将这一切都封装了起来,我们可以非常方便的在子线程中去更新UI。
总之,什么是AsyncTask?
AsyncTask就是一个封装好的线程池,比起Thread+Handler的方法,AsyncTask在操作UI线程上更方便,因为onPreExecute()、onPostExecute()及更新UI方法onProgressUpdate()均运行在主线程中,这样就不用Handler发消息处理了。
AsyncTask的基本结构介绍
一、构建AsyncTask子类的参数
要传入的三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用Java.lang.Void类型代替。
二、构建AsyncTask子类的回调方法
doInBackground:必须重写的方法,所有耗时的操作都将在这个方法中进行,此方法将接收输入参数和返回计算结果。在执行过程中可以调publishProgress(Progress...values)来更新进度信息。
onPreExecute: 通常将一些初始化操作放在这里执行
onPostExecute: 当dolnBackground执行后,立即被调用,传入dolnBackground方法所返回的值,即展示我们处理完的结果,将结果显示到UI组件上。
onProgressUpdate: 当dolnBackground执行事,调用了publishProgress方法,自动触发该方法,直接将进度信息更新到UI组件上,通过这个方法,我们可以清楚的知道当前耗时操作的完成进度。
另外有个重点要说明的是,这四个函数中只有doInBackground是在其他线程执行的,需要异步处理,其他三个函数都是在主线程中调用的,也就是说只有doInBackground方法内部是不能出现更新UI的操作
除了这四个函数之外,当然我们还要一个方法也要说一下
Execute:执行一个异步任务,触发异步任务的执行。简而言之,这个函数就是执行的意思,就类似于Thread线程中的start方法,但这个方法只被允许在UI线程中调用。
执行顺序:
Execute->onPreExecute->dolnBackground->(onProgressUpdate)->onPostExecute
首先我们通过execute方法开启执行一个异步任务,然后我们通过onPreExecute方法去完成开始异步任务前的一些准备操作,比如初始化。然后再调用dolnBackground方法来执行真正的耗时操作,如果在该方法里执行了publishProgress方法,则接下里调用onProgressUpdate方法用于获取进度,更新进度条,然后再执行onPostExecute方法,更新UI
顺序验证:
新建一个MyAsyncTask.java,继承于AsyncTask,所有参数暂时使用Java.lang.Void类型代替,只有doInBackground方法是必须要重写的,同时我们在每个函数中都加入一个Log,方便我们知道他们之间执行的顺序。
然后我们在虚拟机跑一跑,执行顺序就出来了,严格执行,我们上面所说的顺序
AsyncTask实际操作(网络获取图片)
网络操作式一个不稳定的耗时操作,从Android4.0开始就被严禁放进主线程中。所以在加载网络图片的操作中,我们需要在异步处理下载图片,并且在UI线程中去设置图像
首先我们准备一张网络图片的链接,要是失效了就去百度搜索一张就行了
http://image.sinajs.cn/newchart/monthly/n/sh601003.gif
actvity_main.xml
<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"
tools:context="${relativePackage}.${activityClass}" >
<Button
android:id="@+id/btn_loadImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="46dp"
android:layout_marginTop="80dp"
android:text="Image Test" />
</RelativeLayout>
image.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" >
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
</LinearLayout>
ImageTest.java
package bnuz.lwj.asynctaskteaching;
import java.net.URLConnection;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class ImageTest extends Activity{
private ImageView mImageView;
private ProgressBar mProgressBar;
private String URL = "http://image.sinajs.cn/newchart/monthly/n/sh601003.gif";
MyAsyncTask myAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.image);
mImageView=(ImageView) findViewById(R.id.image);
mProgressBar=(ProgressBar) findViewById(R.id.progressBar1);
//设置传递进去的参数,是doInBackground获取的
MyAsyncTask myAsyncTask=new MyAsyncTask(mProgressBar,mImageView);
myAsyncTask.execute(URL);
}
@Override
protected void onPause() {
//当一个Activity跳转到另一个activity是调用
super.onPause();
if(myAsyncTask!=null && myAsyncTask.getStatus()==AsyncTask.Status.RUNNING){
//这并不是真正的关闭这个异步任务,关闭线程,只是给线程标记了一个关闭的信号
//还需要在线程内部判断标记信号为什么是跳出线程
//目的是让这个异步任务和ImageTest的Activity的周期同步,一起存货,以免出现bug
myAsyncTask.cancel(true);
}
}
}
MyAsyncTask.java
package bnuz.lwj.asynctaskteaching;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
//我们要传入一个URL,所以第一个参数我们定义为String类型
//第二个参数是进度,所以我们传进去一个integer,它就是publishProgress(value)的参数类型
//因为我们需要返回的一个参数类型是一张图片,所以设置为bitmap类型
//从这里我们可以知道,以后如果有返回复杂参数的时候,我们还需要定义javaBean类型
public class MyAsyncTask extends AsyncTask<String,Integer,Bitmap>{
private ProgressBar mProgressBar;
private ImageView mImageView;
public MyAsyncTask(ProgressBar mProgressBar, ImageView mImageView) {
super();
this.mProgressBar = mProgressBar;
this.mImageView = mImageView;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
//将隐藏的mProgressBar显示出来
mProgressBar.setVisibility(View.VISIBLE);
}
//该方法传进来的是一个可变长的数组String... params
//因为我们只传进来一个URL,所以只要取这个数组索引为0的值就行了
@Override
protected Bitmap doInBackground(String... params) {
String url=params[0];
Bitmap bitmap=null;
URLConnection connection;
InputStream is;
//模拟更新进度
for(int i=0;i<100;i++){
//判断
if(isCancelled())
{
//跳出线程
break;
}
publishProgress(i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//通过URL.openConnection方法获得一个URLConnection对象
try {
URL myurl = new URL(url);
connection=myurl.openConnection();
connection.connect();
is=connection.getInputStream();
//包装一下
BufferedInputStream bis=new BufferedInputStream(is);
//将一个输入流解析为一个BitMap对象
bitmap=BitmapFactory.decodeStream(is);
//关闭输入流
is.close();
bis.close();
} catch (MalformedURLException e) {
Log.i("info", "MalformedURLException");
e.printStackTrace();
} catch (IOException e) {
Log.i("info", "IOException:"+e.toString());
e.printStackTrace();
}
return bitmap;
}
//操作UI,获取doInBackground从返回的bitmap图像
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
/*mProgressBar.setVisibility(View.GONE);*/
mImageView.setImageBitmap(bitmap);
this.cancel(true);
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
if(isCancelled()){
return;
}
//从doInBackground的publishProgress方法获取到i值
//为什么是value[0],原理跟doInBackground获取URL原理一样
mProgressBar.setProgress(values[0]);
}
}
MainActvity.java
package bnuz.lwj.asynctaskteaching;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.ProgressBar;
public class ImageTest extends Activity{
private ImageView mImageView;
private ProgressBar mProgressBar;
private String URL = "http://image.sinajs.cn/newchart/monthly/n/sh601003.gif";
MyAsyncTask myAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.image);
mImageView=(ImageView) findViewById(R.id.image);
mProgressBar=(ProgressBar) findViewById(R.id.progressBar1);
//实例一个异步任务
MyAsyncTask myAsyncTask=new MyAsyncTask(mProgressBar,mImageView);
//设置传递进去的参数,是doInBackground获取的
myAsyncTask.execute(URL);
}
@Override
protected void onPause() {
//当一个Activity跳转到另一个activity是调用
super.onPause();
if(myAsyncTask!=null && myAsyncTask.getStatus()==AsyncTask.Status.RUNNING){
//这并不是真正的关闭这个异步任务,关闭线程,只是给线程标记了一个关闭的信号
//还需要在线程内部判断标记信号为什么是跳出线程
//目的是让这个异步任务和ImageTest的Activity的周期同步,一起存货,以免出现bug
myAsyncTask.cancel(true);
}
}
}
注意:最后别忘记了在AndroidManifest.xml加入网络权限和给新建的ImageTest这个Activity注册奥
在<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />下输入
<uses-permission android:name="android.permission.INTERNET" />
在MainActivity的注册下面输入
<activity android:name=".ImageTest"></activity>
另外在写这个例子的时候,我因为网络问题报了IO异常,折腾了一几乎一天,大家可以注意一下
URLConnection解析URL的UnknownHostException异常解决办法
效果就是从MainActivity点击button跳转到ImageTest,然后进度条先执行到100,再显示图片
总结到此结束~
欢迎关注我的博客,一起学习讨论
要转载,请附上原文链接,作者:SnailMann
传送门:【Android学习笔记系列】BaseAdapter适配器的介绍、使用及优化(详细)
大家也可以关注我的私人github: https://github.com/SnailMann,欢迎watch ,star, fork
虽然现在暂时没有什么东西,但是总会有的