提到Android中的消息机制,首先想到的必定是Handler,Looper和MesageQueue,我们在开发中接触到的最多的就是Handler,至于Looper和MessageQueue我们是知道的,但是并不会对其进行操作,最多的用法就是在子线程请求数据,然后在UI线程创建一个Handler,用创建的Handler在子线程发送消息,然后在UI线程的接受消息并更新UI。然而我们还是会碰到一些问题的,比如为什么不能再子线程创建Handler(其实是可以的),以及为什么多线程能够解决UI线程的阻塞问题。 Android应用在启动时,默认会有一个主线程(UI线程),在这个UI线程中会关联一个消息队列,所有的操作都会被封装成消息然后交给主线程来处理。一个完整的消息队列必须包含Handler ,Looper,MessageQueue,Handler是需要我们创建的,而UI线程的线程循环Looper是在ActivityThread.main()方法中创建的,贴出部分源码如下:
public static void main(String[] args) { //省略代码 Looper.prepareMainLooper(); //1.创建消息队列 ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler();//创建UI线程中的Handler } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); //2.执行消息循环 throw new RuntimeException("Main thread loop unexpectedly exited"); }复制代码
执行过ActivityThread.main()方法后应用程序就启动了,Looper会一直在消息队列中拉取消息,然后处理消息,使得整个系统运行起来。那么消息又是如何产生的,又是如何从队列中获取消息并且处理消息的?答案就是Handler。例如在子线程发出消息到UI线程,并且在UI线程处理消息,其中更新UI的Handler是属于UI线程。然而更新UI的Handler为什么必须属于UI线程,我们打开Handler的相关的源代码看一下:
public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } }//1.获取到Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); }//2.获取到消息队列 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }复制代码
从Handler的构造函数可以看到,通过Looper.myLooper()得到Looper,然后我们看一下myLooper这个方法:
/** * Return the Looper object associated with the current thread. Returns null if the calling thread is not associated with a Looper. */public static @Nullable Looper myLooper() { return sThreadLocal.get();}复制代码
可以看到这里返回了一个和当前线程关联的Looper,而这个Looper又是在什么地方和sThreadLocal关联起来的呢?
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }复制代码
可以看到在我们上面提到的prepareMainLooper() 方法中,调用了prepare()方法,在prepare()方法中创建了一个Looper并且和sThreadLocal关联起来( sThreadLocal.set(new Looper(quitAllowed));),通过这一个步骤消息就和线程关联了起来,不同线程之间就不能彼此访问对方的消息队列了。消息队列通过Looper和线程关联起来,而Handler又和Looper关联起来,而UI线程中的Looper是在ActivityThread.main()方法中创建的,当我们单独开启一个线程的时候,由于并没有创建Looper,所以在创建Handler的时候会抛出“Can't create handler inside thread that has not called Looper.prepare()”的异常,知道了这个异常的原因,我们就可以解决了这个异常了。
new Thread(){ Handler mHandler = null; @Override public void run() { super.run(); //1.为当前线程传建一个Looper,并且和线程关联起来 Looper.prepare(); mHandler = new Handler(); //2.进行消息的循环 Looper.loop(); } }.start();复制代码
总结一下,每个Handler都会关联一个消息队列,每个消息队列都会被封装进Looper中,每个Looper都会关联一个线程,最终就相当于每个消息队列都会关联一个线程。Handler就是一个消息的处理器,将消息投递给消息队列,然后从消息队列中取出消息,并进行处理。默认情况下,只有UI线程中有一个Looper,是在ActivityThread.main()方法中创建的。这也就解释了为什么更新UI的Handler为什么要在UI线程创建,就是因为Handler要和UI线程的消息队列关联起来,这样handleMessage()方法才会在UI线程执行,此时更新UI才是线程安全的。至于Handler是在哪一步关联的Looper的,我们可以打开Handler的构造方法:
/** * Default constructor associates this handler with the {@link Looper} for the * current thread. * * If this thread does not have a looper, this handler won't be able to receive messages * so an exception is thrown. */ //默认的构造器,和当前的线程关联起来,若果当前线程没有Looper,则会出现异常 public Handler() { this(null, false); }复制代码
那我们能不能再分线程创建Handler,然后发送消息到主线程呢?继续看Handler的其他构造函数:
/** * Use the provided {@link Looper} instead of the default one. * * @param looper The looper, must not be null. */ public Handler(Looper looper) { this(looper, null, false); }复制代码
我们可以传入一个非当前线程的Looper,来向传入的Looper所在的线程的消息队列发送消息:
public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); testForMianToMain(); } private void testForMianToMain() { new Thread(){ @Override public void run() { super.run(); //这里不是在主线程创建的Handler,但是我传入的是主线程的Looper,既然是主线程的Looper,那么消息队列就一定是主线程的 MainHandler mainHandler = new MainHandler(getMainLooper()); Message mainMessage = new Message(); mainMessage.obj="Main To Main"; mainHandler.sendMessage(mainMessage); } }.start(); } class MainHandler extends Handler{ public MainHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.e(TAG,msg.obj.toString()); } }}复制代码
补充:为什么只能在主线程更新UI?先看一下如果不在主线程更新UI会发生什么错误,会报出如下错误:"Only the original thread that created a view hierarchy can touch its views.",这个错误是在哪里定义的呢?打开源码,可以看到是在ViewRootImpl.java这个类中定义的,ViewRootImpl类是View内部类AttachInfo的一个成员变量,是连接Window和View的桥梁,View在更新页面的时候会调用 checkThread()这个方法检查是否是在主线程,若不在主线程(因为View更新只能在主线程),则会报出"Only the original thread that created a view hierarchy can touch its views."这个错误。
简书迁移过来的文章