Android中的Handler机制

本文详细介绍了Android中Handler机制的使用方法及原理,包括如何利用Handler更新UI、在线程间传递消息,以及Handler与Looper、MessageQueue的关系。还探讨了HandlerThread的作用,并展示了主线程与子线程间的通讯方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      我们知道在android中是不能在子线程中更新UI的,为此官方提供了Handler机制和AsyncTask 异步任务。当然我个人现在一般使用AsyncTask。两者的区别与介绍我在另外一篇博客里面也做了介绍l连接为   Android中同步与异步的问题 但是学习Handler机制也是很有必要的。有助于我们理解线程之间通讯问题等等。而且在面试当中也会经常提到这个问题。

      为什么要使用Handler

      Handler是Android给我们提供用来更新UI的一套机制,也是一套消息处理的机制,我们可以发送消息,也可以通过它处理消息;例如在我们的Activity的生命周期中的底层的实现方法,很多都是使用Handler机制来完成的。
 
 

如何使用Handler

       (一)简单认识Handler——更新UI
        我们知道Handler可以在子线程中来更新UI的。那么如何来更新Ui呢?先看一个简单实例,下面再一步步由浅入深的认识Handler
        实例:我们在Acyivity中的Oncreate方法中创建一个子线程,用于更新一个TextView。
public class MainActivity extends Activity {
	private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView=(TextView) findViewById(R.id.textView);
        //定义一个子线程更新UI
        new Thread(){
        	public void run() {
        		try {
					Thread.sleep(1000);
					textView.setText("我是新的TextView");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
        	};
        }.start();
    }
}
运行一下我们就会看到我们的程序崩溃了,而且会在后台显示如下信息。意思就是只能在主线程中更新UI。

       但是如果我们使用了Handler会怎么样呢?
new Thread(){
        public void run() {
        	try {
			Thread.sleep(1000);
			hanlder.post(new Runnable() {
				@Override
				public void run() {
					textView.setText("我是新的TextView");
				}
			});
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
        };
 }.start();
    好了,添加了Hanlder之后就可以更新UI了。当然你也可以通过Handler实现其他的效果,我最常用的就是使用Handler去进行界面跳转。也就是在我们欢迎界面2秒钟后进入登录界面。先看一下效果吧;
    
代码那就很简单了;在我们的Activity中的OnCreate添加以下代码就可以了
handler.postDelayed(new Runnable() {
			
			@Override
			public void run() {
				Intent intent=new Intent(SplashActivity.this,MainActivity.class);
				startActivity(intent);
				overridePendingTransition(android.R.anim.slide_in_left, android.R.anim.slide_out_right);
			}
		}, 2000);
    Handler中还有很多的方法,这里给出Api,自己可以看看点击打开链接

          二)使用Handler在线程之间传递消息
        我们首先定义一个子线程在里面发送消息。
//定义子线程,向主线程发送信息
        new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(2000);
					Message  msg=new Message();//与Message  msg=hanlder.obtainMessage()这句话等价
					msg.arg1=1;//设置发送的消息
					msg.arg2=2;
					hanlder.sendMessage(msg);
					
				} catch (InterruptedException e) {
					e.printStackTrace();
				}	
			}
		}).start();
接下来在我们的主线程中去接受消息,然后处理消息,我们在上面讲到处理消息时就可以直接更新UI了。
//这里是主线程,处理子线程发过来的信息
	private Handler hanlder=new Handler(){
		//此方法处理子线程中发过来的信息
		public void handleMessage(Message msg) {
			int a=msg.arg1;//接收发过来的信息
			int b=msg.arg2;//接收发过来的信息
			textView.setText("接收的参数a="+a+" 参数b="+b);
		};
	};
       这里我们发现我们发送的只有俩个int类型的整数,有时候我们会从网络里面接收很多信息,例如JSON,文件等等各种信息,我们如何去处理呢?其实我们的msg也可以发送对象Object。我们可以定义一个对象去发送,发送过程和上面的一样只不过把int换成了我们要发送的对象。
        既然Handler能够发送消息那肯定也能移除消息。 hanlder.removeCallbacks(runnable);将我们的发送消息的Runnable对象移除就好了。这个移除整个发送消息的方法,我们也可以移除单个消息   hanlder.removeMessage(what);
        Handler还可以拦截消息。 拦截的话我们需要实现一个回调方法去拦截。下面代码演示一下;
      第一步:发送一个消息  和上面的一样,在子线程中发送消息。
      第二步:拦截消息;
//这里是主线程,处理子线程发过来的信息
	private Handler hanlder=new Handler(new Callback() {
		//在这个方法里面去截获信息。
		@Override
		public boolean handleMessage(Message msg) {
			int a=msg.arg1;//接收发过来的信息
			int b=msg.arg2;//接收发过来的信息
			textView.setText("截获接收的参数a="+a+" 参数b="+b);
			Log.i("info", "截获 a="+a+" : b="+b);
			return false;
		}
	}){
		//此方法处理子线程中发过来的信息
		public void handleMessage(Message msg) {
			int a=msg.arg1;//接收发过来的信息
			int b=msg.arg2;//接收发过来的信息
			textView.setText("处理参数a="+a+" 参数b="+b);
			Log.i("info", "处理 a="+a+" : b="+b);
		}
	};
