AsnyncTask与handler(一)——AsyncTask异步处理

时间:2021-08-03 04:05:44

 前言:以前就遇到过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只能被执行一次,否则多次调用时将会出现异常

 <四>实例

 效果:

            初始状态                                       加载中                                           完成

 AsnyncTask与handler(一)——AsyncTask异步处理 AsnyncTask与handler(一)——AsyncTask异步处理 AsnyncTask与handler(一)——AsyncTask异步处理

效果讲解:总体上来讲是从网上加载一张图片并贴到当前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,已经报错了,错误信息如下:

 AsnyncTask与handler(一)——AsyncTask异步处理

 这里不影响程序运行是因为外面加着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 ,不胜感激!