死磕Handler(3)

2019-11-21  本文已影响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。

具体使用场景,比如初始化任务,如下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;

上一篇 下一篇

猜你喜欢

热点阅读