1.前言大家好,我是呈祥墨影!runWithScissors()是Handler的一个方法,标记为@hide,普通开发者不允许调用。这种方法比较冷门。如果面试中问面试官,面试官不知道,面试官通常会换个问题:“如何在子线程中通过Handler向主线程发送一个任务,等待主线程处理这个任务。你想继续吗?”。这个场景可以在runWithScissors()的帮助下实现。这个方法虽然标记为@hide,但是在Framework中很多场景都会用到。但是,它也存在一些隐患。正是因为这些隐患,Android工程师将其标记为@hide,普通开发者不得使用。今天我们就来说说Handler的冷门方法runWithScissors(),以及可能出现的一些问题。2.Handler.runWithScissors()2.1runWithScissors()抛开runWithScissors()方法不谈,由于两个线程之间存在通信,所以必须考虑多线程同步。首先想到的是Synchronized锁及其等待/通知机制。通过Handler跨线程通信时,如果要发送一个“任务”,Runnable肯定比Message更合适。接下来,让我们看看runWithScissors()的实现是否符合预期。publicfinalbooleanrunWithScissors(finalRunnabler,longtimeout){if(r==null){thrownewIllegalArgumentException("runnablemustnotbenull");}if(timeout<0){thrownewIllegalArgumentException("timeoutmustbenon-negative");}if(Looper.myLooper()==mLooper){r.run();returntrue;}BlockingRunnablebr=newBlockingRunnable(r);returnbr.postAndWait(this,timeout);}你可以看到runWithScissors()接受Runnable并可以设置超时。过程也很简单:先简单检查输入参数;如果当前线程与Handler的处理线程一致,则直接运行run()方法;如果线程不一致,用BlockingRunnable包裹起来,执行它的postAndWait()方法;然后继续看BlockingRunnable的源码。privatestaticfinalclassBlockingRunnableimplementsRunnable{privatefinalRunnablemTask;privatebooleanmDone;publicBlockingRunnable(Runnabletask){mTask=task;}@Overridepublicvoidrun(){try{//RuninHandlerthreadmTask.run();}finally{synchronized(this){mDone=true;notifyAll();}}}publicbooleanpostAndWait(Handlerhandler,longtimeout){if(!handler.post(this)){returnfalse;}synchronized(this){if(timeout>0){finallongexpirationTime=SystemClock.uptimeMillis()+timeout;while(!mDone){longdelay=expirationTime-SystemClock.uptimeMillis();if(delay<=0){returnfalse;//timeout}try{wait(delay);}catch(InterruptedExceptionex){}}}else{while(!mDone){try{wait();}catch(InterruptedExceptionex){}}}}returntrue;}}待执行的任务会记录在BlockingRunnable的mTask中,等待后续调用执行。postAndWait()的逻辑也很简单。首先尝试通过handler发送BlockingRunnable,然后进入Synchronized临界区,尝试wait()进行阻塞。如果设置了超时,使用wait(timeout)进入阻塞。如果超时唤醒,则直接返回false,表示任务执行失败。所以现在您可以看到postAndWait()返回false。有两种场景:Handlerpost()失败,说明Looper有问题;等待超时,任务还没有完成;除了超时唤醒,我们还需要在任务执行完毕后,唤醒当前线程。回顾一下BlockingRunnable的run()方法,run()是由Handler调度的,在其线程中执行。在里面调用mTask.run(),mTask就是我们需要执行的Runnable任务。执行结束后标记mDone,通过notifyAll()唤醒wait。任务发起线程被唤醒后会判断mDone。如果为true则任务执行完成,直接返回true退出。2.2runWithScissors()在Framework中的使用被标记为@hide,在应用开发中一般不会用到,但是在Framework中,有很多使用场景。比如我们熟悉的WMS启动流程,在main()和initPolicy()中,通过runWithScissors()切换到“android.display”和“android.ui”线程做一些初始化工作。privatevoidinitPolicy(){UiThread.getHandler().runWithScissors(newRunnable(){publicvoidrun(){//在“android.ui”线程中运行WindowManagerPolicyThread.set(Thread.currentThread(),Looper.myLooper());mPolicy.init(mContext,WindowManagerService.this,WindowManagerService.this);}},0);}例如,上面的代码是通过切换到“android.ui”线程,从“android.display”线程执行任务。3.runWithScissors()的问题看来runWithScissors()是利用Synchronized等待通知机制配合Handler发送Runnable执行阻塞任务。貌似没有问题,但是还是被Android工程师设置为@hide。让我们继续讨论它的问题。3.1超时则无取消逻辑。通过runWithScissors()发送Runnable时,可以指定超时时间。超时唤醒,直接false退出。当超时退出时,Runnable还在目标线程的MessageQueue中,并没有被移除。最终会被Handler线程调度执行。此时的执行显然没有达到我们的业务预期。3.2可能造成死锁。更严重的是,使用runWithScissors()可能会导致调用线程阻塞,无法唤醒。如果它当前持有其他锁,也会造成死锁。我们通过Handler发送的MessageQueue消息一般都会被执行,而当线程Looper通过quit()退出时,会清理未执行的任务,此时发送线程永远不会被唤醒。那么在使用runWithScissors()时,一定不能让Handler所在的线程Looper退出,或者使用quitSafely()退出。quit()和quitSafely()都是退出的意思,都会清理对应的MessageQueue。不同的是,qiut()会清理MessageQueue中的所有消息,而quitSafely()只会清理当前时间点之后(when>now)的消息,当前时间之前的消息,仍然会被执行。那么只要使用quitSafely()退出,通过runWithScissors()发送的任务仍然会被执行。也就是说,runWithScissors()的安全使用必须满足两个条件:Handler的Looper不允许退出,比如Android主线程Looper不允许退出;当Looper退出时,使用quitSafely()安全退出;四、总结时刻今天我们介绍了一个冷门方法runWithScissors()及其原理,它可以通过阻塞等待任务执行结束的方式向目标线程发送任务。虽然标记为@hide,不能直接使用,但这是一个纯软件实现。我们实际上可以自己实现一个BlockingRunnable来使用它。当然,在使用时也需要注意存在的问题。我知道这个方法即使没有标记为@hide,使用场景也很少,但是它仍然可以帮助我们思考一些关键的问题,比如线程同步、死锁、Handler退出方法对消息的影响等。
