Handler 面试相关

Handler机制整体流程;Looper.loop()为什么不会阻塞主线程;IdHandler(闲时机制);postDelay()的具体实现;post()与sendMessage()区别;使用Handler需要注意什么问题,怎么解决的?

https://www.jianshu.com/p/a77af781f678

第一个问题:Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

loop在没有消息的时候会沉眠,释放CPU,只有在有消息的时候在会被再次唤醒,阻碍线程的不是Looper.loop(),是loop中调用handler进行主线程操作才阻碍了线程

 Activity 的生命周期都有对应的 case 条件了,ActivityThread 有个 getHandler 方法,得到这个 handler 就可以发送消息,然后 loop 里就分发消息,然后就发给 handler, 然后就执行到 H(Handler )里的对应代码。所以这些代码就不会卡死~,有消息过来就能执行。举个例子,在 ActivityThread 里的内部类 ApplicationThread 中就有很多 sendMessage 的方法。 

简单的来说:ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的程序也就可以退出了。 

从消息队列中取消息可能会阻塞,取到消息会做出相应的处理。如果某个消息处理时间过长,就可能会影响UI线程的刷新速率,造成卡顿的现象。

如果你了解下linux的epoll你就知道为什么不会被卡住了,先说结论:阻塞是有的,但是不会卡住 

主要原因有2个

  1. epoll模型 

当没有消息的时候会epoll.wait,等待句柄写的时候再唤醒,这个时候其实是阻塞的。

  1. 所有的ui操作都通过handler来发消息操作。 

比如屏幕刷新16ms一个消息,你的各种点击事件,所以就会有句柄写操作,唤醒上文的wait操作,所以不会被卡死了。

这里涉及线程,先说说说进程/线程: 

进程:每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。 

线程:线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。

 

总结:

这里涉及线程,先说说说进程/线程,进程:每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。

线程:线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。

有了这么准备,再说说死循环问题:

对于线程既然是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。

真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

 

 

第二个问题?

 

 

第三个问题:postDelay()的具体实现

源码中调用 sendMessageDelayed(getPostMessage(r), delayMillis); 这个方法 参数一是 Message 对象,参数二是延时的时间

阅读之前先问大家一个问题:Handler.postDelayed()是先delay一定的时间,然后再放入messageQueue中,还是先直接放入MessageQueue中,然后在里面wait delay的时间?为什么?如果你不答不上来的话,那么此文值得你看看?

答案:先发消息,在延时等待

https://www.cnblogs.com/ldq2016/p/9260783.html

 

先说一下:是先发消息后延时的,MessageQueue.next() 在这个方法中进行了延时

1. postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;

2. 紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;

3. MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;

4. Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;

5. 直到阻塞时间到或者下一次有Message进队;

这样,基本上就能保证Handler.postDelayed()发布的消息能在相对精确的时间被传递给Looper进行处理而又不会阻塞队列了。

 

Handler.postDelayed()的调用路径

一步一步跟一下Handler.postDelayed()的调用路径:

  1. Handler.postDelayed(Runnable r, long delayMillis)
  2. Handler.sendMessageDelayed(getPostMessage(r), delayMillis)
  3. Handler.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
  4. Handler.enqueueMessage(queue, msg, uptimeMillis)
  5. MessageQueue.enqueueMessage(msg, uptimeMillis)

最后发现Handler没有自己处理Delay,而是交给了MessageQueue处理

 

总结:另外,这里在阅读原文的基础上添加一点思考内容:

MessageQueue会根据post delay的时间排序放入到链表中,链表头的时间小,尾部时间最大。因此能保证时间Delay最长的不会block住时间短的。当每次post message的时候会进入到MessageQueue的next()方法,会根据其delay时间和链表头的比较,如果更短则,放入链表头,并且看时间是否有delay,如果有,则block,等待时间到来唤醒执行,否则将唤醒立即执行。

所以handler.postDelay并不是先等待一定的时间再放入到MessageQueue中,而是直接进入MessageQueue,以MessageQueue的时间顺序排列和唤醒的方式结合实现的。使用后者的方式,我认为是集中式的统一管理了所有message,而如果像前者的话,有多少个delay message,则需要起多少个定时器。前者由于有了排序,而且保存的每个message的执行时间,因此只需一个定时器按顺序next即可。

 

第四个问题:post()与sendMessage()区别

post()与sendMessage()区别,本质上是没区别的,看源码都是调用 sendMessageDelayed(getPostMessage(r), delayMillis); 这个方法 参数一是 Message 对象,参数二是延时的时间 最后走的 sendMessageAtTime 这个方法

