美文网首页
死磕Handler(3)

死磕Handler(3)

作者: 程序员要多喝水 | 来源:发表于2019-11-21 13:53 被阅读0次

    这节介绍些Handler的隐藏小技巧:
    (1)利用Handler统计耗时任务:
    Loop.loop方法源码可以看出,处理消息是可以统计时长的,也就是1和3之间时差;
    Loop#loop

     public static void loop() {
       ...
       for (;;) {
                Message msg = queue.next(); // might block
                ...
                //1.处理消息之前的日志
                final Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
                ...
                //2.处理消息
                try {
                    msg.target.dispatchMessage(msg);
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                ...
                //3.处理完消息之后的日志
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
       ...
     }
    

    统计时长方法:

     private static final String MONITOR_START = ">>>>> Dispatching to";
     private static final String MONITOR_END = "<<<<< Finished to";
     
     looper.setMessageLogging(new Printer() {
                long startTime = 0;
                @Override
                public void println(String x) {
                    if (x.contains(MONITOR_START)){
                        startTime = SystemClock.currentThreadTimeMillis();
                    }
                    if (x.contains(MONITOR_END)){
                        if (SystemClock.currentThreadTimeMillis()-startTime>1000){
                            Log.w(TAG,"slow dispatch message");
                        }
                    }
                }
            });
    

    当然光统计时长也不行,往往需要定位具体哪个地方引起了耗时,因此需要线程栈打印信息更好:

    private static final String MONITOR_START = ">>>>> Dispatching to";
    private static final String MONITOR_END = "<<<<< Finished to";
    private static final int TIME_THRESHOLD = 300;
    private static HandlerThread thread;
    
    public static void startThreadMonitor(Looper looper, int timeThreshold){
            thread = new HandlerThread("handler-monitor:"+looper.getThread().getName());
            thread.start();
            Handler threadHandler = new Handler(thread.getLooper());
            //耗时任务触发时候,打印stackTrace信息
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    StringBuilder sb = new StringBuilder();
                    StackTraceElement[] stackTrace = looper.getThread().getStackTrace();
                    for (StackTraceElement s:stackTrace){
                        sb.append(s.toString());
                        sb.append("\n");
                    }
                    Log.w(TAG,sb.toString());
                }
            };
            looper.setMessageLogging(new Printer() {
                @Override
                public void println(String x) {
                    if (x.contains(MONITOR_START)){
                       postMessage(threadHandler,runnable,timeThreshold);
                    }
    
                    if (x.contains(MONITOR_END)){
                       removeMessage(threadHandler,runnable);
                    }
                }
            });
        }
        
    public static void stopThreadMonitor(){
        thread.quit();
    }
    
    public static void postMessage(Handler handler, Runnable runnable,
        int timeThreshold) {
        handler.postDelayed(runnable, timeThreshold);
    }
    
    public static void removeMessage(Handler handler, Runnable runnable) {
        handler.removeCallbacks(runnable);
    }
    

    其中大概原理就是在MONITOR_START写日志时候,开始往HandlerThread线程中的MessageQueue发送一个延迟消息,消息类型是Runnable,在MONITOR_END移除消息,如果两个时差超过设定是时间阈值,那么就会触发Runnable方法,执行一次stackTrace打印输出,反之因移除了消息,所以不会有任何打印;其实这里思想和Android上报ANR思想是一样的,Input事件,broadcast,service也都是提前埋一个延迟爆炸雷,然后等结束后拆雷,如果中途耗时太久,雷直接爆炸了;

    (2)等待其他线程执行完执行
    这个主要是利用runWithScissors,不过没对外公开API
    handler除了发送send/post发送消息外还有一个runWithScissors,源码如下

    public final boolean runWithScissors(final Runnable r, long timeout) {
        if (r == null) {
            throw new IllegalArgumentException("runnable must not be null");
        }
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout must be non-negative");
        }
        // 当为同一个线程时,直接执行runnable,而不需要加入到消息队列
        if (Looper.myLooper() == mLooper) {
            r.run();
            return true;
        }
    
        // new 一个BlockRunnable对象
        BlockingRunnable br = new BlockingRunnable(r);
        return br.postAndWait(this, timeout);
    }
    
    public boolean postAndWait(Handler handler, long timeout) {
            if (!handler.post(this)) {
                return false;
            }
    
            synchronized (this) {
                if (timeout > 0) {
                    final long expirationTime = SystemClock.uptimeMillis() + timeout;
                    while (!mDone) {
                        long delay = expirationTime - SystemClock.uptimeMillis();
                        if (delay <= 0) {
                            return false; // timeout
                        }
                        try {
                            // post runnable 之后,将调用线程变为wait状态
                            wait(delay);
                        } catch (InterruptedException ex) {
                        }
                    }
                } else {
                    while (!mDone) {
                        try {
                            // post runnable 之后,将调用线程变为wait状态
                            wait();
                        } catch (InterruptedException ex) {
                        }
                    }
                }
            }
            return true;
        }
    
        public void run() {
            try {
                mTask.run();
            } finally {
                synchronized (this) {
                    mDone = true;
                    // runnable 执行完之后,会通知wait的线程不再wait
                    notifyAll();
                }
            }
        }
    

    runWithScissors(runnable) 发送并执行一个同步的runnable。

    • 当在同一个线程时,会直接执行这个runnable,而不需要放到queue里面。
    • 当不在同一个线程时,会等这个runnable执行完,才返回,不然会block在这里。也就是Handler中提供的BlockRunnable的作用。

    具体使用场景,比如初始化任务,如下WindowManagerService的main和initPolicy初始化时候:

    public static WindowManagerService main(final Context context, final InputManagerService im,
                final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore,
                WindowManagerPolicy policy) {
            DisplayThread.getHandler().runWithScissors(() ->
                    sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
                            onlyCore, policy), 0);
            return sInstance;
        }
        
    private void initPolicy() {
            UiThread.getHandler().runWithScissors(new Runnable() {
                @Override
                public void run() {
                    WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
                    mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
                }
            }, 0);
        }
    

    剩下的好像暂时没有发现其他特别的,Handler大概看完了,下一章节,看下AIDL;

    相关文章

      网友评论

          本文标题:死磕Handler(3)

          本文链接:https://www.haomeiwen.com/subject/rjftwctx.html