提到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<? extends Handler> 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."这个错误。
网友评论