精华内容
下载资源
问答
  • android 子线程使用handler通信

    千次阅读 2019-03-28 11:11:27
    我们日常使用处理线程间的通信是 主线与子线程之间相互通信,也就是使用android提供的handler。 那么子线程之间如何相互通信呢?一般有可能会使用 全局变量来进行通信,其实我们也可以使用系统提供的handler来...

      我们日常使用处理线程间的通信是 主线程与子线程之间相互通信,也就是使用android提供的handler。

      那么子线程之间如何相互通信呢?一般有可能会使用 全局变量来进行通信,其实我们也可以使用系统提供的handler来进行子线程中的通信。

     handler的通信机制

    此处引用:https://blog.csdn.net/blackzhangwei/article/details/51945516

    由上图可以看出  handler负责消息的处理和发送的,messageQueue 是存储消息队列的,Looper 是处理消息的

    主线程在创建时或创建 looper 所以我们子线程可以和主线程通信,那么由此我们也可以在子线程中创建looper 来实现子线程中的通信。

    线程1

    Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                handler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.e("Data", "handleMessage: " + msg.obj.toString());
                    }
                };
                Looper.loop();
            }
        };

    线程2

    Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                //Looper looper = Looper.myLooper();
                Message message = handler.obtainMessage();
                message.obj = "你好啊,线程一";
                handler.sendMessage(message);
            }
        };

    线程二中使用的 handler 是线程一中创建的 handler

    线程一中使用的looper方法

    Looper.prepare();   将当前线程初始化为 消息线程   

    Looper.loop();  在此线程中运行 消息队列

    那么如何停止运行呢

    Looper.quit();   停止运行消息队列,不处理任何消息。

    更多looper方法 

    https://developer.android.google.cn/reference/android/os/Looper.html

    英文是半吊子的,可以像我一样翻译网页。 ??

    展开全文
  • Android 子线程创建handler

    千次阅读 2017-12-24 22:55:05
    我们会很清楚地使用了子线程和Ui线程通信的问题。 即解决了一个网络请求成功或者失败后,通知ui线程更新界面的case。 但是有人会疑问,那子线程中是否可以创建一个Handler,仅仅通知线程呢? 答案是肯定的。 直接上...

    据上篇我转载的 Handler,Looper, Message 三者关系深入消化,理解后。我们会很清楚地使用了子线程和Ui线程通信的问题。

    即解决了一个网络请求成功或者失败后,通知ui线程更新界面的case。

    但是有人会疑问,那子线程中是否可以创建一个Handler,仅仅通知线程呢? 答案是肯定的。

    直接上代码。

    1. new Thread(new Runnable() {  
    2.             public void run() {  
    3.                 Handler handler = new Handler(){  
    4.                     @Override  
    5.                     public void handleMessage(Message msg) {  
    6.                         Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();  
    7.                     }  
    8.                 };  
    9.                 handler.sendEmptyMessage(1);  
    10.                   
    11.             };  
    12.         }).start();  
    很遗憾,会报错:

    01-12 02:49:31.814: E/AndroidRuntime(2226): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare(),需要looper.prepare().

    1. new Thread(new Runnable() {  
    2.             public void run() {  
    3.                 Looper.prepare();  // 此处获取到当前线程的Looper,并且prepare()  
    4.                 Handler handler = new Handler(){  
    5.                     @Override  
    6.                     public void handleMessage(Message msg) {  
    7.                         Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();  
    8.                     }  
    9.                 };  
    10.                 handler.sendEmptyMessage(1);  
    11.                   
    12.             };  
    13.         }).start();  
    运行后发现还是报错,因为上篇博文有提到

    1、Looper.prepare()是给这个Thread创建Looper对象,一个Thead只有一个Looper对象。

    2、Looper对象需要一个MessageQueue对象一个Looper实例也只有一个MessageQueue。

    3、调用Looper.loop();  不断遍历MessageQueue中是否有消息。

    4、Handler 作用是发消息到MessageQueue,从而回掉Handler 的HandleMessage的回掉方法。

    1. new Thread(new Runnable() {  
    2.             public void run() {  
    3.                 Looper.prepare();  
    4.                 Handler handler = new Handler(){  
    5.                     @Override  
    6.                     public void handleMessage(Message msg) {  
    7.                         Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();  
    8.                     }  
    9.                 };  
    10.                 handler.sendEmptyMessage(1);  
    11.                 Looper.loop();  
    12.             };  
    13.         }).start();  
    这样就Ok了。


    方法二:获取主线程的looper,或者说是UI线程的looper

    这个方法简单粗暴,不过和上面的方法不一样的是,这个是通过主线程的looper来实现的


    1. new Thread(new Runnable() {  
    2.             public void run() {  
    3.                 Handler handler = new Handler(Looper.getMainLooper()){ // 区别在这!!!!  
    4.                     @Override  
    5.                     public void handleMessage(Message msg) {  
    6.                         Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();  
    7.                     }  
    8.                 };  
    9.                 handler.sendEmptyMessage(1);  
    10.             };  
    11.         }).start();  


    大家如果有业务需要在子线程处理消息,用以上任意一种方法都是可以的。


    展开全文
  • Handler作为线程之间传递消息的机制,当它被创建时,就会自动关联当前所在位置的线程和消息队列。

    参考文档:

    https://blog.csdn.net/cshichao/article/details/8787357

    http://www.voidcn.com/article/p-ckonfcng-bpx.html

    http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/1014/6675.html

    https://www.cnblogs.com/panhouye/p/6494753.html

    https://blog.csdn.net/qq_30379689/article/details/53394061

    https://www.cnblogs.com/xiaowangabc/p/3973927.html

    Handler作为线程之间传递消息的机制,当它被创建时,就会自动关联当前所在位置的线程和消息队列。Handler通过把Message或Runnable塞进消息队列,等待适当时机处理对应操作。

    (1)Handler的创建:

    基本的创建如下:

    private final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
            }
        };

    但是这样创建,系统会提示你:This Handler class should be static or leaks might occur,大意是指Handler必须是静态类,否则容易引起内存泄露。因为消息队列中等待被被处理的消息持有Handler对应的引用,上述代码创建的Handler是一个匿名内部类,它持有所在外部类的引用(不只是匿名内部类,内部类也会引起这个问题),如果消息队列里的消息长时间未被处理,Handler对象和Handler持有的外部类引用就会一直被持有,导致系统无法及时回收,此时就容易造成内存泄露。

    根据参考文档里的内容,解决这个系统提示的方法有如下两种:

    1,采用new Callback的方式,本方式系统不会有内存泄露的提示:

    private final Handler myHandler = new Handler(new Handler.Callback(){
            @Override
            public boolean handleMessage(@NonNull Message msg) {
                return false;
            }
        });

    但存疑的地方是,有人说这样的写法也有可能发生内存泄露,参考:https://ask.csdn.net/questions/661236

    2,采用弱引用的方式,定义一个static的内部类Handler,然后让它持有Activity的弱引用

    static class XXHandler extends Handler {
        //XXActivity为Handler所在的Activity
        WeakReference<XXActivity> mActivity;
        
        MyHandler(XXActivity activity) {
            mActivity = new WeakReference<XXActivity>(activity);
        }
     
        @Override
        public void handleMessage(Message msg) {
            XXActivity theActivity = mActivity.get();
            switch (msg.what) {
                //对应的具体操作
            }
        }
    };
    
    //具体操作的代码如下
    //实例Handler,持有对应Activity的弱引用
    XXHandler xHandler = new XXHandler(this)
    //在需要操作的方法内执行对应操作
    private void doSomething() {
        xHandler.sendEmptyMessage(0x111);
    }

    使用静态内部类为什么会避免内存泄露呢?static修饰的东西不属于类本身,自然也不会持有类的引用,但系统却要为这些静态的东西开辟额外的内存空间,如果一个应用里含有大量的静态修饰的东西,容易造成卡顿,所以适时释放内存是必要的。

    ****在子线程中创建Handler****

    class MyThread extends Thread {
        @Override
        public void run() {
            //创建Looper对象并关联到子线程
            Looper.prepare();
            //创建Handler对象
            Handler handler = new Handler(Looper.myLooper(), new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    return false;
                }
            });
            //调用loop方法循环遍历消息队列里的消息并发给Handler处理
            Looper.loop();
    
        }
    }

    (2)Handler参数的说明:

    1,Message:不同线程之间用于传递消息的封装,通过what区分消息,arg1、arg2 字段用来传递int类型的数据,obj可以传递任意类型的字段。

    ***new Message和obtainMessage的区别***

    Message msg = new Message(),这是直接在内存中申请空间,相对效率更低,如果多次使用new的方式创建Message,也容易造成内存紧张。

    obtainMessage方法的源码:

    @NonNull
    public final Message obtainMessage(int what) {
            return Message.obtain(this, what);
        }
    //
    public static Message obtain(Handler h, int what) {
            Message m = obtain();
            m.target = h;
            m.what = what;
            return m;
        }
    //
    public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }

    通过源码发现,obtain方法里,如果Handler已经有了对应的what标记的Message就不再次创建,直接取出来使用,反之则新建一个Message。

    做个测试,代码如下:

    private final Handler myHandler = new Handler(new Handler.Callback(){
            @Override
            public boolean handleMessage(@NonNull Message msg) {
                switch (msg.what) {
                    case UPDATE_TXT:
                        String times = (String) msg.obj;
                        txtHint.setText("Handler更新TextView,time=" + times);
                        break;
                }
                return false;
            }
        });
    //即时操作
    private void sendNow() {
            Log.e(TAG, "sendNow");
            long times = System.currentTimeMillis();
            Message message = myHandler.obtainMessage(UPDATE_TXT);
            message.obj = "sendNow" + times;
            message.sendToTarget();
        }
    //延时操作
    private void sendDelay() {
            Log.e(TAG, "sendDelay");
            long times = System.currentTimeMillis();
            Message message = myHandler.obtainMessage(UPDATE_TXT);
            message.obj = "sendDelay" + times;
            myHandler.sendMessageDelayed(message, 30 * 1000);
        }

    如果只调用sendNow方法,txtHint直接更新内容;如果只调用sendDelay,则在30秒后更新txtHint;但如果调用sendDelay后,在30秒的延迟时间里调用sendNow呢?此时Handler会将指定的what参数的Message取出,然后取消掉对应的延时操作,直接将此Message转给当前的操作,所以会立即更新txtHint,而延时30秒的操作就地取消。

    **********************************************************************************

    ****Handler处理异步Message****

    参考:https://www.jianshu.com/p/d28922aa5da9  https://blog.csdn.net/milllulei/article/details/80927539

    通常创建的Handler调用send方法发送的消息都是同步消息,插入到队列中根据触发时间排序。Android系统16ms会刷新一次屏幕,如果主线程的消息过多,在16ms之内没有执行完,必然会造成卡顿或者掉帧。如果要让消息没有延时直接处理呢?此时可以用异步消息,使用异步需要用到一个叫做同步屏幕的东西,目的是让异步消息不用排队等候处理。同步屏障的作用就是一堵墙,先堵住同步消息,让异步消息先执行完后这堵墙消失,接着再执行同步消息。

    查看MessageQueue的postSyncBarrier(低版本是enqueueMessage)方法的源码:

    private int postSyncBarrier(long when) {
            // Enqueue a new sync barrier token.
            // We don't need to wake the queue because the purpose of a barrier is to stall it.
            //将一个新的同步屏障插入到消息队列
            //屏障的目的就是阻拦其他同步消息的处理,所以不需要唤醒消息队列
            synchronized (this) {
                final int token = mNextBarrierToken++;
                final Message msg = Message.obtain();
                msg.markInUse();
                msg.when = when;
                msg.arg1 = token;
                Message prev = null;
                Message p = mMessages;
                if (when != 0) {
                    while (p != null && p.when <= when) {
                        prev = p;
                        p = p.next;
                    }
                }
                if (prev != null) { // invariant: p == prev.next
                    msg.next = p;
                    prev.next = msg;
                } else {
                    msg.next = p;
                    mMessages = msg;
                }
                return token;
            }
        }

    从源码看出方法里生成了一个没有target的Message也就是这个消息没有指定Handler对象(同步消息都有指定Handler),然后将这个消息插入到队列前排,然后再结合MessageQueue的next方法:

    Message next() {
            ...
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
                //根据nextPollTimeoutMillis的值调用本地C++代码阻塞线程
                nativePollOnce(ptr, nextPollTimeoutMillis);
                synchronized (this) {
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    //如果msg是postSyncBarrier方法生成的同步屏障,则while循环直到取出异步消息
                    if (msg != null && msg.target == null) {
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                ...
            }
        }

    从postSyncBarrier方法生成的消息没有target,符合if (msg != null && msg.target == null)的判断条件,形成一个同步屏障阻塞同步消息,while循环取出异步消息进行处理。

    如何发送异步消息:

    <1>Message.setAsynchronous(true)

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
    Message msg = myHandler.obtainMessage(ASYNC_TXT);
    msg.setAsynchronous(true);
    msg.sendToTarget();

    <2>Handler的构造方法,传入 async 参数

    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;
            mCallback = callback;
            mAsynchronous = async;
        }
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }

    以此方式构造的Handler发送的消息都是异步消息。

    使用和移除消息同步屏障

    设置一个同步屏障:

    //通过映射的方式调用postSyncBarrier设置同步屏障
    try {
        Log.d("TAG","插入同步屏障");
        MessageQueue queue=handler.getLooper().getQueue();
        Method method=MessageQueue.class.getDeclaredMethod("postSyncBarrier");
        method.setAccessible(true);
        token= (int) method.invoke(queue);
    } catch (Exception e) {
        e.printStackTrace();
    }

    移除同步障碍:

    //通过映射的方式调用removeSyncBarrier移除屏障
    try {
        Log.d("TAG","移除屏障");
        MessageQueue queue=handler.getLooper().getQueue();
        Method method=MessageQueue.class.getDeclaredMethod("removeSyncBarrier",int.class);
        method.setAccessible(true);
        method.invoke(queue,token);
    } catch (Exception e) {
        e.printStackTrace();
    }

    ---------------------------------------------------------------------------------------------------------------------------------------------

    2,Handler:作为线程之间传递消息的即时接口,既可以在消息队列中创建、插入或移除消息,又可以在handleMessage方法中处理消息。

    参考:https://blog.csdn.net/bzlj2912009596/article/details/80189760

    https://www.cnblogs.com/baiyi168/p/6394628.html

    public Handler(@Nullable 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());
                }
            }
    
            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;
            mCallback = callback;
            mAsynchronous = async;
        }

    查看源码发现,每个Handler默认配置一个Looper,没有Looper,Handler根本无法存放和处理消息队列里的消息。

    Handler里比较重要的方法:

    (1)obtainMessage();obtainMessage(int what);obtainMessage(int what, Object obj);obtainMessage(int what, int arg1, int arg2);obtainMessage(int what, int arg1, int arg2, Object obj);

    从Handler中判断是否有对应的Message引用,有的话直接取出使用,没有再new创建。方法可以接sendToTarget()方法进行链式调用,sendToTarget方法其实还是调用了sendMessage方法,源码如下:

    public void sendToTarget() {
            target.sendMessage(this);
        }

    链式调用的代码举例如下:

    myHandler.obtainMessage(UPDATE_TXT).sendToTarget();

    (2)getLooper(),获取Handler的looper对象。

    (3)post(Runnable);postAtTime(Runnable,long);postDelayed(Runnable long);postAtFrontOfQueue(Runnable r);

    通过查看源码发现这些方法内部其实还是调用了sendMessage等方法,postAtFrontOfQueue方法里实际调用的是sendMessageAtFrontOfQueue(getPostMessage(r)),表示将runnable插入到消息队列头部。

    (4)sendEmptyMessage(int what);sendEmptyMessageAtTime(int what, long uptimeMillis);sendEmptyMessageDelayed(int what, long delayMillis);sendMessage(@NonNull Message msg);

    sendMessageAtTime(@NonNull Message msg, long uptimeMillis);sendMessageDelayed(@NonNull Message msg, long delayMillis);

    Handler用于发送Message的方法,调用sendEmptyMessage系统会自动构建一个message并且设置what参数然后发送。

    //查看Handler的post方法的源码,调用的还是sendMessageDelayed方法
    public final boolean post(@NonNull Runnable r) {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
    //post方法里调用此方法将Runnable封装为Message供Handler发送
    private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();
            m.callback = r;
            return m;
        }
    //sendMessageDelayed最终还是调用sendMessageAtTime
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
    //先判断消息队列是否存在,不存在则抛出异常,存在则enqueueMessage方法将消息存入消息队列里
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
            MessageQueue queue = mQueue;
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                        this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            return enqueueMessage(queue, msg, uptimeMillis);
        }

    (5)hasMessages(int what);hasMessages(int what, @Nullable Object object);

    检查消息队列是否存在对应的Message,实际应用场景用于判断前一个what的Message是否执行完毕,再进行后续的操作。

    (6)removeCallbacks(Runnable r);removeCallbacks(Runnable r, Object token);removeMessages(int what); removeMessages(int what, Object object) ;

    删除消息队列中的指定消息,已经在执行的消息不能被删除。

    (7)removeCallbacksAndMessages(Object token);删除所有的消息(包括已发送的)和回调,当传的token为null时,直接删除Handler里的所有消息和回调。

    原文:Remove any pending posts of callbacks and sent messages whose obj is token. If token is null, all callbacks and messages will be removed.

    注意的是,Handler的生命周期与Activity并不一致,如果Activity要被系统销毁,但Activity中的Handler还有消息要处理,此时会造成阻塞Handler无法释放,同时Activity也无法正常释放,造成内存溢出。

    此时可以调用Handler.removeCallbacksAndMessages(null),将Handler中所有的消息和回调都删除,避免Handler阻塞造成的内存溢出。

    ---------------------------------------------------------------------------------------------------------------------------------------------

    3,MessageQueue

    参考:

    https://www.jianshu.com/p/8c829dc15950

    https://blog.csdn.net/nmyangmo/article/details/82260616

    https://haokan.baidu.com/v?vid=11527397145831332654&pd=bjh&fr=bjhauthor&type=video

    一个Handler配置一个Looper,一个Looper维护了一个MessageQueue,MessageQueue(消息队列)如同一个集合存储Handler传入的Message消息,每插入一个Message都会根据message的触发时间进行重新排序,Handler的handleMessage方法处理触发时间最早的message。

    一个线程只能有一个Looper和一个MessageQueue,但可以有多个Handler,下面的代码以在子线程中创建Handler为例做说明:

    class MyThread extends Thread {
        
        private Handler handler;//与该子线程关联的Handler
        private Handler handler2;//与该子线程关联的Handler
    
        public void run() {
            Looper.prepare();//创建该子线程的Looper
            handler = new Handler(){//默认与当前线程关联
                public void handleMessage(android.os.Message msg) {
                    Log.d("当前子线程是----->", Thread.currentThread()+"");
                };
            };
            //可以创建多个Handler
            handler2 = new Handler(){//默认与当前线程关联
                public void handleMessage(android.os.Message msg) {
                    Log.d("当前子线程是----->", Thread.currentThread()+"");
                };
            };
            Looper.loop();//调用了该方法才能不断循环取出消息
        }
    }

    从代码中可以看到MyThread里可以生成多个Handler,但Looper和MessageQueue在一个线程中却都只有一个。在MyThread中要正常使用Handler的功能,首先要Looper.perpare()创建适用于MyThread的Looper,同时Handler调用无参的构造方法,(无参构造器默认与当前线程相关联),最后不能忘记Looper.loop(),这样Lopper才能循环轮询消息队列里的消息。

    (1)MessageQueue.enqueueMessage,添加Message进消息队列

    当Handler调用sendMessage(sendMessageDelayed等)方法时,大致会由sendMessage-->sendMessageDelayed-->sendMessageAtTime-->enqueueMessage,并最终指向MessageQueue.enqueueMessage方法,此方法为将Handler传递过来的消息插入到指定的消息队列里等待处理,源码如下:

    boolean enqueueMessage(Message msg, long when) {
            //msg.target就是发送此消息的Handler,如果为空,抛出异常
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            //判断是否有相同的Message正被使用,如果是也不再执行下一步
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
            synchronized (this) {
                //mQuitting表示此消息已被消息队列放弃,消息被回收
                if (mQuitting) {
                    IllegalStateException e = new IllegalStateException(
                            msg.target + " sending message to a Handler on a dead thread");
                    Log.w(TAG, e.getMessage(), e);
                    msg.recycle();
                    return false;
                }
                msg.markInUse();//标记自己正被使用
                msg.when = when;//指定消息的触发时间,但实际处理消息的时间可能更晚
                //mMessagess是消息队列里的第一个元素(头元素)
                Message p = mMessages;
                boolean needWake;
                if (p == null || when == 0 || when < p.when) {
                   //如果此队列中头部元素是null(空的队列,一般是第一次),或者此消息不是延时的消息,则此消息需要被立即处理,
                    //此时会将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,然后判断如果Looper获取消息的线程如果是阻塞状态则唤醒它,让它立刻去拿消息处理
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                    //如果此消息是延时的消息,则将其添加到队列中,原理就是链表的添加新元素,按照when,也就是延迟的时间来插入的,延迟的时间越长,越靠后,
                    //这样就得到一条有序的延时消息链表,取出消息的时候,延迟时间越小的,就被先获取了。插入延时消息不需要唤醒Looper线程。
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            break;
                        }
                        if (needWake && p.isAsynchronous()) {
                            needWake = false;
                        }
                    }
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;
                }
                // We can assume mPtr != 0 because mQuitting is false.
                if (needWake) {//唤醒线程
                    nativeWake(mPtr);
                }
            }
            return true;
        }

    整个方法的流程:先判断Message关联的Handler是否为空,而且判断这个Message是否以备使用,如果都正常再进入synchronized中的代码,先判断是否被消息队列放弃了,没有放弃再标记自己正处于被使用的状态,Message是一个链表类型,Message.next表示在它后面的Message对象,mMessagess是消息队列里的第一个元素。

    if (p == null || when == 0 || when < p.when) {这句话的意思是,如果mMessagess为空或者传进来的msg不是延时消息或者msg的延时期间比mMessagess的还要短,将msg作为消息队列里第一个元素,原先的mMessagess放在msg的后面(msg.next = p),needWake = mBlocked判断Looper的获取消息的线程是否阻塞,如果阻塞则立即唤醒去取消息。

    在else的分支里,for循环里,循环取出前一个和后一个Message元素(prev和p),如果后一个元素p为空或者msg的when比p的延时时间when靠前,则退出循环,将msg插入到前一个消息prev的后面(next),将后一个消息p插入到msg的后面(next)。

    所以MessageQueue的enqueueMessage方法作用:插入Message消息到消息队列里;唤醒Looper中的等待线程处理消息。

    (2)MessageQueue.next,取出消息,源码:

    Message next() {
            //如果Looper已退出并被释放,则return null,
            //如果系统尝试重启一个退出后不支持重启的Looper,就可能发生这样的情况
            final long ptr = mPtr;
            //mPtr是MessageQueue的一个long型成员变量,关联的是一个在C++层的MessageQueue,
            //阻塞操作就是通过底层的这个MessageQueue来操作的;当队列被放弃的时候其变为0。
            if (ptr == 0) {
                return null;
            }
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
                //阻塞方法,主要是通过native层的epoll监听文件描述符的写入事件来实现的。
               //如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
               //如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
               //如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
                nativePollOnce(ptr, nextPollTimeoutMillis);
                synchronized (this) {
                    //尝试检索下一条消息,如果找到就返回之。
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    if (msg != null && msg.target == null) {
                        //msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的)
                        //如果msg被一个屏障阻止运行,在消息队列里找到下一个异步类型消息续接上运行
                        //同时所有同步消息都将忽略(平常发送的一般都是同步消息)
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        if (now < msg.when) {
                            //Next message is not ready.  Set a timeout to wake up when it is ready.
                            //下一条消息未准备好,设置超时时间当它准备就绪时可以被唤醒
                            //当前时间还未到msg的触发时间,则设置阻塞时间nextPollTimeoutMillis
                            //进入下次循环时调用nativePollOnce方法进行阻塞直到触发时间
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            //正常取出消息,mBlocked=false表示不进行阻塞
                            mBlocked = false;
                            //msg被取出等待处理
                            if (prevMsg != null) {
                                //如果有前一个消息,把msg的next链接为prevMsg的next
                                prevMsg.next = msg.next;
                            } else {
                                //如果没有前一个消息,说明这个msg是队列头部消息,它被处理后,它的next就是队列的头部消息
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();//标记msg正被使用中
                            return msg;
                        }
                    } else {
                        //没有msg,则一直阻塞线程,直到被主动唤醒,此时只会处理不紧急的任务(IdleHandler)
                        nextPollTimeoutMillis = -1;
                    }
                    // Process the quit message now that all pending messages have been handled.
                    //所有被挂起的message都被处理完后,如果msg已从队列中移除则处理掉
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
                    // If first time idle, then get the number of idlers to run.
                    // Idle handles only run if the queue is empty or if the first message
                    // in the queue (possibly a barrier) is due to be handled in the future.
                    //第一次运行非紧急任务,获取当前需要处理的非紧急任务队列的(集合)的长度
                    //非紧急任务仅在消息队列为空或或头部消息(可能是屏障)被延后处理时运行
                    if (pendingIdleHandlerCount < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount <= 0) {
                        // No idle handlers to run.  Loop and wait some more.
                        //没有Message且没有非紧急任务时,设置阻塞,跳出本次循环
                        mBlocked = true;
                        continue;
                    }
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
                // Run the idle handlers.
                // We only ever reach this code block during the first iteration.
                //运行非紧急任务,只在第一次迭代时到达此代码块
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null; //释放对handler的引用
                    boolean keep = false;
                    try {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
                    //遍历判断这些非紧急任务是否需要保存,不需要则释放
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
                // Reset the idle handler count to 0 so we do not run them again.
                pendingIdleHandlerCount = 0;//重设非紧急任务数量为0
                // While calling an idle handler, a new message could have been delivered
                // so go back and look again for a pending message without waiting.
                //当调起一个非紧急任务时,一个新的message可能已经被传递进来了,此时返回查找等待被处理的message而不再继续等待
                nextPollTimeoutMillis = 0;
            }
        }

    从源码看出next的方法大体的流程如下:

    1,首次进入方法或消息队列里的消息都被执行完时,nextPollTimeoutMillis赋值为-1,然后处理非紧急任务IdleHandler,再次进入循环时,线程被阻塞直到被外部主动唤醒(插入消息后根据消息类型决定是否需要唤醒)。

    2,如果消息队列里的头部消息是消息屏障,while循环里找出异步消息然后执行,同时放弃后面的同步消息。

    3,如果取出的msg消息还未到触发时间,则赋值nextPollTimeoutMillis = 延时的时间,阻塞线程直到msg的触发时间唤醒线程处理msg消息。

    4,如果消息需要即时处理或触发时间已到,取出此消息给Looper处理。

    *********************************************************************************************************************************

    分析enqueueMessage和next方法的源码得出结论:

    消息的入列和出列是生产--消费模式,Looper调用loop方法在对应的线程中不断地用next取出消息,而Handler关联的线程通过enqueueMessage插入消息到消息队列里,同时为了防止多个线程对消息队列进行操作,enqueueMessage和next方法都使用了synchronized (this)同步锁机制,this就是MessageQueue对象,确保操作的唯一性。

    问题1:Handler.sendMessageDelayed是如何实现延迟的?

    从next方法里得出,如果取出的消息还未到触发时间,计算并赋值nextPollTimeoutMillis,阻塞线程直到消息的触发时间,这段时间内调用nativePollOnce(ptr, nextPollTimeoutMillis)这个本地C++方法,调用C++底层并最终会通过Linux的epoll监听文件描述符的写入事件来实现延迟处理消息。

    问题2:Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?

    此问题解答来自:https://www.jianshu.com/p/8c829dc15950

    APP的入口ActivityThread的main方法源码:

    public static void main(String[] args) {
            ...
            Looper.prepareMainLooper();
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
            Looper.loop();
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }

    主线程里Looper通过loop开启死循环不断地接收和处理消息,如果没有这个死循环,主线程处理完手头的事情就退出,app也跟着崩溃。

    主线程的Looper负责从消息队列中读取消息,如果队列里没有消息则阻塞进入休眠状态,如果子线程传入消息则唤醒主线程进行读取操作,读取完毕再次进入阻塞休眠状态。主线程不进行耗时操作,耗时操作都交于子线程处理,主线程的Looper循环会尽量减小对cpu性能的消耗。

    https://blog.csdn.net/weixin_36289667/article/details/107960145里抄录这么一段话做理解

    ActivityThread的 main 方法的主要作用就是做消息循环,一旦退出消息循环,主线程运行完毕,那么应用也就退出了。Android是事件驱动的,在Looper.loop()中循环分发事件,而Activity的生命周期都依靠于主线程的 Loop.loop() 来调度,所以可想而知它的存活周期和 Activity 也是一致的。当没有事件需要处理时,主线程就会阻塞;当子线程往消息队列发送消息,并且往管道文件写数据时,主线程就被唤醒。真正会卡死主线程的操作是在执行回调方法 onCreate/onStart/onResume 等操作的时间过长,导致掉帧,甚至发生ANR,looper.loop() 本身不会导致应用卡死。

    主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

    ---------------------------------------------------------------------------------------------------------------------------------------------

    4,Looper

    参考:https://blog.csdn.net/linghaidong/article/details/54709303
    作为安卓线程间消息通信的机制,Looper管理消息队列,实现多线程间消息的互通,用户无需考虑互斥加锁的问题。Looper循环遍历消息队列中的消息,取出消息交于Handler处理。

    ActivityThread类
    public static void main(String[] args) {
            ...
            //在ActivityThread的main方法里创建主线程的Looper
            Looper.prepareMainLooper();
            ...
            ActivityThread thread = new ActivityThread();
            thread.attach(false, startSeq);
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
            if (false) {
                Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
            }
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            //主线程的Looper调用loop方法循环遍历消息队列,当有消息时分发给对应Handler处理,没有消息时阻塞处于休眠状态,等待被唤醒
            Looper.loop();
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    Activity类
    public void recreate() {
            if (mParent != null) {
                throw new IllegalStateException("Can only be called on top-level activity");
            }
            //判断Activity的Looper是不是主线程的Looper,不是抛出异常
            if (Looper.myLooper() != mMainThread.getLooper()) {
                throw new IllegalStateException("Must be called from main thread");
            }
            mMainThread.scheduleRelaunchActivity(mToken);
        }

    主线程ActivityThread在main方法里调用Looper.prepareMainLooper()初始化一个专供主线程使用的Looper,不停循环遍历消息队列,有消息时交给对应Handler处理,没有消息时阻塞处于休眠,等待被唤醒。查看Activity的源代码recreate方法里会判断当前Activity的looper是不是主线程的looper,如果不是会抛出异常,说明Activity用的是主线程的looper。整个app运行中的组件,Activity,service等等运行时的各种操作都是在主线程中调用looper循环遍历消息队列操作更新的,所以耗时操作不要在主线程里操作就是防止ANR。

    Looper的prepare方法源码:

    //本地化存储机制,一个线程只能对应一个
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    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));
        }
    //Looper绑定当前所在线程,生成属于自己的消息队列
    private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }

    参考:https://blog.csdn.net/sun927/article/details/51031268 https://blog.csdn.net/ly502541243/article/details/87475229

    为什么一个线程只能有一个Looper?

    ThreadLocal实现了线程本地存储。所有线程共享同一个ThreadLocal对象,但不同线程仅能访问与其线程相关联的值,一个线程修改ThreadLocal对象对其他线程没有影响。ThreadLocal保证每个线程都有自己的Looper,同时为了确保唯一性,在prepare方法里会先判断ThreadLocal对象是否已创建,如果已创建则会抛出异常提示"一个线程只能生成一个Looper",sThreadLocal.set(new Looper(quitAllowed))绑定当前线程,生成属于自己的消息队列。

    所以在子线程中创建Handler时为什么要调用Looper.prepare()?

    子线程里没有Looper,子线程中创建Handler不调用Looper.prepare,报错如下:

    Uncaught handler: thread Thread-8 exiting due to uncaught exception 

    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 

    调用Looper.prepare,会创建一个新的消息队列,并绑定这个子线程。调用Looper.loop方法开始循环遍历消息队列里的消息。

    public static void loop() {
            ...
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
                ...
                try {
                    msg.target.dispatchMessage(msg);
                    if (observer != null) {
                        observer.messageDispatched(token, msg);
                    }
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } catch (Exception exception) {
                    if (observer != null) {
                        observer.dispatchingThrewException(token, msg, exception);
                    }
                    throw exception;
                } finally {
                    ThreadLocalWorkSource.restore(origWorkSource);
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                ...
                msg.recycleUnchecked();
            }
        }

    查看loop方法的源码,looper开启一个for循环,不停遍历当前的消息队列,只要找到了消息,就调用Handler的dispatchMessage方法处理消息,然后消息msg进行自我回收。

    Handler的dispatchMessage方法源码:

    public void dispatchMessage(@NonNull Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }

    这个方法大意如此:1,如果msg是通过post传入的Runnable类型,因为post最终调用的还是send方法,runnable当作Message的Callback使用,所以msg.callback!= null,直接运行runnable的run方法;2,if (mCallback != null)表示如果构造Handler时有传入Callback参数,则调用Handler的callback处理消息;3,如果前面两种情况都不触发,即通过Handler mHandler = new Handler() { public boolean handleMessage(@NonNull Message msg) {} }创建Handler,此时将消息msg传回Handler的handleMessage方法中供开发者自行处理。

     

     

     

    展开全文
  • Android: 子线程创建Handler的两种方法

    千次阅读 2018-12-07 17:00:10
    方式一: 获取主线的looper(UI线程的looper---...此方法是通过主线的looper来实现的,简单使用. //noinspection Convert2Lambda new Thread(new Runnable() { @Override public void run() { Handler ha...

    方式一: 获取主线程的looper(UI线程的looper---Looper.getMainLooper())

    此方法是通过主线程的looper来实现的,简单使用.

    //noinspection Convert2Lambda
    new Thread(new Runnable() {
        @Override
        public void run() {
            Handler handler = new Handler(Looper.getMainLooper()) { //重点在此处
                @Override
                public void handleMessage(Message msg) {
                    Toast.makeText(getApplicationContext(), "handler-msg", Toast.LENGTH_LONG).show();
                }
            };
            handler.sendEmptyMessage(1);
        }
    }).start();

    方式二: 直接获取当前子线程的looper

    new Thread(() -> {
        Looper.prepare();
        @SuppressLint("HandlerLeak")
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Toast.makeText(getApplicationContext(), "handler-msg", Toast.LENGTH_LONG).show();
            }
        };
        handler.sendEmptyMessage(1);
        Looper.loop();
    }).start();

    或  new Thread(new Runnable() {

                @Override
                public void run() {
                    Looper.prepare();
                    Handler handler = new Handler(){
                        @Override
                        public void handleMessage(Message msg) {
                            Toast.makeText(getApplicationContext(), "handler-msg", Toast.LENGTH_LONG).show();
                        }
                    };
                    handler.sendEmptyMessage(1);
                    Looper.loop();
                };
            }).start();

     

    展开全文
  • 子线程控制主线程中的组件使用handler 比较好,但有时也可以用下面的方法来实现同样的效果: 此处是demo public class MainActivity extends Activity { private ImageView IV; private Button addBtn; @...
  • 子线程的Handler 在使用handler时,会有在子线程创建handler的场景,那我们从Looper.java的源码中摘抄下面一段创建Hander的代码段: class LooperThread extends Thread { public Handler mHandler; public ...
  • 关键字:Android 使用handler实现线程间发送消息 (主线程 与 子线程之间)、(子线程子线程之间) 相信大家平时都有使用到异步线程往主线程(UI线程)发送消息的情况。本文主要研究Handler的消息发送。包括...
  • 关于Handler,在哪个线程里面new...同理的如果我们在子线程里面new一个Handler,那么Handler对象就运行在子线程中。不同的是,我们不能直接在子线程里面new一个Handler,因为: 在应用App启动的时候,会在执行程序...
  • keyword:Android 使用handler实现线程间发送消息 (主线程 与 子线程之间)、(子线程子线程之间) 相信大家平时都有使用到异步线程往主线程(UI线程)发送消息的情况。本文主要研究Handler的消息发送。...
  • 主要介绍了android使用handler ui线程和子线程通讯更新ui的方法,大家参考使用吧
  • 创建于子线程绑定的handler可以在thread的内部进行公共访问修饰符定义,其外部也可调用。但使用不方便,并且不可以直接使用子线程的looper绑定,要通过一系列( 比如:Looper.prepare(),Looper.Loop() )的手段才能...
  • * 主线程给子线程发送消息 */ public class HandlerTest3Activity extends AppCompatActivity { @BindView(R.id.tv) TextView tv; @BindView(R.id.btn1) Button btn1; Handler handler; @Ove...
  • 转载:https://blog.csdn.net/shaoenxiao/article/details/54561753今天这篇文章只讲一下怎么使用Handler实现子线程与子线程之间、子线程与主线程之间如何进行通信,关于具体的内部实现因为我也没研究过,所以这篇...
  • 转载:https://blog.csdn.net/shaoenxiao/article/details/54561753今天这篇文章只讲一下怎么使用Handler实现子线程与子线程之间、子线程...
  • 1.子线程改变UI主线Handler使用方法:  1>思路:  1.1>开启新线程,新线程中利用handler发送消息  new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method ...
  • 1、先熟悉handler方式实现主线程和子线程互相通信方式,子线程和子线程的通信方式 ...2、贴上简单HandlerThread简单使用(主线程和子线程通信、子线程子线程通信)的例子 1、activity_main.xml文件
  • 本文介绍了如下内容: 1、如何创建一个handler对象并使其与子线程挂钩; 2、HandlerThread的作用以及使用方法。
  • 安卓的主线其实是UI线程来...1、主线创建个handler进行显示(操作UI),这时Activity中的onCreate状态时创建 private final int MSG_HELLO = 0; private Handler handler; @Override protected void onCre...
  • ——使用Handler机制传递消息到主线程(UI线程) 为什么我们不在子线程更新UI呢?——因为Android是单线程模型 为什么要做成单线程模型呢?——多线程并发访问UI可能会导致UI控件处于不可预期的状态。如果加锁,...
  • 1 package com.act262.sockettx; 2 3 import android.app.Activity; 4 import android.os.Bundle; ... 5 import android.os.Handler; 6 import android.os.Message; 7 import android.view.V...
  • 子线程能创建handler

    2019-11-21 15:36:22
    子线程是可以创建hanlder的,但是如果你直接使用new thread来创建,代码会报loop的空指针错误,原因如下文,但是,Google给我们提供了HandlerThread,我们当然也可以利用HandlerThread中的思想在newThread里面实现子...
  • 在你开启的子线程当中返回的数据是无法直接在主线程当中使用的,那怎么办呢,那就这能用Handle了 Handler handler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case 1...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,025
精华内容 410
关键字:

子线程使用handler