使用AsyncTask实现异步处理
由于主线程(也可叫UI线程)负责处理用户输入事件(点击按钮、触摸屏幕、按键等),如果主线程被阻塞,应用就会报ANR错误。为了不阻塞主线程,我们需要在子线程中处理耗时的操作,在处理耗时操作的过程中,子线程可能需要更新UI控件的显示,由于UI控件的更新重绘是由主线程负责的,所以子线程需要通过Handler发送消息给主线程的消息队列,由运行在主线程的消息处理代码接收消息后更新UI控件的显示。
采用线程+Handler实现异步处理时,当每次执行耗时操作都创建一条新线程进行处理,性能开销会比较大。另外,如果耗时操作执行的时间比较长,就有可能同时运行着许多线程,系统将不堪重负。为了提高性能,我们可以使用AsynTask实现异步处理,事实上其内部也是采用线程+Handler来实现异步处理的,只不过是其内部使用了线程池技术,有效的降低了线程创建数量及限定了同时运行的线程数。
private final class AsyncImageTask extendsAsyncTask<String, Integer, String>{
protectedvoid onPreExecute(){ //运行在UI线程
}
protectedStringdoInBackground(String...params) {//在子线程中执行
return“itcast”;
}
protectedvoid onPostExecute(String result) {//运行在UI线程
}
protectedvoid onProgressUpdate(Integer… values) {//运行在UI线程
}
}
AsyncTask<String, Integer, String>中定义的三个泛型参数分别用作了doInBackground、onProgressUpdate的输入方法类型,第三个参数用作了doInBackground的返回参数类型和onPostExecute的输入参数类型。
AsyncTask定义了三种泛型类型Params,Progress和Result。
Params 启动任务执行的输入参数,比如HTTP请求的URL。Progress 后台任务执行的百分比。Result 后台执行任务最终返回的结果,比如String。
使用AsyncTask异步加载数据最少要重写以下这两个方法:
doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回
有必要的话还得重写以下这三个方法,但不是必须的:
onProgressUpdate(Progress…) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。onPreExecute() 这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。onCancelled()用户调用取消时,要做的操作
使用AsyncTask类,以下是几条必须遵守的准则:
Task的实例必须在UI thread中创建;execute方法必须在UI thread中调用;不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;该task只能被执行一次,否则多次调用时将会出现异常;
创建Android应用:Project Name:DataAsyncLoad,Android2.2,Application Name:数据异步加载,Packagename:cn.itcast.asyncload,CreateActivity:MainActivity。
1. 清单文件中添加相应的权限
/DataAsyncLoad/AndroidManifest.xml
<?xml version="1.0"encoding="utf-8"?>
<manifestxmlns:android="/apk/res/android"
package="cn.itcast.asyncload"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
<activity
android:label="@string/app_name"
android:name=".MainActivity" >
<intent-filter >
<actionandroid:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
<!--访问internet权限-->
<uses-permissionandroid:name="android.permission.INTERNET"/>
<!-- 在SDCard中创建与删除文件权限-->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 往SDCard写入数据权限-->
<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>
2. 界面
/DataAsyncLoad/res/layout/main.xml
<?xml version="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/listView"/>
</LinearLayout>
/DataAsyncLoad/res/layout/listview_item.xml
<?xml version="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<ImageView
android:layout_width="120dp"
android:layout_height="120dp"
android:id="@+id/imageView"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="#FFFFFF"
android:id="@+id/textView"
/>
</LinearLayout>
3. 实现
/DataAsyncLoad/src/cn/itcast/asyncload/MainActivity.java
package cn.itcast.asyncload;
import java.io.File;
import java.util.List;
importcn.itcast.adapter.ContactAdapter;
import cn.itcast.domain.Contact;
importcn.itcast.service.ContactService;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.widget.ListView;
public class MainActivity extendsActivity {
ListViewlistView;
Filecache;
Handlerhandler = new Handler(){
publicvoid handleMessage(Message msg) {
listView.setAdapter(new ContactAdapter(MainActivity.this,(List<Contact>)msg.obj, R.layout.listview_item, cache));
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
listView = (ListView)this.findViewById(R.id.listView);
cache = newFile(Environment.getExternalStorageDirectory(), "cache");
if(!cache.exists()) cache.mkdirs();
//在线程中完成数据加载
new Thread(new Runnable() {
publicvoid run() {
try{
List<Contact>data = ContactService.getContacts();
handler.sendMessage(handler.obtainMessage(22,data));
}catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
@Override
protectedvoid onDestroy() {
for(Filefile : cache.listFiles()){
file.delete();
}
cache.delete();
super.onDestroy();
}
}
4. 自定义适配器
/DataAsyncLoad/src/cn/itcast/adapter/ContactAdapter.java
package cn.itcast.adapter;
import java.io.File;
import java.util.List;
import cn.itcast.asyncload.R;
import cn.itcast.domain.Contact;
importcn.itcast.service.ContactService;
import android.content.Context;
import .Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class ContactAdapter extendsBaseAdapter {
privateList<Contact> data;
privateint listviewItem;
privateFile cache;
LayoutInflaterlayoutInflater;
publicContactAdapter(Context context, List<Contact> data, int listviewItem,File cache) {
this.data= data;
this.listviewItem= listviewItem;
this.cache= cache;
layoutInflater= (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* 得到数据的总数
*/
publicint getCount() {
returndata.size();
}
/**
* 根据数据索引得到集合所对应的数据
*/
publicObject getItem(int position) {
returndata.get(position);
}
publiclong getItemId(int position) {
returnposition;
}
//每显示一个条目就调用一次该方法
//显示第二页时会使用第一页创建的缓存
publicViewgetView(intposition, View convertView, ViewGroup parent) {
ImageViewimageView = null;
TextViewtextView = null;
if(convertView== null){
convertView= layoutInflater.inflate(listviewItem, null);
imageView= (ImageView) convertView.findViewById(R.id.imageView);
textView= (TextView) convertView.findViewById(R.id.textView);
convertView.setTag(newDataWrapper(imageView, textView));
}else{
DataWrapperdataWrapper = (DataWrapper) convertView.getTag();
imageView= dataWrapper.imageView;
textView= dataWrapper.textView;
}
Contactcontact = data.get(position);
textView.setText(contact.name);
asyncImageLoad(imageView,contact.image);//异步加载图片
returnconvertView;
}
private voidasyncImageLoad(ImageView imageView, String path) {
AsyncImageTask asyncImageTask = newAsyncImageTask(imageView);
asyncImageTask.execute(path);
}
private final classAsyncImageTaskextends AsyncTask<String,Integer, Uri>{
private ImageView imageView;
publicAsyncImageTask(ImageView imageView) {
this.imageView= imageView;
}
protectedUridoInBackground(String...params) {//子线程中执行的
try{
returnContactService.getImage(params[0], cache);
}catch (Exception e) {
e.printStackTrace();
}
returnnull;
}
protectedvoid onPostExecute(Uri result) {//运行在主线程
if(result!=null&& imageView!= null)
imageView.setImageURI(result);
}
}
/*//不使用这种子线程的方式是因为,每显示一个条目就调用一次getView,就会创建一个线程
privatevoidasyncImageLoad(finalImageView imageView, final String path) {
finalHandler handler = new Handler(){
publicvoid handleMessage(Message msg) {//运行在主线程中
Uriuri = (Uri)msg.obj;
if(uri!=null&& imageView!= null)
imageView.setImageURI(uri);
}
};
Runnablerunnable = new Runnable() {
publicvoid run() {
try{
Uriuri = ContactService.getImage(path, cache);
handler.sendMessage(handler.obtainMessage(10,uri));
}catch (Exception e) {
e.printStackTrace();
}
}
};
newThread(runnable).start();
}
*/
privatefinal class DataWrapper{
publicImageView imageView;
publicTextView textView;
publicDataWrapper(ImageView imageView, TextView textView) {
this.imageView= imageView;
this.textView= textView;
}
}
}
5. JavaBean
/DataAsyncLoad/src/cn/itcast/domain/Contact.java
package cn.itcast.domain;
public class Contact {
publicint id;
publicString name;
publicString image;
publicContact(int id, String name, String image) {
this.id= id;
this.name= name;
this.image= image;
}
publicContact(){}
}
6. 从网络中获取数据的业务Bean
/DataAsyncLoad/src/cn/itcast/service/ContactService.java
package cn.itcast.service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import .HttpURLConnection;
import .URL;
import java.util.ArrayList;
import java.util.List;
importorg.xmlpull.v1.XmlPullParser;
import .Uri;
import android.util.Xml;
import cn.itcast.domain.Contact;
import cn.itcast.utils.MD5;
public class ContactService {
/**
* 获取联系人
* @return
*/
publicstatic List<Contact> getContacts() throws Exception{
Stringpath = "http://192.168.10.100:8080/web/list.xml";
HttpURLConnectionconn = (HttpURLConnection) new URL(path).openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
if(conn.getResponseCode()== 200){
returnparseXML(conn.getInputStream());
}
returnnull;
}
privatestatic List<Contact>parseXML(InputStream xml) throws Exception{
List<Contact>contacts = new ArrayList<Contact>();
Contactcontact = null;
XmlPullParserpullParser = Xml.newPullParser();
pullParser.setInput(xml,"UTF-8");
intevent = pullParser.getEventType();
while(event!= XmlPullParser.END_DOCUMENT){
switch(event) {
caseXmlPullParser.START_TAG:
if("contact".equals(pullParser.getName())){
contact= new Contact();
contact.id= new Integer(pullParser.getAttributeValue(0));
}elseif("name".equals(pullParser.getName())){
contact.name= pullParser.nextText();
}elseif("image".equals(pullParser.getName())){
contact.image =pullParser.getAttributeValue(0);
}
break;
caseXmlPullParser.END_TAG:
if("contact".equals(pullParser.getName())){
contacts.add(contact);
contact= null;
}
break;
}
event= pullParser.next();
}
returncontacts;
}
/**
*获取网络图片,如果图片存在于缓存中,就返回该图片,否则从网络中加载该图片并缓存起来
* @param path 图片路径
* @return
*/
publicstatic UrigetImage(Stringpath, File cacheDir) throws Exception{// path -> MD5 ->32字符串.jpg
FilelocalFile = new File(cacheDir,MD5.getMD5(path)+path.substring(path.lastIndexOf(".")));
if(localFile.exists()){
returnUri.fromFile(localFile);
}else{
HttpURLConnectionconn = (HttpURLConnection) new URL(path).openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
if(conn.getResponseCode()== 200){
FileOutputStreamoutStream = new FileOutputStream(localFile);
InputStreaminputStream = conn.getInputStream();
byte[]buffer = new byte[1024];
intlen = 0;
while((len = inputStream.read(buffer)) != -1){
outStream.write(buffer,0, len);
}
inputStream.close();
outStream.close();
returnUri.fromFile(localFile);
}
}
returnnull;
}
}
7. MD5
/DataAsyncLoad/src/cn/itcast/utils/MD5.java
package cn.itcast.utils;
import java.security.MessageDigest;
importjava.security.NoSuchAlgorithmException;
public class MD5 {
publicstatic String getMD5(String content) {
try{
MessageDigestdigest = MessageDigest.getInstance("MD5");
digest.update(content.getBytes());
returngetHashString(digest);
}catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
returnnull;
}
private static String getHashString(MessageDigest digest) {
StringBuilder builder = newStringBuilder();
for (byte b : digest.digest()) {
builder.append(Integer.toHexString((b >> 4) & 0xf));
builder.append(Integer.toHexString(b & 0xf));
}
return builder.toString();
}
}