Handler是什么
Handler是android提供给我们用来更新UI的一套机制
Handler是android提供给我们的一套消息处理机制:可以发送消息也可以处理消息
例如:Activity的生命周期回调方法都是通过Handler机制进行发送消息的,然后根据不同的Message进行分支处理
为什么要用Handler
Android在创建的时候就封装一个一套消息发送、传递、处理的机制,如果不遵循这样的机制就无法更新UI信息的,会抛出异常(不能再非UI线程中更新UI)
Handler怎么用
post(Runnable)
postDelayed(Runnable,long)
sendMessage()
sendMessageDelayed()
在子线程中更新UI
textView = (TextView) findViewById(R.id.textview); new Thread() { @Override public void run() { try { Thread.sleep(1000); textView.setText("在子线程中更新UI"); } catch (InterruptedException e) { e.printStackTrace(); } } }.start();这样的话程序会出错: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its
使用Handler(注意这里导入的包是os下的Handler) post(Runnable)
private Handler handler = new Handler();在子线程中更新UI
textView = (TextView) findViewById(R.id.textview); new Thread() { @Override public void run() { try { Thread.sleep(1000); handler.post(new Runnable() { @Override public void run() { textView.setText("在子线程中更新UI"); } }); } catch (InterruptedException e) { e.printStackTrace(); } } }.start();
实例:
在ImageView中不断循环更新图片 postDelayed(Runnable,long)
public class MainActivity extends AppCompatActivity { private TextView textView; private Handler handler = new Handler(); private ImageView imageView; //数组存储图片资源ID private int images[] = {R.drawable.image1,R.drawable.image2,R.drawable.image3}; //图片的索引 private int index = 0; //创建一个子线程 private MyRunnable myRunnable = new MyRunnable(); //匿名内部类 class MyRunnable implements Runnable{ @Override public void run() { index++; index = index % images.length; imageView.setImageResource(images[index]); //延迟1秒后在调用线程myRunnable(调用自己),实现循环的功能不断的调用自己 handler.postDelayed(myRunnable,1000); } } /** * ATTENTION: This was auto-generated to implement the App Indexing API. * See https://g.co/AppIndexing/AndroidStudio for more information. */ private GoogleApiClient client; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); textView = (TextView) findViewById(R.id.textview); imageView = (ImageView) findViewById(R.id.imageview); //延迟1秒后调用线程myRunnable handler.postDelayed(myRunnable,1000);}在这里我们还可以移除消息
button = (Button) findViewById(R.id.btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //移除消息 handler.removeCallbacks(myRunnable); } });这样循环的子线程就不会再MessageQueue中了
sendMessage方法
private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); textView.setText(" "+msg.arg1+" "+msg.arg2+" "+msg.obj); } };自定义的类
class Person{ private int age; private String name; @Override public String toString() { return " name="+name+" age="+age; } }子线程更新UI
new Thread(){ @Override public void run() { try { Thread.sleep(2000); //获取Message对象的两种方法:第一种Message message = new Message(); // 第二种 Message message = handler.obtainMessage(); // Message message = new Message(); Message message = handler.obtainMessage(); message.arg1 = 1; message.arg2 = 2; //传递自定义对象 我们从网络中获取的Json解析的对象可以发送过去 Person person = new Person(); person.age = 21; person.name = "xiaoxin"; message.obj = person; //发送消息的两种方法:第一种handler.sendMessage(message); // 第二种: message.sendToTarget(); // handler.sendMessage(message) message.sendToTarget(); } catch (InterruptedException e) { e.printStackTrace(); } } }.start();
其他用法:我们可以拦截Message:实现callback接口,true表示拦截,false表示不拦截
private Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Toast.makeText(getApplicationContext(),"拦截消息",Toast.LENGTH_LONG).show(); //return true表示下面的handleMessage不执行,被拦截了 return true; } }) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); textView.setText(" " + msg.arg1 + " " + msg.arg2 + " " + msg.obj); } };
Handler用法小总结:可以用来发送、移除、截获消息等功能
Android为什么设置只能通过Handler更新UI
根本目的:解决多线程并发问题
假设在一个Activity中,有多个线程去更新UI,并且没有加锁机制,那么会出现界面混乱问题
如果我们对所有更新UI加锁的话那么会出现性能下降的问题
基于上面这种问题android给我们提供了Handler机制,我们只要遵循这套机制,不用去关心多线程问题,所有更新UI的操作都是在主线程的消息队列中去处理的
Handler的原理是什么
Handler封装了消息的发送
Looper:包含了一个消息队列MessageQueue,所有Handler发送的消息都是走向这个队列
Looper.loop方法是一个死循环,不断从MessagQueue中取出消息,如果有消息就处理消息没有消息就阻塞
MessageQueue:是一个消息队列可以添加消息也可以处理消息
在我们创建的一个Activity中都有一个ActivityThread里面有一个main线程 main下有一个Looper Looper中有MessageQueue
总结:Handler负责发送消息,Looper负责Handler发送的消息,并直接把消息回传给Handler自己 MessageQueue是一个存储消息的容器
实现与线程相关的Handler
在主线程中创建Handler,重新handlerMessage,不要做耗时处理不然主界面会卡死
解决办法我们可以再子线程中创建Handler
//在子线程中处理Handler class MyThread extends Thread{ private Handler handler; @Override public void run() { //准备一个Looper Looper.prepare(); handler = new Handler(){ @Override public void handleMessage(Message msg) { //要更新的UI } }; //死循环不断从MessageQueue中取出消息 Looper.loop(); } }
使用方法就是new一个MyThread线程然后开启
HandlerThread是什么
解决多线程导致的空指针问题
创建Handler的时候我们可以指定Looper,所以这个Looper对象是可以别的线程创建的,但是进行的时候我们要确保这个looper对象已经在别的线程中创建好了,否则会出现空指针异常,这个时候我们就可以通过HandlerThread创建这个Looper
为什么Handler要和Looper相关联,是因为Handler要往Looper中的MessageQueue插入Message
将主线程中的new出来的消息队列设置成HandlerThread对象thread的
handler = new Handler(thread.getLoop()),这样handler处理的执行的操作都会在thread中执行而不是在主线程中执行
用于主线程向子线程发送消息
来自别人的看法
创建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) {... };
};
这个时候我们就需要使用HandlerThread这个类来创建这个Looper了,这样消息的处理就在新创建的HandlerThread中进行。
mThread = new HandlerThread("Handler Thread");
mHandler = new Handler(mThread.getLooper()){
public void handleMessage(android.os.Message msg) {... };
};
我的看法:通过源码我们可以知道HandlerThre继承这Thread本身就是一个子线程,这个HandlerThread中通过wat()和notifyAll()的线程同步机制,确保主线程调用自己的Looper对象不为null
如何在主线程中向子线程发送消息
子线程向主线程发送消息,是通过调用主线程的Handler,发送信息给主线程的Looper,该Handler已经绑定主线程的Looper
主线程向子线程发送消息,是通过调用子线程的Handler,发送信息给子线程的Looper,因此必须确保子线程有Looper,为了防止子线程的Looper没有被初始化那么我们可以通过HandlerThread类确保子线程的Looper再被主线程调用的时候已经初始化了
package com.example.myapplication; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Button; import com.google.android.gms.common.api.GoogleApiClient; /** * Created by 小新 on 2016/4/11. */ public class SendStopHandler extends AppCompatActivity implements View.OnClickListener{ private Button send, stop; /** * ATTENTION: This was auto-generated to implement the App Indexing API. * See https://g.co/AppIndexing/AndroidStudio for more information. */ private GoogleApiClient client; Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { Message message = new Message(); Log.e("LOG","main Handler"); //向子线程发送消息 threadHandler.sendEmptyMessageDelayed(1,1000); } }; private Handler threadHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout); send =(Button)findViewById(R.id.send); stop = (Button)findViewById(R.id.stop); send.setOnClickListener(this); stop.setOnClickListener(this); //创建子线程的Handler HandlerThread handlerThread = new HandlerThread("handlerThread"); //这里的 handlerThread.start();一定要启动!!!! handlerThread.start(); threadHandler = new Handler(handlerThread.getLooper()){ @Override public void handleMessage(Message msg) { Message message = handler.obtainMessage(); Log.e("LOG","thread Handler"); //在主线程发送消息 handler.sendEmptyMessageAtTime(1,1000); } }; } @Override public void onClick(View v) { switch (v.getId()){ case R.id.send : handler.sendEmptyMessage(1); break; case R.id.stop: handler.removeMessages(1); threadHandler.removeMessages(1); break; } } }按start按钮:运行结果每隔一秒打印
按stop按钮则停止发送消息
android中更新UI的方式
大概可以分为四种,其实原理都是一样的,到时Handler机制,只是进行了不一样的封装
1.handler.sendMessage(Message msg)
2.handler.post(Runnable run)
3.runOnUIThread(Runnable run)
4.控件.post(Runnable run)
非UI线程真的不能更新UI吗
答案是可以的,但我们还没有OnResume的时候,即在onCreate里面
ViewRootimp是在onResume方法中初始化的,在onCreate方法中创建线程并执行,这个时候是还没有初始化ViewRootimp的,所以没法判断更新UI的线程到底是不是主线程,ViewRootimp包含线程的判断