前言:以前就遇到过AsnyncTask,但由于当时对其理解比较懵懂,所以就先放了一下,没写博客,时隔几月,当再次遇到此问题时,感觉难度更大,却有必要仔细研究一下,这里分两篇写,这篇先写AsncTask的异步处理的实现,第二篇对AsyncTask进行补充,讲述handler消息机制。
一、android开发中存在的问题
当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线程。
在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。也就是说所有有关更新界面的操作必须在主线程中操作,除主线程外的其它自建线程都没有这个权力处理这些UI更新操作,否则将报错!在后面的讲解中会看到即便是最常用的Toast.makeText()也是不允许在非主线程中使用的!
比如说从网上获取一个网页,在一个TextView中将其源代码显示出来,这种涉及到网络操作的程序一般都是需要开一个线程完成网络访问,但是在获得页面源码后,是不能直接在网络操作线程中调用TextView.setText()的.因为其他线程中是不能直接访问主UI线程成员
在单线程模型中始终要记住两条法则:
1. 不要阻塞UI线程
2. 确保只在UI线程中访问Android UI工具包 ,即更新UI
二、AsyncTask
但我们在处理程序时,常常是先做数据处理,然后再数据准备好后更新UI,如果按照上面的单线程模式,就出现了一个问题,更新UI必须写在OnCreate函数中,但我们数据处理操作一般是要自建线程的,如果全部写在OnCreate函数中,那么代码就难以规范。所以为了解决数据处理后更新UI的问题,就产生了AsyncTask!
AsyncTask的执行分为四个步骤,每一步都对应一个回调方法,这些方法不应该由应用程序调用,开发者需要做的就是实现这些方法。
<一>子类化AsyncTask
AsyncTask是抽象类 ,我们必须自己写一个类来继承AsyncTask 类
<二>实现AsyncTask中定义及须重写的方法
重写AsyncTask后可以或必须重写的函数如下:
- onPreExecute() 该方法将在执行实际的后台操作前被UI thread调用。这个方法只是做一些准备工作,如在界面上显示一个进度条。
- doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。
- publishProgress 该方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
- onProgressUpdate(Progress...), 在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,可以通过一个进度条进行展示。
- onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread.
自己写一个类继承自AsyncTask后的形式如下所示:
class MyAsyncTask extends AsyncTask<String, Integer, Bitmap>
{
……………………
}
可以看到有三个参数:
AsyncTask的三个泛型参数说明(三个参数可以是任何类型)
第一个参数:传入doInBackground()方法的参数类型,这里是String
第二个参数:传入onProgressUpdate()方法的参数类型,这里是Integer
第三个参数:传入onPostExecute()方法的参数类型,也是doInBackground()方法返回的类型。这里是Bitmap
<三>AsyncTask遵守准则
为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
<1>Task的实例必须在UI thread中创建
<2>execute方法必须在UI thread中调用
<3>不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法
<4> 该task只能被执行一次,否则多次调用时将会出现异常
<四>实例
效果:
初始状态 加载中 完成
效果讲解:总体上来讲是从网上加载一张图片并贴到当前XML的指定位置
1、添加网络访问权限
在AndroidManifest.xml文件中,添加下面一行代码,获取互联网访问权限
<uses-permission android:name="android.permission.INTERNET"/>
2、XML布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:id="@+id/show"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="下一个" />
<ProgressBar
android:id="@+id/processBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/>
<HorizontalScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="none" >
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</HorizontalScrollView>
</LinearLayout>
关于布局,难度不大,也不是本篇重点,不再多讲,这里仅说下作用。
progressBar初始化为不显示,仅当用户点击“下一个”按钮时,显示,图片加载完成后取消显示。
imageView用来显示加载后的图片。
3、JAVA代码
先贴出完整代码,然后再逐步讲解
package com.example.try_asynctask;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;
public class MainActivity extends Activity {
private ImageViewimage= null;
private Buttonshow;
private ProgressBarprogressBar= null;
private intnumber= 0;
List<String>imageUrl= null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = (ProgressBar) findViewById(R.id.processBar);
image = (ImageView) findViewById(R.id.image);
show = (Button) findViewById(R.id.show);
show.setOnClickListener(new showButtonListener());
imageUrl = new ArrayList<String>(); // 图片地址List
imageUrl.add("http://image.tianjimedia.com/uploadImages/2011/266/AIO90AV2508S.jpg");
imageUrl.add("http://image.tianjimedia.com/uploadImages/2012/090/063N2L5N2HID.jpg");
imageUrl.add("http://comic.sinaimg.cn/2011/0824/U5237P1157DT20110824161051.jpg");
imageUrl.add("http://image.tianjimedia.com/uploadImages/2012/090/1429QO6389U8.jpg");
imageUrl.add("http://new.aliyiyao.com/UpFiles/Image/2011/01/13/nc_129393721364387442.jpg");
}
public class showButtonListener implements OnClickListener
{
@Override
public void onClick(View v)
{
number++;
MyAsyncTask myAsyncTask = new MyAsyncTask(getApplicationContext());
myAsyncTask.execute(imageUrl.get(number % imageUrl.size()));
}
}
class MyAsyncTask extends AsyncTask<String, Integer, Bitmap>
{
// 可变长的输入参数,与AsyncTask.exucute()对应
public MyAsyncTask(Context context)
{
progressBar.setVisibility(View.VISIBLE);
image.setVisibility(View.GONE);
}
@Override
protected Bitmap doInBackground(String... params)
{
Bitmap bitmap = null;
try
{
//根据URL取得图片并返回
URL url = new URL(params[0]);
URLConnection conn = url.openConnection();
conn.connect();
InputStream inputStream = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
Toast.makeText(getApplicationContext(), "传回图片了", Toast.LENGTH_SHORT).show();
inputStream.close();
}
catch (Exception e)
{
Log.e("msg", e.getMessage());
}
return bitmap;
}
/**
* 在doInBackground 执行完成后,onPostExecute方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread.
*/
@Override
protected void onPostExecute(Bitmap bitmap)
{
progressBar.setVisibility(View.GONE);
image.setVisibility(View.VISIBLE);
if (bitmap != null)
{
image.setImageBitmap(bitmap);
}
else
{
Toast.makeText(getApplicationContext(), "网络异常", Toast.LENGTH_SHORT).show();
}
}
/**
* 该方法将在执行实际的后台操作前被UI thread调用。这个方法只是做一些准备工作,如在界面上显示一个进度条。
*/
@Override
protected void onPreExecute()
{
// 任务启动
Toast.makeText(getApplicationContext(), "任务开始......", Toast.LENGTH_SHORT).show();
}
}
}
1、先看OnCreate函数中操作
imageUrl = new ArrayList<String>(); // 图片地址List
imageUrl.add("http://image.tianjimedia.com/uploadImages/2011/266/AIO90AV2508S.jpg");
imageUrl.add("http://image.tianjimedia.com/uploadImages/2012/090/063N2L5N2HID.jpg");
imageUrl.add("http://comic.sinaimg.cn/2011/0824/U5237P1157DT20110824161051.jpg");
imageUrl.add("http://image.tianjimedia.com/uploadImages/2012/090/1429QO6389U8.jpg");
imageUrl.add(http://new.aliyiyao.com/UpFiles/Image/2011/01/13/nc_129393721364387442.jpg);
定义了一个StringArrayList,用来保存要访问的网址集合,点击“下一个”时,逐个循环显示
progressBar = (ProgressBar) findViewById(R.id.processBar);
image = (ImageView) findViewById(R.id.image);
show = (Button) findViewById(R.id.show);
show.setOnClickListener(new showButtonListener());
然后是初始化控件变量,并为“下一个”按钮设置监听函数showButtonListener。
2、“下一个”按钮监听函数showButtonListener
public class showButtonListener implements OnClickListener
{
@Override
public void onClick(View v)
{
number++;
MyAsyncTask myAsyncTask = new MyAsyncTask(getApplicationContext());
myAsyncTask.execute(imageUrl.get(number % imageUrl.size()));
}
}
核心在于最后一句话,imageUrl.get(number % imageUrl.size())循环得到传进去的网址,然后传给myAsyncTask.execute(),所以这里往MyAsyncTask传的参数是String类型,所以在MyAsyncTask派生自AsyncTask的时候,第一个参数也应当是String类型!这就是上面所讲的三个参数中的第一个的意义。
下面就开始今天的核心问题MyAsyncTask的讲解:
3-1 MyAsyncTask构造函数(可省略)
public MyAsyncTask(Context context)
{
progressBar.setVisibility(View.VISIBLE);
image.setVisibility(View.GONE);
}
这里是对XML作初始化设置,其实完全可以在onPreExecute()函数中操作是一样的。
3-2 onPreExecute()处理前操作(可省略)
@Override
protected void onPreExecute()
{
// 任务启动
Toast.makeText(getApplicationContext(), "任务开始......", Toast.LENGTH_SHORT).show();
}
代码没什么好讲的,这里只是想说,这个函数对于类派生不是必须要重写的,如果需要在数据处理前要在UI上加以提示等等的预处理操作,都可以放在这里操作。
3-3 doInBackground()后台数据处理操作(必写)
这里必须注意的一点是,在这个函数里只能做数据处理操作,不能涉及任何更新UI的操作,即便使用Toast.makeText()也会报错!
代码:
@Override
protected Bitmap doInBackground(String... params)
{
Bitmap bitmap = null;
try
{
//根据URL取得图片并返回
URL url = new URL(params[0]);
URLConnection conn = url.openConnection();
conn.connect();
InputStream inputStream = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
//注意!!!!这里写了个Toast.makeText!!!
Toast.makeText(getApplicationContext(), "传回图片了", Toast.LENGTH_SHORT).show();
inputStream.close();
}
catch (Exception e)
{
Log.e("msg", e.getMessage());
}
return bitmap;
}
这行的核心意思就是根据得到的URL获取BitMap对象,然后返回!几点注意:
1、获取传递过来值的方法:
URL url = new URL(params[0]);
2、程序报错
在实际运行时,根本无法看到“传回图片了”消息提示,在看LogCat,已经报错了,错误信息如下:
这里不影响程序运行是因为外面加着try-catch呢,如果去掉程序就会直接崩了。所以,在doInBackground中不准做任何的UI更新操作,即便是Toast也不行!!!!
3-4 onPostExecute()对返回结果处理,更新UI(必写)
@Override
protected void onPostExecute(Bitmap bitmap)
{
progressBar.setVisibility(View.GONE);
image.setVisibility(View.VISIBLE);
if (bitmap != null)
{
image.setImageBitmap(bitmap);
}
else
{
Toast.makeText(getApplicationContext(), "网络异常", Toast.LENGTH_SHORT).show();
}
}
这个函数中处理doInBackground的返回结果,更新UI
至此本篇关于AsyncTask异步处理的操作就全部讲完了,这里存在一个问题,如果我们想在doInBackground处理过程中更新UI怎么办?下篇讲述的handler消息机制,就可以解决这个问题。
参考文章:
《Android进阶2之AsyncTask实现异步处理任务》:http://www.cnblogs.com/snake-hand/archive/2012/03/30/2454368.html
《AsyncTask<>的参数介绍》:http://my.eoe.cn/xuliangbo/archive/6063.html
源码地址:http://download.csdn.net/detail/harvic880925/7275089 不要分,仅供分享
请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/24774883 ,不胜感激!