只是用法上的区别:

 private TextView tv_up;
    private String new_str = "";

    /*post方法解决UI更新问题handler创建方式*/
    private Handler handler_post = new Handler();

    /*sendMessage方法解决UI更新问题handler创建方式*/
    Handler handler_senM = new Handler() {
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                /*sendMessage方法更新UI的操作必须在handler的handleMessage回调中完成*/
                tv_up.setText(new_str);
            }
        };
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                new_str = "更新UI";

                /*sendMessage方法解决UI更新发送消息给handler(主线程中的handler)*/
                handler_senM.sendEmptyMessage(1);


                /*post方法解决UI更新,直接在runnable里面完成更新操作,这个任务会被添加到handler所在线程的消息队列中,即主线程的消息队列中*/
                handler_post.post(new Runnable() {
                    @Override
                    public void run() {
                        tv_up.setText(new_str);
                    }
                });
            }
        }).start();
    }

第五个问题:使用Handler需要注意什么问题,怎么解决的?

https://blog.csdn.net/weixin_30500105/article/details/95117094

注意内存泄漏,加入 WeakReference 弱引用,把Activity 放到弱引用中,

 

uploading.4e448015.gif转存失败重新上传取消

由上图大家可以看到,提示说 Handler 这样写就是一个内部类,其实我也没搞明白,这怎么就成了内部类了,不过话说回来既然是内部类,这么写就有问题了,大家都知道内部类会隐形(或者说默认)持有外部类的引用,这样当外部类销毁的时候,内部类由于被执行中的线程引用了,这时候外部类就不会被GC 回收,就会造成内存泄漏。

 

我们知道,Handler是和Looper以及MessageQueue一起工作的,在Android中,一个应用启动后,系统默认会创建一个为主线程服务的Looper对象,该Looper对象用于处理主线程的所有Message对象,它的生命周期贯穿于整个应用的生命周期。在主线程中使用的Handler都会默认绑定到这个Looper对象。在主线程中创建Handler对象,它会立即关联到主线程Looper对象的MessageQueue,这时发送到MessageQueue中的Message对象都会只有这个Handler对象的引用,这样在Looper处理消息时常能回调到Handler的handlerMessage方法。因此,如果Message还没有被处理完成,那么Handler对象也就不会被垃圾回收。

  在上面的代码中,将Handler的实例声明为HandlerActivity类的内部类。而在Java语言中,非静态内部匿名类会持有外部类的一个隐式的引用,这样就可能会导致外部类无法被垃圾回收。因此,最终由于MessageQueue中Message还没处理完成,就会持有Handler对象的引用,而非静态的Handler对象会持有外部类HandlerActivity的引用,这个Activity无法被垃圾回收,从而导致内存泄漏。

https://www.jianshu.com/p/804e774d9f76

①先说handler导致activity内存泄露的原因:

handler发送的消息在当前handler的消息队列中,如果此时activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类(非静态内部类),我们知道内部类天然持有外部类的实例引用,这样在GC垃圾回收机制进行回收时发现这个Activity居然还有其他引用存在,因而就不会去回收这个Activity,进而导致activity泄露。

②为何handler要定义为static?

因为静态内部类不持有外部类的引用,所以使用静态的handler不会导致activity的泄露

③为何handler要定义为static的同时,还要用WeakReference 包裹外部类的对象?

这是因为我们需要使用外部类的成员,可以通过"activity. "获取变量方法等,如果直接使用强引用,显然会导致activity泄露。

 

静态内部类的Handler的写法

解决内存泄露

public class MainActivity extends AppCompatActivity {

    TextView mTextView;

    MyHandler mMyHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.tv_handler);
        mMyHandler = new MyHandler(MainActivity.this);
        doInBackground();
    }

    private static class MyHandler extends Handler {
        private WeakReference<MainActivity> mWeakReference;

        public MyHandler(MainActivity activity) {
            mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity mainActivity = mWeakReference.get();
            switch (msg.what) {
                case 1:
                    if (mainActivity != null) {
                        mainActivity.mTextView.setText(msg.obj + "");
                    }
                    break;
                default:
                    break;
            }
        }
    }

    private void doInBackground() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模拟耗时操作
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //耗时操作完成,发送消息给UI线程
                Message msg = Message.obtain();
                msg.what = 1;
                msg.obj = "更新UI";
                mMyHandler.sendMessage(msg);
            }
        }).start();
    }

}

