Android_Handler

Handler的基本概念

当有一段处理耗时比较漫长的时候,我们就需要用线程来处理。Android中是用Handler类来处理线程的。


与Handler绑定的有两个队列,一个为消息队列,另一个为线程队列。Handler可以通过这两个队列来分别:

  1. 【消息队列】发送、接受、处理消息
  2. 【线程队列】启动、结束、休眠线程

Android OS中,一个进程(即一个App)被创建之后,主线程(可理解为当前Activity)创建一个消息队列,这个消息队列维护所有顶层应用对象(Activities, Broadcast receivers等)以及主线程创建的窗口。

你可以在主线程中创建新的线程,这些新的线程都通过Handler与主线程进行通信。通信通过新线程调用 Handler的post()方法和sendMessage()方法实现,分别对应功能:

  1. post()  将一个线程加入线程队列(当然,post()方法还有一些变体,比如postDelayed()、postAtTime()分别用来延迟发送、定时发送)
  2. sendMessage() 发送一个消息对象到消息队列


消息的处理,在主线程的Handler对象中进行。具体处理过程,需要在new Handler对象时使用匿名内部类重写Handler的handleMessage(Message msg)方法。

线程加入线程队列可以在主线程中也可以在子线程中进行,但都要通过主线程的Handler对象调用post()。


Handler的基本使用方法

我们用一个简单的例子(隔3000毫秒就打印一行“UpdateThread”的Log)来说明Handler的基本使用方法,一些说明就直接写在注释里头了。


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" >

    <Button
        android:id="@+id/startButton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="start" />

    <Button
        android:id="@+id/endButton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="end" />

</LinearLayout>

Handler_01Activity.java

package com.tianjf;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class Handler_01Activity extends Activity {

	private Button startButton = null;
	private Button endButton = null;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		startButton = (Button) findViewById(R.id.startButton);
		endButton = (Button) findViewById(R.id.endButton);
		startButton.setOnClickListener(new StartButtonListener());
		endButton.setOnClickListener(new EndButtonListener());
	}

	class StartButtonListener implements OnClickListener {
		@Override
		public void onClick(View v) {
			// 调用Handler的post方法,将要执行的线程对象添加到队列当中
			handler.post(updateThread);
		}
	}

	class EndButtonListener implements OnClickListener {
		@Override
		public void onClick(View v) {
			//调用Handler的removeCallbacks()方法,删除队列当中未执行的线程对象 
			handler.removeCallbacks(updateThread);
		}
	}

	// 创建一个Handler对象
	Handler handler = new Handler();
	// Java实现线程有两种方法:①继承Thread类 ②实现Runnable接口
	/*
	 * 以下是用匿名内部类的方式实现Runnable接口 
	 * 		接口 XXXX = new 接口() {
	 * 				@Override 
	 * 				实现接口的方法
	 * 		 } 
	 * 这种new一个接口的情况在Android中还是很常见的(省去了先创建一个类继承接口,再new这个创建的类这一步骤)
	 */
	// 将要执行的操作写在线程对象的run方法当中
	Runnable updateThread = new Runnable() {

		@Override
		public void run() {
			System.out.println("UpdateThread");
			//在run方法内部,执行postDelayed或者是post方法
			//postDelayed方法的作用是:将要执行的线程对象放入到队列当中,待时间结束后,运行制定的线程对象
			//第一个参数是Runnable类型:将要执行的线程对象
			//第二个参数是long类型:延迟的时间,以毫秒为单位 
			handler.postDelayed(updateThread, 3000);
		}
	};
}

本例子涉及到的知识点:

  • 线程接口Runnable
  • 线程接口Runnable中的run()方法
  • 管理Runnable接口的Handler类
  • Handler类中的post()方法和removeCallbacks()方法

使用Handler更新ProgressBar

同样用一个简单的例子来说明怎么使用Handler来更新ProgressBar。


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"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:visibility="gone" />

    <Button
        android:id="@+id/startButton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="start" />

