第一人称实录
1. 类加载初始化
public class Test { public static class A { int a; int b1 = a; int b2 = v++; static int c1 = v++; public A() { a = v++; } } static int v = 1; public static void main(String[] args) { A aa = new A(); System.out.println("v = " + v); System.out.println("c1 = " + A.c1); System.out.println("a = " + aa.a); System.out.println("b1 = " + aa.b1); System.out.println("b2 = " + aa.b2); } }
这里考察的东西很简单,说一下我的思路:首先类加载,于是初始化静态变量,然后执行静态方法,之后是对象的构造方法和成员变量的初始化。于是在我看来,其顺序是:v -> c1 -> a -> b1 -> b2 ; 那么输出的结果不言而喻,这里不展示。
但其实这里有点问题,从字节码文件.class中可以了解到:成员变量初始化的确是放在对象构造方法内的,但还是处于原构造方法之前,那么简单理解就是说现有成员变量的初始化,再有构造方法的调用。其次,static变量初始化先于static代码块,再先于main方法。到这里其实可以大致了解了,顺序是:v -> c1 -> a=0 -> b1 -> b2 -> a = 3。不过这里还有个小细节:静态内部类的static变量和外部类的static变量的初始化顺序如何呢?这里实验后可以发现,静态内部类的内容并不会在外部类加载时也进行加载,而是等到 new A()的时候再加载,缝合的结果就是如上,结果在意料之中,总的来说问得很细,尤其是静态内部类的考察,比较到位。
2. UI操作不能在子线程的原因
那么这个问题其实也不难,我们都知道UI操作是不能在非主线程的线程上进行的,且会抛出异常。根本原因还是为了杜绝这种情况下导致的UI显示不同步问题,但落实到代码上呢?这个异常在哪里抛的,如何抛的?熟悉的同学可能都知道答案了,那我对此是一知半解,首先明显是进行了对操作线程的判断,那么进一步就是在哪里进行的判断,我本能的认为是在一个处理消息的地方,因为安卓的任务和消息总是绕不开Handler,MessageQueue,Looper,于是联想到Looper总是将对应任务进行分发,猜测分发时对对应线程做了判断。那这个答案显然是错误的,接下来梳理一遍这个过程:
首先我们拿TextView的SetText为例:
// 缺少图例
首先SetText()确实是很复杂,这里做了很多操作,那关于UI的主要是这里:
// 缺少图例
在这里你会发现不论如何都会触发invalidate()方法,部分情况会触发requestLayout()方法:
// 缺少图例
requestLayout()更新UI,这里会调用ViewRootImpl的
// 缺少图例
最终在ViewRootImpl中进入requestLayout(),那么这里可以看到首先checkThread():
最后,简单的判断
3. 多个Handler中removeMessage的情况
这个问题比较有意思,简单理解含义:首先是在一个线程里面创建多个Handler,之后再分别加入同种Message,最后再remove其中一个,会发生什么,代码大致如下:
private void test() { Handler h1 = new Handler(); Handler h2 = new Handler(); h1.sendMessageDelayed(Message.obtain(h1, 100),3000); //h1.sendEmptyMessage(100); h2.sendMessageDelayed(Message.obtain(h2, 100),3000); //h2.sendEmptyMessage(100); h1.removeMessages(100); }
这里为了方便观察,将message增加了延迟,实际不影响效果。
第一层思考:既然是不同的handler,那不就应该互不影响吗,那这个handler机制就存在问题了,岂不是成了设计缺陷?所以直觉告诉我应该是不互相影响的,h2还是能收到并执行这个message。
第二层思考:首先我们使用handler机制需要先初始化Looper,即执行Looper.prepare(),而Looper的初始化过程实际确定了唯一的MessageQueue,而在Handler创建过程,会去取当前Looper的对应的MessageQueue来绑定自身。因此,根本逻辑是Looper对应一个MessageQueue,对应多个Handler。那在这种逻辑下,自然而然地:MessageQueue里面的消息被remove掉,两个handler都会受到影响,即都不会继续完成处理。
那真实情况如何呢?我们来实际看下:
首先Looper对应一个MessageQueue,对应多个Handler的分析没有问题,这可以从Looper和Handler的构造方法看到。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } /* 创建looper,配套messageQueue。*/ public Handler(@Nullable Callback callback, boolean async) { ...... mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; ...... } /* Handler直接使用looper的messageQueue。*/
明白了以上,那可以确定的确有两个what = 100的message。再看下removeMessage的情况:
/** * Remove any pending posts of messages with code 'what' that are in the * message queue. */ public final void removeMessages(int what) { mQueue.removeMessages(this, what, null); }
明确指出了remove是根据what来的。且注释中有关键词“any”,再看MessageQueue中:
void removeMessages(Handler h, int what, Object object) { ...... while (p != null && p.target == h && p.what == what && (object == null || p.obj == object)) { Message n = p.next; mMessages = n; p.recycleUnchecked(); p = n; } ...... }
细看就傻眼了,通过while看出确实是“any”,不过不但what要匹配,对这个handler也要匹配,因此其实是把将要分配给对应handler的相应message删除掉,那其实也就说明了,>细看就傻眼了,通过while看出确实是“any”,不过不但what要匹配,对这个handler也要匹配,因此其实是把将要分配给对应handler的相应message删除掉,那其实也就说明了,不会影响到h2处理任务。
到这里其实我是有疑问的,像sendMessageDelayed()方法传的message确实是限定了对应handler,但如果按sendEmptyMessage(int what)呢?这参数可就只有what了,那这时候没有handler怎么算呢?附上关键代码:
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); } /* 这里obtain了一个空消息,这是handler,即msg.target = null,之后调用到enqueueMessage:*/ private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; ...... return queue.enqueueMessage(msg, uptimeMillis); } /* 谜底昭然若揭,handler被赋给了msg。 */
所以给出结论:不会互相影响,这里对Handler消息机制又做了深入探寻,里面的东西确实很多。