博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android中的消息机制
阅读量:5986 次
发布时间:2019-06-20

本文共 6222 字,大约阅读时间需要 20 分钟。

提到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."这个错误。

简书迁移过来的文章

转载地址:http://zpflx.baihongyu.com/

你可能感兴趣的文章
MVC中的扩展点(四)控制器工厂
查看>>
TSC工业型条码打印机的价格的影响因素有哪些呢?
查看>>
mac上xcode4和xcode5共存及修改默认打开方式
查看>>
[Winform]关于cefsharp触屏设备长按文本内容,崩溃问题的修复
查看>>
第七次作业--项目需求分析(团队)
查看>>
Shell脚本
查看>>
【小游戏】C++手工制作贪吃蛇
查看>>
紫书 习题 10-14 UVa 10886(暴力+数据范围)
查看>>
紫书 例题 9-6 UVa 11400 (线性结构上的动态规划)
查看>>
PAT (Advanced Level) 1085. Perfect Sequence (25)
查看>>
CodeForces 708B Recover the String
查看>>
FZU 2193 So Hard
查看>>
detectron和mm-detection是干什么的?
查看>>
postman请求https接口
查看>>
MySQL主从切换
查看>>
[BZOJ] 2442: [Usaco2011 Open]修剪草坪
查看>>
设计模式之-适配器模式
查看>>
正式学习React (六) 项目篇
查看>>
treap
查看>>
刷数据结构记录
查看>>