请忽略activity的弱引用,因为关系不太,之所以弱引用activity,是为了访问activity对象的成员

 

 

抛出一个问题,handler是怎么进行线程间的通讯的呢?

可以从下面这个方面回答,把知识点提炼一下:

一个handler 只能绑定一个 looper,但是多个线程(也就是多个 handler )可以往同一个 looper 发送消息,发消息的时候都是通过aHandler.sendMessage() 或者 aHandler.post() 的方式,这里发送出去的消息会持有该发送 handler 的引用,如这里发送出去的消息就会持有 aHandler 的引用,把消息发送到 aHandler 对应的 looper 内部维护的 MessageQueue 中,轮询分发消息的时候,就会根据 message 找到所持有的 Handler 的引用,找到对应的 Handler 然后传递消息。

 

1、在线程A中要预先创建一个Looper, 同时在Looper中会创建一个MessageQueue对象。

2、在线程A中创建Handler对象并重写了handleMessage方法,等待消息到来处理消息。也就是处理消息的线程是线程A。在Handler构造方法中,会去拿到上面这个Looper对象,和Looper中的MessageQueue对象

3、在线程B发送消息,Handler会把消息放入到MessageQueue中,但在这之前,把自己赋值给了这个Message的target变量。 也就是说,在消息中会有一个target变量就是当初发送自己的handler对象

4、调用Looper中的loop()方法。方法开启了一个死循环,判断当前Looper中的MessageQueue中是否有消息Message,如果有就调用该消息的target变量(handler对象)的dispatchMessage()方法。

5、处理消息,在Handler中的dispatchMessage()方法中分发消息,判断具体把消息交给handler的那个回调方法处理。

最后结果就是,线程B中发出的消息,在线程A中得到处理。。。注意第4步流程,可以直接在第1步之后做,总之是要开启这个死循环。

       其他文章还介绍了ThreadLocal, 这里也说下,ThreadLocal可以创建一个只属于线程自己的变量,这个变量与其他线程是隔离的,不会被共享。线程A中创建的Looper对象就是放到了的ThreadLocal中。Handler构造中也是通过ThreadLocal去拿到的Looper对象。

 

IdHandler

IdleHandler是主线程在开始加载页面完成后调用的方法,可以提高性能:

IdleHandler优化Activity启动时间

    @Override
    protected void onResume() {
        super.onResume();
        Looper.myQueue().addIdleHandler(() -> {
            initializeData();
            return false;
        });
        Looper.myQueue().addIdleHandler(() -> {
            resumeData();
            return true;
        });
    }


如果为true,会重复执行,false只执行一次

IdleHandler是一个回调接口,可以通过MessageQueue的addIdleHandler添加实现类。当MessageQueue中的任务暂时处理完了(没有新任务或者下一个任务延时在之后),这个时候会回调这个接口,返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调。

 

同步屏障

一、消息种类

Message分为3中:普通消息(同步消息)、屏障消息(同步屏障)和异步消息。我们通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。

二、什么是屏障消息

同步屏障是通过MessageQueue的postSyncBarrier方法插入到消息队列的。

MessageQueue#postSyncBarrier
 private int postSyncBarrier(long when) {
        synchronized (this) {
            final int token = mNextBarrierToken++;
            //1、屏障消息和普通消息的区别是屏障消息没有tartget。
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            //2、根据时间顺序将屏障插入到消息链表中适当的位置
            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;
            }
            //3、返回一个序号,通过这个序号可以撤销屏障
            return token;
        }
    }

postSyncBarrier方法就是用来插入一个屏障到消息队列的,可以看到它很简单,从这个方法我们可以知道如下:

  • 屏障消息和普通消息的区别在于屏障没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
  • 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
  • postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
  • postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。
  • 插入普通消息会唤醒消息队列,但是插入屏障不会。
     

三、屏障消息的工作原理

通过postSyncBarrier方法屏障就被插入到消息队列中了,那么屏障是如何挡住普通消息只允许异步消息通过的呢?我们知道MessageQueue是通过next方法来获取消息的。

Message next() {
			//1、如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {        
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {//2、遇到屏障  msg.target == null
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());//3、遍历消息链表找到最近的一条异步消息
                }
                if (msg != null) {
                	//4、如果找到异步消息
                    if (now < msg.when) {//异步消息还没到处理时间,就在等会(超时时间)
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //异步消息到了处理时间,就从链表移除,返回它。
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 如果没有异步消息就一直休眠,等待被唤醒。
                    nextPollTimeoutMillis = -1;
                }
			//。。。。
        }
    }

