Android Handler
【转载】
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://lichen.blog.51cto.com/697816/486402
此文是关于Handler的。 Handler主要接受子线程发送的数据, 并用此数据配合主线程更新UI。 当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) ,主线程为管理界面中的UI控件,进行事件分发, 比如说,你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作。如果此时需要一个耗时的操作,例如: 联网读取数据,或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象, 如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 "强制关闭"。
这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,Android主线程不是线程安全的,也就是说,更新UI只能在主线程 中更新,子线程中操作是危险的。这个时候,Handler就出现了,来解决这个复杂的问题。Handler运行在主线程中(UI线程中),它与子线程可以 通过Message对象来传递数据,这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传递)Message对 象(里面包含数据),把这些消息放入主线程队列中,配合主线程进行更新UI。
Handler 是 android 提供的对于异步消息处理的方案。 Handler 的特点是与调用者处于同一线程,如果 Handler 里面做耗时的动作,调用者线程会阻塞。 Android UI 操作不是线程安全的,并且这些操作必须在 UI 线程中执行。
所以, Handler 经常被用来在另外的线程中更新 UI 界面。因为 UI 操作必须在 UI 线程中完成,可以通过 Handler 在别的线程中向 UI 线程发送刷新消息, UI 线程收到消息后执行相关操作。主要用到下面两个函数:
public final boolean sendMessage (Message msg) 发送消息
public void handleMessage(Message msg) 处理消息
Handler 对于 Message 的处理不是并发的。一个 Looper 只有处理完一条 Message 才会读取下一条,所以消息的处理是阻塞形式的。
Handler一些特点:Handler可以分发Message对象和Runnable对象到主线程中, 每个Handler实例,都会绑定到创建他的线程中(一般是位于主线程),它有两个作用: (1): 安排消息或Runnable 在某个主线程中某个地方执行, (2)安排一个动作在不同的线程中执行。
Handler中分发消息经常使用post(Runnable)和sendMessage(Message)方法。
先是sendMessage的例子:
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:id="@+id/startButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="开始演示" android:onClick="startIt" /> <ProgressBar android:id="@+id/bar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:max="500" android:progress="0" /> <TextView android:id="@+id/text" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="演示信息:" /> </LinearLayout>
Demo.java
package com.zhouzijing.android.demo; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; public class Demo extends Activity{ private final static String TAG = "DEMO"; private TextView textView; private Button startButton; private ProgressBar progressBar; private MyHandler myHandler; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); textView=(TextView)findViewById(R.id.text); startButton = (Button)findViewById(R.id.startButton); progressBar=(ProgressBar)findViewById(R.id.bar); progressBar.setMax(100); myHandler=new MyHandler(); } /** * [开始演示]的按钮事件. * @param v */ public void startIt(View v) { Button btn = (Button)v; //禁止演示按钮重复点击 btn.setClickable(false); btn.setText("演示中..."); progressBar.setProgress(0); textView.setText("演示信息:"); new Thread(new MyThread(1)).start(); } //在对UI进行更新时,执行时所在的线程为主UI线程 //继承Handler类时,必须重写handleMessage方法 class MyHandler extends Handler{ public MyHandler(){ } public MyHandler(Looper l){ super(l); } @Override public void handleMessage(Message msg) { //执行接收到的通知,此时执行的顺序是按照队列进行,即先进先出 Log.i(TAG,"Handler--The ThreadId is: "+Thread.currentThread().getId()); int what = msg.what; //根据message的what属性来进行处理 switch(what){ case 100: Bundle b=msg.getData(); String textStr1=b.getString("textStr"); textView.append(textStr1);//更改TextView中的值 int barValue=b.getInt("barValue"); progressBar.setProgress(barValue);//更改进度条当中的值 if(barValue==100){ //允许演示按钮点击 startButton.setText("演示结束,可以点击重新演示"); startButton.setClickable(true); } break; default: //其他message扔回上级处理. super.handleMessage(msg); } } } //该线程将会在单独的线程中运行 class MyThread implements Runnable{ int index=1; public MyThread(int index) { this.index = index; } @Override public void run() { while(index<11){ Log.i(TAG,"Thread--The ThreadId is: "+Thread.currentThread().getId()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } int what = 100; //从系统message池中取出1个message对象,并设置what属性. Message msg = myHandler.obtainMessage(what); Bundle b=new Bundle(); b.putString("textStr", "线程运行"+index+"次"); b.putInt("barValue", index*10); index++; msg.setData(b); myHandler.sendMessage(msg);//通过sendMessage向Handler发送更新UI的消息 } } } }
简单的说,Activity的onCreate方法里启动一个线程,在这个线程的run方法里使用一个Message对象并使用Handler的 sendMessage方法发送到队列中,最后在Activity里new一个Handler的内部类实现handMessage方法,使用这个方法把队 列中的Message对象取出来以实现异步操作。
得到Message对象的方法:(1)new Message();(2)handler对象的obtainMessage();前者需要新建一个Message对象,而后者直接从系统的Message池中获取1个(不用创建对象)。建议使用后者!
然后是post的例子,这里稍微说一下,直接使用new Handler().post(Runnable)这样的写法并没有新开线程。
xml配置文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <EditText android:id="@+id/edtDownloadUrl" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="http://www.zhouzijing.com/" /> <Button android:id="@+id/btnHandlerPostRunnable" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="开始下载" android:onClick="startIt" /> <ProgressBar android:id="@+id/pBarDownload" android:layout_width="fill_parent" android:layout_height="wrap_content" style="?android:attr/progressBarStyleHorizontal" /> <TextView android:id="@+id/tvProgressBarPercent" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>
由于下面代码要访问网络和在sdcard上写文件,因此需要在AndroidManifest.xml配置权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
代码文件:
package com.zhouzijing.android.demo; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import org.apache.http.HttpEntity; 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.app.AlertDialog; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; public class HandlerPostRunnable extends Activity { private EditText edtDownloadUrl; private ProgressBar pBarDownload; private TextView tvProgressBarPercent; private Button btnHandlerPostRunnable; private int fileSize = 0; private int progress = 0; private final String TAG = "HandlerPostRunnable"; private Handler handler = new Handler(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.handler_post_runnable); //定义组件 edtDownloadUrl = (EditText)findViewById(R.id.edtDownloadUrl); pBarDownload = (ProgressBar)findViewById(R.id.pBarDownload); tvProgressBarPercent = (TextView)findViewById(R.id.tvProgressBarPercent); btnHandlerPostRunnable = (Button)findViewById(R.id.btnHandlerPostRunnable); } //开始演示-按钮事件 public void startIt(View v) { Button btn = (Button)v; btn.setText("下载中..."); btn.setClickable(false); downloadFile(edtDownloadUrl.getText().toString()); } /** * 获取文件名. * @param url * @return */ private String getFileNameFromUrl(String url) { String fileName = url.substring(url.lastIndexOf("/") + 1); return fileName; } /** * 下载文件. * @param url */ private void downloadFile(final String url) { new Thread() { public void run() { HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(url); HttpResponse response = null; FileOutputStream fos = null; try { response = client.execute(get); HttpEntity entity = response.getEntity(); InputStream is = entity.getContent(); if (is != null) { File file = new File( Environment.getExternalStorageDirectory(), getFileNameFromUrl(url)); fos = new FileOutputStream(file); fileSize = (int)entity.getContentLength(); progress = 0; downloadStart(); byte[] buf = new byte[1024]; int ch = -1; while ((ch = is.read(buf)) != -1) { fos.write(buf, 0, ch); progress = progress+ch; downloading(); } } fos.flush(); fos.close(); fos = null; downloadFinished(); } catch (Exception e) { Log.e(TAG, e.getMessage(), e); downloadError(e); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { Log.e(TAG, e.getMessage(), e); } fos = null; } } } }.start(); } // 下载开始 private void downloadStart() { handler.post(new Runnable() { public void run() { pBarDownload.setMax(fileSize); pBarDownload.setProgress(0); } }); } /** * 设置下载进度百分比, */ private void setDownloadPercent() { float percent = (float)progress/(float)fileSize*100; int iPercent = (int)percent; tvProgressBarPercent.setText(String.valueOf(iPercent)+"%"); } // 下载中. private void downloading() { handler.post(new Runnable() { public void run() { pBarDownload.setProgress(progress); setDownloadPercent(); } }); } // 下载成功结束 private void downloadFinished() { handler.post(new Runnable() { public void run() { pBarDownload.setProgress(fileSize); btnHandlerPostRunnable.setText("下载成功,点击再开始下载"); btnHandlerPostRunnable.setClickable(true); setDownloadPercent(); new AlertDialog.Builder(HandlerPostRunnable.this) .setTitle("提示").setMessage("下载成功") .setPositiveButton("确定", null).show(); } }); } // 下载失败 private void downloadError(final Exception err) { handler.post(new Runnable() { public void run() { btnHandlerPostRunnable.setText("下载失败,点击再开始下载"); btnHandlerPostRunnable.setClickable(true); new AlertDialog.Builder(HandlerPostRunnable.this) .setTitle("提示").setMessage("下载失败:"+err.getMessage()) .setPositiveButton("确定", null).show(); } }); } }
代码运行结果如下图: