当前位置:   article > 正文

Android打印主线程所有方法执行时间、handler耗时_android 打印当前线程

android 打印当前线程

前言:

我们知道Android卡顿主要是主线程中有耗时操作导致的,那么我们怎么能方便快捷的获取主线程中的所有耗时方法执行时间呢?今天我们来介绍两个方案

方案一:利用Looper.java中loop()方法的logging.print的特殊关键字进行耗时打印:

在消息分发时,主线程的looper.loop()方法会遍历所有的消息进行分发,执行耗时任务。我们看下源码的loop()方法:

  1. for (;;) {
  2. Message msg = queue.next(); // might block
  3. if (msg == null) {
  4. // No message indicates that the message queue is quitting.
  5. return;
  6. }
  7. // This must be in a local variable, in case a UI event sets the logger
  8. final Printer logging = me.mLogging;
  9. if (logging != null) {
  10. logging.println(">>>>> Dispatching to " + msg.target + " " +
  11. msg.callback + ": " + msg.what);
  12. }
  13. final long traceTag = me.mTraceTag;
  14. ... ...
  15. ... ...
  16. if (logging != null) {
  17. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
  18. }

可以发现,每个消息在分发开始和执行结束后,在logging.print()方法里有 >>>>>  和 <<<<<< 的特殊字符,那么我们可以根据这个特殊字符来作为这个消息执行的开始和结束标志,进而依此来打印主线程中的消息执行时间,具体代码如下:

  1. /**
  2. * 方案一:只能打印耗时消息,无法知道具体是哪个消息耗时
  3. */
  4. private void methodOne() {
  5. outputMainLooper();
  6. }
  7. private void outputMainLooper() {
  8. Looper.getMainLooper().setMessageLogging(new Printer() {
  9. @Override
  10. public void println(String x) {
  11. if (x.startsWith(">>>>>")) {
  12. startTime = System.currentTimeMillis();
  13. } else if (x.startsWith("<<<<<")) {
  14. long end = System.currentTimeMillis();
  15. //时间阈值可以自己定义,这里是10
  16. if ((end - startTime) > 10) {
  17. Log.i("buder mainLoop ------ :", (end - startTime)+ " ");
  18. }
  19. }
  20. }
  21. });
  22. }

这种方案能简单快速打印出主线程中消息的具体执行时间,但是我们的目的是要找出具体哪个方法耗时,需要打印出耗时方法的堆栈信息才能帮助我们快速定位到卡顿点。因此方案一仅打印message的耗时时间而无法定位到具体函数,局限性较大,没有多大意义。

方案二:利用Handler.java的sendMessageAtTime()、dispatchMessage()方法,找出耗时函数并打印耗时时间:

我们知道主线程中发送消息,最终会调用sendMessageAtTime方法入消息队列,然后通过dispatchMessage进行消息分发执行。那么我们分别利用这两个方法就可以监控到消息是谁发的,以及这个消息的执行时间。为了能够做到这些,我们利用epic框架对这两个函数进行hook,具体做法如下:

步骤一:gradle中添加库依赖:

implementation 'me.weishu:epic:0.6.0'

步骤二:hook sendMessageAtTime 和 dispatchMessage

  1. /**
  2. * 方案二:可以打印耗时消息以及耗时时间
  3. */
  4. private void methodTwo() {
  5. final long[] startTime = {0};
  6. //hook sendMessageAtTime,具体msg消息是谁
  7. DexposedBridge.findAndHookMethod(Handler.class, "sendMessageAtTime", Message.class, long.class, new XC_MethodHook() {
  8. @Override
  9. protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  10. super.beforeHookedMethod(param);
  11. sMsgDetail.put((Message) param.args[0], Log.getStackTraceString(new Throwable()).replace("java.lang.Throwable", ""));
  12. }
  13. @Override
  14. protected void afterHookedMethod(MethodHookParam param) throws Throwable {
  15. super.afterHookedMethod(param);
  16. }
  17. });
  18. //hook dispatchMessage,打印耗时时间
  19. DexposedBridge.findAndHookMethod(Handler.class, "dispatchMessage", Message.class, new XC_MethodHook() {
  20. @Override
  21. protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  22. startTime[0] = System.currentTimeMillis();
  23. super.beforeHookedMethod(param);
  24. }
  25. @Override
  26. protected void afterHookedMethod(MethodHookParam param) throws Throwable {
  27. super.afterHookedMethod(param);
  28. long costTime = System.currentTimeMillis() - startTime[0];
  29. String stackMessage = null;
  30. //时间阈值可以自己定义,这里是10
  31. if (costTime > 10) {
  32. stackMessage = sMsgDetail.get(param.args[0]);
  33. Log.i("buder", costTime + "***********");
  34. Log.e("buder", stackMessage);
  35. }
  36. }
  37. });
  38. }

