美文网首页
死磕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)

    这节介绍些Handler的隐藏小技巧:(1)利用Handler统计耗时任务:Loop.loop方法源码可以看出,处...

  • 死磕Handler(1)

    handler作为Android开发最中重要的模型之一,需要理解其工作原理包括Handler,Message,Me...

  • 死磕Handler(2)

    Handler在Thread使用 在子线程中使用handler实例: 可以看到,在子线程中创建handler需要注...

  • 产品经理的三阶段修炼

    初级产品经理的三项修炼 1、死磕界面 2、死磕流程 3、死磕流畅度 以自我为中心的理念,我是专业人士心态 中级产品...

  • “死磕”与学习

    也说“死磕” 死磕到底,死磕精神,死磕侠。互联网的发达,孕育了越来越多的网络词汇,“死磕”现在出现的频率颇高。 那...

  • 这些“死磕成本”的店,却因高体验卖出了惊人销量

    有些店死磕服务,有些死磕产品,还有些死磕成本。可有些品牌除了这些,还死磕别的... 无论何时,店铺的人工成本、租金...

  • 死磕与磕死

    前天晚上,打开百度网盘,准备听梁冬的节目睡睡平安,突然发现所有的音频转哪转哪,就是不出声音。到底哪里出了毛病?听听...

  • 磕,死磕

    疫情期间,你做的最多的是什么? 我啊~大概是反省吧,自省。 我发现反省是扇隐秘的门,一旦打开,就像探险一样,不停的...

  • 死磕别人,不如死磕自己

    【死磕别人,不如死磕自己。】有朋友是干销售的,任凭那股子死磕别人的毅力,一切都是那么不可控,最后只剩毅力。与其死磕...

  • 3月,死磕到底

    写作基础班开营了,依伊、清月、洛央三位老师分别发言了,但一反常态,三位老师一再强调大家做好心理准备,这是一件...

网友评论

      本文标题:死磕Handler(3)

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