Android应用程序的消息处理机制由消息循环、消息发送和消息处理三个部分组成的。而Handler、HandlerThread、AsyncTask类在消息处理中极其重要,它扮演者负责处理消息的角色。
1. Handler :
废话不多说了,先看下面的代码:
- package com.feixun.hu.hd;
- import android.app.Activity;
- import android.os.Bundle;
- import android.os.Handler;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.EditText;
- public class HandlerDemo extends Activity
- {
- private int count = 0;
- private EditText edit;
- private Button start, stop;
- private static final String TAG = "HandlerDemo";
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- //打Log,查看主线程ID
- Log.i(TAG, "MainThreadId" + Thread.currentThread().getId());
- edit = (EditText)findViewById(R.id.edit);
- start = (Button)findViewById(R.id.start);
- stop = (Button)findViewById(R.id.stop);
- //为按钮绑定监听
- start.setOnClickListener(new OnClickListener()
- {
- @Override
- public void onClick(View v)
- {
- //执行线程run方法
- mHandler.post(r);
- }
- });
- stop.setOnClickListener(new OnClickListener()
- {
- @Override
- public void onClick(View v)
- {
- //移除线程run方法
- mHandler.removeCallbacks(r);
- }
- });
- }
- private Handler mHandler = new Handler();
- private Runnable r = new Runnable()
- {
- @Override
- public void run()
- {
- // TODO Auto-generated method stub
- count++;
- edit.setText("" + count);
- //每隔2秒执行一次run方法
- mHandler.postDelayed(r, 2000);
- //打Log查看线程所在ID
- Log.i(TAG, "ThreadID" + Thread.currentThread().getId());
- }
- };
- }
上面的代码主要通过hanlder类是实现隔两秒count自增1的计数功能,然后在edit(EditText对象)中显示不断增加的计数值count;点击start按钮时会启动计数,点击stop按钮时停止计数; 上面的代码主要通过hanlder类是实现隔两秒count自增1的计数功能,然后在edit(EditText对象)中显示不断增加的计数值count;点击start按钮时会启动计数,点击stop按钮时停止计数;
效果图如下:
通过打Log可知,run方法所在的线程Id和onCreate方法所在的线程Id是相同的,也就是说,该run方法是在主线程中实现的。所以,Handler类的处理实现始终是在主线程上实现。但是,有的时候,需要开辟一个新的子线程处理一些超时的操作,避免你的应用程序主线程(UI线程)自己去处理这些超时的操作,从而出现ANR(Application Not Responding)异常。这时候就需要用到HandlerThread、AsyncTask来处理了。
2. HandlerThread :
在介绍HandlerThread之前,先给大家上代码,如下:
- package com.feixun.hu.htd;
- import android.app.Activity;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.HandlerThread;
- import android.os.Looper;
- import android.os.Message;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.ProgressBar;
- public class HandlerThreadDemo extends Activity
- {
- private ProgressBar bar;
- private BarHandler mHandler;
- private Runnable BarThread;
- private static final String TAG = "HandlerThreadDemo";
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- //打log,查看主线程id
- Log.i(TAG, "Main Thread Id:" + Thread.currentThread().getId());
- //开启一个新的异步线程
- HandlerThread handlerThread = new HandlerThread("handler_thread");
- //在调用getLooper方法之前必须先调用start方法启动线程
- handlerThread.start();
- //异步线程加入循环消息队列处理机制
- mHandler = new BarHandler(handlerThread.getLooper());
- bar = (ProgressBar)findViewById(R.id.bar);
- Button start = (Button)findViewById(R.id.start);
- //为按钮绑定监听
- start.setOnClickListener(new OnClickListener()
- {
- @Override
- public void onClick(View v)
- {
- bar.setVisibility(ProgressBar.VISIBLE);
- mHandler.post(BarThread);
- }
- });
- BarThread = new Runnable()
- {
- int count = 0;
- @Override
- public void run()
- {
- //打log,查看该方法所在的线程id
- Log.i(TAG, "Runnable Thread Id:" + Thread.currentThread().getId());
- //进度条每秒增加5分值
- count += 5;
- //创建消息发送至handlerMessage方法处理
- Message msg = new Message();
- Bundle data = new Bundle();
- data.putInt("count", count);
- msg.setData(data);
- mHandler.sendMessage(msg);
- //线程休眠一秒
- try
- {
- Thread.sleep(1000);
- } catch (Exception e)
- {
- e.printStackTrace();
- }
- }
- };
- }
- private class BarHandler extends Handler
- {
- public BarHandler(Looper looper)
- {
- super(looper);
- }
- @Override
- public void handleMessage(Message msg)
- {
- super.handleMessage(msg);
- bar.setProgress(msg.getData().getInt("count"));
- mHandler.post(BarThread);
- if(msg.getData().getInt("count") >= 100)
- {
- mHandler.removeCallbacks(BarThread);
- }
- }
- };
- }
上面的Demo主要实现点击start按钮,进度条展现出来,以5分值的进度不断增加显示,直到count大于等于100,才终止进度。
main.xml文件如下:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <ProgressBar
- android:id="@+id/bar"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:max="100"
- style="@android:style/Widget.ProgressBar.Horizontal"
- android:visibility="gone"/>
- <Button
- android:id="@+id/start"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/start"/>
- </LinearLayout>
效果图如下:
上面的代码,通过打Log可知两个方法所在的线程Id不一样,也就是说,通过HandlerThread类实现开启一个新的异步线程处理循环消息队列机制,值得注意的是,handlerMessage方法是在主线程中的,该方法可以接收来着异步线程run()方法里发过来的消息(通过hanlder.senMessage()方法发送),然后通过接收的消息来更新相关的UI组件(亲们,要谨记,UI组件的相关处理操作只能在主线程中)。这样就实现超时的操作在另一个开辟的子线程里,而UI组件才相关操作在主线程里,从而避免ANR异常。
3. AsyncTask :
我们开发应用程序的时候,经常中需要创建一个子线程来在后台执行一个特定的计算任务,而在这个任务计算的过程中,需要不断地将计算进度或者计算结果展现在应用程序的界面中。典型的例子是从网上下载文件,为了不阻塞应用程序的主线程,我们开辟一个子线程来执行下载任务,子线程在下载的同时不断地将下载进度在应用程序界面上显示出来,这样做出来程序就非常友好。由于子线程不能直接操作应用程序的UI,因此,这时候,我们就可以通过往应用程序的主线程中发送消息来通知应用程序主线程更新界面上的下载进度。因为类似的这种情景在实际开发中经常碰到,Android系统为开发人员提供了一个异步任务类(AsyncTask)来实现上面所说的功能,即它会在一个子线程中执行计算任务,同时通过主线程的消息循环来获得更新应用程序界面的机会。
实现AsyncTask类的回调方法介绍如下:
i. onPreExecute(),该回调函数在任务被执行之后立即由主线程(UI线程)调用。这个步骤通常用来建立任务,在用户接口(UI)上显示进度条。(准备运行)
ii. doInBackground(Params...),该回调函数由后台线程在onPreExecute()方法执行结束后立即调用,即该函数不在主线程中,而是在开启的一个异步线程。通常在这里执行耗时的后台计算。计算的结果必须由该函数返回,返回值被传递到onPostExecute()中处理。在该函数内也可以使用publishProgress(Progress...)来发布一个或多个进度单位(unitsof progress)。这些值将会在onProgressUpdate(Progress...)中被发布到UI线程。(后台运行)
iii.onProgressUpdate(Progress...),该函数由UI线程在publishProgress(Progress...)方法调用完后被调用。一般用于动态地显示一个进度条。实现进度更新。
iv. onPostExecute(Result),当后台计算结束后调用。后台计算的结果会被作为参数传递给这一函数处理。实现完成后台任务。
v. onCancelled (),在调用AsyncTask的cancel()方法时调用,从而实现取消任务。
AsyncTask构造函数的三个模板参数:
i. Params,传递给后台任务的参数类型。
ii. Progress,后台计算执行过程中,进步单位(progress units)的类型。(就是后台程序已经执行了百分之几了。)
ii. Result, 后台执行返回的结果的类型。
注:AsyncTask并不总是需要使用上面的全部3种类型。标识不使用的类型很简单,只需要使用Void类型即可。
ok,仅仅介绍回调方法还是不够,上代码,通过代码分析讲解才深刻,Demo代码如下:
- package com.feixun.com.atd;
- import org.apache.http.HttpResponse;
- import org.apache.http.client.HttpClient;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.impl.client.DefaultHttpClient;
- import android.app.Activity;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.AsyncTask;
- import android.os.Bundle;
- 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 AsyncTaskDemo extends Activity
- {
- private ImageView image;
- private ProgressBar bar;
- private Button get;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- image = (ImageView)findViewById(R.id.image);
- bar = (ProgressBar)findViewById(R.id.bar);
- get = (Button)findViewById(R.id.btn);
- get.setOnClickListener(new OnClickListener()
- {
- @Override
- public void onClick(View v)
- {
- GetPictureTask task = new GetPictureTask();
- //执行下载网络图片,传入参数为图片所在的URL地址
- task.execute("http://csdnimg.cn/www/images/csdnindex_logo.gif");
- }
- });
- }
- private class GetPictureTask extends AsyncTask<String, Integer, Bitmap>
- {
- //在后台执行之前被调用,在UI线程中执行
- @Override
- protected void onPreExecute()
- {
- image.setImageBitmap(null);
- bar.setProgress(0);
- }
- @Override
- protected Bitmap doInBackground(String... params)
- {
- // TODO Auto-generated method stub
- //该方法执行后,将会调用onProgressUpdate(Integer... values) 方法
- publishProgress(0);
- //创建HttpCilent对象
- HttpClient hc = new DefaultHttpClient();
- publishProgress(30);
- //通过传入的参数URL字符串值,发送get请求
- HttpGet hg = new HttpGet(params[0]);
- Bitmap bm = null ;
- try
- {
- //响应请求
- HttpResponse hr = hc.execute(hg);
- //根据响应的内容获取Bitmap对象
- bm = BitmapFactory.decodeStream(hr.getEntity().getContent());
- } catch (Exception e)
- {
- // TODO: handle exception
- e.printStackTrace();
- }
- return bm;
- }
- @Override
- protected void onProgressUpdate(Integer... values)
- {
- // TODO Auto-generated method stub
- super.onProgressUpdate(values);
- bar.setProgress(values[0]);
- }
- @Override
- protected void onPostExecute(Bitmap result)
- {
- // TODO Auto-generated method stub
- super.onPostExecute(result);
- if(result != null)
- {
- //根据获取返回的位图Bitmap获取图片
- image.setImageBitmap(result);
- Toast.makeText(AsyncTaskDemo.this, "成功获取图片",
- Toast.LENGTH_LONG).show();
- }
- else
- {
- Toast.makeText(AsyncTaskDemo.this, "获取图片失败",
- Toast.LENGTH_LONG).show();
- }
- }
- //在UI线程执行
- @Override
- protected void onCancelled()
- {
- // TODO Auto-generated method stub
- super.onCancelled();
- bar.setProgress(0);
- }
- @Override
- protected void onCancelled(Bitmap result)
- {
- // TODO Auto-generated method stub
- super.onCancelled(result);
- }
- }
- }
上面的代码实现点击一个按钮下载网络图片,然后以进度条的形式显示图片下载的进度。
main.xml文件代码:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <ProgressBar
- android:id="@+id/bar"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:max="100"
- style="@android:style/Widget.ProgressBar.Horizontal"/>
- <Button
- android:id="@+id/btn"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/get"/>
- <ImageView
- android:id="@+id/image"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- />
- </LinearLayout>
注:最后,别忘了在manifest.xml文件里注册权限<uses-permission android:name="android.permission.INTERNET"/>