执行方法后,可以清楚看到程序中具体是哪里执行了耗时操作:

  

这里即打印出了两个方案的耗时时间,又看到了MainActivity的第24和36行含有耗时操作。

完整代码:

  • MainActivity.java,点击事件模拟耗时任务:
  1. public class MainActivity extends AppCompatActivity {
  2. private Button mButton;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.activity_main);
  7. mButton = findViewById(R.id.btn);
  8. mButton.setOnClickListener(new View.OnClickListener() {
  9. @Override
  10. public void onClick(View v) {
  11. //耗时操作任务一
  12. new Handler().postDelayed(new Runnable() {
  13. @Override
  14. public void run() {
  15. for(int i = 0; i < 100000; i++) {
  16. System.out.println("test 1");
  17. }
  18. Toast toast = Toast.makeText(getApplicationContext(),"点击完成", Toast.LENGTH_LONG);
  19. toast.show();
  20. }
  21. }, 1000);
  22. //耗时操作任务二
  23. new Handler().post(new Runnable() {
  24. @Override
  25. public void run() {
  26. for(int i = 0; i < 200000; i++) {
  27. System.out.println("test 1");
  28. }
  29. }
  30. });
  31. }
  32. });
  33. }
  34. }
  • DemoApplication.java 包含上述两种统计方案:
  • 记得在Manifest中添加android:name=".DemoApplication"
  1. public class DemoApplication extends Application {
  2. private long startTime = 0;
  3. private static ConcurrentHashMap<Message, String> sMsgDetail = new ConcurrentHashMap<>();
  4. @Override
  5. protected void attachBaseContext(Context base) {
  6. super.attachBaseContext(base);
  7. //获取耗时方案一
  8. methodOne();
  9. //获取耗时方案二
  10. methodTwo();
  11. }
  12. /**
  13. * 方案二:可以打印耗时消息以及耗时时间
  14. */
  15. private void methodTwo() {
  16. final long[] startTime = {0};
  17. //hook sendMessageAtTime,具体msg消息是谁
  18. DexposedBridge.findAndHookMethod(Handler.class, "sendMessageAtTime", Message.class, long.class, new XC_MethodHook() {
  19. @Override
  20. protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  21. super.beforeHookedMethod(param);
  22. sMsgDetail.put((Message) param.args[0], Log.getStackTraceString(new Throwable()).replace("java.lang.Throwable", ""));
  23. }
  24. @Override
  25. protected void afterHookedMethod(MethodHookParam param) throws Throwable {
  26. super.afterHookedMethod(param);
  27. }
  28. });
  29. //hook dispatchMessage,打印耗时时间
  30. DexposedBridge.findAndHookMethod(Handler.class, "dispatchMessage", Message.class, new XC_MethodHook() {
  31. @Override
  32. protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  33. startTime[0] = System.currentTimeMillis();
  34. super.beforeHookedMethod(param);
  35. }
  36. @Override
  37. protected void afterHookedMethod(MethodHookParam param) throws Throwable {
  38. super.afterHookedMethod(param);
  39. long costTime = System.currentTimeMillis() - startTime[0];
  40. String stackMessage = null;
  41. //时间阈值可以自己定义,这里是100
  42. if (costTime > 100) {
  43. stackMessage = sMsgDetail.get(param.args[0]);
  44. Log.i("buder", costTime + "***********");
  45. Log.e("buder", stackMessage);
  46. }
  47. }
  48. });
  49. }
  50. /**
  51. * 方案一:只能打印耗时消息,无法知道具体是哪个消息耗时
  52. */
  53. private void methodOne() {
  54. outputMainLooper();
  55. }
  56. private void outputMainLooper() {
  57. Looper.getMainLooper().setMessageLogging(new Printer() {
  58. @Override
  59. public void println(String x) {
  60. if (x.startsWith(">>>>>")) {
  61. startTime = System.currentTimeMillis();
  62. } else if (x.startsWith("<<<<<")) {
  63. long end = System.currentTimeMillis();
  64. //时间阈值可以自己定义,这里是100
  65. if ((end - startTime) > 100) {
  66. Log.i("buder mainLoop ------ :", (end - startTime)+ " ");
  67. }
  68. }
  69. }
  70. });
  71. }
  72. @Override
  73. public void onCreate() {
  74. super.onCreate();
  75. }
  76. }

完整地址:base_component_learn/timeConsumingPrinter at master · buder-cp/base_component_learn · GitHub

参考博客:深入探索Android卡顿优化(下)_JsonChao的博客-CSDN博客_android lancet

epic相关:Weishu's Notes - 为数不多的维术

android-hacker/epic :android-hacker/epic - Gitter

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/251039
推荐阅读
相关标签
  

闽ICP备14008679号