├── LICENSE ├── MethodTrace ├── .classpath ├── .project ├── .settings │ └── org.eclipse.jdt.core.prefs └── src │ ├── InfoBean.java │ ├── Log.java │ ├── MethodTabModel.java │ ├── Trace.java │ ├── TraceScanner.java │ ├── Utils.java │ └── VerticalFlowLayout.java ├── README.md ├── art └── preview.png └── tool └── MethodTrace.jar /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MethodTrace/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /MethodTrace/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | MethodTrace 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /MethodTrace/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.8 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.8 12 | -------------------------------------------------------------------------------- /MethodTrace/src/InfoBean.java: -------------------------------------------------------------------------------- 1 | 2 | public class InfoBean { 3 | private String tId;//2939 4 | private String type;//"xit ent" 5 | private int xitTime;//11494456 6 | private int entTime;//11494489 7 | private int result; 8 | private String key; 9 | private String clsName;//PartInquiryListAdapter 10 | private String methodName;//PartInquiryListAdapter$1.onClick (Landroid/view/View;)V 11 | private String fullClassName;//trading.adapter.part.purchase.PartInquiryListAdapter 12 | 13 | public String getFullClassName() { 14 | return fullClassName; 15 | } 16 | public void setFullClassName(String fullClassName) { 17 | this.fullClassName = fullClassName; 18 | } 19 | public String getClsName() { 20 | return clsName; 21 | } 22 | public void setClsName(String clsName) { 23 | this.clsName = clsName; 24 | } 25 | public String getMethodName() { 26 | return methodName; 27 | } 28 | public void setMethodName(String methodName) { 29 | this.methodName = methodName; 30 | } 31 | public String getKey() { 32 | return key; 33 | } 34 | public void setKey(String key) { 35 | this.key = key; 36 | } 37 | public String gettId() { 38 | return tId; 39 | } 40 | public void settId(String tId) { 41 | this.tId = tId; 42 | } 43 | public String getType() { 44 | return type; 45 | } 46 | public void setType(String type) { 47 | this.type = type; 48 | } 49 | public int getXitTime() { 50 | return xitTime; 51 | } 52 | public void setXitTime(int xitTime) { 53 | this.xitTime = xitTime; 54 | } 55 | public int getEntTime() { 56 | return entTime; 57 | } 58 | public void setEntTime(int entTime) { 59 | this.entTime = entTime; 60 | } 61 | public int getResult() { 62 | return result; 63 | } 64 | public void setResult(int result) { 65 | this.result = result; 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /MethodTrace/src/Log.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 打印Log信息,格式: 3 | * [类名 | 行号 | 方法名] =======: 信息 4 | * 例如: 5 | * [Utils.java | 118 | fillClassInfo] ======: classInfo: Launcher.java 6 | *

7 | * Created by yuchuan on 15/06/2017. 8 | */ 9 | public class Log { 10 | 11 | private final static boolean sEnablePrint = true; 12 | 13 | public static String getTag() { 14 | if (!sEnablePrint) { 15 | return ""; 16 | } 17 | // 获取当前方法名 18 | // StackTraceElement traceElement = ((new Exception()).getStackTrace())[0]; 19 | // 获取调用者方法名 20 | StackTraceElement traceElement = ((new Exception()).getStackTrace())[1]; 21 | return "[" + traceElement.getFileName() + " | " 22 | + traceElement.getLineNumber() + " | " 23 | + traceElement.getMethodName() + "]"; 24 | 25 | } 26 | 27 | public static void e(String tag, String log) { 28 | if (sEnablePrint) { 29 | System.out.println(tag + " ======: " + log); 30 | } 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /MethodTrace/src/MethodTabModel.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.List; 3 | 4 | import javax.swing.table.AbstractTableModel; 5 | 6 | /** 7 | * 内容显示面板Tab栏 8 | */ 9 | public class MethodTabModel extends AbstractTableModel { 10 | private ArrayList content = new ArrayList(); 11 | private String[] columns = new String[]{"Thread", "Time(μs)", "Class", "Method"}; 12 | 13 | public MethodTabModel() { 14 | 15 | } 16 | 17 | @Override 18 | public int getRowCount() {// 行数 19 | return content.size(); 20 | } 21 | 22 | @Override 23 | public int getColumnCount() {// 列数 24 | return columns.length; 25 | } 26 | 27 | @Override 28 | public Object getValueAt(int rowIndex, int columnIndex) { 29 | InfoBean bean = this.content.get(rowIndex); 30 | switch (columnIndex) {// 每个列显示的内容定制 31 | case 0: 32 | return bean.gettId(); 33 | case 1: 34 | if (bean.getXitTime() == 0) { 35 | return "unknown"; 36 | } else { 37 | return bean.getXitTime() - bean.getEntTime(); 38 | } 39 | case 2: 40 | return bean.getFullClassName(); 41 | case 3: 42 | return bean.getMethodName(); 43 | 44 | } 45 | return null; 46 | } 47 | 48 | @Override 49 | public String getColumnName(int column) { 50 | return columns[column]; 51 | } 52 | 53 | public void setContent(List content) { 54 | this.content.clear(); 55 | this.content.addAll(content); 56 | fireTableDataChanged(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /MethodTrace/src/Trace.java: -------------------------------------------------------------------------------- 1 | import java.awt.BorderLayout; 2 | import java.awt.FlowLayout; 3 | import java.awt.datatransfer.DataFlavor; 4 | import java.awt.dnd.DnDConstants; 5 | import java.awt.dnd.DropTarget; 6 | import java.awt.dnd.DropTargetAdapter; 7 | import java.awt.dnd.DropTargetDropEvent; 8 | import java.awt.event.ActionEvent; 9 | import java.awt.event.ActionListener; 10 | import java.awt.event.MouseEvent; 11 | import java.awt.event.MouseListener; 12 | import java.io.File; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import javax.swing.BoxLayout; 17 | import javax.swing.JButton; 18 | import javax.swing.JFileChooser; 19 | import javax.swing.JFrame; 20 | import javax.swing.JLabel; 21 | import javax.swing.JMenu; 22 | import javax.swing.JMenuBar; 23 | import javax.swing.JMenuItem; 24 | import javax.swing.JOptionPane; 25 | import javax.swing.JPanel; 26 | import javax.swing.JScrollPane; 27 | import javax.swing.JTable; 28 | import javax.swing.JTextField; 29 | 30 | public class Trace { 31 | 32 | static JLabel JlPath; 33 | private static final String PATH = "........................................................"; 34 | 35 | public static void main(String[] args) { 36 | String packageName = null; 37 | 38 | int windowWidth = 1280; 39 | int windowHeight = 600; 40 | if (Utils.isMacOS()) { 41 | windowWidth = 1080; 42 | windowHeight = 720; 43 | } 44 | 45 | // 窗口标题名 46 | JFrame window = new JFrame("App method trace analysis"); 47 | window.setSize(windowWidth, windowHeight); 48 | window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 49 | window.setResizable(false);// 窗体大小不可变 50 | 51 | JMenuBar menuBar = new JMenuBar(); 52 | 53 | // 设置菜单--File 54 | JMenu menuFile = new JMenu("File"); 55 | menuBar.add(menuFile); 56 | JMenuItem itemOpen = new JMenuItem("Open"); 57 | menuFile.add(itemOpen); 58 | 59 | // 设置菜单--About 60 | JMenu menuAbout = new JMenu("About"); 61 | JMenuItem itemAbout = new JMenuItem("About Me"); 62 | menuAbout.add(itemAbout); 63 | menuBar.add(menuAbout); 64 | 65 | // 添加菜单 66 | window.setJMenuBar(menuBar); 67 | 68 | JPanel panel = new JPanel(); 69 | JPanel root = new JPanel(); 70 | window.setContentPane(root); 71 | root.setLayout(new BoxLayout(root, BoxLayout.Y_AXIS)); 72 | panel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 5, 5, 73 | true, false)); 74 | 75 | JPanel path = new JPanel(); 76 | path.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 5)); 77 | path.add(new JLabel("File Path:")); 78 | JlPath = new JLabel(PATH); 79 | path.add(JlPath); 80 | panel.add(path); 81 | 82 | drag();//启用拖拽 83 | 84 | JPanel top = new JPanel(); 85 | top.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 5)); 86 | top.add(new JLabel("Filter Package Name:")); 87 | JTextField jpName = new JTextField("package name", 30);// com.jushi.trading 88 | jpName.setBounds(10, 10, 100, 20); 89 | // 设置文本的水平对齐方式 90 | jpName.setHorizontalAlignment(JTextField.CENTER); 91 | top.add(jpName, BorderLayout.PAGE_START); 92 | 93 | // 分析按钮 94 | JButton jGo = new JButton("analysis"); 95 | top.add(jGo); 96 | panel.add(top); 97 | 98 | JPanel jpStatus = new JPanel(); 99 | jpStatus.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 5)); 100 | JLabel jlStatus = new JLabel("Info:"); 101 | jpStatus.add(jlStatus); 102 | panel.add(jpStatus); 103 | jpStatus.setVisible(false); 104 | 105 | root.add(panel, BorderLayout.PAGE_START); 106 | 107 | // 内容显示列表 108 | JTable table = new JTable(new MethodTabModel()); 109 | // 可滑动面板 110 | JScrollPane scrollPane = new JScrollPane(table); 111 | root.add(scrollPane, BorderLayout.CENTER); 112 | table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 113 | // 点击监听 114 | table.addMouseListener(new MouseListener() { 115 | 116 | @Override 117 | public void mouseReleased(MouseEvent e) { 118 | // TODO Auto-generated method stub 119 | 120 | } 121 | 122 | @Override 123 | public void mousePressed(MouseEvent e) { 124 | // TODO Auto-generated method stub 125 | 126 | } 127 | 128 | @Override 129 | public void mouseExited(MouseEvent e) { 130 | // TODO Auto-generated method stub 131 | 132 | } 133 | 134 | @Override 135 | public void mouseEntered(MouseEvent e) { 136 | // TODO Auto-generated method stub 137 | 138 | } 139 | 140 | @Override 141 | public void mouseClicked(MouseEvent e) { 142 | if (e.getClickCount() == 2) {// 143 | Log.e(Log.getTag(), "双击"); 144 | } 145 | 146 | } 147 | }); 148 | 149 | // 分析按钮点击监听 150 | jGo.addActionListener(new ActionListener() { 151 | 152 | @Override 153 | public void actionPerformed(ActionEvent e) { 154 | Log.e(Log.getTag(), JlPath.getText()); 155 | if (JlPath.getText().equals(PATH)) { 156 | JOptionPane.showMessageDialog(root, "请导入trace文件", "提示消息", 157 | JOptionPane.WARNING_MESSAGE); 158 | } else if (!JlPath.getText().endsWith("trace")) { 159 | JOptionPane.showMessageDialog(root, "请导入有效的trace文件", 160 | "提示消息", JOptionPane.WARNING_MESSAGE); 161 | } else if (jpName.getText().equals("package name") || jpName.getText().equals("")) { 162 | JOptionPane.showMessageDialog(root, "请输入需过滤的包名", "提示消息", 163 | JOptionPane.WARNING_MESSAGE); 164 | } else { 165 | TraceScanner scanner = new TraceScanner(new File(JlPath.getText())); 166 | // 赋值 167 | scanner.setPackageName(jpName.getText()); 168 | jlStatus.setText("analysis..."); 169 | MethodTabModel model = (MethodTabModel) table.getModel(); 170 | ArrayList tableArray = Utils 171 | .mapConvert2Array(scanner.convertFile(JlPath 172 | .getText())); 173 | ArrayList xmlBean = new ArrayList(); 174 | xmlBean.addAll(tableArray); 175 | model.setContent(tableArray); 176 | Utils.fitTableColumns(table); 177 | for (int i = 0; i < xmlBean.size(); i++) { 178 | System.out.println(""); 179 | } 180 | } 181 | } 182 | }); 183 | 184 | itemAbout.addActionListener(new ActionListener() { 185 | 186 | @Override 187 | public void actionPerformed(ActionEvent e) { 188 | Object[] options = {"Go", "No!"}; 189 | int n = JOptionPane 190 | .showOptionDialog(null, 191 | "Visit me :https://github.com/Harlber", 192 | "About Me", JOptionPane.YES_NO_OPTION, 193 | JOptionPane.QUESTION_MESSAGE, null, options, 194 | options[0]); 195 | if (n == 0) { 196 | try { 197 | Utils.browse("https://github.com/Harlber"); 198 | } catch (Exception e1) { 199 | e1.printStackTrace(); 200 | } 201 | } 202 | } 203 | }); 204 | 205 | // File菜单中Open选项点击事件 206 | itemOpen.addActionListener(new ActionListener() { 207 | 208 | @Override 209 | public void actionPerformed(ActionEvent arg0) { 210 | JFileChooser jfc = new JFileChooser(); 211 | jfc.setFileSelectionMode(JFileChooser.FILES_ONLY); 212 | jfc.showDialog(new JLabel(), "选择"); 213 | File file = jfc.getSelectedFile(); 214 | if (file.isDirectory()) { 215 | Log.e(Log.getTag(), "文件夹:" + file.getAbsolutePath()); 216 | } else if (file.isFile()) { 217 | Log.e(Log.getTag(), "文件:" + file.getAbsolutePath()); 218 | } 219 | JlPath.setText(file.getAbsolutePath()); 220 | jpName.setText(Utils.getPackageName(file.getAbsolutePath())); 221 | } 222 | }); 223 | window.setVisible(true); 224 | } 225 | 226 | 227 | public static void drag()//定义的拖拽方法 228 | { 229 | //panel表示要接受拖拽的控件 230 | new DropTarget(JlPath, DnDConstants.ACTION_COPY_OR_MOVE, new DropTargetAdapter() { 231 | public void drop(DropTargetDropEvent dtde)//重写适配器的drop方法 232 | { 233 | try { 234 | if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor))//如果拖入的文件格式受支持 235 | { 236 | dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);//接收拖拽来的数据 237 | List list = (List) (dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)); 238 | String temp = ""; 239 | for (File file : list) { 240 | temp = file.getAbsolutePath(); 241 | JlPath.setText(temp); 242 | break; 243 | } 244 | //JOptionPane.showMessageDialog(null, temp); 245 | dtde.dropComplete(true);//指示拖拽操作已完成 246 | } else { 247 | dtde.rejectDrop();//否则拒绝拖拽来的数据 248 | } 249 | } catch (Exception e) { 250 | e.printStackTrace(); 251 | } 252 | } 253 | }); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /MethodTrace/src/TraceScanner.java: -------------------------------------------------------------------------------- 1 | import java.io.BufferedReader; 2 | import java.io.File; 3 | import java.io.InputStreamReader; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | 7 | /** 8 | * 文件扫描 9 | */ 10 | public class TraceScanner { 11 | private File file; 12 | private String packageName = ""; 13 | private AnalysisListener listener; 14 | 15 | public TraceScanner(File file) { 16 | super(); 17 | this.file = file; 18 | System.out.println("file:" + file); 19 | } 20 | 21 | public void setFile(File file) { 22 | this.file = file; 23 | } 24 | 25 | public void setPackageName(String packageName) { 26 | this.packageName = packageName; 27 | } 28 | 29 | public void setListener(AnalysisListener listener) { 30 | this.listener = listener; 31 | } 32 | 33 | public HashMap convertFile(String path) { 34 | ArrayList index = new ArrayList(); 35 | HashMap result = new HashMap(); 36 | String osName = System.getProperty("os.name"); 37 | 38 | Log.e(Log.getTag(),"packageName:" + packageName); 39 | index.add("PackageName: " + packageName + "\n"); 40 | index.add("start dmtracedump\n"); 41 | String oName = packageName.replaceAll("[.]", "/"); 42 | if (listener != null) { 43 | listener.startAnalysis(); 44 | } 45 | if (!file.exists()) { 46 | System.out.println("File does not exist"); 47 | } else { 48 | Runtime runtime = Runtime.getRuntime(); 49 | BufferedReader br = null; 50 | try { 51 | Process p; 52 | if (osName.contains("Windows") || osName.contains("windows")) { 53 | p = Runtime.getRuntime().exec(Utils.getWinCommand(path)); 54 | } else { 55 | // mac or linux 56 | p = Runtime.getRuntime().exec(Utils.getMacCommand(path)); 57 | } 58 | 59 | br = new BufferedReader(new InputStreamReader( 60 | p.getInputStream())); 61 | String lineTxt = null; 62 | // 读取每行的数据 63 | while ((lineTxt = br.readLine()) != null) { 64 | if (lineTxt.contains(packageName) 65 | || lineTxt.contains(oName)) { 66 | result = Utils.convert2Tab(result, lineTxt.replaceAll("[.]{2,}+", "")); 67 | index.add(lineTxt + "\n"); 68 | } 69 | } 70 | } catch (Exception e) { 71 | result = Utils.convert2Tab(result, 72 | "unknown ent 0 exec dmtracedump exception"); 73 | index.add("执行dmtracedump命令异常\n"); 74 | e.printStackTrace(); 75 | } finally { 76 | if (br != null) { 77 | try { 78 | br.close(); 79 | } catch (Exception e) { 80 | e.printStackTrace(); 81 | } 82 | } 83 | index.add("end dmtracedump\n"); 84 | System.out.println("cmd end"); 85 | if (listener != null) { 86 | listener.afterAnalysis(); 87 | } 88 | } 89 | } 90 | return result; 91 | 92 | } 93 | 94 | public interface AnalysisListener { 95 | void startAnalysis(); 96 | 97 | void afterAnalysis(); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /MethodTrace/src/Utils.java: -------------------------------------------------------------------------------- 1 | import java.lang.reflect.Method; 2 | import java.util.ArrayList; 3 | import java.util.Arrays; 4 | import java.util.Enumeration; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | 8 | import javax.swing.JTable; 9 | import javax.swing.table.JTableHeader; 10 | import javax.swing.table.TableColumn; 11 | 12 | public class Utils { 13 | 14 | public static String getWinCommand(String path) { 15 | return "dmtracedump -o " + path; 16 | } 17 | 18 | public static String[] getMacCommand(String path) { 19 | String dumpCmd = "dmtracedump -o " + path; 20 | String[] result = {"bash", "-c", dumpCmd}; 21 | return result; 22 | } 23 | 24 | @SuppressWarnings("finally") 25 | public static String getPackageName(String str) { 26 | if (null == str || str.equals("")) { 27 | return ""; 28 | } 29 | String result = ""; 30 | int start = 0; 31 | try { 32 | if (System.getProperty("os.name").contains("Windows") 33 | || System.getProperty("os.name").contains("windows")) { 34 | start = str.lastIndexOf("\\"); 35 | } else if (isMacOS()) { 36 | start = str.lastIndexOf("/"); 37 | } else { 38 | start = str.lastIndexOf("//"); 39 | } 40 | int end = str.indexOf("_"); 41 | result = str.substring(start + 1, end); 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | } finally { 45 | return result; 46 | } 47 | } 48 | 49 | public static String[] removeArrayEmptyTextBackNewArray(String[] strArray) { 50 | List strList = Arrays.asList(strArray); 51 | List strListNew = new ArrayList<>(); 52 | for (int i = 0; i < strList.size(); i++) { 53 | if (strList.get(i) != null && !strList.get(i).equals("")) { 54 | strListNew.add(strList.get(i)); 55 | } 56 | } 57 | String[] strNewArray = strListNew 58 | .toArray(new String[strListNew.size()]); 59 | return strNewArray; 60 | } 61 | 62 | public static HashMap convert2Tab( 63 | HashMap keyInfo, String index) { 64 | 65 | String[] indexStr = index.split(" ");// replaceAll("[.]+", "") 66 | String[] str = removeArrayEmptyTextBackNewArray(indexStr); 67 | if (str.length > 6) { 68 | for (int i = 0; i < str.length; i++) 69 | System.out.println(str[i]); 70 | } 71 | /* 72 | * System.out.println("[0]:" + str[0] + "[1]:" + str[1] + "[3]:" + 73 | * str[3] + "length:" + str.length); 74 | */ 75 | StringBuffer key = new StringBuffer(); 76 | for (int i = 3; i < str.length; i++) { 77 | key.append(str[i]); 78 | key.append(" "); 79 | } 80 | 81 | if (keyInfo.containsKey(key.toString())) {// 82 | InfoBean bean = keyInfo.get(key.toString()); 83 | if (str[1].equals("ent")) { 84 | bean.setXitTime(Integer.valueOf(str[2])); 85 | } else { 86 | bean.setXitTime(Integer.valueOf(str[2])); 87 | } 88 | fillClassInfo(index, bean); 89 | keyInfo.replace(key.toString(), bean); 90 | } else { 91 | InfoBean bean = new InfoBean(); 92 | bean.setKey(key.toString()); 93 | bean.settId(str[0]); 94 | bean.setType(str[1]); 95 | if (str[1].equals("ent")) { 96 | bean.setEntTime(Integer.valueOf(str[2])); 97 | } else { 98 | bean.setXitTime(Integer.valueOf(str[2])); 99 | } 100 | fillClassInfo(index, bean); 101 | keyInfo.put(key.toString(), bean); 102 | } 103 | return keyInfo; 104 | 105 | } 106 | 107 | /* 坑点:内部类 adapter$1.ViewHolder */ 108 | static void fillClassInfo(String index, InfoBean info) { 109 | if (isEmpty(index)) { 110 | return; 111 | } 112 | Log.e(Log.getTag(), "index: " + index); 113 | // unknown ent 0 exec dmtracedump exception 114 | if (index.indexOf("java") < 0 && index.contains("dmtracedump")) { 115 | info.setClsName("Exception"); 116 | info.setMethodName(index); 117 | } else { 118 | String[] names = index.split("\t"); 119 | if (names.length < 1) { 120 | return; 121 | } 122 | Log.e(Log.getTag(), "names: " + names.length); 123 | String classInfo = names[names.length - 1]; 124 | String className; 125 | if (classInfo.contains(".")) { 126 | className = classInfo.substring(0, classInfo.indexOf(".")); 127 | } else { 128 | className = classInfo; 129 | } 130 | String class_pot = className + "."; 131 | String class_$ = className + "$"; 132 | 133 | String methodName = ""; 134 | String methodStr = names[names.length - 2]; 135 | Log.e(Log.getTag(), "methodStr: " + methodStr); 136 | 137 | 138 | int dexPot = methodStr.indexOf(class_pot); 139 | int dex$ = methodStr.indexOf(class_$); 140 | 141 | Log.e(Log.getTag(), "dexPot: " + dexPot); 142 | Log.e(Log.getTag(), "dex$: " + dex$); 143 | int start = Math.max(dexPot, dex$); 144 | methodName = methodStr.substring(start < 0 ? 0 : start, 145 | methodStr.length()); 146 | 147 | info.setClsName(className); 148 | info.setMethodName(methodName); 149 | info.setFullClassName(info.getKey().substring(0, 150 | info.getKey().indexOf(className)) 151 | + className); 152 | } 153 | } 154 | 155 | public static ArrayList mapConvert2Array( 156 | HashMap map) { 157 | ArrayList result = new ArrayList(); 158 | for (InfoBean value : map.values()) { 159 | result.add(value); 160 | } 161 | return result; 162 | } 163 | 164 | public static void fitTableColumns(JTable myTable) { 165 | 166 | JTableHeader header = myTable.getTableHeader(); 167 | int rowCount = myTable.getRowCount(); 168 | Enumeration columns = myTable.getColumnModel().getColumns(); 169 | while (columns.hasMoreElements()) { 170 | TableColumn column = (TableColumn) columns.nextElement(); 171 | int col = header.getColumnModel().getColumnIndex( 172 | column.getIdentifier()); 173 | int width = (int) myTable 174 | .getTableHeader() 175 | .getDefaultRenderer() 176 | .getTableCellRendererComponent(myTable, 177 | column.getIdentifier(), false, false, -1, col) 178 | .getPreferredSize().getWidth(); 179 | for (int row = 0; row < rowCount; row++) { 180 | int preferedWidth = (int) myTable 181 | .getCellRenderer(row, col) 182 | .getTableCellRendererComponent(myTable, 183 | myTable.getValueAt(row, col), false, false, 184 | row, col).getPreferredSize().getWidth(); 185 | width = Math.max(width, preferedWidth); 186 | } 187 | header.setResizingColumn(column); // 此行很重要 188 | column.setWidth(width + myTable.getIntercellSpacing().width + 4);// 使表格看起来不是那么拥挤,起到间隔作用 189 | } 190 | } 191 | 192 | public static void browse(String url) throws Exception { 193 | // 获取操作系统的名字 194 | String osName = System.getProperty("os.name", ""); 195 | if (osName.startsWith("Mac OS")) { 196 | // 苹果的打开方式 197 | Class fileMgr = Class.forName("com.apple.eio.FileManager"); 198 | Method openURL = fileMgr.getDeclaredMethod("openURL", 199 | new Class[]{String.class}); 200 | openURL.invoke(null, new Object[]{url}); 201 | } else if (osName.startsWith("Windows")) { 202 | // windows的打开方式。 203 | Runtime.getRuntime().exec( 204 | "rundll32 url.dll,FileProtocolHandler " + url); 205 | } else { 206 | // Unix or Linux的打开方式 207 | String[] browsers = {"firefox", "opera", "konqueror", "epiphany", 208 | "mozilla", "netscape"}; 209 | String browser = null; 210 | for (int count = 0; count < browsers.length && browser == null; count++) 211 | // 执行代码,在brower有值后跳出, 212 | // 这里是如果进程创建成功了,==0是表示正常结束。 213 | if (Runtime.getRuntime() 214 | .exec(new String[]{"which", browsers[count]}) 215 | .waitFor() == 0) 216 | browser = browsers[count]; 217 | if (browser == null) 218 | throw new Exception("Could not find web browser"); 219 | else 220 | // 这个值在上面已经成功的得到了一个进程。 221 | Runtime.getRuntime().exec(new String[]{browser, url}); 222 | } 223 | } 224 | 225 | 226 | public static boolean isMacOS() { 227 | return System.getProperty("os.name", "").startsWith("Mac OS"); 228 | } 229 | 230 | public static boolean isEmpty(String s) { 231 | return s == null || s.isEmpty(); 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /MethodTrace/src/VerticalFlowLayout.java: -------------------------------------------------------------------------------- 1 | import java.awt.Component; 2 | import java.awt.Container; 3 | import java.awt.Dimension; 4 | import java.awt.FlowLayout; 5 | import java.awt.Insets; 6 | 7 | /** 8 | * VerticalFlowLayout is similar to FlowLayout except it lays out components 9 | * vertically. Extends FlowLayout because it mimics much of the behavior of the 10 | * FlowLayout class, except vertically. An additional feature is that you can 11 | * specify a fill to edge flag, which causes the VerticalFlowLayout manager to 12 | * resize all components to expand to the column width Warning: This causes 13 | * problems when the main panel has less space that it needs and it seems to 14 | * prohibit multi-column output. Additionally there is a vertical fill flag, 15 | * which fills the last component to the remaining height of the container. 16 | */ 17 | public class VerticalFlowLayout extends FlowLayout { 18 | 19 | /** 20 | * Specify alignment top. 21 | */ 22 | public static final int TOP = 0; 23 | 24 | /** 25 | * Specify a middle alignment. 26 | */ 27 | public static final int MIDDLE = 1; 28 | 29 | /** 30 | * Specify the alignment to be bottom. 31 | */ 32 | public static final int BOTTOM = 2; 33 | 34 | int hgap; 35 | int vgap; 36 | boolean hfill; 37 | boolean vfill; 38 | 39 | /** 40 | * Construct a new VerticalFlowLayout with a middle alignment, and the fill 41 | * to edge flag set. 42 | */ 43 | public VerticalFlowLayout() { 44 | this(TOP, 5, 5, true, false); 45 | } 46 | 47 | /** 48 | * Construct a new VerticalFlowLayout with a middle alignment. 49 | * 50 | * @param hfill 51 | * the fill to edge flag 52 | * @param vfill 53 | * the vertical fill in pixels. 54 | */ 55 | public VerticalFlowLayout(boolean hfill, boolean vfill) { 56 | this(TOP, 5, 5, hfill, vfill); 57 | } 58 | 59 | /** 60 | * Construct a new VerticalFlowLayout with a middle alignment. 61 | * 62 | * @param align 63 | * the alignment value 64 | */ 65 | public VerticalFlowLayout(int align) { 66 | this(align, 5, 5, true, false); 67 | } 68 | 69 | /** 70 | * Construct a new VerticalFlowLayout. 71 | * 72 | * @param align 73 | * the alignment value 74 | * @param hfill 75 | * the horizontalfill in pixels. 76 | * @param vfill 77 | * the vertical fill in pixels. 78 | */ 79 | public VerticalFlowLayout(int align, boolean hfill, boolean vfill) { 80 | this(align, 5, 5, hfill, vfill); 81 | } 82 | 83 | /** 84 | * Construct a new VerticalFlowLayout. 85 | * 86 | * @param align 87 | * the alignment value 88 | * @param hgap 89 | * the horizontal gap variable 90 | * @param vgap 91 | * the vertical gap variable 92 | * @param hfill 93 | * the fill to edge flag 94 | * @param vfill 95 | * true if the panel should vertically fill. 96 | */ 97 | public VerticalFlowLayout(int align, int hgap, int vgap, boolean hfill, 98 | boolean vfill) { 99 | setAlignment(align); 100 | this.hgap = hgap; 101 | this.vgap = vgap; 102 | this.hfill = hfill; 103 | this.vfill = vfill; 104 | } 105 | 106 | /** 107 | * Returns the preferred dimensions given the components in the target 108 | * container. 109 | * 110 | * @param target 111 | * the component to lay out 112 | */ 113 | public Dimension preferredLayoutSize(Container target) { 114 | Dimension tarsiz = new Dimension(0, 0); 115 | 116 | for (int i = 0; i < target.getComponentCount(); i++) { 117 | Component m = target.getComponent(i); 118 | if (m.isVisible()) { 119 | Dimension d = m.getPreferredSize(); 120 | tarsiz.width = Math.max(tarsiz.width, d.width); 121 | if (i > 0) { 122 | tarsiz.height += hgap; 123 | } 124 | tarsiz.height += d.height; 125 | } 126 | } 127 | Insets insets = target.getInsets(); 128 | tarsiz.width += insets.left + insets.right + hgap * 2; 129 | tarsiz.height += insets.top + insets.bottom + vgap * 2; 130 | return tarsiz; 131 | } 132 | 133 | /** 134 | * Returns the minimum size needed to layout the target container. 135 | * 136 | * @param target 137 | * the component to lay out. 138 | * @return the minimum layout dimension. 139 | */ 140 | public Dimension minimumLayoutSize(Container target) { 141 | Dimension tarsiz = new Dimension(0, 0); 142 | 143 | for (int i = 0; i < target.getComponentCount(); i++) { 144 | Component m = target.getComponent(i); 145 | if (m.isVisible()) { 146 | Dimension d = m.getMinimumSize(); 147 | tarsiz.width = Math.max(tarsiz.width, d.width); 148 | if (i > 0) { 149 | tarsiz.height += vgap; 150 | } 151 | tarsiz.height += d.height; 152 | } 153 | } 154 | Insets insets = target.getInsets(); 155 | tarsiz.width += insets.left + insets.right + hgap * 2; 156 | tarsiz.height += insets.top + insets.bottom + vgap * 2; 157 | return tarsiz; 158 | } 159 | 160 | /** 161 | * Set true to fill vertically. 162 | * 163 | * @param vfill 164 | * true to fill vertically. 165 | */ 166 | public void setVerticalFill(boolean vfill) { 167 | this.vfill = vfill; 168 | } 169 | 170 | /** 171 | * Returns true if the layout vertically fills. 172 | * 173 | * @return true if vertically fills the layout using the specified. 174 | */ 175 | public boolean getVerticalFill() { 176 | return vfill; 177 | } 178 | 179 | /** 180 | * Set to true to enable horizontally fill. 181 | * 182 | * @param hfill 183 | * true to fill horizontally. 184 | */ 185 | public void setHorizontalFill(boolean hfill) { 186 | this.hfill = hfill; 187 | } 188 | 189 | /** 190 | * Returns true if the layout horizontally fills. 191 | * 192 | * @return true if horizontally fills. 193 | */ 194 | public boolean getHorizontalFill() { 195 | return hfill; 196 | } 197 | 198 | /** 199 | * places the components defined by first to last within the target 200 | * container using the bounds box defined. 201 | * 202 | * @param target 203 | * the container. 204 | * @param x 205 | * the x coordinate of the area. 206 | * @param y 207 | * the y coordinate of the area. 208 | * @param width 209 | * the width of the area. 210 | * @param height 211 | * the height of the area. 212 | * @param first 213 | * the first component of the container to place. 214 | * @param last 215 | * the last component of the container to place. 216 | */ 217 | private void placethem(Container target, int x, int y, int width, 218 | int height, int first, int last) { 219 | int align = getAlignment(); 220 | if (align == MIDDLE) 221 | y += height / 2; 222 | if (align == BOTTOM) 223 | y += height; 224 | 225 | for (int i = first; i < last; i++) { 226 | Component m = target.getComponent(i); 227 | Dimension md = m.getSize(); 228 | if (m.isVisible()) { 229 | int px = x + (width - md.width) / 2; 230 | m.setLocation(px, y); 231 | y += vgap + md.height; 232 | } 233 | } 234 | } 235 | 236 | /** 237 | * Lays out the container. 238 | * 239 | * @param target 240 | * the container to lay out. 241 | */ 242 | public void layoutContainer(Container target) { 243 | Insets insets = target.getInsets(); 244 | int maxheight = target.getSize().height 245 | - (insets.top + insets.bottom + vgap * 2); 246 | int maxwidth = target.getSize().width 247 | - (insets.left + insets.right + hgap * 2); 248 | int numcomp = target.getComponentCount(); 249 | int x = insets.left + hgap, y = 0; 250 | int colw = 0, start = 0; 251 | 252 | for (int i = 0; i < numcomp; i++) { 253 | Component m = target.getComponent(i); 254 | if (m.isVisible()) { 255 | Dimension d = m.getPreferredSize(); 256 | // fit last component to remaining height 257 | if ((this.vfill) && (i == (numcomp - 1))) { 258 | d.height = Math.max((maxheight - y), 259 | m.getPreferredSize().height); 260 | } 261 | 262 | // fit component size to container width 263 | if (this.hfill) { 264 | m.setSize(maxwidth, d.height); 265 | d.width = maxwidth; 266 | } else { 267 | m.setSize(d.width, d.height); 268 | } 269 | 270 | if (y + d.height > maxheight) { 271 | placethem(target, x, insets.top + vgap, colw, 272 | maxheight - y, start, i); 273 | y = d.height; 274 | x += hgap + colw; 275 | colw = d.width; 276 | start = i; 277 | } else { 278 | if (y > 0) 279 | y += vgap; 280 | y += d.height; 281 | colw = Math.max(colw, d.width); 282 | } 283 | } 284 | } 285 | placethem(target, x, insets.top + vgap, colw, maxheight - y, start, 286 | numcomp); 287 | } 288 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 分析应用方法耗时的swing工具 2 | > 采用该项目(https://github.com/zjw-swun/AppMethodOrder) 的思路,基于trace文件分析过滤出当前应用(包名)下的方法信息. 3 | # 使用方法 4 | - 将``sdk\platform-tools``下的dmtracedump添加到系统环境变量 5 | - 使用tool文件夹下的``MethodTrace.jar`` 直接导入`.trace文件`,一键分析 6 | # 效果图 7 | 8 | 9 | 10 | # 注意点 11 | - 建议在`jdk 1.8`环境下运行,如是1.7及以下的jdk,请自行修改代码兼容 12 | - 目前该小工具可使用于Windows平台下,Mac及Linux可自行修改代码兼容(目前无Mac设备,无法测试-_-!) 13 | >Mac下可通过 `java -jar tool/MethodTrace.jar`执行 14 | - 需先将`dmtracedump`添加至环境变量 15 | - 方法耗时的单位为`μs` 16 | - `unknow`意味着该方法未结束调用 17 | # TODO 18 | - 可视化方法调用图 19 | 20 | 21 | # 编译Method_Trace_Tool工程 22 | 23 | 1. Method_Trace_Tool工程包括编译好的jar包,以及项目源码 24 | 2. MethodTrace目录对应的是一个eclipse工程,可以选择用eclipse开发工具import这个工程,或者是使用IntelliJ IDEA工具open这个工程 25 | 3. 打开工程后,直接在Trace.java类处run main方法即可启动图形化界面 26 | 4. 或者是选择export为jar包的形式,具体可[参考](https://stackoverflow.com/a/9463915/5279354)(idea开发工具) -------------------------------------------------------------------------------- /art/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harlber/Method_Trace_Tool/2ee23ba7a3a6823c087b3d56b540c2401c158c56/art/preview.png -------------------------------------------------------------------------------- /tool/MethodTrace.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Harlber/Method_Trace_Tool/2ee23ba7a3a6823c087b3d56b540c2401c158c56/tool/MethodTrace.jar --------------------------------------------------------------------------------