可以看到,在注释2如果碰到屏障就遍历整个消息链表找到最近的一条异步消息,在遍历的过程中只有异步消息才会被处理执行到 if (msg != null){}中的代码。可以看到通过这种方式就挡住了所有的普通消息。

四、如何发送异步消息

Handler有几个构造方法,可以传入async标志为true,这样构造的Handler发送的消息就是异步消息。不过可以看到,这些构造函数都是hide的,正常我们是不能调用的,不过利用反射机制可以使用@hide方法。

     /**
      * @hide
      */
    public Handler(boolean async) {}

    /**
     * @hide
     */
    public Handler(Callback callback, boolean async) { }

    /**
     * @hide
     */
    public Handler(Looper looper, Callback callback, boolean async) {}

当调用handler.sendMessage(msg)发送消息,最终会走到:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);//把消息设置为异步消息
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以看到如果这个handler的mAsynchronous为true就把消息设置为异步消息,设置异步消息其实也就是设置msg内部的一个标志。而这个mAsynchronous就是构造handler时传入的async。除此之外,还有一个公开的方法:

	    Message message=Message.obtain();
        message.setAsynchronous(true);
        handler.sendMessage(message);

在发送消息时通过 message.setAsynchronous(true)将消息设为异步的,这个方法是公开的,我们可以正常使用。

五、移除屏障

移除屏障可以通过MessageQueue的removeSyncBarrier方法:

//注释已经写的很清楚了,就是通过插入同步屏障时返回的token 来移除屏障
/**
     * Removes a synchronization barrier.
     *
     * @param token The synchronization barrier token that was returned by
     * {@link #postSyncBarrier}.
     *
     * @throws IllegalStateException if the barrier was not found.
     *
     * @hide
     */
    public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            //找到token对应的屏障
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            final boolean needWake;
            //从消息链表中移除
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            //回收这个Message到对象池中。
            p.recycleUnchecked();
			// If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);//唤醒消息队列
            }
    }

 

测试:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Handler handler;
    private int token;

    public static final int MESSAGE_TYPE_SYNC=1;
    public static final int MESSAGE_TYPE_ASYN=2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initHandler();
        initListener();
    }

    private void initHandler() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                handler=new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        if (msg.what == MESSAGE_TYPE_SYNC){
                            Log.d("MainActivity","收到普通消息");
                        }else if (msg.what == MESSAGE_TYPE_ASYN){
                            Log.d("MainActivity","收到异步消息");
                        }
                    }
                };
                Looper.loop();
            }
        }).start();
    }

    private void initListener() {
        findViewById(R.id.btn_postSyncBarrier).setOnClickListener(this);
        findViewById(R.id.btn_removeSyncBarrier).setOnClickListener(this);
        findViewById(R.id.btn_postSyncMessage).setOnClickListener(this);
        findViewById(R.id.btn_postAsynMessage).setOnClickListener(this);
    }

    //往消息队列插入同步屏障
    @RequiresApi(api = Build.VERSION_CODES.M)
    public void sendSyncBarrier(){
        try {
            Log.d("MainActivity","插入同步屏障");
            MessageQueue queue=handler.getLooper().getQueue();
            Method method=MessageQueue.class.getDeclaredMethod("postSyncBarrier");
            method.setAccessible(true);
            token= (int) method.invoke(queue);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //移除屏障
    @RequiresApi(api = Build.VERSION_CODES.M)
    public void removeSyncBarrier(){
        try {
            Log.d("MainActivity","移除屏障");
            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();
        }
    }

    //往消息队列插入普通消息
    public void sendSyncMessage(){
        Log.d("MainActivity","插入普通消息");
        Message message= Message.obtain();
        message.what=MESSAGE_TYPE_SYNC;
        handler.sendMessageDelayed(message,1000);
    }

    //往消息队列插入异步消息
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
    private void sendAsynMessage() {
        Log.d("MainActivity","插入异步消息");
        Message message=Message.obtain();
        message.what=MESSAGE_TYPE_ASYN;
        message.setAsynchronous(true);
        handler.sendMessageDelayed(message,1000);
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    public void onClick(View v) {
        int id=v.getId();
        if (id == R.id.btn_postSyncBarrier) {
            sendSyncBarrier();
        }else if (id == R.id.btn_removeSyncBarrier) {
            removeSyncBarrier();
        }else if (id == R.id.btn_postSyncMessage) {
            sendSyncMessage();
        }else if (id == R.id.btn_postAsynMessage){
            sendAsynMessage();
        }
    }
    
}

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值