AsyncTask的使用详解
AsyncTask介绍
AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.
Android的AsyncTask比Handler更轻量级一些,适用于简单的异步处理。
首先明确Android之所以有Handler和AsyncTask,都是为了不阻塞主线程(UI线程),且UI的更新只能在主线程中完成,因此异步处理是不可避免的。为了能理解AsyncTask,需要了解多线程、Main Thread和Worker Thread的概念。
Android当中的多线程
在Android当中,当一个应用程序的组件启动的时候,并且没有其他的应用程序组件在运行时,Android系统就会为该应用程序组件开辟一个新的线程来执行。默认的情况下,在一个相同Android应用程序当中,其里面的组件都是运行在同一个线程里面的,这个线程我们称之为Main线程。当我们通过某个组件来启动另一个组件的时候,这个时候默认都是在同一个线程当中完成的。当然,我们可以自己来管理我们的Android应用的线程,我们可以根据我们自己的需要来给应用程序创建额外的线程。
Main Thread 和 Worker Thread
在Android当中,通常将线程分为两种,一种叫做Main Thread,除了Main Thread之外的线程都可称为Worker Thread。
当一个应用程序运行的时候,Android操作系统就会给该应用程序启动一个线程,这个线程就是我们的Main Thread,这个线程非常的重要,它主要用来加载我们的UI界面,完成系统和我们用户之间的交互,并将交互后的结果又展示给我们用户,所以Main Thread又被称为UI Thread。
Android系统默认不会给我们的应用程序组件创建一个额外的线程,所有的这些组件默认都是在同一个线程中运行。然而,某些时候当我们的应用程序需要完成一个耗时的操作的时候,例如访问网络或者是对数据库进行查询时,此时我们的UI Thread就会被阻塞。例如,当我们点击一个Button,然后希望其从网络中获取一些数据,如果此操作在UI Thread当中完成的话,当我们点击Button的时候,UI线程就会处于阻塞的状态,此时,我们的系统不会调度任何其它的事件,更糟糕的是,当我们的整个现场如果阻塞时间超过5秒钟(官方是这样说的),这个时候就会出现 ANR (Application Not Responding)的现象,此时,应用程序会弹出一个框,让用户选择是否退出该程序。对于Android开发来说,出现ANR的现象是绝对不能被允许的。
另外,由于我们的Android UI控件是线程不安全的,所以我们不能在UI Thread之外的线程当中对我们的UI控件进行操作。因此在Android的多线程编程当中,我们有两条非常重要的原则必须要遵守:
- 绝对不能在UI Thread当中进行耗时的操作,不能阻塞我们的UI Thread
- 不能在UI Thread之外的线程当中操纵我们的UI元素
AsyncTask的使用方法
AsyncTask就相当于Android给我们提供了一个多线程编程的一个框架,其介于Thread和Handler之间,我们如果要定义一个AsyncTask,就需要定义一个类来继承AsyncTask这个抽象类,并实现其唯一的一个 doInBackgroud 抽象方法。要掌握AsyncTask,我们就必须要一个概念,总结起来就是: 3个泛型,4个步骤。
3个泛型
当我们定义一个类来继承AsyncTask这个类的时候,我们需要为其指定3个泛型参数:
AsyncTask <Params, Progress, Result>
- Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型,对应的方法是doInBackground(Params… params)
- Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型,对应的方法是publishProgress(Progress)
- Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型,对应方法是onPostExecute(Result… result)
在定义一个类继承AsyncTask类的时候,必须要指定好这三个泛型的类型,如果都不指定的话,则都将其写成Void,如:
AsyncTask <Void, Void, Void>
4个步骤
当我们执行一个异步任务的时候,其需要按照下面的4个步骤分别执行
- onPreExecute(): 这个方法是在执行异步任务之前的时候执行,并且是在UI Thread当中执行的,通常我们在这个方法里做一些UI控件的初始化的操作,例如弹出要给ProgressDialog
- doInBackground(Params… params): 在onPreExecute()方法执行完之后,会马上执行这个方法,这个方法就是来处理异步任务的方法,Android操作系统会在后台的线程池当中开启一个worker thread来执行我们的这个方法,所以这个方法是在worker thread当中执行的,这个方法执行完之后就可以将我们的执行结果发送给我们的最后一个 onPostExecute 方法,在这个方法里,我们可以从网络当中获取数据等一些耗时的操作
- onProgressUpdate(Progess… values): 这个方法也是在UI Thread当中执行的,我们在异步任务执行的时候,有时候需要将执行的进度返回给我们的UI界面,例如下载一张网络图片,我们需要时刻显示其下载的进度,就可以使用这个方法来更新我们的进度。这个方法在调用之前,我们需要在 doInBackground 方法中调用一个 publishProgress(Progress) 的方法来将我们的进度时时刻刻传递给 onProgressUpdate 方法来更新
- onPostExecute(Result… result): 当我们的异步任务执行完之后,就会将结果返回给这个方法,这个方法也是在UI Thread当中调用的,我们可以将返回的结果显示在UI控件上
为什么我们的AsyncTask抽象类只有一个 doInBackground 的抽象方法呢??原因是,我们如果要做一个异步任务,我们必须要为其开辟一个新的Thread,让其完成一些操作,而在完成这个异步任务时,我可能并不需要弹出要给ProgressDialog,我并不需要随时更新我的ProgressDialog的进度条,我也并不需要将结果更新给我们的UI界面,所以除了 doInBackground 方法之外的三个方法,都不是必须有的,因此我们必须要实现的方法是 doInBackground 方法。
使用AsyncTask类,以下是几条必须遵守的准则:
- AsyncTask类必须在UI Thread当中加载
- Task的实例必须在UI thread中创建;
- execute方法必须在UI thread中调用;
- 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法;
例子
main_activity.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:context="com.gjg.learn.photoswall.MainActivity"> <GridView android:id="@+id/photo_wall" android:layout_width="match_parent" android:layout_height="wrap_content" android:columnWidth="90dip" android:gravity="center" android:numColumns="auto_fit" android:stretchMode="columnWidth" android:verticalSpacing="10dip"> GridView>LinearLayout>
photo_layout.xml
<?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" > <ImageView android:id="@+id/photo" android:layout_width="150dip" android:layout_height="150dip" android:layout_centerInParent="true" android:src="@drawable/empty_photo" />RelativeLayout>
MainActivity
public class MainActivity extends Activity { /** * 用于展示照片墙的GridView */ private GridView mPhotoWall; /** * GridView的适配器 */ private PhotoWallAdapter adapter; private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPhotoWall = (GridView) findViewById(R.id.photo_wall); adapter = new PhotoWallAdapter(this, 0, Images.imagesUrls, mPhotoWall); mPhotoWall.setAdapter(adapter); } @Override protected void onDestroy() { super.onDestroy(); // 退出程序时结束所有的下载任务 adapter.cancelAllTasks(); }}
Images
public class Images { public final static String[] imagesUrls=new String[]{ "http://img.mukewang.com/55237dcc0001128c06000338-300-170.jpg", "http://img.mukewang.com/55249cf30001ae8a06000338-300-170.jpg", "http://img.mukewang.com/5523711700016d1606000338-300-170.jpg", "http://img.mukewang.com/551e470500018dd806000338-300-170.jpg", "http://img.mukewang.com/551de0570001134f06000338-300-170.jpg", "http://img.mukewang.com/552640c300018a9606000338-300-170.jpg", "http://img.mukewang.com/551b92340001c9f206000338-300-170.jpg", "http://img.mukewang.com/5518c3d7000175af06000338-300-170.jpg", "http://img.mukewang.com/551b98ae0001e57906000338-300-170.jpg", "http://img.mukewang.com/550b86560001009406000338-300-170.jpg", "http://img.mukewang.com/551916790001125706000338-300-170.jpg", "http://img.mukewang.com/5518ecf20001cb4e06000338-300-170.jpg", "http://img.mukewang.com/5518bbe30001c32006000338-300-170.jpg", "http://img.mukewang.com/551380400001da9b06000338-300-170.jpg", "http://img.mukewang.com/550a33b00001738a06000338-300-170.jpg", "http://img.mukewang.com/5513a1b50001752806000338-300-170.jpg", "http://img.mukewang.com/5513e20600017c1806000338-300-170.jpg", "http://img.mukewang.com/550a78720001f37a06000338-300-170.jpg", "http://img.mukewang.com/550a836c0001236606000338-300-170.jpg", "http://img.mukewang.com/550a87da000168db06000338-300-170.jpg", "http://img.mukewang.com/530f0ef700019b5906000338-300-170.jpg", "http://img.mukewang.com/549bda090001c53e06000338-300-170.jpg", "http://img.mukewang.com/547d5a45000156f406000338-300-170.jpg", "http://img.mukewang.com/54780ea90001f3b406000338-300-170.jpg", "http://img.mukewang.com/547ed1c9000150cc06000338-300-170.jpg", "http://img.mukewang.com/54214727000160e306000338-300-170.jpg", "http://img.mukewang.com/54125edc0001ce6306000338-300-170.jpg", "http://img.mukewang.com/548165820001b4b006000338-300-170.jpg", "http://img.mukewang.com/53d74f960001ae9d06000338-300-170.jpg", "http://img.mukewang.com/54c87c73000150cf06000338-300-170.jpg", };}
PhotoWallAdapter
public class PhotoWallAdapter extends ArrayAdapter<String> implements AbsListView.OnScrollListener { /** * 记录所有正在下载或等待下载的任务。 */ private Set taskCollection; /** * 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。 */ private LruCache mMemoryCache; /** * GridView的实例 */ private GridView mPhotoWall; /** * 第一张可见图片的下标 */ private int mFirstVisibleItem; /** * 一屏有多少张图片可见 */ private int mVisibleItemCount; /** * 记录是否刚打开程序,用于解决进入程序不滚动屏幕,不会下载图片的问题。 */ private boolean isFirstEnter = true; public PhotoWallAdapter(Context context, int textViewResourceId, String[] objects, GridView photoWall) { super(context, textViewResourceId, objects); mPhotoWall = photoWall; taskCollection = new HashSet(); // 获取应用程序最大可用内存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory / 8; // 设置图片缓存大小为程序最大可用内存的1/8 mMemoryCache = new LruCache(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount(); } }; mPhotoWall.setOnScrollListener(this); } @Override public View getView(int position, View convertView, ViewGroup parent) { final String url = getItem(position); View view; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null); } else { view = convertView; } final ImageView photo = (ImageView) view.findViewById(R.id.photo); // 给ImageView设置一个Tag,保证异步加载图片时不会乱序 photo.setTag(url); setImageView(url, photo); return view; } /** * 给ImageView设置图片。首先从LruCache中取出图片的缓存,设置到ImageView上。如果LruCache中没有该图片的缓存, * 就给ImageView设置一张默认图片。 * * @param imageUrl * 图片的URL地址,用于作为LruCache的键。 * @param imageView * 用于显示图片的控件。 */ private void setImageView(String imageUrl, ImageView imageView) { Bitmap bitmap = getBitmapFromMemoryCache(imageUrl); if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { imageView.setImageResource(R.drawable.empty_photo); } } /** * 将一张图片存储到LruCache中。 * * @param key * LruCache的键,这里传入图片的URL地址。 * @param bitmap * LruCache的键,这里传入从网络上下载的Bitmap对象。 */ public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemoryCache(key) == null) { mMemoryCache.put(key, bitmap); } } /** * 从LruCache中获取一张图片,如果不存在就返回null。 * * @param key * LruCache的键,这里传入图片的URL地址。 * @return 对应传入键的Bitmap对象,或者null。 */ public Bitmap getBitmapFromMemoryCache(String key) { return mMemoryCache.get(key); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // 仅当GridView静止时才去下载图片,GridView滑动时取消所有正在下载的任务 if (scrollState == SCROLL_STATE_IDLE) { loadBitmaps(mFirstVisibleItem, mVisibleItemCount); } else { cancelAllTasks(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mFirstVisibleItem = firstVisibleItem; mVisibleItemCount = visibleItemCount; // 下载的任务应该由onScrollStateChanged里调用,但首次进入程序时onScrollStateChanged并不会调用, // 因此在这里为首次进入程序开启下载任务。 if (isFirstEnter && visibleItemCount > 0) { loadBitmaps(firstVisibleItem, visibleItemCount); isFirstEnter = false; } } /** * 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象, * 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。 * * @param firstVisibleItem * 第一个可见的ImageView的下标 * @param visibleItemCount * 屏幕中总共可见的元素数 */ private void loadBitmaps(int firstVisibleItem, int visibleItemCount) { try { for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) { String imageUrl = Images.imagesUrls[i]; Bitmap bitmap = getBitmapFromMemoryCache(imageUrl); if (bitmap == null) { BitmapWorkerTask task = new BitmapWorkerTask(); taskCollection.add(task); task.execute(imageUrl); } else { ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl); if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); } } } } catch (Exception e) { e.printStackTrace(); } } /** * 取消所有正在下载或等待下载的任务。 */ public void cancelAllTasks() { if (taskCollection != null) { for (BitmapWorkerTask task : taskCollection) { //只是设置了一个取消的标量,真正取消任务需要在doInBackground方法中通过 //isCancelled()方法判断是否有取消标量,然后break task.cancel(false);//true:正在执行的线程强行中断,false:等线程执行完后再中断 } } } /** * 异步下载图片的任务。 * */ class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> { /** * 图片的URL地址 */ private String imageUrl; @Override protected Bitmap doInBackground(String... params) { imageUrl = params[0]; // 在后台开始下载图片 Bitmap bitmap = downloadBitmap(params[0]); if (bitmap != null) { // 图片下载完成后缓存到LrcCache中 addBitmapToMemoryCache(params[0], bitmap); } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); // 根据Tag找到相应的ImageView控件,将下载好的图片显示出来。 ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl); if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); } taskCollection.remove(this); } /** * 建立HTTP请求,并获取Bitmap对象。 * * @param imageUrl * 图片的URL地址 * @return 解析后的Bitmap对象 */ private Bitmap downloadBitmap(String imageUrl) { Bitmap bitmap = null; HttpURLConnection con = null; try { URL url = new URL(imageUrl); con = (HttpURLConnection) url.openConnection(); con.setConnectTimeout(5 * 1000); con.setReadTimeout(10 * 1000); con.setDoInput(true); con.setDoOutput(true); bitmap = BitmapFactory.decodeStream(con.getInputStream()); } catch (Exception e) { e.printStackTrace(); } finally { if (con != null) { con.disconnect(); } } return bitmap; } }}
运行效果:
2017-3-1
补充:
今天在看《Android开发艺术探索》中的ImageLoader时提到“AsyncTask在Android3.0 以上和以下实现的效果不一样”,以前没注意过这方面的问题,特意记录一下:
Google在3.0以后的版本中做了修改,将asyncTask.execute(task)修改为了顺序执行,即只有当一个的实例的任务完成后在执行下一个实例的任务。
后来在Android3.0后新增了一个方法executeOnExecutor(Executor exec, Object…
params),该方法接受2个参数,第一个是Executor,第二个是任务参数。
其中第一个参数是线程池实例,Google为我们预定义了两种: 第一种是AsyncTask.SERIAL_EXECUTOR
第二种是AsyncTask.THREAD_POOL_EXECUTOR
顾名思义,第一种其实就像3.0以后的execute方法,是顺序执行的。第二种就是3.0以前的execute方法,是可以并发执行的。我们直接用asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
task) 就可以多任务并发执行了。
更多相关文章
- Android(安卓)ViewPager嵌套ViewPager滑动冲突的解决方法
- 一些摘抄
- android中JSON数据的读写方法
- Retrofit2.0+RxJava+MVP+Bmob的使用
- Android自适应屏幕大小和layout布局(横屏|竖屏)
- Android(安卓)RecyclerView使用详解
- 在EditText中插入表情图片 (CharacterStyle&SpannableString)
- Android带多选功能的PhotoPicker
- Android中使用google Analytics