</LinearLayout>

Handler_02Activity.java

package com.tianjf;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;

public class Handler_02Activity extends Activity {

	ProgressBar progressBar = null;
	Button startButton = null;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		progressBar = (ProgressBar) findViewById(R.id.bar);
		startButton = (Button) findViewById(R.id.startButton);
		startButton.setOnClickListener(new ButtonListener());
	}

	class ButtonListener implements OnClickListener {

		@Override
		public void onClick(View v) {
			progressBar.setVisibility(View.VISIBLE);
			handler.post(updateThread);
		}
	}

	// 使用匿名内部类来复写Handler当中的handleMessage方法
	Handler handler = new Handler() {

		// handleMessage方法会从消息队列中依次取消息,取出来之后做一系列处理
		@Override
		public void handleMessage(Message msg) {
			progressBar.setProgress(msg.arg1);
			handler.post(updateThread);
		}

	};
	// 线程类,该类使用匿名内部类的方式进行声明
	Runnable updateThread = new Runnable() {
		int i = 0;

		@Override
		public void run() {
			System.out.println("Begin Thread");
			i = i + 10;
			// 得到一个消息对象,Message类是有Android操作系统提供
			Message msg = handler.obtainMessage();
			// 将msg对象的arg1参数的值设置为i,用arg1和arg2这两个成员变量传递消息,优点是系统性能消耗较少
			// 还可以对msg对象的obj参数设值,例如msg.obj = "abc"
			// 另外还可以用msg对象的setData()方法设值,这个要涉及到Bundle类,请看例子Handler_03
			msg.arg1 = i;
			try {
				// 设置当前显示睡眠1秒
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// 将msg对象加入到消息队列当中,消息能够被handleMessage(Message msg)方法接收到
			handler.sendMessage(msg);
			if (i == 100) {
				// 如果当i的值为100时,就将线程对象从handler当中移除
				handler.removeCallbacks(updateThread);
			}
		}
	};
}

本例子涉及到的知识点:

  • 消息类:Message
  • handler.sendMessage(msg);将一个消息对象加入到消息队列中
  • 在new Handler对象时使用匿名内部类重写Handler的handleMessage(Message msg)方法,来依次取出消息队列中的消息,然后对这些消息做处理


意想不到的事情:上面例子中的创建出来的线程居然还是在主线程运行的

你可能会说:不可能吧。但是我要说:看下面的例子,我们来见证奇迹。

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;

public class HandlerTest extends Activity {
	private Handler handler = new Handler();
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		handler.post(runnable);
		setContentView(R.layout.main);
		System.out.println("activity--->" + Thread.currentThread().getId());
		System.out.println("activityname--->" + Thread.currentThread().getName());
	}
	
	Runnable runnable = new Runnable(){
		@Override
		public void run() {
			System.out.println("handler--->" + Thread.currentThread().getId());
			System.out.println("handlername--->" + Thread.currentThread().getName());
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	};
}
我们将setContentView(R.layout.main);放在handler.post(runnable);之后是为什么呢?

我们知道setContentView(R.layout.main);执行完了之后,画面的控件才会显示出来,而我们又在Runnable里面设置了Thread.sleep(10000);让程序睡10秒,如果创建出来的线程是在主线程运行的话,那么程序启动后会过10秒才看到画面,如果是两个线程的话,那么画面会立即显示。

除了通过UI来确认,我们还可以通过程序中的Log来确认。

下面是见证奇迹的时刻:

Log很明确的显示,都是在主线程执行的。如果你还是不相信,那么请看看UI,看是不是过了10秒画面才显示出来。


因为handler.post(runnable);是直接执行runnable的run()方法,没有另外起一个线程,所以还是在主线程执行。

我们把handler.post(runnable);注释掉,换成Java的标准启动子线程的方法

Thread thread = new Thread(runnable);
thread.start();
运行之后会发现

已经另起了一个线程了,而且画面也是立即显示。