这里面有一个细节需要注意;在创建callback的时候。我们导入的包是    import android.os.Handler.Callback;
      在这里我们重写了一个handleMessage方法。只不过这个方法返回一个boolean值。当返回的为true的时候,我们就成功的截获,那么下面的返回值为Void的方法将不会调用。返回为false下面的方法将会调用。

        (三)Handler、Looper、MessageQueue之间的关系
   先给出一张图,比较好理解,边看图边解释
     
       我们可以看到,我们的Handler封装了消息的发送与接收,我们的Looper相当于一个消息的载体。里面有一个消息队列。这个消息队列就相当于是Handler发送过来的多条消息的容器。Handler在发送消息时候需要找到Looper,只要找到了Looper也就找到了我们的消息队列。Looper.loop相当于是一个死循环,不断地把消息取出来。
    注意:虽然我们的Handler可以更新UI,但是尽量不要做一些好事操作,否则会出现卡死的状态。

   handlerThread       

         为什么Handler需要和Looper关联,是因为Handler需要往Looper中的mQeueu里插入Message。所以,如果主线程需要和子线程之间通信,那有两个方法:
 1. 主线程拥有子线程的Handler(注意:子线程的Handler需要关联自己(主线程)的Looper),通过该Handler发送消息即可。
2. 主线程创建一个Handler,但是将子线程的Looper传递给Hander,这样Handler也是往子线程Looper对象的mQueue里插入msg,子线程Looper.loop自然可以拿到主线程消息了。
       但是,这两种情况都有弊端。就是线程并发的时候,不能保证子线程的Handler或者Looper对象已经被初始化了。什么意思呢?举个例子吧,现在有A和B,A需要使用到B创建的一个资源。他们并发执行,执行到某一地步的时候现在A需要B中的资源了,但是呢B还没有创建出来,这个时候就会出现一些问题了。所以才需要用到HandlerThread。
          如何去使用呢?  
public class SecondActivity extends Activity {
	
	public Handler handler;
	private HandlerThread thread;	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_second);
		super.onCreate(savedInstanceState);
		//创建HandlerThread
		thread=new HandlerThread("我是HandlerThread");
		thread.start();	
		//创建Handler    相当于在HandlerThread的Hanlder处理消息。
		handler=new Handler(thread.getLooper()){
			@Override
			public void handleMessage(Message msg) {
				Log.i("info", "当前线程是"+Thread.currentThread());
				super.handleMessage(msg);
			}
		};	
		handler.sendEmptyMessage(1);//在main线程发送消息
	}
}
 HandlerThread的用处
        创建Handler的时指定的looper,可以是别的线程创建的。所以Handler中MessageQueue的轮询不一定非要是创建Handler的线程进行,还可以在别的线程中进行。
这个时候我们就需要使用HandlerThread这个类来创建这个Looper了,这样消息的处理就在新创建的HandlerThread中进行。
mThread = new HandlerThread("Handler Thread");
mHandler = new Handler(mThread.getLooper()){
public void handleMessage(android.os.Message msg) {... };
};
同样需要注意:在新创建的ThreadThread中不能有更新UI的操作。因为其是子线程。

     主线程与子线程之间的通讯

      我们前面提到了HandlerThread,就可以使用handler向主线程发送消息。也可以接受主线程发来的消息。
      然后我们在Main线程中定义一个Handler用于处理子线程发送给过来的msg。
public class SecondActivity extends Activity {
	
	public Handler handler;//子线程的Handler
	private HandlerThread thread;	//子线程
	
	//主线程处理子线程发送过来的消息
	private Handler hanlderMain=new Handler(){
		public void handleMessage(Message msg) {
			
			Log.i("main", "当前线程是"+Thread.currentThread());
			Log.i("main", ""+msg.toString());
		};
	};
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_second);
		super.onCreate(savedInstanceState);
		
		//创建HandlerThread
		thread=new HandlerThread("我是HandlerThread");
		thread.start();	
		//相当于是子线程的Handler
		handler=new Handler(thread.getLooper()){
			@Override
			public void handleMessage(Message msg) {
				Log.i("info", "当前线程是"+Thread.currentThread());
				Log.i("info", ""+msg.toString());
				//子线程向主线程发送消息
				hanlderMain.sendEmptyMessage(2);//这里使用主线程的Handler发送消息
				super.handleMessage(msg);
			}
		};	
		//主线程向子线程发送消息
		handler.sendEmptyMessage(1);//这里使用子线程的handler发送消息
	}
}
         我们定handle一个一个HandlerTread在里面使用主线程的handlerMain向主线程发送消息。然后在下面使用子线程的handler向我们的主线程发送消息。输出日至。

     看到信息都已经打印出来。

      runOnUiThread来更新UI
runOnUiThread(new Runnable() {
			
			@Override
			public void run() {
				TextView tv=(TextView) findViewById(R.id.second_tv);
				tv.setText("使用runOnUiThread来更新UI");
			}
		});

    总结:

     1.不能直接在非UI线程直接更新UI(大多数时候)。
     2.每次创建Handler时需要给它绑定一个looper,如果是主线程不给定具体的looper则会绑定默认的looper。
     3.子线程运行时一定要调用start()方法。
     4.在某些特殊情况下在非UI线程是可以更新UI的(不推荐使用
     (当刚启动Activity即onCreate里面此时onResume方法还没有执行的时候可以,因为在线程中更新UI时会调用ViewParent.invalidateChild()方法检查当前的Thread是否是UIThread,若为UIThread则可以更新(ViewParent是一个接口类,其实现是ViewRootImpl;invalidateChild()方法调用checkThread()方法来检查线程是否为主线程)。ViewRootImp是在onResume方法中初始化的,所以只要在ViewRootImpl创建之前更新UI(在onCreate方法中创建线程并执行,此时还没有初始化ViewRootImp),就可以逃避掉checkThread()的检查进而更新UI。)
        总之一句话:Handler机制很麻烦,建议使用异步任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值