上面的程序仅仅是另外启动了一个子线程,但是还没有讲到怎么传递消息。

如果要另起一线程并想传递消息并处理消息,那么就要用到Looper类,这个是用来封装消息循环和消息队列的一个类,也就是可以循环从消息队列中取消息。Looper会一直从消息队列中取消息并处理消息,如果没有消息了,线程就处于休眠状态。

在Android中,我们很少创建一个Looper对象,因为Android框架中的HandlerThread类本身就具备了循环处理消息的功能,我们只要调用HandlerThread类的对象的getLooper方法就可以了。

具体看下面代码

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;

public class HandlerTest2 extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		System.out.println("Activity-->" + Thread.currentThread().getId());
		HandlerThread handlerThread = new HandlerThread("handler_thread");
		handlerThread.start();
		MyHandler myHandler = new MyHandler(handlerThread.getLooper());
		Message msg = myHandler.obtainMessage();
		Bundle b = new Bundle();
		b.putInt("age", 20);
		b.putString("name", "Jhon");
		msg.setData(b);
		msg.sendToTarget();
	}
	
	class MyHandler extends Handler{
		public MyHandler(){
			
		}
		public MyHandler(Looper looper){
			super(looper);
		}
		@Override
		public void handleMessage(Message msg) {
			Bundle b = msg.getData();
			int age = b.getInt("age");
			String name = b.getString("name");
			System.out.println("age is " + age + ", name is" + name);
			System.out.println("Handler--->" + Thread.currentThread().getId());
			System.out.println("handlerMessage");
		}
	}
}

需要了解的知识点如下

  • Looper类

             Handler其实可以看做是一个工具类,用于向消息队列中插入消息的。如果想让子线程可以循环处理消息队列中的消息,那么必须要用到一个类:Looper

             Looper是封装消息循环和消息队列的一个类,线程默认是没有消息循环的,Looper用于在线程中建立一个消息循环

             也就是说Looper可以循环从消息队列中取消息。Looper会一直从消息队列中取消息并处理消息,如果没有消息了,线程就处于休眠状态。

             在Android中,我们很少创建一个Looper对象,因为Android框架中的HandlerThread类本身就具备了循环处理消息的功能。我们只要调用HandlerThread类的对象的getLooper方法就可以了。需要注意的是,只有在handlerThread.start();之后才能调用getLooper方法,否在取出来的Looper对象为null。

  • public MyHandler(Looper looper)构造方法

             这个构造函数目的就是接收一个Looper对象作为参数,那么这个Looper对象就绑定到了这个myHandler上面。我们在程序里面有这么一句话MyHandler myHandler = new MyHandler(handlerThread.getLooper());,传进去的looper是handlerThread里面的looper,既然这个looper绑定到了myHandler上面,那么也就相当于myHandler绑定到了handlerThread这个线程上面,那么也就相当于myHandler的一切操作就在handlerThread这个子线程上执行,也就实现了多线程。

  • 另外一种传递消息的方法

              前面我们介绍了两种传递消息的方法

  1. msg.arg1 = i;
  2. msg.obj = "abc";

第一种是传递int类型的方法,用arg1和arg2这两个成员变量传递消息,优点是系统性能消耗较少            

第二种是传递object类型的方法

此例中用了第三种方法

Bundle b = new Bundle();
b.putInt("age", 20);
b.putString("name", "Jhon");
msg.setData(b);
myHandler.sendMessage(msg);
msg.sendToTarget();

此方法用于传递大量的数据

  • Bundle类

msg.setData(b);方法传递的是Bundle对象。其实Bundle就是一个数据存储的工具,我们可以将数据存储到Bundle里面然后传递到另外一个地方。

Bundle和HashMap类似,不同点如下

  1. Bundle的key都是String类型的
  2. Bundel的value是基本类型以及基本类型的数组

            

好了,over。

你可能感兴趣的:(thread,android,layout,Class,button)