├── .gitignore
├── README.md
├── bin
├── adb
│ ├── AdbWinApi.dll
│ ├── AdbWinUsbApi.dll
│ ├── linux
│ ├── macos
│ └── windows.exe
├── apktool.jar
├── cfr.jar
├── dex2jar
│ ├── dex2jar
│ ├── dex2jar.bat
│ └── lib
│ │ ├── asm-debug-all-5.0.3.jar
│ │ ├── d2j-base-cmd-2.2-SNAPSHOT.jar
│ │ ├── dex-ir-2.2-SNAPSHOT.jar
│ │ ├── dex-reader-2.2-SNAPSHOT.jar
│ │ ├── dex-reader-api-2.2-SNAPSHOT.jar
│ │ ├── dex-tools-2.2-SNAPSHOT.jar
│ │ ├── dex-translator-2.2-SNAPSHOT.jar
│ │ └── dex-writer-2.2-SNAPSHOT.jar
└── terminal.sh
├── config
├── androidkiller4j.keystore
├── apkShell.properties
└── application.properties
├── logs
└── .gitkeep
├── pom.xml
├── project
└── .gitkeep
├── projectSrc
└── .gitkeep
├── src
└── main
│ ├── assembly
│ └── assembly.xml
│ ├── java
│ └── com
│ │ └── richardtang
│ │ └── androidkiller4j
│ │ ├── Application.java
│ │ ├── LoadWindow.java
│ │ ├── MainWindow.java
│ │ ├── bean
│ │ └── Apk.java
│ │ ├── constant
│ │ ├── Cert.java
│ │ ├── R.java
│ │ ├── Size.java
│ │ ├── Suffix.java
│ │ └── SvgName.java
│ │ ├── ddmlib
│ │ ├── AndroidDeviceManager.java
│ │ ├── listener
│ │ │ └── AndroidDeviceChangeListener.java
│ │ └── process
│ │ │ ├── ProcessMessage.java
│ │ │ ├── ProcessMessageListener.java
│ │ │ ├── ProcessMessageParser.java
│ │ │ └── ProcessMessageReceiverTask.java
│ │ ├── log
│ │ ├── DateFileHandler.java
│ │ └── UiConsoleHandler.java
│ │ ├── setting
│ │ ├── ApkShellSetting.java
│ │ └── ApplicationSetting.java
│ │ ├── task
│ │ ├── command
│ │ │ ├── ApkInspectShellTask.java
│ │ │ ├── ApkSignatureTask.java
│ │ │ ├── ApkToolCompileTask.java
│ │ │ ├── ApkToolDecompileTask.java
│ │ │ ├── CertGenerateTask.java
│ │ │ ├── Dex2JarTask.java
│ │ │ ├── TerminalCommandCallback.java
│ │ │ └── TerminalCommandTask.java
│ │ └── watch
│ │ │ ├── BaseFileWatchTask.java
│ │ │ └── CertFileWatchTask.java
│ │ ├── ui
│ │ ├── action
│ │ │ ├── ClickAction.java
│ │ │ ├── ClickActionInstaller.java
│ │ │ └── ClickActionType.java
│ │ ├── balloon
│ │ │ └── LeftAbovePositionerWrapper.java
│ │ ├── border
│ │ │ └── MLineBorder.java
│ │ ├── control
│ │ │ ├── CustomTextComboBox.java
│ │ │ ├── IconComboBox.java
│ │ │ ├── NumberTextField.java
│ │ │ ├── PopupMenuButton.java
│ │ │ └── PrintStreamTextArea.java
│ │ ├── device
│ │ │ ├── DeviceDirTree.java
│ │ │ └── DeviceFileTable.java
│ │ ├── document
│ │ │ └── NumberDocument.java
│ │ ├── panel
│ │ │ └── CommonTablePanel.java
│ │ ├── rsyntaxtextarea
│ │ │ ├── SmaliTokenMaker.java
│ │ │ └── SyntaxConstantsWrapper.java
│ │ ├── tabframe
│ │ │ ├── TabFrameBar.java
│ │ │ ├── TabFrameItem.java
│ │ │ ├── TabFrameItemPanel.java
│ │ │ └── TabFramePanel.java
│ │ ├── table
│ │ │ └── CommonTable.java
│ │ ├── tabpane
│ │ │ ├── Tab.java
│ │ │ └── TabPane.java
│ │ └── tree
│ │ │ ├── FileContentHighlightNode.java
│ │ │ ├── FileContentHighlightTree.java
│ │ │ └── SystemFileTree.java
│ │ ├── util
│ │ ├── CompressUtils.java
│ │ ├── ControlUtils.java
│ │ ├── FileUtils.java
│ │ ├── JadxUtils.java
│ │ ├── RSyntaxTextAreaUtils.java
│ │ └── YamlUtils.java
│ │ ├── validator
│ │ ├── DeviceOnlineValidator.java
│ │ ├── SelectedIsWorkbenchTabValidator.java
│ │ ├── TabAddedValidator.java
│ │ └── component
│ │ │ └── TextFieldVerifier.java
│ │ └── view
│ │ ├── ConsoleView.java
│ │ ├── MainView.java
│ │ ├── SettingView.java
│ │ ├── SignatureView.java
│ │ ├── TaskView.java
│ │ ├── ToolkitView.java
│ │ ├── WorkbenchView.java
│ │ └── device
│ │ ├── DeviceExplorerView.java
│ │ ├── DeviceLogView.java
│ │ └── DeviceProcessView.java
│ └── resources
│ ├── META-INF
│ └── MANIFEST.MF
│ ├── image
│ ├── about.svg
│ ├── add.svg
│ ├── android.svg
│ ├── apk.svg
│ ├── automation.svg
│ ├── banner.jpg
│ ├── bug.svg
│ ├── checkbox-switch.svg
│ ├── clear.svg
│ ├── compile.svg
│ ├── connect.svg
│ ├── custom.svg
│ ├── device.svg
│ ├── directory.svg
│ ├── disconnect.svg
│ ├── download.svg
│ ├── eye.svg
│ ├── f.svg
│ ├── info.svg
│ ├── install.svg
│ ├── java.svg
│ ├── letter-a.svg
│ ├── letter-i.svg
│ ├── letter-r.svg
│ ├── letter-s.svg
│ ├── letter-u.svg
│ ├── logo.png
│ ├── manager.svg
│ ├── message.svg
│ ├── move.svg
│ ├── open.svg
│ ├── process.svg
│ ├── python.svg
│ ├── refresh.svg
│ ├── remove.svg
│ ├── result.svg
│ ├── save.svg
│ ├── search.svg
│ ├── setting.svg
│ ├── signature.svg
│ ├── system.svg
│ ├── task.svg
│ ├── terminal.svg
│ └── upload.svg
│ └── logging.properties
├── startup.bat
└── startup.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | /target/
3 | /out/
4 | /src/main/java/META-INF/
5 | /src/main/resources/META-INF/
6 | *.log
7 | .DS_Store
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 介绍
2 |
3 | 这是一款用JavaSwing编写的AndroidKiller,实现了原先AndroidKiller的相关功能,可运行在MacOS和Windows平台,在基础上增加了Cfr和Jadx对Smali的反编译。
4 |
5 | # 使用
6 |
7 | ## MacOS
8 |
9 | ```bash
10 | chmod 777
11 | ./startup.sh
12 | ```
13 |
14 | ## Windows
15 |
16 | 双击`startup.bat`脚本运行
17 |
18 | # 效果图
19 |
20 | ## MacOS
21 |
22 | 
23 |
24 | 
25 |
26 | 
27 |
28 | 
29 |
30 | ## Windows
31 |
32 | 
33 |
34 | 
35 |
36 | 
37 |
38 | # 编译
39 |
40 | 安装好Jdk17,导入到IDEA后运行Maven的Install,会在target目录下生成编译后的工具文件夹。
41 |
42 | 
43 |
44 |
45 |
46 | 通过jlink生成精简的jre
47 |
48 | ```bash
49 | jlink --output jre --compress=2 --no-header-files --no-man-pages --add-modules java.base,java.datatransfer,java.desktop,java.logging,java.xml,java.base,java.sql,jdk.unsupported,java.management,java.naming
50 | ```
51 |
52 | 将生成后的jre文件夹,放置工具文件夹中。
53 |
54 |
55 |
--------------------------------------------------------------------------------
/bin/adb/AdbWinApi.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/adb/AdbWinApi.dll
--------------------------------------------------------------------------------
/bin/adb/AdbWinUsbApi.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/adb/AdbWinUsbApi.dll
--------------------------------------------------------------------------------
/bin/adb/linux:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/adb/linux
--------------------------------------------------------------------------------
/bin/adb/macos:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/adb/macos
--------------------------------------------------------------------------------
/bin/adb/windows.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/adb/windows.exe
--------------------------------------------------------------------------------
/bin/apktool.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/apktool.jar
--------------------------------------------------------------------------------
/bin/cfr.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/cfr.jar
--------------------------------------------------------------------------------
/bin/dex2jar/dex2jar:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # dex2jar - Tools to work with android .dex and java .class files
5 | # Copyright (c) 2009-2013 Panxiaobo
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 |
20 | # copy from $Tomcat/bin/startup.sh
21 | # resolve links - $0 may be a softlink
22 | PRG="$0"
23 | while [ -h "$PRG" ] ; do
24 | ls=`ls -ld "$PRG"`
25 | link=`expr "$ls" : '.*-> \(.*\)$'`
26 | if expr "$link" : '/.*' > /dev/null; then
27 | PRG="$link"
28 | else
29 | PRG=`dirname "$PRG"`/"$link"
30 | fi
31 | done
32 | PRGDIR=`dirname "$PRG"`
33 | #
34 |
35 | _classpath="."
36 | if [ `uname -a | grep -i -c cygwin` -ne 0 ]; then # Cygwin, translate the path
37 | for k in "$PRGDIR"/lib/*.jar
38 | do
39 | _classpath="${_classpath};`cygpath -w ${k}`"
40 | done
41 | else
42 | for k in "$PRGDIR"/lib/*.jar
43 | do
44 | _classpath="${_classpath}:${k}"
45 | done
46 | fi
47 |
48 | java -Xms512m -Xmx2048m -classpath "${_classpath}" "com.googlecode.dex2jar.tools.Dex2jarCmd" "$@"
49 |
--------------------------------------------------------------------------------
/bin/dex2jar/dex2jar.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | REM
4 | REM dex2jar - Tools to work with android .dex and java .class files
5 | REM Copyright (c) 2009-2013 Panxiaobo
6 | REM
7 | REM Licensed under the Apache License, Version 2.0 (the "License");
8 | REM you may not use this file except in compliance with the License.
9 | REM You may obtain a copy of the License at
10 | REM
11 | REM http://www.apache.org/licenses/LICENSE-2.0
12 | REM
13 | REM Unless required by applicable law or agreed to in writing, software
14 | REM distributed under the License is distributed on an "AS IS" BASIS,
15 | REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | REM See the License for the specific language governing permissions and
17 | REM limitations under the License.
18 | REM
19 |
20 | REM call d2j_invoke.bat to setup java environment
21 | @"%~dp0d2j_invoke.bat" com.googlecode.dex2jar.tools.Dex2jarCmd %*
22 |
--------------------------------------------------------------------------------
/bin/dex2jar/lib/asm-debug-all-5.0.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/dex2jar/lib/asm-debug-all-5.0.3.jar
--------------------------------------------------------------------------------
/bin/dex2jar/lib/d2j-base-cmd-2.2-SNAPSHOT.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/dex2jar/lib/d2j-base-cmd-2.2-SNAPSHOT.jar
--------------------------------------------------------------------------------
/bin/dex2jar/lib/dex-ir-2.2-SNAPSHOT.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/dex2jar/lib/dex-ir-2.2-SNAPSHOT.jar
--------------------------------------------------------------------------------
/bin/dex2jar/lib/dex-reader-2.2-SNAPSHOT.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/dex2jar/lib/dex-reader-2.2-SNAPSHOT.jar
--------------------------------------------------------------------------------
/bin/dex2jar/lib/dex-reader-api-2.2-SNAPSHOT.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/dex2jar/lib/dex-reader-api-2.2-SNAPSHOT.jar
--------------------------------------------------------------------------------
/bin/dex2jar/lib/dex-tools-2.2-SNAPSHOT.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/dex2jar/lib/dex-tools-2.2-SNAPSHOT.jar
--------------------------------------------------------------------------------
/bin/dex2jar/lib/dex-translator-2.2-SNAPSHOT.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/dex2jar/lib/dex-translator-2.2-SNAPSHOT.jar
--------------------------------------------------------------------------------
/bin/dex2jar/lib/dex-writer-2.2-SNAPSHOT.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/bin/dex2jar/lib/dex-writer-2.2-SNAPSHOT.jar
--------------------------------------------------------------------------------
/bin/terminal.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # 这是用来启动终端窗口的脚本,请不要随意修改。
4 | osascript <
2 |
3 | ${project.version}
4 |
5 |
6 |
7 | dir
8 | zip
9 | tar.gz
10 |
11 |
12 |
13 | false
14 |
15 |
16 |
17 |
18 | false
19 | libs
20 | false
21 |
22 |
23 |
24 |
25 |
26 |
27 | ${project.basedir}/bin
28 | bin
29 |
30 |
31 | ${project.basedir}/config
32 | config
33 |
34 |
35 | ${project.basedir}/logs
36 | logs
37 |
38 |
39 | ${project.basedir}/project
40 | project
41 |
42 |
43 | ${project.basedir}/projectSrc
44 | projectSrc
45 |
46 |
47 | ${project.basedir}
48 | /
49 |
50 | startup.sh
51 | startup.bat
52 |
53 |
54 |
55 |
56 | ${project.build.directory}
57 | /libs/
58 |
59 | AndroidKiller4J.jar
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/Application.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j;
2 |
3 | import cn.hutool.log.StaticLog;
4 | import com.formdev.flatlaf.FlatLaf;
5 | import com.formdev.flatlaf.FlatLightLaf;
6 | import com.formdev.flatlaf.util.SystemInfo;
7 | import com.richardtang.androidkiller4j.setting.ApplicationSetting;
8 | import com.richardtang.androidkiller4j.util.FileUtils;
9 |
10 | import javax.swing.*;
11 | import java.awt.*;
12 | import java.net.URL;
13 | import java.util.*;
14 |
15 | /**
16 | * AndroidKiller4J
17 | *
18 | * @author RichardTang
19 | */
20 | public class Application {
21 |
22 | static {
23 | // UI相关属性,需要在加载窗口出来之前就优先加载。
24 |
25 | if (SystemInfo.isMacOS) {
26 | // 设置应用名称
27 | System.setProperty("apple.awt.application.name", ApplicationSetting.APP_NAME);
28 | // 设置Mac下跟随系统色调
29 | System.setProperty("apple.awt.application.appearance", "system");
30 |
31 | // 设置Dock图标
32 | URL logoUrl = FileUtils.getResource("image/logo.png");
33 | Taskbar.getTaskbar().setIconImage(new ImageIcon(logoUrl).getImage());
34 | }
35 |
36 | // 设置语言
37 | Locale.setDefault(Locale.CHINA);
38 |
39 | // 根据配置文件加载主题
40 | try {
41 | String applicationTheme = ApplicationSetting.getInstance().getApplicationTheme();
42 | FlatLaf flatLafTheme = (FlatLaf) Class.forName(applicationTheme).getDeclaredConstructor().newInstance();
43 | FlatLaf.setup(flatLafTheme);
44 | } catch (Exception e) {
45 | StaticLog.error("配置文件application.theme值有误!");
46 | FlatLightLaf.setup();
47 | }
48 |
49 | // 对所有的PasswordField组件启用密码或隐藏功能
50 | UIManager.put("PasswordField.showRevealButton", true);
51 | }
52 |
53 | public static void main(String[] args) {
54 | // 启动加载界面
55 | SwingUtilities.invokeLater(() -> new LoadWindow().start());
56 | }
57 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/LoadWindow.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j;
2 |
3 | import cn.hutool.core.io.resource.ResourceUtil;
4 | import cn.hutool.core.thread.ThreadUtil;
5 | import cn.hutool.core.util.StrUtil;
6 | import cn.hutool.db.ThreadLocalConnection;
7 | import cn.hutool.log.StaticLog;
8 | import cn.hutool.system.SystemUtil;
9 | import com.formdev.flatlaf.FlatLaf;
10 | import com.formdev.flatlaf.FlatLightLaf;
11 | import com.formdev.flatlaf.util.SystemInfo;
12 | import com.richardtang.androidkiller4j.constant.R;
13 | import com.richardtang.androidkiller4j.setting.ApplicationSetting;
14 | import com.richardtang.androidkiller4j.ui.rsyntaxtextarea.SmaliTokenMaker;
15 | import com.richardtang.androidkiller4j.ui.rsyntaxtextarea.SyntaxConstantsWrapper;
16 | import com.richardtang.androidkiller4j.util.CompressUtils;
17 | import com.richardtang.androidkiller4j.util.ControlUtils;
18 | import com.richardtang.androidkiller4j.util.FileUtils;
19 | import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory;
20 | import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
21 |
22 | import javax.swing.*;
23 | import java.awt.*;
24 | import java.io.File;
25 | import java.util.List;
26 | import java.util.Locale;
27 |
28 | /**
29 | * 程序启动时的加载动画窗口
30 | *
31 | * @author RichardTang
32 | */
33 | public class LoadWindow extends JWindow implements Runnable {
34 |
35 | // 进度条
36 | private JProgressBar progress;
37 |
38 | // 加载时的提示信息
39 | private String LOADING_TIP = "加载程序中,请稍候......";
40 |
41 | private String PACK_SUFFIX = "pack.gz";
42 |
43 | public LoadWindow() {
44 | setLayout(new BorderLayout());
45 |
46 | // 初始化进度条
47 | progress = new JProgressBar(1, 100);
48 | progress.setValue(0);
49 | progress.setStringPainted(true);
50 | progress.setBackground(Color.white);
51 | progress.setString(LOADING_TIP);
52 | progress.putClientProperty("JProgressBar.square", true);
53 |
54 | // 启动界面的Banner
55 |
56 | ImageIcon banner = ControlUtils.getImageIcon(FileUtils.getResource("image/banner.jpg"), 450, 300);
57 |
58 | // 鼠标悬浮显示加载光标
59 | setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
60 |
61 | // 设置banner和添加进度条
62 | Container container = getContentPane();
63 | container.add(new JLabel(banner), BorderLayout.CENTER);
64 | container.add(progress, BorderLayout.SOUTH);
65 |
66 | // 得到屏幕尺寸。
67 | Dimension screen = getToolkit().getScreenSize();
68 | // 窗口适应组件尺寸
69 | pack();
70 | // 设置窗口位置
71 | setLocation((screen.width - getSize().width) / 2, (screen.height - getSize().height) / 2);
72 | }
73 |
74 | /**
75 | * 进度条加载线程
76 | */
77 | public void run() {
78 | // 显示进度条加载窗口
79 | setVisible(true);
80 | try {
81 | freeResource();
82 | loadEditorHighlighting();
83 | } catch (Exception e) {
84 | StaticLog.error(e);
85 | }
86 | // 释放窗口
87 | dispose();
88 |
89 | // 运行主窗口
90 | SwingUtilities.invokeLater(() -> new MainWindow().start());
91 | }
92 |
93 | /**
94 | * 释放资源
95 | */
96 | public void freeResource() {
97 | // 释放pack.gz包
98 | List packGzFiles = FileUtils.loopFiles(R.BIN_DIR, pathname -> pathname.getName().contains(PACK_SUFFIX));
99 |
100 | // 之前已经释放过资源
101 | if (packGzFiles.size() == 0) {
102 | // 延迟一秒,不然进度条太快体验不好
103 | try {
104 | Thread.sleep(1000);
105 | } catch (Exception e) {
106 | StaticLog.error(e);
107 | }
108 | // 直接进度条拉满
109 | progress.setValue(100);
110 | return;
111 | }
112 |
113 | // 释放资源
114 | for (int i = 0; i < packGzFiles.size(); i++) {
115 | String fileFullPath = packGzFiles.get(i).getAbsolutePath();
116 | String unpackerAfterPath = StrUtil.subBefore(fileFullPath, File.separator, true);
117 | boolean unpackerFlag = CompressUtils.unpacker(fileFullPath, unpackerAfterPath + File.separator);
118 | // 解压完成后将.pack.gz包删除。
119 | if (unpackerFlag) {
120 | FileUtils.del(packGzFiles.get(i));
121 | }
122 | // 更新进度条
123 | progress.setValue((int) (((i + 1) / (double) packGzFiles.size()) * 100.0));
124 | }
125 | }
126 |
127 | /**
128 | * 加载代码编辑器语法高亮
129 | */
130 | public void loadEditorHighlighting() {
131 | ((AbstractTokenMakerFactory) TokenMakerFactory.getDefaultInstance())
132 | .putMapping(SyntaxConstantsWrapper.SYNTAX_STYLE_SMALI, SmaliTokenMaker.class.getName());
133 | }
134 |
135 | /**
136 | * 启动加载窗口,并启动进度条加载。
137 | */
138 | public void start() {
139 | // 窗口前端显示
140 | this.toFront();
141 |
142 | // 启动进度条加载线程
143 | ThreadUtil.execAsync(this);
144 | }
145 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/MainWindow.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import com.richardtang.androidkiller4j.bean.Apk;
5 | import com.richardtang.androidkiller4j.constant.Suffix;
6 | import com.richardtang.androidkiller4j.setting.ApplicationSetting;
7 | import com.richardtang.androidkiller4j.view.*;
8 | import lombok.SneakyThrows;
9 |
10 | import javax.swing.*;
11 | import java.awt.datatransfer.DataFlavor;
12 | import java.awt.dnd.DnDConstants;
13 | import java.awt.dnd.DropTarget;
14 | import java.awt.dnd.DropTargetAdapter;
15 | import java.awt.dnd.DropTargetDropEvent;
16 | import java.awt.event.WindowAdapter;
17 | import java.awt.event.WindowEvent;
18 | import java.io.File;
19 | import java.util.List;
20 |
21 | /**
22 | * 应用主窗口
23 | *
24 | * @author RichardTang
25 | */
26 | public class MainWindow extends JFrame {
27 |
28 | // 注意View的声明顺序是有要求的
29 | public static final TaskView taskView = new TaskView();
30 | public static final ToolkitView toolkitView = new ToolkitView();
31 | public static final ConsoleView consoleView = new ConsoleView();
32 | public static final SignatureView signatureView = new SignatureView();
33 | public static final SettingView settingView = new SettingView();
34 | public static final MainView mainView = new MainView();
35 |
36 | public MainWindow() {
37 | // 设置窗口属性
38 | setTitle(ApplicationSetting.APP_NAME);
39 | setSize(ApplicationSetting.getInstance().getMainDefSizeDimension());
40 | setMinimumSize(ApplicationSetting.getInstance().getMainMinSizeDimension());
41 |
42 | // 关闭动作
43 | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
44 |
45 | // 添加窗口关闭事件
46 | addWindowListener(new WindowAdapter() {
47 | @Override
48 | public void windowClosed(WindowEvent e) {
49 | super.windowClosed(e);
50 | System.exit(0);
51 | }
52 | });
53 |
54 | // 添加文件拖拽处理
55 | new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, new ApkTaskDropTargetAdapter());
56 |
57 | // 设置显示的主视图
58 | setContentPane(mainView);
59 | }
60 |
61 | /**
62 | * 启动应用窗口
63 | */
64 | public void start() {
65 | setVisible(true);
66 | }
67 |
68 | /**
69 | * 用于处理Apk文件拖拽进工具后自动添加至任务列表
70 | */
71 | private static class ApkTaskDropTargetAdapter extends DropTargetAdapter {
72 | @SneakyThrows
73 | @Override
74 | public void drop(DropTargetDropEvent dtde) {
75 | if (!dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
76 | // 拒绝拖拽来的数据
77 | dtde.rejectDrop();
78 | }
79 |
80 | // 接受处理该拖拽数据
81 | dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
82 |
83 | // 拖拽进来的数据,可能是多个文件也可能是文件夹。
84 | List files = (List) (dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor));
85 |
86 | // 校验后缀,为apk的添加至任务中
87 | for (File file : files) {
88 | if (file.isDirectory()) {
89 | return;
90 | }
91 | // 符合apk的文件添加到任务表中
92 | if (Suffix.APK.equals(FileUtil.getSuffix(file))) {
93 | MainWindow.toolkitView.apkDecompiler(file);
94 | }
95 | }
96 |
97 | // 指示拖拽操作已完成
98 | dtde.dropComplete(true);
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/constant/Cert.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.constant;
2 |
3 | /**
4 | * 用到的一些证书信息
5 | *
6 | * @author RichardTang
7 | */
8 | public interface Cert {
9 |
10 | // 生成证书文件的后缀
11 | String CERT_FILE_SUFFIX = ".keystore";
12 |
13 | // 所有生成的证书所使用的密码和别名都指定成这个
14 | String CERT_PASS_ALIAS = "AndroidKiller4J";
15 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/constant/R.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.constant;
2 |
3 | import cn.hutool.system.SystemUtil;
4 | import com.formdev.flatlaf.util.SystemInfo;
5 | import com.richardtang.androidkiller4j.util.FileUtils;
6 |
7 | import java.io.File;
8 |
9 | /**
10 | * 资源类
11 | *
12 | * @author RichardTang
13 | */
14 | public interface R {
15 |
16 | // 工作目录
17 | String WORK_DIR = System.getProperty("user.dir") + File.separator;
18 |
19 | // 分别对应根目录下的子文件夹
20 | String BIN_DIR = WORK_DIR + "bin" + File.separator;
21 | String LOGS_DIR = WORK_DIR + "logs" + File.separator;
22 | String CONFIG_DIR = WORK_DIR + "config" + File.separator;
23 | String PROJECT_DIR = WORK_DIR + "project" + File.separator;
24 | String PROJECT_SRC_DIR = WORK_DIR + "projectSrc" + File.separator;
25 |
26 | // 默认的keystore文件路径
27 | String DEFAULT_KEYSTORE = CONFIG_DIR + File.separator + "androidkiller4j.keystore";
28 |
29 | // application.properties文件路径
30 | String APPLICATION_PROPERTIES = CONFIG_DIR + File.separator + "application.properties";
31 |
32 | // apkShell.properties文件路径
33 | String APKSHELL_PROPERTIES = CONFIG_DIR + File.separator + "apkShell.properties";
34 |
35 | // cfr java反编译工具
36 | String CFR = BIN_DIR + "cfr.jar";
37 |
38 | // dex2jar工具
39 | String DEX2JAR = BIN_DIR + "dex2jar" + File.separator + (SystemInfo.isWindows ? "dex2jar.bat" : "dex2jar");
40 |
41 | // mac系统下的terminal.sh脚本位置
42 | String MAC_TERMINAL = BIN_DIR + File.separator + "terminal.sh";
43 |
44 | // adb程序
45 | String ADB = getAdbByPlatform();
46 |
47 | // Java主程序
48 | String JAVA = getJavaBinByPlatform();
49 |
50 | static String getAdbByPlatform() {
51 | String adb = BIN_DIR + "adb" + File.separator;
52 | if (SystemInfo.isWindows) {
53 | adb += "windows.exe";
54 | } else if (SystemInfo.isMacOS) {
55 | adb += "macos";
56 | } else {
57 | adb += "linux";
58 | }
59 | return adb;
60 | }
61 |
62 | static String getJavaBinByPlatform() {
63 | String java = SystemUtil.getJavaRuntimeInfo().getHomeDir() + File.separator + "bin" + File.separator;
64 | if (SystemInfo.isWindows) {
65 | java += "java.exe";
66 | } else {
67 | java += "java";
68 | }
69 | return java;
70 | }
71 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/constant/Size.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.constant;
2 |
3 | /**
4 | * 项目中用的比较多的UI尺寸大小
5 | *
6 | * @author RichardTang
7 | */
8 | public interface Size {
9 | int BIG = 20;
10 | int MEDIUM = 15;
11 | int SMALL = 5;
12 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/constant/Suffix.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.constant;
2 |
3 | public interface Suffix {
4 |
5 | String APK = "apk";
6 | String CLASS = "class";
7 | String JAVA = "java";
8 | String SMALI = "smali";
9 | String POINT_APK = ".apk";
10 | String POINT_CLASS = ".class";
11 | String POINT_JAVA = ".java";
12 | String POINT_JAR = ".jar";
13 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/constant/SvgName.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.constant;
2 |
3 | public interface SvgName {
4 | String F = "f.svg";
5 | String APK = "apk.svg";
6 | String BUG = "bug.svg";
7 | String ADD = "add.svg";
8 | String MOVE = "move.svg";
9 | String TASK = "task.svg";
10 | String ABOUT = "about.svg";
11 | String SYSTEM = "system.svg";
12 | String UPLOAD = "upload.svg";
13 | String REMOVE = "remove.svg";
14 | String SEARCH = "search.svg";
15 | String CUSTOM = "custom.svg";
16 | String DEVICE = "device.svg";
17 | String ANDROID = "android.svg";
18 | String CONNECT = "connect.svg";
19 | String REFRESH = "refresh.svg";
20 | String PROCESS = "process.svg";
21 | String COMPILE = "compile.svg";
22 | String INSTALL = "install.svg";
23 | String SETTING = "setting.svg";
24 | String DOWNLOAD = "download.svg";
25 | String TERMINAL = "terminal.svg";
26 | String SIGNATURE = "signature.svg";
27 | String DIRECTORY = "directory.svg";
28 | String DISCONNECT = "disconnect.svg";
29 | String MESSAGE = "message.svg";
30 | String EYE = "eye.svg";
31 | String RESULT = "result.svg";
32 | String SAVE = "save.svg";
33 | String JAVA = "java.svg";
34 | String LETTER_A = "letter-a.svg";
35 | String LETTER_U = "letter-u.svg";
36 | String LETTER_R = "letter-r.svg";
37 | String LETTER_S = "letter-s.svg";
38 | String LETTER_I = "letter-i.svg";
39 | String INFO = "info.svg";
40 | String MANAGER = "manager.svg";
41 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ddmlib/AndroidDeviceManager.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ddmlib;
2 |
3 | import cn.hutool.core.util.RuntimeUtil;
4 | import com.android.ddmlib.AndroidDebugBridge;
5 | import com.android.ddmlib.IDevice;
6 | import com.richardtang.androidkiller4j.constant.R;
7 | import com.richardtang.androidkiller4j.ddmlib.listener.AndroidDeviceChangeListener;
8 | import lombok.Data;
9 |
10 | /**
11 | * Android设备管理类,对Google-ddmlib包做的一些简单封装。
12 | *
13 | * @author RichardTang
14 | */
15 | @Data
16 | public class AndroidDeviceManager {
17 |
18 | // 当前选中的设备
19 | private IDevice device;
20 |
21 | // 设备桥
22 | private AndroidDebugBridge bridge;
23 |
24 | // 单利
25 | private static final AndroidDeviceManager instance = new AndroidDeviceManager();
26 |
27 | private AndroidDeviceManager() {
28 | AndroidDebugBridge.init(false);
29 | bridge = AndroidDebugBridge.createBridge(R.ADB, false);
30 | AndroidDebugBridge.addDeviceChangeListener(new AndroidDeviceChangeListener());
31 | }
32 |
33 | public static AndroidDeviceManager getInstance() {
34 | return instance;
35 | }
36 |
37 | /**
38 | * 获得设备列表
39 | *
40 | * @return 设备数组
41 | */
42 | public IDevice[] getDevices() {
43 | return bridge.getDevices();
44 | }
45 |
46 | /**
47 | * 判断ADB是否已经连接指定设备
48 | *
49 | * @return true:已连接 false:未连接
50 | */
51 | public boolean isConnected() {
52 | return device != null;
53 | }
54 |
55 | /**
56 | * 执行adb kill-server命令
57 | */
58 | public void killServer() {
59 | RuntimeUtil.exec(R.ADB + " kill-server");
60 | }
61 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ddmlib/listener/AndroidDeviceChangeListener.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ddmlib.listener;
2 |
3 | import cn.hutool.log.StaticLog;
4 | import com.android.ddmlib.AndroidDebugBridge;
5 | import com.android.ddmlib.IDevice;
6 | import com.richardtang.androidkiller4j.Application;
7 | import com.richardtang.androidkiller4j.MainWindow;
8 | import com.richardtang.androidkiller4j.constant.Size;
9 | import com.richardtang.androidkiller4j.ddmlib.AndroidDeviceManager;
10 | import com.richardtang.androidkiller4j.ui.control.IconComboBox;
11 | import com.richardtang.androidkiller4j.util.ControlUtils;
12 |
13 | import javax.swing.*;
14 |
15 | /**
16 | * Device设备监听,当有新设备连接、断开、切换时会触发该类中对应的函数。
17 | *
18 | * @author RichardTang
19 | */
20 | public class AndroidDeviceChangeListener implements AndroidDebugBridge.IDeviceChangeListener {
21 |
22 | // 设备图标
23 | public final Icon DEVICE_ICON = ControlUtils.getSVGIcon("device.svg", Size.MEDIUM);
24 |
25 | /**
26 | * 电脑使用数据线连接了新的设备时触发
27 | *
28 | * @param device 设备对象
29 | */
30 | @Override
31 | public void deviceConnected(IDevice device) {
32 | MainWindow.toolkitView.getDeviceComboBox().addItem(
33 | new IconComboBox.IconComboBoxData(device.getSerialNumber(), device.getName(), DEVICE_ICON, device)
34 | );
35 | StaticLog.info("新设备上线: " + device.getName());
36 | }
37 |
38 | /**
39 | * 设备断开连接时触发,当在adb devices中看不到该设备时就代表该设备断开了连接。
40 | *
41 | * @param device 设备对象
42 | */
43 | @Override
44 | public void deviceDisconnected(IDevice device) {
45 | IDevice selectDevice = AndroidDeviceManager.getInstance().getDevice();
46 | if (selectDevice != null && device == selectDevice) {
47 | AndroidDeviceManager.getInstance().setDevice(null);
48 | MainWindow.toolkitView.setDeviceConnectStateUI(true);
49 | }
50 | MainWindow.toolkitView.getDeviceComboBox().removeItemById(device.getSerialNumber());
51 | StaticLog.info("设备断开连接: " + device.getName());
52 | }
53 |
54 | @Override
55 | public void deviceChanged(IDevice device, int changeMask) {
56 |
57 | }
58 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ddmlib/process/ProcessMessage.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ddmlib.process;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 进程消息实体类
7 | *
8 | * @author RichardTang
9 | */
10 | @Data
11 | public class ProcessMessage {
12 | private String pid;
13 | private String user;
14 | private String pr;
15 | private String ni;
16 | private String virt;
17 | private String res;
18 | private String shr;
19 | private String s;
20 | private String cpu;
21 | private String mem;
22 | private String time;
23 | private String args;
24 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ddmlib/process/ProcessMessageListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.richardtang.androidkiller4j.ddmlib.process;
18 |
19 | import java.util.List;
20 |
21 | /**
22 | * 进程消息结果处理接口
23 | *
24 | * @author RichardTang
25 | */
26 | public interface ProcessMessageListener {
27 |
28 | /**
29 | * 定义处理Top命令执行的结果处理方式
30 | *
31 | * @param msgList top命令产生的每一行数据都会被封装为一个元素并存如集合中
32 | */
33 | void log(List msgList);
34 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ddmlib/process/ProcessMessageParser.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ddmlib.process;
2 |
3 | import cn.hutool.core.util.StrUtil;
4 | import lombok.NonNull;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Arrays;
8 | import java.util.List;
9 |
10 | /**
11 | * 解析android设备执行top命令的结果,将top命令的字符串结果转换为{@link ProcessMessage}对象。
12 | * top命令的每一行为一个{@link ProcessMessage}(除了前边5行),每一行的每一个项为ProcessMessage中对应的属性。
13 | * 每一次运行/刷新top命令的显示都得到一个List结果。
14 | *
15 | * @author RichardTang
16 | */
17 | class ProcessMessageParser {
18 |
19 | // top命令每一行中的每一项以空格分隔
20 | private final String LINE_ITEM_SPACE_REGEX = "\\s+";
21 |
22 | /**
23 | * 处理top命令产生的字符串,封装成对应的集合。
24 | *
25 | * @param lines 这是一个存储了top命令多行字符串的数组
26 | * @return 解析成ProcessMessage对象集合
27 | */
28 | @NonNull
29 | public List processLogLines(@NonNull String[] lines) {
30 | List messages = new ArrayList<>(lines.length);
31 | // 前5行不进行处理因为数据为不同的格式
32 | for (int i = 5; i < lines.length; i++) {
33 | // 解析成对应的ProcessMessage对象
34 | messages.add(parserLineItemToProcessMessage(lines[i]));
35 | }
36 | return messages;
37 | }
38 |
39 | /**
40 | * 解析Top命令里每一行数据里的每一个项
41 | * 将这些项封装到{@link ProcessMessage}对象。
42 | *
43 | * @param line top命令里要处理的指定行数据
44 | * @return {@link ProcessMessage}对象
45 | */
46 | private ProcessMessage parserLineItemToProcessMessage(String line) {
47 | String[] items = line.trim().split(LINE_ITEM_SPACE_REGEX);
48 |
49 | ProcessMessage processMessage = new ProcessMessage();
50 | processMessage.setPid(items[0]);
51 | processMessage.setUser(items[1]);
52 | processMessage.setPr(items[2]);
53 | processMessage.setNi(items[3]);
54 | processMessage.setVirt(items[4]);
55 | processMessage.setRes(items[5]);
56 | processMessage.setShr(items[6]);
57 | processMessage.setS(items[7]);
58 | processMessage.setCpu(items[8]);
59 | processMessage.setMem(items[9]);
60 | processMessage.setTime(items[10]);
61 |
62 | // 切割后大于11的情况是程序存在多参数的情况
63 | if (items.length > 11) {
64 | String join = StrUtil.join(" ", Arrays.copyOfRange(items, 11, items.length));
65 | processMessage.setArgs(join);
66 | }
67 |
68 | return processMessage;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ddmlib/process/ProcessMessageReceiverTask.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ddmlib.process;
2 |
3 | import com.android.ddmlib.IDevice;
4 | import com.android.ddmlib.MultiLineReceiver;
5 |
6 | import java.util.HashSet;
7 | import java.util.List;
8 | import java.util.Set;
9 | import java.util.concurrent.atomic.AtomicBoolean;
10 |
11 | /**
12 | * 进程消息处理任务
13 | *
14 | * @author RichardTang
15 | */
16 | public class ProcessMessageReceiverTask implements Runnable {
17 |
18 | private final String COMMAND = "top";
19 | private final int DEVICE_POLL_INTERVAL_MSEC = 1000;
20 |
21 | private final IDevice mDevice;
22 | private final AtomicBoolean mCancelled;
23 | private final ProcessMessageParser mParser;
24 | private final ProcessMessageOutputReceiver mReceiver;
25 | private final Set mListeners = new HashSet<>();
26 |
27 | public ProcessMessageReceiverTask(IDevice device) {
28 | mDevice = device;
29 | mReceiver = new ProcessMessageOutputReceiver();
30 | mParser = new ProcessMessageParser();
31 | mCancelled = new AtomicBoolean();
32 | }
33 |
34 | @Override
35 | public void run() {
36 | while (!mDevice.isOnline()) {
37 | try {
38 | Thread.sleep(DEVICE_POLL_INTERVAL_MSEC);
39 | } catch (InterruptedException e) {
40 | return;
41 | }
42 | }
43 |
44 | try {
45 | mDevice.executeShellCommand(COMMAND, mReceiver, 0);
46 | } catch (Exception e) {
47 | System.out.println("进程监听失败");
48 | }
49 | }
50 |
51 | public void stop() {
52 | mCancelled.set(true);
53 | }
54 |
55 | private class ProcessMessageOutputReceiver extends MultiLineReceiver {
56 |
57 | public ProcessMessageOutputReceiver() {
58 | setTrimLine(false);
59 | }
60 |
61 | @Override
62 | public boolean isCancelled() {
63 | return mCancelled.get();
64 | }
65 |
66 | @Override
67 | public void processNewLines(String[] lines) {
68 | if (!mCancelled.get()) {
69 | processLogLines(lines);
70 | }
71 | }
72 |
73 | private void processLogLines(String[] lines) {
74 | List newMessages = mParser.processLogLines(lines);
75 | if (!newMessages.isEmpty()) {
76 | notifyListeners(newMessages);
77 | }
78 | }
79 | }
80 |
81 | public synchronized void addLogCatListener(ProcessMessageListener l) {
82 | mListeners.add(l);
83 | }
84 |
85 | public synchronized void removeLogCatListener(ProcessMessageListener l) {
86 | mListeners.remove(l);
87 | }
88 |
89 | private synchronized void notifyListeners(List messages) {
90 | for (ProcessMessageListener l : mListeners) {
91 | l.log(messages);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/log/DateFileHandler.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.log;
2 |
3 | import cn.hutool.core.date.DateUtil;
4 | import com.richardtang.androidkiller4j.constant.R;
5 |
6 | import java.io.IOException;
7 | import java.util.Date;
8 | import java.util.logging.FileHandler;
9 |
10 | /**
11 | * 定义日志已日期格式创建文件,因logging.properties文件中无法直接配置日期生成,所以此处通过代码实现。
12 | *
13 | * @author RichardTang
14 | */
15 | public class DateFileHandler extends FileHandler {
16 |
17 | public DateFileHandler() throws IOException {
18 | super(R.LOGS_DIR + DateUtil.format(new Date(), "yyyy-MM-dd") + ".log");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/log/UiConsoleHandler.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.log;
2 |
3 | import com.richardtang.androidkiller4j.MainWindow;
4 |
5 | import java.util.logging.ConsoleHandler;
6 |
7 | /**
8 | * 将日志数据流导向到ConsoleView组件中
9 | *
10 | * @author RichardTang
11 | */
12 | public class UiConsoleHandler extends ConsoleHandler {
13 |
14 | public UiConsoleHandler() {
15 | super();
16 | setOutputStream(MainWindow.consoleView.getPrintStreamTextArea().getPrintStream());
17 | }
18 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/setting/ApkShellSetting.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.setting;
2 |
3 | import cn.hutool.setting.Setting;
4 | import com.richardtang.androidkiller4j.constant.R;
5 |
6 | public class ApkShellSetting extends Setting {
7 |
8 | // 单利
9 | private static final ApkShellSetting instance = new ApkShellSetting();
10 |
11 | private ApkShellSetting() {
12 | super(R.APKSHELL_PROPERTIES);
13 | this.autoLoad(true);
14 | }
15 |
16 | public static ApkShellSetting getInstance() {
17 | return instance;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/setting/ApplicationSetting.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.setting;
2 |
3 | import cn.hutool.core.util.ObjectUtil;
4 | import cn.hutool.core.util.StrUtil;
5 | import cn.hutool.setting.Setting;
6 | import com.formdev.flatlaf.FlatLaf;
7 | import com.formdev.flatlaf.FlatLightLaf;
8 | import com.richardtang.androidkiller4j.constant.R;
9 |
10 | import java.awt.*;
11 |
12 | /**
13 | * application.properties配置文件类
14 | *
15 | * @author RichardTang
16 | */
17 | public class ApplicationSetting {
18 |
19 | // 应用信息
20 | public static final String APP_NAME = "AndroidKiller4J";
21 | public static final String APP_VERSION = "1.0.0";
22 |
23 | // 应用配置
24 | private Integer mainWindowDefWidth;
25 | private Integer mainWindowDefHeight;
26 | private Integer mainWindowMinWidth;
27 | private Integer mainWindowMinHeight;
28 | private String applicationTheme;
29 |
30 | // 环境配置
31 | private String javaBinPath;
32 |
33 | // 对应配置文件中的key值
34 | private static final String JAVA_BIN_PATH_KEY = "java.bin.path";
35 | private static final String APPLICATION_THEME_KEY = "application.theme";
36 | private static final String MAIN_WINDOW_DEF_WIDTH_KEY = "main.window.def.width";
37 | private static final String MAIN_WINDOW_DEF_HEIGHT_KEY = "main.window.def.height";
38 | private static final String MAIN_WINDOW_MIN_WIDTH_KEY = "main.window.min.width";
39 | private static final String MAIN_WINDOW_MIN_HEIGHT_KEY = "main.window.min.height";
40 |
41 | // 默认和最小 窗口高度宽度 默认值
42 | private static final Integer MAIN_WINDOWS_DEF_WIDTH_DEF_VALUE = 1200;
43 | private static final Integer MAIN_WINDOWS_DEF_HEIGHT_DEF_VALUE = 850;
44 | private static final Integer MAIN_WINDOWS_MIN_WIDTH_DEF_VALUE = 700;
45 | private static final Integer MAIN_WINDOWS_MIN_HEIGHT_DEF_VALUE = 500;
46 |
47 | // Properties操作类
48 | private final Setting setting = new Setting(R.APPLICATION_PROPERTIES);
49 |
50 | // 单利
51 | private static final ApplicationSetting instance = new ApplicationSetting();
52 |
53 | private ApplicationSetting() {
54 | this.setting.autoLoad(true);
55 | this.javaBinPath = this.setting.getStr(JAVA_BIN_PATH_KEY);
56 | this.applicationTheme = this.setting.getStr(APPLICATION_THEME_KEY);
57 | this.mainWindowMinHeight = this.setting.getInt(MAIN_WINDOW_MIN_HEIGHT_KEY);
58 | this.mainWindowMinWidth = this.setting.getInt(MAIN_WINDOW_MIN_WIDTH_KEY);
59 | this.mainWindowDefHeight = this.setting.getInt(MAIN_WINDOW_DEF_HEIGHT_KEY);
60 | this.mainWindowDefWidth = this.setting.getInt(MAIN_WINDOW_DEF_WIDTH_KEY);
61 | }
62 |
63 | public static ApplicationSetting getInstance() {
64 | return instance;
65 | }
66 |
67 | /**
68 | * 获取主视图窗口默认宽度
69 | *
70 | * @return 主视图窗口默认宽度
71 | */
72 | public Integer getMainWindowDefWidth() {
73 | if (mainWindowDefWidth == null) {
74 | mainWindowDefWidth = MAIN_WINDOWS_DEF_WIDTH_DEF_VALUE;
75 |
76 | }
77 | return mainWindowDefWidth;
78 | }
79 |
80 | /**
81 | * 设置窗口默认宽度值
82 | *
83 | * @param width 宽度
84 | */
85 | public void setMainWindowDefWidth(Integer width) {
86 | this.mainWindowDefWidth = width;
87 | this.setting.put(MAIN_WINDOW_DEF_WIDTH_KEY, String.valueOf(width));
88 | }
89 |
90 | /**
91 | * 获取主视图窗口默认高度
92 | *
93 | * @return 主视图窗口默认高度
94 | */
95 | public Integer getMainWindowDefHeight() {
96 | if (mainWindowDefHeight == null) {
97 | mainWindowDefHeight = MAIN_WINDOWS_DEF_HEIGHT_DEF_VALUE;
98 | }
99 | return mainWindowDefHeight;
100 | }
101 |
102 | /**
103 | * 设置窗口默认高度值
104 | *
105 | * @param height 高度
106 | */
107 | public void setMainWindowDefHeight(Integer height) {
108 | this.mainWindowDefHeight = height;
109 | this.setting.put(MAIN_WINDOW_DEF_HEIGHT_KEY, String.valueOf(height));
110 | }
111 |
112 | /**
113 | * 获取主视图窗口最小宽度
114 | *
115 | * @return 主视图窗口最小宽度
116 | */
117 | public Integer getMainWindowMinWidth() {
118 | if (ObjectUtil.isEmpty(mainWindowMinWidth)) {
119 | mainWindowMinWidth = MAIN_WINDOWS_MIN_WIDTH_DEF_VALUE;
120 | }
121 | return mainWindowMinWidth;
122 | }
123 |
124 | /**
125 | * 设置窗口最小宽度
126 | *
127 | * @param width 宽度
128 | */
129 | public void setMainWindowMinWidth(Integer width) {
130 | this.mainWindowMinWidth = width;
131 | this.setting.put(MAIN_WINDOW_MIN_WIDTH_KEY, String.valueOf(width));
132 | }
133 |
134 | /**
135 | * 获取主视图窗口最小高度
136 | *
137 | * @return 主视图窗口最小高度
138 | */
139 | public Integer getMainWindowMinHeight() {
140 | if (ObjectUtil.isEmpty(mainWindowMinHeight)) {
141 | mainWindowMinHeight = MAIN_WINDOWS_MIN_HEIGHT_DEF_VALUE;
142 | }
143 | return mainWindowMinHeight;
144 | }
145 |
146 | /**
147 | * 设置窗口最小高度值
148 | *
149 | * @param height 高度
150 | */
151 | public void setMainWindowMinHeight(Integer height) {
152 | this.mainWindowMinHeight = height;
153 | this.setting.put(MAIN_WINDOW_MIN_HEIGHT_KEY, String.valueOf(height));
154 | }
155 |
156 | /**
157 | * 获取窗口默认大小,已Dimension方式返回。
158 | *
159 | * @return Dimension方式大小
160 | */
161 | public Dimension getMainDefSizeDimension() {
162 | Integer width = getMainWindowDefWidth();
163 | Integer height = getMainWindowDefHeight();
164 | return new Dimension(width, height);
165 | }
166 |
167 | /**
168 | * 获取最小窗口大小,已Dimension方式返回。
169 | *
170 | * @return Dimension方式大小
171 | */
172 | public Dimension getMainMinSizeDimension() {
173 | return new Dimension(getMainWindowMinWidth(), getMainWindowMinHeight());
174 | }
175 |
176 | /**
177 | * 获取Java主程序路径配置
178 | *
179 | * @return Java主程序路径
180 | */
181 | public String getJavaBinPath() {
182 | if (StrUtil.isEmpty(javaBinPath)) {
183 | javaBinPath = R.JAVA;
184 | }
185 | return javaBinPath;
186 | }
187 |
188 | /**
189 | * 设置java主程序路径
190 | *
191 | * @param javaBinPath java主程序路径
192 | */
193 | public void setJavaBinPath(String javaBinPath) {
194 | this.javaBinPath = javaBinPath;
195 | this.setting.put(JAVA_BIN_PATH_KEY, javaBinPath);
196 | }
197 |
198 | public String getApplicationTheme() {
199 | if (StrUtil.isEmpty(applicationTheme)) {
200 | applicationTheme = FlatLightLaf.class.getName();
201 | }
202 | return applicationTheme;
203 | }
204 |
205 | public void setApplicationTheme(Class extends FlatLaf> applicationThemeClass) {
206 | this.applicationTheme = applicationThemeClass.getName();
207 | this.setting.put(APPLICATION_THEME_KEY, applicationTheme);
208 | }
209 |
210 | /**
211 | * 存储至配置文件
212 | */
213 | public void store() {
214 | this.setting.store();
215 | }
216 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/task/command/ApkInspectShellTask.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.task.command;
2 |
3 | import cn.hutool.log.StaticLog;
4 | import com.richardtang.androidkiller4j.constant.Suffix;
5 | import com.richardtang.androidkiller4j.setting.ApkShellSetting;
6 | import com.richardtang.androidkiller4j.util.ControlUtils;
7 | import com.richardtang.androidkiller4j.util.CompressUtils;
8 |
9 | import javax.swing.*;
10 | import java.io.File;
11 | import java.util.HashMap;
12 | import java.util.List;
13 | import java.util.Map;
14 |
15 | /**
16 | * APK查壳任务
17 | *
18 | * @author RichardTang
19 | */
20 | public class ApkInspectShellTask extends SwingWorker {
21 |
22 | // 需要进行查壳的Apk文件对象
23 | private File apkFile;
24 |
25 | public ApkInspectShellTask(File apkFile) {
26 | this.apkFile = apkFile;
27 | }
28 |
29 | @Override
30 | protected String doInBackground() {
31 | // 判断文件后缀
32 | if (apkFile == null || !apkFile.getName().endsWith(Suffix.POINT_APK)) {
33 | return "此apk未采用加固或为未知加固厂商!";
34 | }
35 | // 判断压缩包内是否有文件
36 | List filePath = CompressUtils.readZipFiles(apkFile);
37 | if (filePath.size() == 0 || filePath.get(0).startsWith("ERROR:")) {
38 | return "此apk未采用加固或为未知加固厂商!";
39 | }
40 | // 根据文件名在markMap中进行查找
41 | String key = filePath.stream().filter(s -> ApkShellSetting.getInstance().containsKey(s)).findFirst().orElse(null);
42 | return key != null ? ApkShellSetting.getInstance().getStr(key) : "此apk未采用加固或为未知加固厂商!";
43 | }
44 |
45 | @Override
46 | protected void done() {
47 | try {
48 | ControlUtils.showMsgDialog("查壳结果", get());
49 | } catch (Exception e) {
50 | StaticLog.error(e);
51 | ControlUtils.showMsgDialog("查壳结果", "此apk未采用加固或为未知加固厂商!");
52 | }
53 | super.done();
54 | }
55 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/task/command/ApkSignatureTask.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.task.command;
2 |
3 | import com.richardtang.androidkiller4j.constant.Cert;
4 | import com.richardtang.androidkiller4j.constant.R;
5 |
6 | /**
7 | * 调用jarsigner进行签名
8 | *
9 | * @author RichardTang
10 | */
11 | public class ApkSignatureTask extends TerminalCommandTask {
12 |
13 | // 需要签名的apk路径
14 | private String signApkPath;
15 |
16 | // 签名后的存放路径
17 | private String apkDstPath;
18 |
19 | // 签名时需要使用的keystore文件名称
20 | private String keystoreName;
21 |
22 | private final String COMMAND = "jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -storepass %s -keypass %s -keystore %s -signedjar %s %s %s";
23 |
24 | /**
25 | * 使用jarsigner进行签名
26 | *
27 | * @param signApkPath 需要签名的apk路径
28 | * @param apkDstPath 签名后的存放路径
29 | * @param keystoreName 签名时需要使用的keystore文件名称
30 | */
31 | public ApkSignatureTask(String signApkPath, String apkDstPath, String keystoreName) {
32 | this.signApkPath = signApkPath;
33 | this.apkDstPath = apkDstPath;
34 | this.keystoreName = keystoreName;
35 | }
36 |
37 | /**
38 | * 具体需要执行的命令
39 | *
40 | * @return 命令
41 | */
42 | @Override
43 | protected String getCommand() {
44 | return String.format(COMMAND, Cert.CERT_PASS_ALIAS, Cert.CERT_PASS_ALIAS, R.CONFIG_DIR + keystoreName, apkDstPath, signApkPath, Cert.CERT_PASS_ALIAS);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/task/command/ApkToolCompileTask.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.task.command;
2 |
3 | /**
4 | * 使用apktool进行回编译、打包。
5 | * 需要回编译的APK工程根路径需要是已apktool进行解包的文件夹才能进行回编译。
6 | *
7 | * @author RichardTang
8 | */
9 | public class ApkToolCompileTask extends TerminalCommandTask {
10 |
11 | // 需要回编译的APK工程根路径
12 | private String apkRootPath;
13 |
14 | private final String COMMAND = "java -jar bin/apktool.jar b %s/";
15 |
16 | /**
17 | * 使用apktool进行回编译任务
18 | *
19 | * @param apkRootPath 需要回编译的APK工程根路径
20 | */
21 | public ApkToolCompileTask(String apkRootPath) {
22 | this.apkRootPath = apkRootPath;
23 | }
24 |
25 | /**
26 | * 具体的命令
27 | *
28 | * @return 命令
29 | */
30 | @Override
31 | protected String getCommand() {
32 | return String.format(COMMAND, apkRootPath);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/task/command/ApkToolDecompileTask.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.task.command;
2 |
3 | /**
4 | * 调用apktool工具对apk进行解包操作
5 | * foo: java -jar bin/apktool.jar d xx.apk -f -o /tmp/xx/
6 | *
7 | * @author RichardTang
8 | */
9 | public class ApkToolDecompileTask extends TerminalCommandTask {
10 |
11 | // 需要解包的apk路径
12 | private String apkPath;
13 |
14 | // 解包后输出的位置
15 | private String outputPath;
16 |
17 | private final String COMMAND = "java -jar bin/apktool.jar d %s -f -o %s";
18 |
19 | /**
20 | * 使用apktool进行解包任务
21 | *
22 | * @param apkPath 需要解包的apk路径
23 | * @param outputPath 解包后输出的位置
24 | */
25 | public ApkToolDecompileTask(String apkPath, String outputPath) {
26 | this.apkPath = apkPath;
27 | this.outputPath = outputPath;
28 | }
29 |
30 | /**
31 | * 具体的命令
32 | *
33 | * @return 命令
34 | */
35 | @Override
36 | protected String getCommand() {
37 | return String.format(COMMAND, apkPath, outputPath);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/task/command/CertGenerateTask.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.task.command;
2 |
3 | import lombok.Builder;
4 |
5 | /**
6 | * 证书生成任务
7 | *
8 | * @author RichardTang
9 | */
10 | @Builder
11 | public class CertGenerateTask extends TerminalCommandTask {
12 |
13 | // 生成证书所需的参数
14 | private String alias;
15 | private String keyPass;
16 | private String storePass;
17 | private String keyStore;
18 | private String keyAlg;
19 | private String keySize;
20 | private String validity;
21 | private String commonName;
22 | private String organization;
23 | private String organizationUnit;
24 | private String city;
25 | private String province;
26 | private String country;
27 |
28 | // 生成证书命令
29 | private final String COMMAND = "keytool -genkey -v -alias %s -keyalg %s -keysize %s -keypass %s -storepass %s -validity %s -keystore %s -dname CN=%s,OU=%s,O=%s,L=%s,ST=%s,C=%s";
30 |
31 | /**
32 | * 具体的命令
33 | *
34 | * @return 命令
35 | */
36 | @Override
37 | protected String getCommand() {
38 | System.out.println(String.format(COMMAND, alias, keyAlg, keySize, keyPass, storePass, validity, keyStore, commonName, organizationUnit, organization, city, province, country));
39 | return String.format(COMMAND, alias, keyAlg, keySize, keyPass, storePass, validity, keyStore, commonName, organizationUnit, organization, city, province, country);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/task/command/Dex2JarTask.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.task.command;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import com.richardtang.androidkiller4j.constant.R;
5 | import com.richardtang.androidkiller4j.constant.Suffix;
6 |
7 | /**
8 | * 使用dex2jar组件,将apk中的dex文件转换成jar文件。
9 | *
10 | * @author RichardTang
11 | */
12 | public class Dex2JarTask extends TerminalCommandTask {
13 |
14 | private String apkPath;
15 |
16 | private String outputPath;
17 |
18 | public Dex2JarTask(String apkPath, String outputPath) {
19 | this.apkPath = apkPath;
20 | this.outputPath = outputPath;
21 | }
22 |
23 | @Override
24 | protected String getCommand() {
25 | String fileName = FileUtil.getName(apkPath).replace(Suffix.POINT_APK, "");
26 | return String.format("%s %s -o %s%s.jar", R.DEX2JAR, apkPath, outputPath, fileName);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/task/command/TerminalCommandCallback.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.task.command;
2 |
3 | /**
4 | * 用于在TerminalCommandTask类任务中进行回调函数的实现
5 | *
6 | * @author RichardTang
7 | */
8 | public interface TerminalCommandCallback {
9 |
10 | void apply();
11 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/task/command/TerminalCommandTask.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.task.command;
2 |
3 | import cn.hutool.core.util.RuntimeUtil;
4 | import cn.hutool.log.StaticLog;
5 | import lombok.SneakyThrows;
6 |
7 | import javax.swing.*;
8 | import java.io.BufferedReader;
9 | import java.io.InputStreamReader;
10 | import java.util.List;
11 |
12 | /**
13 | * 所有以命令行终端执行命令来处理的任务都继承该类
14 | * 并实现getCommand()函数,该函数中主要填写具体需要执行的命令。
15 | * 同时该父类中已经实现将命令执行的Process结果流导向到System.out中。
16 | *
17 | * @author RichardTang
18 | */
19 | public abstract class TerminalCommandTask extends SwingWorker {
20 |
21 | private TerminalCommandCallback callback;
22 |
23 | /**
24 | * 处理的任务具体执行的命令
25 | *
26 | * @return 命令字符串
27 | */
28 | protected abstract String getCommand();
29 |
30 | public void setCallback(TerminalCommandCallback callback) {
31 | this.callback = callback;
32 | }
33 |
34 | @SneakyThrows
35 | @Override
36 | protected void done() {
37 | if (callback != null) {
38 | callback.apply();
39 | }
40 | }
41 |
42 | /**
43 | * 后台执行任务
44 | *
45 | * @return Void无任务执行结束后需要返回的数据
46 | */
47 | @Override
48 | protected Void doInBackground() {
49 | Process process = RuntimeUtil.exec(getCommand());
50 | try {
51 | String line;
52 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
53 | while ((line = reader.readLine()) != null) {
54 | publish(line);
55 | }
56 | } catch (Exception e) {
57 | StaticLog.error(e);
58 | }
59 | return null;
60 | }
61 |
62 | /**
63 | * 将结果重定向到System.out中进行输出打印。
64 | *
65 | * @param chunks publish函数中接收到的数据
66 | */
67 | @Override
68 | protected void process(List chunks) {
69 | super.process(chunks);
70 | chunks.forEach(StaticLog::info);
71 | }
72 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/task/watch/BaseFileWatchTask.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.task.watch;
2 |
3 | import cn.hutool.core.io.watch.SimpleWatcher;
4 | import cn.hutool.core.io.watch.WatchMonitor;
5 |
6 | import java.io.File;
7 | import java.nio.file.Path;
8 | import java.nio.file.WatchEvent;
9 |
10 | /**
11 | * 文件监测任务基类
12 | *
13 | * @author RichardTang
14 | */
15 | public abstract class BaseFileWatchTask extends WatchMonitor {
16 |
17 | protected BaseFileWatchTask(File watchDirectory) {
18 | super(watchDirectory, WatchMonitor.EVENTS_ALL);
19 | setMaxDepth(getMaxDepth());
20 | setWatcher(getSimpleWatcher());
21 | }
22 |
23 | /**
24 | * 基于hutool的SimpleWatcher实现文件的监测
25 | *
26 | * @return hutool的SimpleWatcher
27 | */
28 | protected SimpleWatcher getSimpleWatcher() {
29 | return new SimpleWatcher() {
30 | @Override
31 | public void onCreate(WatchEvent> event, Path currentPath) {
32 | BaseFileWatchTask.this.onCreate(event, currentPath);
33 | }
34 |
35 | @Override
36 | public void onModify(WatchEvent> event, Path currentPath) {
37 | BaseFileWatchTask.this.onModify(event, currentPath);
38 | }
39 |
40 | @Override
41 | public void onDelete(WatchEvent> event, Path currentPath) {
42 | BaseFileWatchTask.this.onDelete(event, currentPath);
43 | }
44 | };
45 | }
46 |
47 | /**
48 | * 配置监测的目录深度,默认为1层,子类可通过覆盖函数进行修改。
49 | *
50 | * @return 目录深度
51 | */
52 | protected int getMaxDepth() {
53 | return 1;
54 | }
55 |
56 | /**
57 | * 开启文件监测
58 | */
59 | @Override
60 | public synchronized void start() {
61 | super.start();
62 | startAfter();
63 | }
64 |
65 | /**
66 | * 启用文件监测后的回调函数
67 | */
68 | protected void startAfter() {
69 |
70 | }
71 |
72 | /**
73 | * 有新文件创建时的回到函数
74 | *
75 | * @param watchEvent 监测事件
76 | * @param path 触发回调函数的路径
77 | */
78 | protected void onCreate(WatchEvent> watchEvent, Path path) {
79 |
80 | }
81 |
82 | /**
83 | * 有文件被修改时的回到函数
84 | *
85 | * @param watchEvent 监测事件
86 | * @param path 触发回调函数的路径
87 | */
88 | protected void onModify(WatchEvent> watchEvent, Path path) {
89 |
90 | }
91 |
92 | /**
93 | * 有文件被删除时的回调函数
94 | *
95 | * @param watchEvent 监测事件
96 | * @param path 触发回调函数的路径
97 | */
98 | protected void onDelete(WatchEvent> watchEvent, Path path) {
99 |
100 | }
101 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/task/watch/CertFileWatchTask.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.task.watch;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import cn.hutool.core.io.watch.SimpleWatcher;
5 | import cn.hutool.core.io.watch.WatchMonitor;
6 | import com.richardtang.androidkiller4j.constant.Cert;
7 | import com.richardtang.androidkiller4j.constant.R;
8 |
9 | import javax.swing.*;
10 | import java.io.File;
11 | import java.nio.file.Path;
12 | import java.nio.file.WatchEvent;
13 |
14 | /**
15 | * 证书文件创建-监控
16 | *
17 | * @author RichardTang
18 | */
19 | public final class CertFileWatchTask extends BaseFileWatchTask {
20 |
21 | private final JComboBox selectKeystoreComboBox;
22 |
23 | public CertFileWatchTask(final JComboBox selectKeystoreComboBox) {
24 | super(new File(R.CONFIG_DIR));
25 | this.selectKeystoreComboBox = selectKeystoreComboBox;
26 | }
27 |
28 | @Override
29 | protected void startAfter() {
30 | loadKeyStore();
31 | }
32 |
33 | @Override
34 | protected void onCreate(WatchEvent> watchEvent, Path path) {
35 | loadKeyStore();
36 | }
37 |
38 | @Override
39 | protected void onModify(WatchEvent> watchEvent, Path path) {
40 | loadKeyStore();
41 | }
42 |
43 | @Override
44 | protected void onDelete(WatchEvent> watchEvent, Path path) {
45 | loadKeyStore();
46 | }
47 |
48 | /**
49 | * 加载证书文件到签名视图
50 | */
51 | private void loadKeyStore() {
52 | selectKeystoreComboBox.removeAllItems();
53 | for (File f : FileUtil.ls(R.CONFIG_DIR)) {
54 | if (!f.getName().contains(Cert.CERT_FILE_SUFFIX)) continue;
55 | selectKeystoreComboBox.addItem(f.getName());
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/action/ClickAction.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.action;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Target(ElementType.METHOD)
6 | @Retention(RetentionPolicy.RUNTIME)
7 | public @interface ClickAction {
8 | // 成员属性名称
9 | String value();
10 |
11 | // 指定组件类型,默认为按钮类型。
12 | ClickActionType type() default ClickActionType.ButtonComponent;
13 |
14 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/action/ClickActionInstaller.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.action;
2 |
3 | import cn.hutool.log.StaticLog;
4 | import lombok.SneakyThrows;
5 |
6 | import javax.swing.*;
7 | import java.awt.event.*;
8 | import java.lang.reflect.*;
9 |
10 | /**
11 | * 通过反射实现注解绑定组件事件
12 | *
13 | * @author RichardTang
14 | */
15 | public class ClickActionInstaller {
16 |
17 | private static final String ADD_MOUSE_LISTENER = "addMouseListener";
18 | private static final String ADD_ACTION_LISTENER = "addActionListener";
19 |
20 | /**
21 | * 绑定事件
22 | *
23 | * @param obj 需要使用注解进行绑定事件的所在类实例
24 | */
25 | public static void bind(Object obj) {
26 | try {
27 | Class> cls = obj.getClass();
28 | for (Method m : cls.getDeclaredMethods()) {
29 | // 判断函数上是否有存在这个注解标记
30 | ClickAction clickAction = m.getAnnotation(ClickAction.class);
31 |
32 | // 函数上没有设置@ClickAction
33 | if (clickAction == null) {
34 | continue;
35 | }
36 |
37 | // 根据注解上的value找到成员属性
38 | Field field = cls.getDeclaredField(clickAction.value());
39 | boolean accessible = field.isAccessible();
40 |
41 | // 是否为私有属性
42 | if (!accessible) {
43 | field.setAccessible(true);
44 | }
45 |
46 | // 添加事件
47 | addListener(field.get(obj), clickAction.type(), obj, m);
48 |
49 | // 还原私有
50 | field.setAccessible(accessible);
51 | }
52 | } catch (Exception e) {
53 | StaticLog.error(e);
54 | }
55 | }
56 |
57 | /**
58 | * 代理形式注入方法
59 | *
60 | * @param source 标记了注解函数对应的源
61 | * @param componentType 需要绑定事件的类型
62 | * @param sourceMethodParam 标记了注解的函数所需的参数
63 | * @param sourceMethod 标记了注解的函数
64 | */
65 | private static void addListener(Object source, ClickActionType componentType, final Object sourceMethodParam, final Method sourceMethod) throws Exception {
66 | // 目标函数可能是Private
67 | sourceMethod.setAccessible(true);
68 | if (componentType == ClickActionType.ButtonComponent) {
69 | // 因为ClickActionType默认为ButtonComponent类型,如果成员属性不是一个JButton的话则抛出异常进行提示。
70 | if (source.getClass() != JButton.class) {
71 | throw new RuntimeException("@ClickAction指定的type类型和value的成员属性类型不一致!");
72 | } else {
73 | Method addActionListener = source.getClass().getMethod(ADD_ACTION_LISTENER, ActionListener.class);
74 | addActionListener.invoke(source, new ActionListener() {
75 | @SneakyThrows
76 | @Override
77 | public void actionPerformed(ActionEvent e) {
78 | sourceMethod.invoke(sourceMethodParam, e);
79 | }
80 | });
81 | }
82 | } else {
83 | Method addMouseListener = source.getClass().getMethod(ADD_MOUSE_LISTENER, MouseListener.class);
84 | addMouseListener.invoke(source, new MouseAdapter() {
85 | @SneakyThrows
86 | @Override
87 | public void mouseClicked(MouseEvent e) {
88 | sourceMethod.invoke(sourceMethodParam, e);
89 | }
90 | });
91 | }
92 | }
93 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/action/ClickActionType.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.action;
2 |
3 | /**
4 | * 指定要绑定事件的组件类型
5 | *
6 | * @author RichardTang
7 | */
8 | public enum ClickActionType {
9 | ButtonComponent, // 按钮组件
10 | LabelComponent // 标签组件
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/balloon/LeftAbovePositionerWrapper.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.balloon;
2 |
3 | import net.java.balloontip.positioners.BasicBalloonTipPositioner;
4 |
5 | import java.awt.*;
6 |
7 | /**
8 | * 对{@link net.java.balloontip.positioners.LeftAbovePositioner}增加左边距功能
9 | * 代码基本上是和LeftAbovePositioner是一样的
10 | *
11 | * @author RichardTang
12 | */
13 | public class LeftAbovePositionerWrapper extends BasicBalloonTipPositioner {
14 |
15 | private Integer leftMargin;
16 |
17 | public LeftAbovePositionerWrapper(Integer leftMargin) {
18 | super(10, 10);
19 | this.leftMargin = leftMargin;
20 | }
21 |
22 | public LeftAbovePositionerWrapper(int hO, int vO) {
23 | super(hO, vO);
24 | }
25 |
26 | @Override
27 | protected void determineLocation(Rectangle attached) {
28 | int balloonWidth = this.balloonTip.getPreferredSize().width;
29 | int balloonHeight = this.balloonTip.getPreferredSize().height;
30 | this.flipX = false;
31 | this.flipY = false;
32 | this.hOffset = this.preferredHorizontalOffset;
33 | if (this.fixedAttachLocation) {
34 | this.x = (new Float((float) attached.x + (float) attached.width * this.attachLocationX)).intValue() - this.hOffset;
35 | this.y = (new Float((float) attached.y + (float) attached.height * this.attachLocationY)).intValue() - balloonHeight;
36 | } else {
37 | this.x = attached.x;
38 | this.y = attached.y - balloonHeight;
39 | }
40 |
41 | if (this.orientationCorrection) {
42 | if (this.y < 0) {
43 | this.flipY = true;
44 | if (this.fixedAttachLocation) {
45 | this.y += balloonHeight;
46 | } else {
47 | this.y = attached.y + attached.height;
48 | }
49 | }
50 |
51 | if (this.x < 0) {
52 | this.flipX = true;
53 | if (this.fixedAttachLocation) {
54 | this.x -= balloonWidth - 2 * this.hOffset;
55 | } else {
56 | this.x = attached.x + attached.width - balloonWidth;
57 | }
58 |
59 | this.hOffset = balloonWidth - this.hOffset;
60 | }
61 | }
62 |
63 | this.x += leftMargin;
64 |
65 | if (this.offsetCorrection) {
66 | this.applyOffsetCorrection();
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/border/MLineBorder.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.border;
2 |
3 | import javax.swing.*;
4 | import javax.swing.border.LineBorder;
5 | import java.awt.*;
6 |
7 | /**
8 | * 组件边框的显示效果
9 | */
10 | public class MLineBorder extends LineBorder {
11 |
12 | // 想起项目UI风格中边框的默认颜色
13 | public static Color defaultBorderColor = UIManager.getColor("Component.borderColor");
14 |
15 | private boolean left = true, right = true, top = true, bottom = true;
16 |
17 | /**
18 | * 返回默认颜色的边框
19 | */
20 | public MLineBorder() {
21 | super(defaultBorderColor);
22 | }
23 |
24 | /**
25 | * 配置边框颜色
26 | *
27 | * @param color 颜色
28 | */
29 | public MLineBorder(Color color) {
30 | super(color);
31 | }
32 |
33 | /**
34 | * 配置边框颜色、厚度。
35 | *
36 | * @param color 颜色
37 | * @param thickness 厚度
38 | */
39 | public MLineBorder(Color color, int thickness) {
40 | super(color, thickness);
41 | }
42 |
43 | /**
44 | * 配置边框四条边、颜色、厚度。
45 | *
46 | * @param color 边框颜色
47 | * @param thickness 厚度
48 | * @param left 左边线
49 | * @param right 右边线
50 | * @param top 上边线
51 | * @param bottom 下边线
52 | */
53 | public MLineBorder(Color color, int thickness, boolean left, boolean right, boolean top, boolean bottom) {
54 | super(color, thickness);
55 | this.left = left;
56 | this.right = right;
57 | this.bottom = bottom;
58 | this.top = top;
59 | }
60 |
61 | /**
62 | * 配置边框四条边、厚度,使用项目默认颜色作为边框颜色。
63 | *
64 | * @param thickness 厚度
65 | * @param left 左边线
66 | * @param right 右边线
67 | * @param top 上边线
68 | * @param bottom 下边线
69 | */
70 | public MLineBorder(int thickness, boolean left, boolean right, boolean top, boolean bottom) {
71 | super(defaultBorderColor, thickness);
72 | this.left = left;
73 | this.right = right;
74 | this.bottom = bottom;
75 | this.top = top;
76 | }
77 |
78 | public void setInsets(boolean left, boolean right, boolean top, boolean bottom) {
79 | this.left = left;
80 | this.right = right;
81 | this.bottom = bottom;
82 | this.top = top;
83 | }
84 |
85 | @Override
86 | public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
87 | if ((this.thickness > 0) && (g instanceof Graphics2D)) {
88 | Graphics2D g2d = (Graphics2D) g;
89 | Color oldColor = g2d.getColor();
90 | g2d.setColor(this.lineColor);
91 | if (left) {
92 | g2d.drawLine(x, y, x, y + height);
93 | }
94 | if (top) {
95 | g2d.drawLine(x, y, x + width, y);
96 | }
97 | if (right) {
98 | g2d.drawLine(x + width, y, x + width, y + height);
99 | }
100 | if (bottom) {
101 | g2d.drawLine(x, y + height, x + width, y + height);
102 | }
103 | g2d.setColor(oldColor);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/control/CustomTextComboBox.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.control;
2 |
3 | import org.jetbrains.annotations.Nullable;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 |
8 | /**
9 | * 可自定义ComboBox选项显示的内容的组件
10 | *
11 | * @param ComboBox组件的选项中的类型
12 | * @author RichardTang
13 | */
14 | public abstract class CustomTextComboBox extends JComboBox {
15 |
16 | {
17 | // 创建时就启用自定义的渲染
18 | setRenderer(new CustomTextComboBoxCellRender());
19 | }
20 |
21 | /**
22 | * 用户自定义实现的内容获取逻辑
23 | *
24 | * @param e ComboBox组件的选项中的类型
25 | * @return 显示在ComboBox组件的选项文字
26 | */
27 | protected abstract String getText(E e);
28 |
29 | @Nullable
30 | @Override
31 | public E getSelectedItem() {
32 | return (E) super.getSelectedItem();
33 | }
34 |
35 | /**
36 | * 自定义选项的渲染
37 | */
38 | public class CustomTextComboBoxCellRender extends JLabel implements ListCellRenderer {
39 |
40 | public CustomTextComboBoxCellRender() {
41 | setOpaque(true);
42 | }
43 |
44 | @Override
45 | public Component getListCellRendererComponent(JList extends E> list, E value, int index, boolean isSelected, boolean cellHasFocus) {
46 | try {
47 | setText(CustomTextComboBox.this.getText(value));
48 | } catch (Exception ignored) {
49 | // 抛异常情况下不进行处理 直接跳过
50 | }
51 | return this;
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/control/IconComboBox.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.control;
2 |
3 | import lombok.Data;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 | import java.util.Vector;
8 |
9 | /**
10 | * 在ComboBox基础上增加了图标,实现了ComboBox上的Item项中可以显示图标的功能。
11 | *
12 | * @author RichardTang
13 | */
14 | public class IconComboBox extends JComboBox {
15 |
16 | // ComboBox中的选项
17 | private Vector vector = new Vector<>();
18 |
19 | // 默认选项
20 | private IconComboBoxData defaultOption;
21 |
22 | // 默认选项id
23 | public static final String DEFAULT_OPTION_ID = "DEFAULT";
24 |
25 | public IconComboBox() {
26 | setModel(new DefaultComboBoxModel<>(vector));
27 | setRenderer(new IconComboBoxCellRender());
28 | }
29 |
30 | /**
31 | * 带有默认选项的ComboBox,并默认选中。
32 | *
33 | * @param defaultOption 默认选项
34 | */
35 | public IconComboBox(IconComboBoxData defaultOption) {
36 | this();
37 | this.defaultOption = defaultOption;
38 | addItem(defaultOption);
39 | setSelectedIndex(0);
40 | }
41 |
42 | /**
43 | * 带有默认选项的ComboBox,并默认选中。
44 | *
45 | * @param defaultOptionText 默认选项的文本
46 | */
47 | public IconComboBox(String defaultOptionText) {
48 | this(new IconComboBox.IconComboBoxData(DEFAULT_OPTION_ID, defaultOptionText));
49 | }
50 |
51 | /**
52 | * 根据id删除选项
53 | *
54 | * @param id 选项的id
55 | */
56 | public void removeItemById(String id) {
57 | int index = -1;
58 | for (int i = 0; i < vector.size(); i++) {
59 | if (id.equals(vector.get(i).getId())) {
60 | index = i;
61 | break;
62 | }
63 | }
64 | if (index != -1) {
65 | removeItemAt(index);
66 | }
67 | }
68 |
69 | /**
70 | * 删除全部,当存在默认选项时保留默认选项。
71 | */
72 | @Override
73 | public void removeAllItems() {
74 | super.removeAllItems();
75 | if (defaultOption != null) {
76 | addItem(defaultOption);
77 | }
78 | }
79 |
80 | /**
81 | * 单元格渲染
82 | */
83 | public static class IconComboBoxCellRender extends JLabel implements ListCellRenderer {
84 |
85 | public IconComboBoxCellRender() {
86 | setOpaque(true);
87 | }
88 |
89 | @Override
90 | public Component getListCellRendererComponent(
91 | JList extends IconComboBoxData> list,
92 | IconComboBoxData value,
93 | int index,
94 | boolean isSelected,
95 | boolean cellHasFocus) {
96 |
97 | if (value != null) {
98 | setText(value.getText());
99 | setIcon(value.getIcon());
100 | setFont(list.getFont());
101 | setBackground(isSelected ? list.getSelectionBackground() : list.getBackground());
102 | setForeground(isSelected ? list.getSelectionForeground() : list.getForeground());
103 | }
104 | return this;
105 | }
106 | }
107 |
108 | /**
109 | * 选项实体
110 | */
111 | @Data
112 | public static class IconComboBoxData {
113 |
114 | private String id;
115 | private Icon icon;
116 | private String text;
117 | private Object data;
118 |
119 | public IconComboBoxData(String id, String text) {
120 | this(id, text, null, null);
121 | }
122 |
123 | public IconComboBoxData(String id, String text, Icon icon) {
124 | this(id, text, icon, null);
125 | }
126 |
127 | public IconComboBoxData(String id, String text, Icon icon, Object data) {
128 | this.id = id;
129 | this.text = text;
130 | this.icon = icon;
131 | this.data = data;
132 | }
133 | }
134 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/control/NumberTextField.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.control;
2 |
3 | import com.richardtang.androidkiller4j.ui.document.NumberDocument;
4 |
5 | import javax.swing.*;
6 |
7 | /**
8 | * 只能输入数字的TextField
9 | *
10 | * @author RichardTang
11 | */
12 | public class NumberTextField extends JTextField {
13 |
14 | {
15 | setDocument(new NumberDocument());
16 | }
17 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/control/PopupMenuButton.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.control;
2 |
3 | import javax.swing.*;
4 |
5 | /**
6 | * 带下拉层的按钮
7 | *
8 | * @author RichardTang
9 | */
10 | public class PopupMenuButton extends JButton {
11 |
12 | // 按钮对应的菜单组件
13 | private JPopupMenu popupMenu;
14 |
15 | /**
16 | * 创建一个菜单按钮
17 | *
18 | * @param icon 按钮图标
19 | * @param popupMenu 按钮点击后显示的菜单
20 | */
21 | public PopupMenuButton(Icon icon, JPopupMenu popupMenu) {
22 | this(null, icon, popupMenu);
23 | }
24 |
25 | /**
26 | * 创建一个菜单按钮
27 | *
28 | * @param text 按钮显示的文本
29 | * @param popupMenu 按钮点击后显示的菜单
30 | */
31 | public PopupMenuButton(String text, JPopupMenu popupMenu) {
32 | this(text, null, popupMenu);
33 | }
34 |
35 | /**
36 | * 创建一个菜单按钮
37 | *
38 | * @param text 按钮显示的文本
39 | * @param icon 按钮图标
40 | * @param popupMenu 按钮点击后显示的菜单
41 | */
42 | public PopupMenuButton(String text, Icon icon, JPopupMenu popupMenu) {
43 | super(text, icon);
44 | this.popupMenu = popupMenu;
45 | initListener();
46 | }
47 |
48 | /**
49 | * 初始化事件
50 | */
51 | private void initListener() {
52 | addActionListener(e -> {
53 | JButton button = ((JButton) e.getSource());
54 | if (!popupMenu.isShowing()) {
55 | popupMenu.show(button, 0, button.getBounds().height);
56 | } else {
57 | popupMenu.setVisible(false);
58 | }
59 | });
60 | }
61 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/control/PrintStreamTextArea.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.control;
2 |
3 | import lombok.Data;
4 |
5 | import javax.swing.*;
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.PrintStream;
8 |
9 | /**
10 | * 控制台文本域,可将指定的流数据输出到该JTextArea中进行显示
11 | *
12 | * @author RichardTang
13 | */
14 | @Data
15 | public class PrintStreamTextArea extends JTextArea {
16 |
17 | private PrintStream printStream = new PrintStream(new ByteArrayOutputStream()) {
18 |
19 | @Override
20 | public void write(byte[] buf, int off, int len) {
21 | print(new String(buf, off, len));
22 | }
23 |
24 | /**
25 | * 将数据追到到JTextArea的文本域中
26 | *
27 | * @param s 写入的字符
28 | */
29 | @Override
30 | public void print(String s) {
31 | SwingUtilities.invokeLater(() -> PrintStreamTextArea.this.append(s));
32 | }
33 | };
34 |
35 | public PrintStreamTextArea() {
36 | // 定义文本域不可编辑
37 | setEditable(false);
38 | }
39 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/document/NumberDocument.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.document;
2 |
3 | import javax.swing.text.AttributeSet;
4 | import javax.swing.text.BadLocationException;
5 | import javax.swing.text.PlainDocument;
6 |
7 | /**
8 | * Java11之后该类被设置为了private类,这里单独提取出来使用。
9 | *
10 | * @author RichardTang
11 | */
12 | public class NumberDocument extends PlainDocument {
13 |
14 | public void insertString(int offs, String str, AttributeSet atts) throws BadLocationException {
15 | if (Character.isDigit(str.charAt(0))) {
16 | super.insertString(offs, str, atts);
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/panel/CommonTablePanel.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.panel;
2 |
3 | import cn.hutool.core.util.StrUtil;
4 | import cn.hutool.log.StaticLog;
5 | import com.formdev.flatlaf.FlatClientProperties;
6 | import com.formdev.flatlaf.icons.FlatSearchIcon;
7 | import com.richardtang.androidkiller4j.ui.control.PopupMenuButton;
8 | import com.richardtang.androidkiller4j.ui.table.CommonTable;
9 | import com.richardtang.androidkiller4j.util.ControlUtils;
10 | import net.miginfocom.swing.MigLayout;
11 |
12 | import javax.swing.*;
13 | import javax.swing.border.EmptyBorder;
14 | import javax.swing.event.DocumentEvent;
15 | import javax.swing.event.DocumentListener;
16 | import javax.swing.table.TableColumn;
17 | import javax.swing.table.TableModel;
18 | import javax.swing.table.TableRowSorter;
19 | import java.awt.*;
20 | import java.awt.event.ItemEvent;
21 | import java.awt.event.ItemListener;
22 | import java.util.ArrayList;
23 | import java.util.Enumeration;
24 | import java.util.HashMap;
25 |
26 | /**
27 | * 带有表格、搜索框、显示/隐藏表格标题功能的通用页面。
28 | *
29 | * @author RichardTang
30 | */
31 | public abstract class CommonTablePanel extends JPanel {
32 |
33 | // 工具栏面板
34 | protected JPanel toolBarPanel = new JPanel(new MigLayout("insets 2,gapx 1", "[grow][]", ""));
35 |
36 | // 显示和隐藏标题
37 | private final Icon checkboxSwitchIcon = ControlUtils.getSVGIcon("checkbox-switch.svg");
38 |
39 | // 设置隐藏/显示标题的菜单
40 | private JPopupMenu popupMenu = new JPopupMenu();
41 |
42 | // 被点击进行隐藏/显示的按钮
43 | private PopupMenuButton popupMenuButton = new PopupMenuButton(checkboxSwitchIcon, popupMenu);
44 |
45 | // TableColumn
46 | private ArrayList indexOfColumns = new ArrayList<>();
47 | private HashMap keyOfColumnIndex = new HashMap<>();
48 |
49 | // 内容搜索组件
50 | protected JTextField filterTextField = new JTextField();
51 |
52 | protected CommonTable table = new CommonTable<>() {
53 | @Override
54 | public Object bindColumnValue(T obj, int columnIndex) {
55 | return bindTableColumnValue(obj, columnIndex);
56 | }
57 |
58 | @Override
59 | public String[] bindColumns() {
60 | return setTableColumns();
61 | }
62 | };
63 |
64 | // 用于实现条件过滤
65 | protected TableRowSorter tableRowSorter = new TableRowSorter<>(table.getModel());
66 |
67 | public CommonTablePanel() {
68 | int index = 0;
69 | Enumeration columns = table.getColumnModel().getColumns();
70 | while (columns.hasMoreElements()) {
71 | TableColumn column = columns.nextElement();
72 | String text = column.getHeaderValue().toString();
73 | JCheckBoxMenuItem checkBoxMenuItem = new JCheckBoxMenuItem(text);
74 | indexOfColumns.add(column);
75 | popupMenu.add(checkBoxMenuItem);
76 | keyOfColumnIndex.put(text, index++);
77 | checkBoxMenuItem.addItemListener(new PopupMenuItemListenerImpl());
78 | }
79 |
80 | table.setRowSorter(tableRowSorter);
81 | popupMenuButton.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
82 |
83 | initConditionSearchLayout();
84 |
85 | // 绑定事件搜索框内容发生改变时触发。
86 | filterTextField.getDocument().addDocumentListener(new FilterTextFieldDocumentListener());
87 |
88 | setLayout(new BorderLayout());
89 | add(new JScrollPane(table), BorderLayout.CENTER);
90 | add(toolBarPanel, BorderLayout.NORTH);
91 | }
92 |
93 | /**
94 | * 条件搜索组件布局初始化
95 | */
96 | private void initConditionSearchLayout() {
97 | toolBarPanel.add(filterTextField, "growx");
98 | toolBarPanel.add(popupMenuButton);
99 |
100 | // 搜索框前加上搜索图标
101 | filterTextField.putClientProperty(FlatClientProperties.TEXT_FIELD_LEADING_ICON, new FlatSearchIcon());
102 | }
103 |
104 | /**
105 | * 创建过滤器,对表格进行过滤。
106 | */
107 | private void newFilter() {
108 | try {
109 | String text = filterTextField.getText();
110 | if (StrUtil.isNotEmpty(text)) {
111 | tableRowSorter.setRowFilter(RowFilter.regexFilter(text));
112 | } else {
113 | tableRowSorter.setRowFilter(null);
114 | }
115 | } catch (Exception e) {
116 | StaticLog.error(e);
117 | }
118 | }
119 |
120 | /**
121 | * 获取Panel中对应的表格
122 | *
123 | * @return 表格对象
124 | */
125 | public CommonTable getTable() {
126 | return table;
127 | }
128 |
129 | /**
130 | * 获取工具栏(ToolBarPanel)
131 | *
132 | * @return 工具类面板
133 | */
134 | public JPanel getToolBarPanel() {
135 | return toolBarPanel;
136 | }
137 |
138 | /**
139 | * 使用者需要自定义T类型中哪些数据对应columnIndex的值
140 | *
141 | * @param obj JavaBean的具体类型数据对象
142 | * @param columnIndex 当前表格中第columnIndex个位置应该对应JavaBean中的哪个属性
143 | * @return 当前columnIndex列对应的属性值
144 | */
145 | public abstract Object bindTableColumnValue(T obj, int columnIndex);
146 |
147 | /**
148 | * 设置表格标题
149 | *
150 | * @return 表格标题
151 | */
152 | public abstract String[] setTableColumns();
153 |
154 | /**
155 | * 内容搜索过滤
156 | */
157 | private class FilterTextFieldDocumentListener implements DocumentListener {
158 | @Override
159 | public void insertUpdate(DocumentEvent e) {
160 | newFilter();
161 | }
162 |
163 | @Override
164 | public void removeUpdate(DocumentEvent e) {
165 | newFilter();
166 | }
167 |
168 | @Override
169 | public void changedUpdate(DocumentEvent e) {
170 | newFilter();
171 | }
172 | }
173 |
174 | /**
175 | * 隐藏/显示按钮的菜单选项事件
176 | */
177 | private class PopupMenuItemListenerImpl implements ItemListener {
178 |
179 | @Override
180 | public void itemStateChanged(ItemEvent e) {
181 | JCheckBoxMenuItem selectedItem = (JCheckBoxMenuItem) e.getItem();
182 | if (selectedItem.getState()) {
183 | // 隐藏标题
184 | Integer columnIndex = keyOfColumnIndex.get(selectedItem.getText());
185 | table.removeColumn(table.getColumnModel().getColumn(columnIndex));
186 | } else {
187 | // 显示标题
188 | Integer columnIndex = keyOfColumnIndex.get(selectedItem.getText());
189 | table.addColumn(indexOfColumns.get(columnIndex));
190 | table.moveColumn(table.getColumnCount() - 1, columnIndex);
191 | }
192 | }
193 | }
194 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/rsyntaxtextarea/SyntaxConstantsWrapper.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.rsyntaxtextarea;
2 |
3 | import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
4 |
5 | public interface SyntaxConstantsWrapper extends SyntaxConstants {
6 |
7 | String SYNTAX_STYLE_SMALI = "text/smali";
8 |
9 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/tabframe/TabFrameBar.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.tabframe;
2 |
3 | import com.formdev.flatlaf.util.UIScale;
4 | import com.richardtang.androidkiller4j.ui.border.MLineBorder;
5 |
6 | import javax.swing.*;
7 | import javax.swing.border.EmptyBorder;
8 | import java.awt.*;
9 | import java.util.ArrayList;
10 |
11 | /**
12 | * 工具条,TabFrame最底下的功能条目。
13 | *
14 | * @author RichardTang
15 | */
16 | public class TabFrameBar extends JPanel {
17 |
18 | private JToolBar toolBar = new JToolBar();
19 |
20 | // 存储ToolBar中的按钮
21 | private ArrayList toolBarItems = new ArrayList<>();
22 |
23 | /**
24 | * 创建一个TabFramePanel中的ToolBar
25 | *
26 | * @param barItems 需要添加到ToolBar中的按钮
27 | */
28 | public TabFrameBar(JToggleButton... barItems) {
29 | toolBar.setRollover(true);
30 | toolBar.setFloatable(false);
31 | toolBar.setBorder(new MLineBorder(-1, false, false, false, false));
32 |
33 | for (JToggleButton barItem : barItems) {
34 | addBarItem(barItem);
35 | }
36 |
37 | setLayout(new BorderLayout());
38 | add(toolBar, BorderLayout.WEST);
39 | setBorder(new MLineBorder(1, false, false, true, true));
40 | }
41 |
42 | /**
43 | * 向TabFrameBar中的toolBar添加按钮
44 | *
45 | * @param barItem 需要添加的item按钮
46 | */
47 | public void addBarItem(JToggleButton barItem) {
48 | barItem.setFont(new Font(Font.DIALOG, Font.PLAIN, 12));
49 | barItem.setBorder(new EmptyBorder(UIScale.scale(4), UIScale.scale(10), UIScale.scale(4), UIScale.scale(10)));
50 | toolBar.add(barItem);
51 | this.toolBarItems.add(barItem);
52 | }
53 |
54 | /**
55 | * 获取ToolBar中存储的所有JToggleButton按钮
56 | *
57 | * @return 存储toolBar中按钮的集合
58 | */
59 | public ArrayList getToolBarItems() {
60 | return toolBarItems;
61 | }
62 |
63 | /**
64 | * 添加组件之Bar的右下角
65 | *
66 | * @param component 需要添加的组件
67 | */
68 | public void addBarRightItem(JComponent component) {
69 | add(component, BorderLayout.EAST);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/tabframe/TabFrameItem.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.tabframe;
2 |
3 | import javax.swing.*;
4 |
5 | /**
6 | * TabFrameItem代表TabFrameBar中的每个选项
7 | *
8 | * @author RichardTang
9 | */
10 | public class TabFrameItem {
11 |
12 | // 显示在ToolBar上的具体按钮
13 | private JToggleButton toolBarItemBtn;
14 |
15 | // 每个toolBarItemBtn对应一个在内容面板中具体显示的内容组件
16 | private JComponent contentComponent;
17 |
18 | // 每个toolBarItemBtn对应一个内容面板
19 | private TabFrameItemPanel tabFrameItemPanel;
20 |
21 | // 小组件按钮,显示在内容面板处标题右边的按钮。
22 | private JButton[] gadgetButtons;
23 |
24 | /**
25 | * 创建一个不带小组件按钮的TabFrameItem
26 | *
27 | * @param toolBarItemBtn 显示在ToolBar上的按钮
28 | * @param contentComponent 显示在内容面板中的具体组件
29 | */
30 | public TabFrameItem(JToggleButton toolBarItemBtn, JComponent contentComponent) {
31 | this(toolBarItemBtn, contentComponent, null);
32 | }
33 |
34 | /**
35 | * 创建带小组件按钮的TabFrameItem,小组件面板中默认会带一个缩小按钮。
36 | *
37 | * @param toolBarItemBtn 显示在ToolBar上的按钮
38 | * @param contentComponent 显示在内容面板中的具体组件
39 | * @param gadgetButtons 小组件按钮组
40 | */
41 | public TabFrameItem(JToggleButton toolBarItemBtn, JComponent contentComponent, JButton... gadgetButtons) {
42 | this.toolBarItemBtn = toolBarItemBtn;
43 | this.contentComponent = contentComponent;
44 | this.gadgetButtons = gadgetButtons;
45 |
46 | createTabFrameItemPanel();
47 | }
48 |
49 | /**
50 | * TabFrameItem中需要依赖TabFrameItemPanel,这里需要进行创建。
51 | */
52 | private void createTabFrameItemPanel() {
53 | JLabel titleLabel = new JLabel(toolBarItemBtn.getText());
54 | tabFrameItemPanel = new TabFrameItemPanel(titleLabel, contentComponent);
55 | // 判断是否有小组件需要创建
56 | if (gadgetButtons != null) {
57 | for (JButton gadgetButton : gadgetButtons) {
58 | tabFrameItemPanel.getHeaderToolBar().add(gadgetButton);
59 | }
60 | }
61 | }
62 |
63 | /**
64 | * 获取当前TabFrameItem在TabFrameBar中显示的按钮
65 | *
66 | * @return 返回用于在TabFrameBar中显示的按钮
67 | */
68 | public JToggleButton getToolBarItemBtn() {
69 | return toolBarItemBtn;
70 | }
71 |
72 | /**
73 | * 获取当前TabFrameItem对应的内容面板组件
74 | *
75 | * @return 返回当前TabFrameItem对应的内容面板组件
76 | */
77 | public TabFrameItemPanel getTabFrameItemPanel() {
78 | return tabFrameItemPanel;
79 | }
80 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/tabframe/TabFrameItemPanel.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.tabframe;
2 |
3 | import com.formdev.flatlaf.icons.FlatTreeExpandedIcon;
4 | import com.formdev.flatlaf.ui.FlatEmptyBorder;
5 | import com.richardtang.androidkiller4j.ui.border.MLineBorder;
6 |
7 | import javax.swing.*;
8 | import java.awt.*;
9 |
10 | /**
11 | * 用于显示标题和用户自定义作为内容的组件,对应着Bar中每个Item选项点击后展开显示出来的面板。
12 | *
13 | * @author RichardTang
14 | */
15 | public class TabFrameItemPanel extends JPanel {
16 |
17 | // 顶部标题
18 | private JLabel titleLabel;
19 | private JPanel headerPanel;
20 |
21 | // 标题栏处带的小按钮组
22 | private JToolBar headerToolBar;
23 | // 默认带的缩小按钮
24 | private JButton minimizeButton;
25 |
26 | /**
27 | * 创建一个用于TabFrameItem中的内容面板
28 | *
29 | * @param titleLabel 显示的标题Label
30 | * @param contentComponent 显示的组件
31 | */
32 | public TabFrameItemPanel(JLabel titleLabel, JComponent contentComponent) {
33 | this.titleLabel = titleLabel;
34 | initializeHeader();
35 |
36 | setLayout(new BorderLayout());
37 | add(headerPanel, BorderLayout.NORTH);
38 | add(contentComponent, BorderLayout.CENTER);
39 | }
40 |
41 | /**
42 | * 初始化标题、小组件
43 | */
44 | private void initializeHeader() {
45 | // 标题label
46 | titleLabel.setFont(new Font(Font.DIALOG, Font.PLAIN, 12));
47 | titleLabel.setBorder(new FlatEmptyBorder(4, 4, 4, 4));
48 |
49 | // 缩小按钮初始化
50 | minimizeButton = new JButton(new FlatTreeExpandedIcon());
51 |
52 | // 初始化小工具栏
53 | headerToolBar = new JToolBar();
54 | headerToolBar.setFloatable(false);
55 | headerToolBar.add(minimizeButton);
56 |
57 | // 组合标题处的组件
58 | headerPanel = new JPanel(new BorderLayout());
59 | headerPanel.add(titleLabel, BorderLayout.WEST);
60 | headerPanel.add(headerToolBar, BorderLayout.EAST);
61 | headerPanel.setBorder(new MLineBorder(1, false, false, true, true));
62 | }
63 |
64 | public JLabel getTitleLabel() {
65 | return titleLabel;
66 | }
67 |
68 | public JPanel getHeaderPanel() {
69 | return headerPanel;
70 | }
71 |
72 | public JToolBar getHeaderToolBar() {
73 | return headerToolBar;
74 | }
75 |
76 | public JButton getMinimizeButton() {
77 | return minimizeButton;
78 | }
79 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/tabframe/TabFramePanel.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.tabframe;
2 |
3 | import javax.swing.*;
4 | import java.awt.*;
5 | import java.awt.event.MouseEvent;
6 | import java.awt.event.MouseListener;
7 | import java.awt.event.MouseMotionListener;
8 |
9 | /**
10 | * TabFrame功能的主面板,组合Content和Bar两块的内容。
11 | * Content中拖拽的实现主要思路是,通过鼠标事件去动态修改TabFramePanel中的Height。
12 | *
13 | * @author RichardTang
14 | */
15 | public class TabFramePanel extends JPanel implements MouseListener, MouseMotionListener {
16 |
17 | // 每一个TabFramePanel只有一个tabFrameBar其实就是toolBar
18 | private TabFrameBar tabFrameBar;
19 |
20 | // 存储用于定义的TabFrameItem
21 | private TabFrameItem[] tabFrameItems;
22 |
23 | // 记录面板处于显示状态时的高宽值,当在进行隐藏显示操作时充当第三方临时变量。
24 | private Dimension tempDimension;
25 |
26 | // 用于标记鼠标是否开始进行拖拽改变面板大小
27 | private boolean isDragged = false;
28 |
29 | // 即将进行拖拽操作时,记录一下鼠标点击在Panel中的位置。
30 | private double clickDraggedPanelPosition;
31 |
32 | /**
33 | * 因为存在多个TabFrameItem的情况,每次只能显示其中一个的内容组件。
34 | * 需要通过一个中间的JPanel来承载TabFrameItem中的内容组件
35 | */
36 | private JPanel contentPanel = new JPanel(new BorderLayout());
37 |
38 | /**
39 | * 根据TabFrameItem来创建TabFrame,每一个Item代表一个功能选项。
40 | *
41 | * @param tabFrameItems 需要添加到ToolBar的多个TabFrameItem
42 | */
43 | public TabFramePanel(TabFrameItem... tabFrameItems) {
44 | this.tabFrameItems = tabFrameItems;
45 | this.tabFrameBar = new TabFrameBar();
46 | for (TabFrameItem item : tabFrameItems) {
47 | addTabFrameItem(item);
48 | }
49 | setLayout(new BorderLayout());
50 | add(contentPanel, BorderLayout.CENTER);
51 | add(tabFrameBar, BorderLayout.SOUTH);
52 | }
53 |
54 | /**
55 | * 添加TabFrameItem
56 | *
57 | * @param tabFrameItem 用于显示具体内容的Item实体
58 | */
59 | public void addTabFrameItem(TabFrameItem tabFrameItem) {
60 | // 取到ToolBar上的按钮和按钮对应的Content
61 | JToggleButton itemButton = tabFrameItem.getToolBarItemBtn();
62 | TabFrameItemPanel itemContent = tabFrameItem.getTabFrameItemPanel();
63 |
64 | // 给内容组件添加鼠标拖拽改变大小事件
65 | itemContent.getHeaderPanel().addMouseListener(this);
66 | itemContent.getHeaderPanel().addMouseMotionListener(this);
67 |
68 | // 添加缩小按钮点击事件,上班使用addActionListener这里才可以使用doClick()。
69 | itemContent.getMinimizeButton().addActionListener(e -> itemButton.doClick());
70 |
71 | // 点击TabFrameItem面板时的隐藏和显示事件
72 | itemButton.addActionListener(e -> {
73 | // 清空ToolBar中的按钮状态,不清空当前触发事件的按钮。
74 | for (JToggleButton btn : tabFrameBar.getToolBarItems()) {
75 | if (btn != itemButton) {
76 | btn.setSelected(false);
77 | }
78 | }
79 |
80 | // 因为多个TabFrameItem的原因,需要清空之前显示的内容。
81 | contentPanel.removeAll();
82 | contentPanel.add(itemContent);
83 |
84 | // 内容面板显示和隐藏实现
85 | if (itemButton.isSelected()) {
86 | contentPanel.setVisible(true);
87 | setPreferredSize(tempDimension);
88 | } else {
89 | tempDimension = getSize();
90 | contentPanel.setVisible(false);
91 | setPreferredSize(new Dimension(tempDimension.width, itemButton.getHeight()));
92 | }
93 | updateUI();
94 | });
95 | tabFrameBar.addBarItem(itemButton);
96 | }
97 |
98 | /**
99 | * 获取TabFrameBar
100 | *
101 | * @return {@link TabFrameBar}
102 | */
103 | public TabFrameBar getTabFrameBar() {
104 | return this.tabFrameBar;
105 | }
106 |
107 | @Override
108 | public void mousePressed(MouseEvent e) {
109 | clickDraggedPanelPosition = e.getPoint().getY();
110 |
111 | isDragged = true;
112 | }
113 |
114 | @Override
115 | public void mouseReleased(MouseEvent e) {
116 | isDragged = false;
117 | }
118 |
119 | @Override
120 | public void mouseEntered(MouseEvent e) {
121 | ((JPanel) e.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
122 | }
123 |
124 | @Override
125 | public void mouseDragged(MouseEvent e) {
126 | if (isDragged) {
127 | Dimension size = getSize();
128 | size.height += clickDraggedPanelPosition;
129 | size.height += Math.negateExact(e.getY());
130 | setPreferredSize(size);
131 | updateUI();
132 | }
133 | }
134 |
135 | @Override
136 | public void mouseClicked(MouseEvent e) {
137 | }
138 |
139 | @Override
140 | public void mouseExited(MouseEvent e) {
141 |
142 | }
143 |
144 | @Override
145 | public void mouseMoved(MouseEvent e) {
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/table/CommonTable.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.table;
2 |
3 | import javax.swing.*;
4 | import javax.swing.table.AbstractTableModel;
5 | import javax.swing.table.DefaultTableCellRenderer;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | /**
10 | * 通用表格,默认带表格标题、表格内容居中、显示序号列。
11 | *
12 | * @param 表格显示的数据类型
13 | * @author RichardTang
14 | */
15 | public abstract class CommonTable extends JTable {
16 |
17 | // 存储表格数据的集合
18 | protected List data = new ArrayList<>();
19 |
20 | // 表格标题
21 | protected List columns = new ArrayList<>();
22 |
23 | // 表格模型
24 | protected AbstractTableModel model = new AbstractTableModel() {
25 |
26 | /**
27 | * 获取每一列的名称
28 | *
29 | * @param column 第几列
30 | * @return 对应的列名
31 | */
32 | @Override
33 | public String getColumnName(int column) {
34 | return columns.get(column);
35 | }
36 |
37 | /**
38 | * 得到表头列数,需要显示在表格上的列数量。
39 | *
40 | * @return 需要展示的列数量
41 | */
42 | @Override
43 | public int getColumnCount() {
44 | return columns.size();
45 | }
46 |
47 | /**
48 | * 数据行数
49 | *
50 | * @return 数据展示的行数
51 | */
52 | @Override
53 | public int getRowCount() {
54 | return data.size();
55 | }
56 |
57 | /**
58 | * 根据行和列的值获取指定的数据
59 | *
60 | * @param rowIndex 需要获取的数据所在行
61 | * @param columnIndex 需要获取的数据所在列
62 | * @return 对应的数据
63 | */
64 | @Override
65 | public Object getValueAt(int rowIndex, int columnIndex) {
66 | if (columnIndex == 0) {
67 | return rowIndex + 1;
68 | }
69 | return bindColumnValue(data.get(rowIndex), columnIndex);
70 | }
71 | };
72 |
73 | // 表格渲染
74 | private DefaultTableCellRenderer tableCellRenderer = new DefaultTableCellRenderer();
75 |
76 | public CommonTable() {
77 | // 初始化表格标题。
78 | columns.add("序号");
79 | columns.addAll(List.of(bindColumns()));
80 |
81 | // 表格样式
82 | setModel(model);
83 | setShowGrid(true);
84 | setRowHeight(30);
85 | setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
86 |
87 | // 表格渲染
88 | tableCellRenderer.setHorizontalAlignment(SwingConstants.CENTER);
89 | setDefaultRenderer(Object.class, tableCellRenderer);
90 | }
91 |
92 | /**
93 | * 根据索引获取数据
94 | *
95 | * @param index 索引
96 | * @return T类型数据
97 | */
98 | public T getDataItem(int index) {
99 | return data.get(index);
100 | }
101 |
102 | /**
103 | * 获取存储T类型的集合
104 | *
105 | * @return 数据集合
106 | */
107 | public List getData() {
108 | return data;
109 | }
110 |
111 | /**
112 | * 添加一行数据
113 | *
114 | * @param t 数据对应的JavaBean
115 | */
116 | public void addRow(T t) {
117 | data.add(t);
118 | model.fireTableRowsInserted(data.size() - 1, data.size() - 1);
119 | }
120 |
121 | /**
122 | * 添加多行数据
123 | *
124 | * @param items 数据集合
125 | */
126 | public void addRows(List items) {
127 | int addBeforeIndex = data.size();
128 | data.addAll(items);
129 | model.fireTableRowsInserted(addBeforeIndex - 1, data.size() - 1);
130 | }
131 |
132 | /**
133 | * 根据行号删除数据
134 | *
135 | * @param rowIndex 行号
136 | */
137 | public void removeRow(int rowIndex) {
138 | data.remove(rowIndex);
139 | model.fireTableRowsDeleted(rowIndex, rowIndex);
140 | }
141 |
142 | /**
143 | * 清空所有数据
144 | */
145 | public void removeAll() {
146 | int dataSize = data.size();
147 | if (dataSize > 0) {
148 | data.clear();
149 | model.fireTableRowsDeleted(0, dataSize - 1);
150 | }
151 | }
152 |
153 | /**
154 | * 使用者需要自定义T类型中哪些数据对应columnIndex的值
155 | *
156 | * @param obj JavaBean的具体类型数据对象
157 | * @param columnIndex 当前表格中第columnIndex个位置应该对应JavaBean中的哪个属性
158 | * @return 当前columnIndex列对应的属性值
159 | */
160 | public abstract Object bindColumnValue(T obj, int columnIndex);
161 |
162 | /**
163 | * 设置表格标题
164 | *
165 | * @return 表格标题
166 | */
167 | public abstract String[] bindColumns();
168 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/tabpane/Tab.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.tabpane;
2 |
3 | import lombok.Builder;
4 | import lombok.Data;
5 |
6 | import javax.swing.*;
7 | import java.awt.*;
8 |
9 | /**
10 | * Tab页
11 | *
12 | * @author RichardTang
13 | */
14 | @Data
15 | public class Tab {
16 |
17 | // 鼠标悬浮时的提示信息
18 | private String tip;
19 | // 图标
20 | private Icon icon;
21 | // 携带的数据
22 | private Object data;
23 | // 标题
24 | private String title;
25 | // 是否显示关闭按钮
26 | private Boolean close;
27 | // 对应的内容组件
28 | private Component component;
29 |
30 | public Tab(String title, Icon icon, String tip, Boolean close, Component component, Object data) {
31 | this.title = title;
32 | this.icon = icon;
33 | this.tip = tip;
34 | this.component = component;
35 | this.data = data;
36 | setClose(close);
37 | }
38 |
39 | public Tab(String title, Component component) {
40 | this(title, null, null, true, component, null);
41 | }
42 |
43 | public Tab(String title, Object data) {
44 | this(title, null, null, true, null, data);
45 | }
46 |
47 | public Tab(String title, Icon icon, Component component) {
48 | this(title, icon, null, true, component, null);
49 | }
50 |
51 | public Tab(String title, Component component, Boolean close) {
52 | this(title, null, null, close, component, null);
53 | }
54 |
55 | public Tab(String title, Icon icon, Component component, Boolean close) {
56 | this(title, icon, null, close, component, null);
57 | }
58 |
59 | public Tab(String title, Component component, Object data) {
60 | this(title, null, null, true, component, data);
61 | }
62 |
63 | public Tab(String title, Icon icon, Component component, Object data) {
64 | this(title, icon, null, true, component, data);
65 | }
66 |
67 | public Tab(String title, Component component, Boolean close, Object data) {
68 | this(title, null, null, close, component, data);
69 | }
70 |
71 | public Tab(String title, Icon icon, Component component, Boolean close, Object data) {
72 | this(title, icon, null, close, component, data);
73 | }
74 |
75 | public void setClose(Boolean close) {
76 | this.close = close;
77 | ((JComponent) component).putClientProperty("JTabbedPane.tabClosable", close);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/tabpane/TabPane.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.tabpane;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 | import java.util.LinkedHashMap;
8 | import java.util.function.BiConsumer;
9 | import java.util.function.IntConsumer;
10 |
11 | /**
12 | * 进一步封装JTabbedPane,主要提供Tab的默认关闭按钮,以及Tab页可以挂靠数据对象。
13 | *
14 | * @author RichardTang
15 | */
16 | public class TabPane extends JTabbedPane {
17 |
18 | // 用于存放Tab类,Key为Tab类中的Title
19 | private LinkedHashMap tabContainer = new LinkedHashMap<>();
20 |
21 | public TabPane() {
22 | // 配置Tab默认就显示关闭按钮的动作
23 | this.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_CLOSE_CALLBACK, (IntConsumer) this::removeTabAt);
24 | }
25 |
26 | /**
27 | * 添加一个tab页
28 | *
29 | * @param tab tab页实体类
30 | */
31 | public void addTab(Tab tab) {
32 | tabContainer.put(tab.getTitle(), tab);
33 | super.addTab(tab.getTitle(), tab.getIcon(), tab.getComponent(), tab.getTip());
34 | }
35 |
36 | /**
37 | * 重写原有的addTab函数,需要在添加Tab时将tab组件对象添加到集合容器中。
38 | *
39 | * @param title tab标题
40 | * @param component tab中显示的内容组件
41 | */
42 | @Override
43 | public void addTab(String title, Component component) {
44 | addTab(new Tab(title, component));
45 | }
46 |
47 | @Override
48 | public void addTab(String title, Icon icon, Component component) {
49 | addTab(new Tab(title, icon, component));
50 | }
51 |
52 | @Override
53 | public void addTab(String title, Icon icon, Component component, String tip) {
54 | addTab(new Tab(title, icon, component, tip));
55 | }
56 |
57 | public void addTab(String title, Icon icon, Component component, Boolean close) {
58 | addTab(new Tab(title, icon, component, close));
59 | }
60 |
61 | /**
62 | * 根据标题删除指定tab
63 | *
64 | * @param title 需要被删除的tab对应的标题
65 | */
66 | public void removeTabByTitle(String title) {
67 | Tab tab = tabContainer.get(title);
68 | remove(tab.getComponent());
69 | tabContainer.remove(title);
70 | }
71 |
72 | /**
73 | * 根据索引值删除tab页
74 | *
75 | * @param index tab对应的索引
76 | */
77 | @Override
78 | public void removeTabAt(int index) {
79 | Component componentAt = getComponentAt(index);
80 | for (String key : tabContainer.keySet()) {
81 | if (componentAt == tabContainer.get(key).getComponent()) {
82 | tabContainer.remove(key);
83 | break;
84 | }
85 | }
86 | super.removeTabAt(index);
87 | }
88 |
89 | /**
90 | * 根据标题获取Tab
91 | *
92 | * @param title tab对应的标题
93 | * @return 对应title的tab,返回null为没找到该tab。
94 | */
95 | public Tab getTabByTitle(String title) {
96 | return tabContainer.get(title);
97 | }
98 |
99 | /**
100 | * 获取当前处于选中状态的tab
101 | *
102 | * @return 当前处于选中状态的tab
103 | */
104 | public Tab getSelectedTab() {
105 | Component selectedComponent = getSelectedComponent();
106 | for (String key : tabContainer.keySet()) {
107 | Component component = tabContainer.get(key).getComponent();
108 | if (component == selectedComponent) {
109 | return tabContainer.get(key);
110 | }
111 | }
112 | return null;
113 | }
114 |
115 | /**
116 | * 获取选中的Tab的标题
117 | *
118 | * @return 选中的Tab的标题
119 | */
120 | public String getSelectedTabTitle() {
121 | int selectedIndex = getSelectedIndex();
122 | return getTitleAt(selectedIndex);
123 | }
124 |
125 | /**
126 | * 根据Tab的标题显示对应的Tab
127 | *
128 | * @param title 需要显示的Tab对应的标题
129 | * @return false为tab未打开,true为tab已打开并进行显示。
130 | */
131 | public boolean showTabByTitle(String title) {
132 | Tab tab = tabContainer.get(title);
133 | if (tab != null) {
134 | setSelectedComponent(tab.getComponent());
135 | return true;
136 | } else {
137 | return false;
138 | }
139 | }
140 |
141 | /**
142 | * 获取Tab数量
143 | *
144 | * @return tab数量
145 | */
146 | public int getTabSize() {
147 | return tabContainer.size();
148 | }
149 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/tree/FileContentHighlightNode.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.tree;
2 |
3 | import javax.swing.tree.DefaultMutableTreeNode;
4 | import java.io.File;
5 |
6 | /**
7 | * 文件内容高亮节点,用于配合{@see FileContentHighlightTree},主要用在搜索功能上。
8 | *
9 | * @author RichardTang
10 | */
11 | public class FileContentHighlightNode extends DefaultMutableTreeNode {
12 |
13 | // 每个节点都对应一个文件
14 | private File file;
15 |
16 | // 关键字所在行号
17 | private int lineNumber;
18 |
19 | // 是否为行节点
20 | private boolean isLineNode;
21 |
22 | public FileContentHighlightNode(String text, File file, int lineNumber) {
23 | this(text, file, lineNumber, false);
24 | }
25 |
26 | public FileContentHighlightNode(String text, File file, int lineNumber, boolean isLineNode) {
27 | super(text);
28 | this.file = file;
29 | this.isLineNode = isLineNode;
30 | this.lineNumber = lineNumber;
31 | }
32 |
33 | public File getFile() {
34 | return file;
35 | }
36 |
37 | public int getLineNumber() {
38 | return lineNumber;
39 | }
40 |
41 | public boolean isLineNode() {
42 | return isLineNode;
43 | }
44 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/tree/FileContentHighlightTree.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.tree;
2 |
3 | import cn.hutool.http.HtmlUtil;
4 | import com.formdev.flatlaf.icons.FlatTreeClosedIcon;
5 | import com.formdev.flatlaf.icons.FlatTreeLeafIcon;
6 |
7 | import javax.swing.*;
8 | import javax.swing.tree.DefaultTreeCellRenderer;
9 | import javax.swing.tree.DefaultTreeModel;
10 | import java.awt.*;
11 |
12 | /**
13 | * 高亮显示文件内容的关键字,已一种树的形式进行展现。
14 | * 实现的方式主要是靠JLabel,JLabel可以以HTML代码的方式设置样式,从而实现高亮。
15 | *
16 | * @author RichardTang
17 | */
18 | public class FileContentHighlightTree extends JTree {
19 |
20 | // 关键字
21 | private String keyword;
22 |
23 | // 高亮的关键字
24 | private String highlightKeyword;
25 |
26 | /**
27 | * 创建带有高亮效果的树
28 | *
29 | * @param root 根节点
30 | * @param keyword 需要高亮的关键字
31 | */
32 | public FileContentHighlightTree(FileContentHighlightNode root, String keyword) {
33 | this.keyword = keyword;
34 | this.highlightKeyword = String.format("%s", keyword);
35 |
36 | setRootVisible(true);
37 | setModel(new DefaultTreeModel(root));
38 | setCellRenderer(new FileContentHighlightCellRenderer());
39 | }
40 |
41 | /**
42 | * 节点渲染
43 | */
44 | private class FileContentHighlightCellRenderer extends DefaultTreeCellRenderer {
45 |
46 | @Override
47 | public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
48 | boolean leaf, int row, boolean hasFocus) {
49 | JLabel label = (JLabel) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
50 | FileContentHighlightNode currentNode = (FileContentHighlightNode) value;
51 |
52 | // 只有为文件节点时才需要进行高亮替换
53 | if (currentNode.isLineNode()) {
54 | // 如果内容是html格式的内容,则需要先进行转义。
55 | String text = String.format("第%s行: %s",
56 | currentNode.getLineNumber(),
57 | HtmlUtil.escape(label.getText()));
58 |
59 | // 将关键字替换为高亮关键字
60 | label.setText(text.replaceAll(keyword, highlightKeyword));
61 | label.setIcon(null);
62 | } else if (currentNode.getFile().isDirectory()) {
63 | // 目录的话不进行高亮操作
64 | label.setIcon(new FlatTreeClosedIcon());
65 | } else {
66 | // 根节点的情况
67 | label.setIcon(new FlatTreeLeafIcon());
68 | }
69 | return label;
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/ui/tree/SystemFileTree.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.ui.tree;
2 |
3 | import lombok.EqualsAndHashCode;
4 | import lombok.Getter;
5 |
6 | import javax.swing.*;
7 | import javax.swing.event.TreeExpansionEvent;
8 | import javax.swing.event.TreeWillExpandListener;
9 | import javax.swing.tree.DefaultMutableTreeNode;
10 | import javax.swing.tree.DefaultTreeCellRenderer;
11 | import javax.swing.tree.DefaultTreeModel;
12 | import javax.swing.tree.TreeNode;
13 | import java.awt.*;
14 | import java.io.File;
15 |
16 | /**
17 | * 操作系统文件树
18 | *
19 | * @author RichardTang
20 | */
21 | public class SystemFileTree extends JTree {
22 |
23 | private SystemFileTreeNode rootNode;
24 |
25 | public SystemFileTree(File rootFile) {
26 | rootNode = new SystemFileTreeNode(rootFile);
27 |
28 | // 设置JTree模型、渲染、节点折叠和展开事件
29 | setShowsRootHandles(true);
30 | setModel(new SystemFileTreeModel(rootNode));
31 | setCellRenderer(new SystemFileTreeRenderer());
32 | addTreeWillExpandListener(new TreeWillExpandListener() {
33 | @Override
34 | public void treeWillExpand(TreeExpansionEvent event) {
35 | loadChildNode((SystemFileTreeNode) event.getPath().getLastPathComponent());
36 | }
37 |
38 | @Override
39 | public void treeWillCollapse(TreeExpansionEvent event) {
40 |
41 | }
42 | });
43 | }
44 |
45 | /**
46 | * 给定父节点,加载子节点。
47 | *
48 | * @param pNode 父节点
49 | */
50 | public void loadChildNode(SystemFileTreeNode pNode) {
51 | for (File file : pNode.getFile().listFiles()) {
52 | pNode.add(new SystemFileTreeNode(file));
53 | }
54 | }
55 |
56 | @Getter
57 | public class SystemFileTreeModel extends DefaultTreeModel {
58 |
59 | public SystemFileTreeModel(TreeNode root) {
60 | super(root);
61 |
62 | // 根节点添加初始化节点,必须在该函数内完成,不然显示不出来。
63 | loadChildNode((SystemFileTreeNode) root);
64 | }
65 |
66 | @Override
67 | public boolean isLeaf(Object node) {
68 | return !((SystemFileTreeNode) node).isDirectory();
69 | }
70 | }
71 |
72 | @Getter
73 | @EqualsAndHashCode(callSuper = true)
74 | public class SystemFileTreeNode extends DefaultMutableTreeNode {
75 |
76 | private String name;
77 | private File file;
78 | private boolean isDirectory;
79 |
80 | public SystemFileTreeNode(File file) {
81 | this.file = file;
82 | this.name = file.getName();
83 | this.isDirectory = file.isDirectory();
84 | }
85 |
86 | /**
87 | * toString用于JTree组件在显示时的名称作用
88 | *
89 | * @return 节点名称
90 | */
91 | @Override
92 | public String toString() {
93 | return name;
94 | }
95 | }
96 |
97 | @Getter
98 | @EqualsAndHashCode(callSuper = true)
99 | public class SystemFileTreeRenderer extends DefaultTreeCellRenderer {
100 |
101 | /**
102 | * 重写显示的样式
103 | */
104 | @Override
105 | public Component getTreeCellRendererComponent(JTree tree,
106 | Object value,
107 | boolean sel,
108 | boolean expanded,
109 | boolean leaf,
110 | int row,
111 | boolean hasFocus) {
112 |
113 | JLabel label = (JLabel) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
114 |
115 | if (value instanceof SystemFileTreeNode) {
116 | SystemFileTreeNode fileNode = (SystemFileTreeNode) value;
117 | label.setText(fileNode.getName());
118 | label.setOpaque(false);
119 | }
120 |
121 | return label;
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/util/CompressUtils.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.util;
2 |
3 | import cn.hutool.core.util.ZipUtil;
4 | import cn.hutool.log.StaticLog;
5 | import org.apache.commons.compress.java.util.jar.Pack200;
6 |
7 | import java.io.*;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 | import java.util.jar.JarFile;
11 | import java.util.jar.JarOutputStream;
12 | import java.util.zip.*;
13 |
14 | /**
15 | * 压缩包工具类
16 | *
17 | * @author RichardTang
18 | */
19 | @SuppressWarnings("all")
20 | public class CompressUtils extends ZipUtil {
21 |
22 | // Pack200压包
23 | private static final Pack200.Packer packer = Pack200.newPacker();
24 |
25 | // Pack200解包
26 | private static final Pack200.Unpacker unpacker = Pack200.newUnpacker();
27 |
28 | /**
29 | * 对Jar包进行Pack200操作,保存的位置只需要写对应的根路径即可。
30 | * 默认保存的文件名为Jar包的名称.pack.gz。
31 | *
32 | * @param jarFile 需要进行Pack200的Jar包
33 | * @param outputRootPath Pack200之后输出的根位置
34 | * @return true:压缩成功 false:压缩失败
35 | */
36 | public static boolean packer(JarFile jarFile, String outputRootPath) {
37 | try {
38 | String outputPath = outputRootPath + jarFile.getName() + ".pack.gz";
39 | GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(outputPath));
40 | packer.pack(jarFile, gzipOutputStream);
41 | jarFile.close();
42 | gzipOutputStream.close();
43 | return true;
44 | } catch (Exception e) {
45 | StaticLog.error(e);
46 | }
47 | return false;
48 | }
49 |
50 | /**
51 | * 使用Pack200进行解压
52 | *
53 | * @param unpackFilePath .pack.gz文件路径
54 | * @param outputRootPath 解压后的jar包输出的根路径
55 | * @return true:解压成功 false:解压失败
56 | */
57 | public static boolean unpacker(String unpackFilePath, String outputRootPath) {
58 | try {
59 | String outputPath = outputRootPath + FileUtils.getName(unpackFilePath).replace(".pack.gz", "") + ".jar";
60 | GZIPInputStream in = new GZIPInputStream(new FileInputStream(unpackFilePath));
61 | JarOutputStream out = new JarOutputStream(new FileOutputStream(outputPath));
62 | unpacker.unpack(in, out);
63 | out.close();
64 | in.close();
65 | return true;
66 | } catch (Exception e) {
67 | StaticLog.error(e);
68 | }
69 | return false;
70 | }
71 |
72 | /**
73 | * 读取压缩文件内的文件路径
74 | *
75 | * @param file 压缩文件格式的文件
76 | * @return 该压缩文件内的文件路径
77 | */
78 | public static List readZipFiles(File file) {
79 | List result = new ArrayList<>();
80 | try {
81 | ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(file)));
82 | ZipEntry zipEntry;
83 | while ((zipEntry = zipInputStream.getNextEntry()) != null) {
84 | if (zipEntry.isDirectory()) {
85 | continue;
86 | }
87 | String path = zipEntry.getName();
88 | if (path.contains("/")) {
89 | path = path.substring(path.lastIndexOf("/") + 1);
90 | }
91 | result.add(path);
92 | }
93 | zipInputStream.closeEntry();
94 | } catch (Exception e) {
95 | StaticLog.error(e);
96 | result.add("ERROR:" + e.getMessage());
97 | }
98 | return result;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/util/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.util;
2 |
3 | import cn.hutool.core.date.DateUtil;
4 | import cn.hutool.core.io.FileUtil;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.net.URL;
9 | import java.nio.file.attribute.BasicFileAttributes;
10 | import java.util.Date;
11 |
12 | /**
13 | * 文件工具类
14 | *
15 | * @author RichardTang
16 | */
17 | public class FileUtils extends FileUtil {
18 |
19 | /**
20 | * 获取文件创建时间
21 | *
22 | * @param path 需要获取时间的文件路径
23 | * @return 文件创建时间
24 | */
25 | public static Date creationTime(String path) {
26 | return creationTime(file(path));
27 | }
28 |
29 | /**
30 | * 获取文件创建时间
31 | *
32 | * @param file 需要获取时间的文件
33 | * @return 文件创建时间
34 | */
35 | public static Date creationTime(File file) {
36 | BasicFileAttributes attributes = getAttributes(file.toPath(), false);
37 | return DateUtil.date(attributes.creationTime().toMillis()).toJdkDate();
38 | }
39 |
40 | /**
41 | * 获取存放在/resources/文件夹下的资源
42 | * 注意: name参数不要以/号开头
43 | *
44 | * @param name 相当于/resources/下的路径资源
45 | * @return 路径
46 | */
47 | public static URL getResource(String name) {
48 | return FileUtil.class.getClassLoader().getResource(name);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/util/JadxUtils.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.util;
2 |
3 | import jadx.api.CommentsLevel;
4 | import jadx.api.JadxArgs;
5 | import jadx.api.JadxDecompiler;
6 | import jadx.api.JavaClass;
7 | import jadx.api.impl.NoOpCodeCache;
8 |
9 | import java.io.File;
10 |
11 | /**
12 | * 反编译工具类
13 | *
14 | * @author RichardTang
15 | */
16 | public class JadxUtils {
17 |
18 | /**
19 | * 使用Jadx进行反编译
20 | *
21 | * @param srcDir 需要反编译的资源目录
22 | * @param dstDir 反编译后的结果输出目录
23 | */
24 | public static void decompile(File srcDir, File dstDir) {
25 | JadxArgs jadxArgs = new JadxArgs();
26 | jadxArgs.setDebugInfo(false);
27 | jadxArgs.setInputFile(srcDir);
28 | jadxArgs.setOutDirSrc(dstDir);
29 | jadxArgs.setCodeCache(new NoOpCodeCache());
30 | jadxArgs.setCommentsLevel(CommentsLevel.NONE);
31 | try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
32 | jadx.load();
33 | jadx.save();
34 | } catch (Exception e) {
35 | e.printStackTrace();
36 | } finally {
37 | jadxArgs.close();
38 | }
39 | }
40 |
41 | public static String decompileClass(File classFile) {
42 | JadxArgs jadxArgs = new JadxArgs();
43 | jadxArgs.setDebugInfo(false);
44 | jadxArgs.setInputFile(classFile);
45 | jadxArgs.setCommentsLevel(CommentsLevel.NONE);
46 | jadxArgs.setCodeCache(new NoOpCodeCache());
47 | try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
48 | jadx.load();
49 | for (JavaClass aClass : jadx.getClasses()) {
50 | return aClass.getCode();
51 | }
52 | } catch (Exception e) {
53 | e.printStackTrace();
54 | } finally {
55 | jadxArgs.close();
56 | }
57 | return "反编译失败!";
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/util/RSyntaxTextAreaUtils.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.util;
2 |
3 | import cn.hutool.log.StaticLog;
4 | import org.fife.ui.rtextarea.RTextArea;
5 | import org.fife.ui.rtextarea.SearchContext;
6 | import org.fife.ui.rtextarea.SearchEngine;
7 |
8 | /**
9 | * RSyntaxTextArea组件工具类
10 | *
11 | * @author RichardTang
12 | */
13 | public class RSyntaxTextAreaUtils {
14 |
15 | /**
16 | * 光标聚焦到指定位置
17 | *
18 | * @param rTextArea RTextArea组件
19 | * @param position 位置
20 | */
21 | public static void focusPosition(RTextArea rTextArea, int position) {
22 | rTextArea.setCaretPosition(position);
23 | rTextArea.setFocusable(true);
24 | rTextArea.requestFocus();
25 | }
26 |
27 | /**
28 | * 搜索关键字
29 | *
30 | * @param rTextArea RTextArea组件
31 | * @param keyword 关键字
32 | */
33 | public static void findKeyword(RTextArea rTextArea, String keyword) {
34 | SearchEngine.find(rTextArea, new SearchContext(keyword)).wasFound();
35 | }
36 |
37 | /**
38 | * 光标聚焦到指定行的开头
39 | *
40 | * @param line 行号
41 | */
42 | public static void focusLineStart(RTextArea rTextArea, int line) {
43 | try {
44 | int lineStartOffset = rTextArea.getLineStartOffset(line - 1);
45 | focusPosition(rTextArea, lineStartOffset);
46 | } catch (Exception e) {
47 | StaticLog.error(e);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/util/YamlUtils.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.util;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import cn.hutool.log.StaticLog;
5 | import org.yaml.snakeyaml.Yaml;
6 |
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | /**
13 | * Yaml工具类
14 | *
15 | * @author RichardTang
16 | */
17 | public class YamlUtils {
18 |
19 | private static final Yaml yaml = new Yaml();
20 |
21 | /**
22 | * Yaml文件配置转Map
23 | *
24 | * @param path 需要转换的yaml文件路径
25 | * @return 转换后的Map集合
26 | */
27 | public static Map fileToMap(String path) {
28 | return fileToMap(new File(path));
29 | }
30 |
31 | /**
32 | * Yaml文件配置转Map
33 | *
34 | * @param file 需要转换的yaml文件
35 | * @return 转换后的Map集合
36 | */
37 | public static Map fileToMap(File file) {
38 | if (FileUtil.isNotEmpty(file)) {
39 | try {
40 | return yaml.loadAs(new FileInputStream(file), Map.class);
41 | } catch (Exception e) {
42 | StaticLog.error(e);
43 | }
44 | }
45 | return new HashMap<>();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/validator/DeviceOnlineValidator.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.validator;
2 |
3 | import com.android.ddmlib.IDevice;
4 | import com.richardtang.androidkiller4j.Application;
5 | import com.richardtang.androidkiller4j.ddmlib.AndroidDeviceManager;
6 | import com.richardtang.androidkiller4j.util.ControlUtils;
7 |
8 | /**
9 | * 校验当前用户是否选择需要操作的设备
10 | *
11 | * @author RichardTang
12 | */
13 | public class DeviceOnlineValidator {
14 |
15 | public static boolean verify() {
16 | IDevice device = AndroidDeviceManager.getInstance().getDevice();
17 | // 判断是否连接了相应ADB设备
18 | if (device != null && device.isOnline()) {
19 | return true;
20 | } else {
21 | ControlUtils.showMsgDialog("提示", "请先连接设备!");
22 | return false;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/validator/SelectedIsWorkbenchTabValidator.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.validator;
2 |
3 | import com.richardtang.androidkiller4j.MainWindow;
4 | import com.richardtang.androidkiller4j.util.ControlUtils;
5 | import com.richardtang.androidkiller4j.view.WorkbenchView;
6 |
7 | /**
8 | * 校验当前TaskView中选中的Tab是否为Workbench类型
9 | */
10 | public class SelectedIsWorkbenchTabValidator {
11 |
12 | public static boolean verify() {
13 | if (MainWindow.taskView.getSelectedComponent() instanceof WorkbenchView) return true;
14 | ControlUtils.showMsgDialog("提示信息", "该功能只适用APK工作台页面");
15 | return false;
16 | }
17 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/validator/TabAddedValidator.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.validator;
2 |
3 | import com.richardtang.androidkiller4j.MainWindow;
4 |
5 | /**
6 | * 根据Title校验TaskView中的TabTitle
7 | *
8 | * @author RichardTang
9 | */
10 | public class TabAddedValidator {
11 |
12 | public static boolean verify(String title) {
13 | return MainWindow.taskView.showTabByTitle(title);
14 | }
15 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/validator/component/TextFieldVerifier.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.validator.component;
2 |
3 | import com.formdev.flatlaf.FlatClientProperties;
4 | import com.richardtang.androidkiller4j.util.ControlUtils;
5 |
6 | import javax.swing.*;
7 | import java.util.function.Function;
8 |
9 | /**
10 | * 对JTextField的InputVerifier简单的封装
11 | *
12 | * @author RichardTang
13 | */
14 | public abstract class TextFieldVerifier {
15 |
16 | /**
17 | * 校验函数,这里主要增加了FlatLaf对JTextField的边框显示。
18 | *
19 | * @param jTextField 需要校验的JTextField组件
20 | * @param errorTip 校验不通过时的提示信息
21 | * @param function 校验的逻辑
22 | */
23 | public static void verify(JTextField jTextField, String errorTip, Function function) {
24 | jTextField.setInputVerifier(new InputVerifier() {
25 | @Override
26 | public boolean verify(JComponent input) {
27 | Boolean flag = function.apply(jTextField);
28 | if (!flag) {
29 | ControlUtils.getTimeBTip(jTextField, errorTip, 2000);
30 | jTextField.putClientProperty(FlatClientProperties.OUTLINE, FlatClientProperties.OUTLINE_ERROR);
31 | } else {
32 | jTextField.putClientProperty(FlatClientProperties.OUTLINE, null);
33 | }
34 | return flag;
35 | }
36 | });
37 | }
38 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/view/ConsoleView.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.view;
2 |
3 | import com.formdev.flatlaf.extras.FlatSVGIcon;
4 | import com.richardtang.androidkiller4j.MainWindow;
5 | import com.richardtang.androidkiller4j.constant.SvgName;
6 | import com.richardtang.androidkiller4j.ui.action.ClickActionInstaller;
7 | import com.richardtang.androidkiller4j.ui.control.PrintStreamTextArea;
8 | import com.richardtang.androidkiller4j.ui.tabframe.TabFrameItem;
9 | import com.richardtang.androidkiller4j.ui.tabframe.TabFramePanel;
10 | import com.richardtang.androidkiller4j.ui.tabpane.Tab;
11 | import com.richardtang.androidkiller4j.ui.tabpane.TabPane;
12 | import com.richardtang.androidkiller4j.ui.tree.FileContentHighlightNode;
13 | import com.richardtang.androidkiller4j.ui.tree.FileContentHighlightTree;
14 | import com.richardtang.androidkiller4j.util.ControlUtils;
15 | import com.richardtang.androidkiller4j.util.RSyntaxTextAreaUtils;
16 | import lombok.Data;
17 | import org.fife.ui.rtextarea.RTextArea;
18 | import org.fife.ui.rtextarea.RTextScrollPane;
19 |
20 | import javax.swing.*;
21 |
22 | /**
23 | * 控制台视图,也就是程序打开后最下边的选项卡。
24 | * 带有日志输出的文本域那一处
25 | *
26 | * @author RichardTang
27 | */
28 | @Data
29 | public final class ConsoleView extends TabFramePanel {
30 |
31 | private final FlatSVGIcon MSG_SVG_ICON = ControlUtils.getSVGIcon(SvgName.MESSAGE);
32 | private final FlatSVGIcon SEARCH_SVG_ICON = ControlUtils.getSVGIcon(SvgName.RESULT);
33 |
34 | // 日志组件
35 | private final PrintStreamTextArea printStreamTextArea = new PrintStreamTextArea();
36 | private final JToggleButton logToggleButton = new JToggleButton("消息", MSG_SVG_ICON);
37 | private final TabFrameItem logTabFrameItem = new TabFrameItem(logToggleButton, new JScrollPane(printStreamTextArea));
38 |
39 | // 搜索结果
40 | private final JScrollPane searchResultSrollPanel = new JScrollPane();
41 | private final JToggleButton searchResultToggleButton = new JToggleButton("搜索结果", SEARCH_SVG_ICON);
42 | private final TabFrameItem searchResultTabFrameItem = new TabFrameItem(searchResultToggleButton, searchResultSrollPanel);
43 |
44 | // 加载条
45 | private final JProgressBar loadingProgressBar = new JProgressBar();
46 |
47 | public ConsoleView() {
48 | System.setOut(printStreamTextArea.getPrintStream());
49 | System.setErr(printStreamTextArea.getPrintStream());
50 |
51 | // 添加2个默认的Item,消息和搜索结果!
52 | addTabFrameItem(logTabFrameItem);
53 | addTabFrameItem(searchResultTabFrameItem);
54 |
55 | // 添加进度条样式
56 | loadingProgressBar.setStringPainted(true);
57 | JPanel loadingProgressBarPanel = new JPanel();
58 | loadingProgressBarPanel.add(loadingProgressBar);
59 | getTabFrameBar().addBarRightItem(loadingProgressBarPanel);
60 |
61 | // 绑定事件
62 | ClickActionInstaller.bind(this);
63 | }
64 |
65 | /**
66 | * 设置WorkbenchView试图的文件搜索结果
67 | *
68 | * @param root 根节点
69 | * @param keyword 搜索的关键字
70 | * @param workbenchView 对应的Workbench试图
71 | */
72 | public void setSearchResultRoot(FileContentHighlightNode root, String keyword, WorkbenchView workbenchView) {
73 | FileContentHighlightTree tree = new FileContentHighlightTree(root, keyword);
74 | tree.addTreeSelectionListener(e -> {
75 | FileContentHighlightNode clickedNode = (FileContentHighlightNode) tree.getLastSelectedPathComponent();
76 | if (clickedNode == null) {
77 | return;
78 | }
79 |
80 | // 找到TaskView中对应的工作台
81 | TabPane tabPane = workbenchView.getCodeAreaTabPane();
82 | Tab currentNodeTab = tabPane.getTabByTitle(clickedNode.getFile().getName());
83 |
84 | // 当前点击的节点文件没有打开
85 | if (currentNodeTab == null) {
86 | // 创建新的代码编辑区域
87 | currentNodeTab = new Tab(clickedNode.getFile().getName(), workbenchView.createCodeEditorComponent(clickedNode.getFile()), clickedNode.getFile());
88 | tabPane.addTab(currentNodeTab);
89 | }
90 | tabPane.setSelectedComponent(currentNodeTab.getComponent());
91 | RTextArea rTextArea = ((RTextScrollPane) tabPane.getSelectedTab().getComponent()).getTextArea();
92 | RSyntaxTextAreaUtils.focusPosition(rTextArea, 0);
93 |
94 | if (clickedNode.isLineNode()) {
95 | RSyntaxTextAreaUtils.focusLineStart(rTextArea, clickedNode.getLineNumber());
96 | RSyntaxTextAreaUtils.findKeyword(rTextArea, keyword);
97 | }
98 | });
99 | searchResultSrollPanel.getViewport().add(tree);
100 | }
101 |
102 | /**
103 | * 进度条加载开始
104 | *
105 | * @param msg 需要显示的信息
106 | */
107 | public synchronized void startLoadingProgressBar(String msg) {
108 | loadingProgressBar.setValue(1);
109 | loadingProgressBar.setIndeterminate(true);
110 | loadingProgressBar.setString(msg);
111 | }
112 |
113 | /**
114 | * 进度条加载停止
115 | *
116 | * @param msg 需要显示的信息
117 | */
118 | public synchronized void stopLoadingProgressBar(String msg) {
119 | loadingProgressBar.setValue(100);
120 | loadingProgressBar.setIndeterminate(false);
121 | loadingProgressBar.setString(msg);
122 | }
123 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/view/MainView.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.view;
2 |
3 | import com.richardtang.androidkiller4j.MainWindow;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 |
8 | /**
9 | * 主视图
10 | *
11 | * @author RichardTang
12 | */
13 | public final class MainView extends JPanel {
14 |
15 | public MainView() {
16 | setLayout(new BorderLayout());
17 | add(MainWindow.taskView, BorderLayout.CENTER);
18 | add(MainWindow.toolkitView, BorderLayout.NORTH);
19 | add(MainWindow.consoleView, BorderLayout.SOUTH);
20 | }
21 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/view/TaskView.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.view;
2 |
3 | import cn.hutool.core.date.DateUtil;
4 | import cn.hutool.core.io.FileUtil;
5 | import com.richardtang.androidkiller4j.bean.Apk;
6 | import com.richardtang.androidkiller4j.constant.SvgName;
7 | import com.richardtang.androidkiller4j.constant.R;
8 | import com.richardtang.androidkiller4j.constant.Size;
9 | import com.richardtang.androidkiller4j.ui.panel.CommonTablePanel;
10 | import com.richardtang.androidkiller4j.ui.table.CommonTable;
11 | import com.richardtang.androidkiller4j.ui.tabpane.TabPane;
12 | import com.richardtang.androidkiller4j.util.ControlUtils;
13 | import lombok.Data;
14 |
15 | import javax.swing.*;
16 | import javax.swing.table.DefaultTableCellRenderer;
17 | import java.awt.*;
18 | import java.awt.event.MouseAdapter;
19 | import java.awt.event.MouseEvent;
20 | import java.io.File;
21 |
22 | /**
23 | * 任务列表视图
24 | *
25 | * @author RichardTang
26 | */
27 | @Data
28 | public final class TaskView extends TabPane {
29 |
30 | private final CommonTablePanel commonTablePanel = new CommonTablePanel<>() {
31 | @Override
32 | public Object bindTableColumnValue(Apk apk, int columnIndex) {
33 | return switch (columnIndex) {
34 | case 1 -> apk.getImageIcon();
35 | case 2 -> apk.getApplicationName();
36 | case 3 -> apk.getPackageName();
37 | case 4 -> apk.getTargetSdkVersion();
38 | case 5 -> apk.getMinSdkVersion();
39 | default -> DateUtil.formatChineseDate(apk.getDecodeApkDate(), false, true);
40 | };
41 | }
42 |
43 | @Override
44 | public String[] setTableColumns() {
45 | return new String[]{"应用图标", "应用名称", "应用包名", "目标SDK", "最小SDK", "创建时间"};
46 | }
47 | };
48 |
49 | // 任务列表表格
50 | private final CommonTable table = commonTablePanel.getTable();
51 |
52 | private final Icon taskIcon = ControlUtils.getSVGIcon(SvgName.TASK, Size.BIG);
53 |
54 | public TaskView() {
55 | // 表格ApkIcon自定义渲染
56 | table.getColumnModel().getColumn(1).setCellRenderer(new AppIconCellRender());
57 | // Panel边框颜色
58 | setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.decode("#c4c4c4")));
59 | // 添加一个默认的Tab页
60 | addTab("任务列表", taskIcon, commonTablePanel, false);
61 | // 表格点击事件
62 | table.addMouseListener(new MouseAdapter() {
63 | public void mouseClicked(MouseEvent e) {
64 | // 非双击时不向下执行
65 | if (e.getClickCount() != 2) return;
66 |
67 | // 获取选中,并校验是否已经打开。
68 | Apk apk = getSelectedApk();
69 | boolean isShow = showTabByTitle(apk.getFileName());
70 | if (!isShow) {
71 | addTab(apk.getFileName(), apk.getImageIcon(), new WorkbenchView(apk));
72 | }
73 | }
74 | });
75 | // 刷新一次任务,将apk添加至列表中
76 | refreshTask();
77 | }
78 |
79 | /**
80 | * 刷新任务列表
81 | */
82 | public void refreshTask() {
83 | for (File file : FileUtil.ls(R.PROJECT_DIR)) {
84 | if (file.isDirectory()) {
85 | table.addRow(new Apk(R.PROJECT_DIR + file.getName()));
86 | }
87 | }
88 | }
89 |
90 | /**
91 | * 添加任务
92 | *
93 | * @param apk 需要添加到任务列表中的apk对象
94 | */
95 | public void addTask(Apk apk) {
96 | table.addRow(apk);
97 | }
98 |
99 | /**
100 | * 删除任务
101 | *
102 | * @param index 索引号
103 | */
104 | public void removeTask(int index) {
105 | table.removeRow(index - 1);
106 | }
107 |
108 | /**
109 | * 根据apk根路径查找任务列表中是否存在该apk对应的任务
110 | *
111 | * @param apkBasePath apk根路径包
112 | * @return 查找到的apk对象在task表中的索引号,未找到则返回-1。
113 | */
114 | public Integer findTaskByBasePath(String apkBasePath) {
115 | for (int i = 0; i < table.getData().size(); i++) {
116 | if (table.getData().get(i).getBasePath().equals(apkBasePath)) {
117 | return i;
118 | }
119 | }
120 | return -1;
121 | }
122 |
123 | /**
124 | * 获取选中的APK对象
125 | *
126 | * @return 用户选中的APK对象
127 | */
128 | public Apk getSelectedApk() {
129 | return table.getDataItem(table.getSelectedRow());
130 | }
131 |
132 | /**
133 | * 应用图标单元格自定义渲染效果
134 | */
135 | public static class AppIconCellRender extends DefaultTableCellRenderer {
136 |
137 | public AppIconCellRender() {
138 | setHorizontalAlignment(SwingConstants.CENTER);
139 | }
140 |
141 | @Override
142 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
143 | setIcon((Icon) value);
144 | // 这里需要调用原有的函数保留样式效果,但是不要设置value的文本值。
145 | return super.getTableCellRendererComponent(table, "", isSelected, hasFocus, row, column);
146 | }
147 | }
148 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/view/device/DeviceLogView.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.view.device;
2 |
3 | import cn.hutool.core.thread.ThreadUtil;
4 | import com.android.ddmlib.IDevice;
5 | import com.android.ddmlib.logcat.LogCatListener;
6 | import com.android.ddmlib.logcat.LogCatMessage;
7 | import com.android.ddmlib.logcat.LogCatReceiverTask;
8 | import com.richardtang.androidkiller4j.ui.action.ClickAction;
9 | import com.richardtang.androidkiller4j.ui.action.ClickActionInstaller;
10 | import com.richardtang.androidkiller4j.ui.panel.CommonTablePanel;
11 | import lombok.Data;
12 |
13 | import javax.swing.*;
14 | import java.awt.event.ActionEvent;
15 |
16 | @Data
17 | public class DeviceLogView extends CommonTablePanel {
18 |
19 | private IDevice iDevice;
20 |
21 | // 开始、暂停
22 | public JButton switchButton = new JButton("开始");
23 |
24 | // 日志收集任务类,控制任务的开始和暂停。
25 | private LogCatReceiverTask logCatReceiverTask;
26 |
27 | // 定义日志收集到的数据需要存放的位置
28 | private final LogCatListener logCatListener = msgList -> table.addRows(msgList);
29 |
30 | public DeviceLogView(IDevice iDevice) {
31 | super();
32 | this.iDevice = iDevice;
33 |
34 | // 设置按钮为正方形
35 | switchButton.putClientProperty("JButton.buttonType", "square");
36 | toolBarPanel.add(switchButton);
37 |
38 | // 绑定事件
39 | ClickActionInstaller.bind(this);
40 | }
41 |
42 | @Override
43 | public Object bindTableColumnValue(LogCatMessage logCatMessage, int columnIndex) {
44 | return switch (columnIndex) {
45 | case 1 -> logCatMessage.getHeader().getAppName();
46 | case 2 -> logCatMessage.getMessage();
47 | case 3 -> logCatMessage.getHeader().getLogLevel().getStringValue();
48 | case 4 -> logCatMessage.getHeader();
49 | case 5 -> logCatMessage.getHeader().getTag();
50 | case 6 -> logCatMessage.getHeader().getTid();
51 | default -> logCatMessage.getHeader().getTimestamp();
52 | };
53 | }
54 |
55 | @Override
56 | public String[] setTableColumns() {
57 | return new String[]{"应用名称", "消息", "级别", "头信息", "标签", "TID", "时间"};
58 | }
59 |
60 | /**
61 | * 启动日志收集
62 | */
63 | private void runLogCatReceiverTask() {
64 | logCatReceiverTask = new LogCatReceiverTask(iDevice);
65 | logCatReceiverTask.addLogCatListener(logCatListener);
66 | ThreadUtil.execAsync(logCatReceiverTask);
67 | }
68 |
69 | /**
70 | * 暂停日志收集
71 | */
72 | private void stopLogCatReceiverTask() {
73 | logCatReceiverTask.stop();
74 | logCatReceiverTask.removeLogCatListener(logCatListener);
75 | logCatReceiverTask = null;
76 | }
77 |
78 | /**
79 | * 暂停/开始按钮鼠标点击事件
80 | *
81 | * @param event 事件对象
82 | */
83 | @ClickAction("switchButton")
84 | public void switchButtonClick(ActionEvent event) {
85 | if ("开始".equals(switchButton.getText())) {
86 | switchButton.setText("暂停");
87 | runLogCatReceiverTask();
88 | } else {
89 | switchButton.setText("开始");
90 | stopLogCatReceiverTask();
91 | }
92 | }
93 | }
--------------------------------------------------------------------------------
/src/main/java/com/richardtang/androidkiller4j/view/device/DeviceProcessView.java:
--------------------------------------------------------------------------------
1 | package com.richardtang.androidkiller4j.view.device;
2 |
3 | import cn.hutool.core.thread.ThreadUtil;
4 | import com.android.ddmlib.IDevice;
5 | import com.formdev.flatlaf.FlatClientProperties;
6 | import com.richardtang.androidkiller4j.ddmlib.process.ProcessMessage;
7 | import com.richardtang.androidkiller4j.ddmlib.process.ProcessMessageListener;
8 | import com.richardtang.androidkiller4j.ddmlib.process.ProcessMessageReceiverTask;
9 | import com.richardtang.androidkiller4j.ui.action.ClickAction;
10 | import com.richardtang.androidkiller4j.ui.action.ClickActionInstaller;
11 | import com.richardtang.androidkiller4j.ui.panel.CommonTablePanel;
12 | import lombok.Data;
13 |
14 | import javax.swing.*;
15 | import java.awt.event.ActionEvent;
16 |
17 | @Data
18 | public class DeviceProcessView extends CommonTablePanel {
19 |
20 | private IDevice iDevice;
21 |
22 | // 开始、暂停
23 | private JButton switchButton = new JButton("开始");
24 |
25 | // 日志收集任务类,控制任务的开始和暂停。
26 | private ProcessMessageReceiverTask processMessageReceiverTask;
27 |
28 | // 定义日志收集到的数据需要存放的位置
29 | private final ProcessMessageListener processMessageListener = msgList -> {
30 | table.removeAll();
31 | table.addRows(msgList);
32 | };
33 |
34 | public DeviceProcessView(IDevice iDevice) {
35 | super();
36 | this.iDevice = iDevice;
37 |
38 | // 设置按钮为正方形
39 | switchButton.putClientProperty(FlatClientProperties.BUTTON_TYPE, "square");
40 | toolBarPanel.add(switchButton);
41 |
42 | // 绑定事件
43 | ClickActionInstaller.bind(this);
44 | }
45 |
46 | @Override
47 | public Object bindTableColumnValue(ProcessMessage processMessage, int columnIndex) {
48 | return switch (columnIndex) {
49 | case 1 -> processMessage.getPid();
50 | case 2 -> processMessage.getUser();
51 | case 3 -> processMessage.getPr();
52 | case 4 -> processMessage.getNi();
53 | case 5 -> processMessage.getVirt();
54 | case 6 -> processMessage.getShr();
55 | case 7 -> processMessage.getS();
56 | case 8 -> processMessage.getCpu();
57 | case 9 -> processMessage.getMem();
58 | case 10 -> processMessage.getTime();
59 | default -> processMessage.getArgs();
60 | };
61 | }
62 |
63 | @Override
64 | public String[] setTableColumns() {
65 | return new String[]{"PID", "用户", "PR", "NI", "VIRT", "SHR", "S", "CPU使用率", "内存占用", "时间", "参数值"};
66 | }
67 |
68 | /**
69 | * 开启进程监听
70 | */
71 | public void runProcessMessageReceiverTask() {
72 | processMessageReceiverTask = new ProcessMessageReceiverTask(iDevice);
73 | processMessageReceiverTask.addLogCatListener(processMessageListener);
74 | ThreadUtil.execAsync(processMessageReceiverTask);
75 | }
76 |
77 | /**
78 | * 停止进程监听
79 | */
80 | public void stopProcessMessageReceiverTask() {
81 | processMessageReceiverTask.stop();
82 | processMessageReceiverTask.removeLogCatListener(processMessageListener);
83 | processMessageReceiverTask = null;
84 | }
85 |
86 | @ClickAction("switchButton")
87 | public void switchButtonClick(ActionEvent event) {
88 | if ("开始".equals(switchButton.getText())) {
89 | switchButton.setText("暂停");
90 | runProcessMessageReceiverTask();
91 | } else {
92 | switchButton.setText("开始");
93 | stopProcessMessageReceiverTask();
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/src/main/resources/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Main-Class: com.richardtang.androidkiller4j.Main
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/image/about.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/add.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/android.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/apk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/automation.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/src/main/resources/image/banner.jpg
--------------------------------------------------------------------------------
/src/main/resources/image/bug.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/checkbox-switch.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/clear.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/compile.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/connect.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/custom.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/device.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/directory.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/disconnect.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/download.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/f.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/info.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/install.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/java.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/letter-a.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/letter-i.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/letter-r.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/letter-s.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/letter-u.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaskCyberSecurityTeam/AndroidKiller4J/ada03b333e02acd8f43e7c9e9caef9db67dfd166/src/main/resources/image/logo.png
--------------------------------------------------------------------------------
/src/main/resources/image/manager.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/message.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/move.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/process.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/python.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/refresh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/remove.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/result.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/save.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/setting.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/signature.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/system.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/task.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/terminal.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/image/upload.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/logging.properties:
--------------------------------------------------------------------------------
1 | # 当前项目中只有日志级别为LOG的才需要进行记录,只是临时的信息可以使用INFO类型进行打印。
2 | .level=INFO
3 | # 执行的LogHandler,使用逗号隔开
4 | # com.richardtang.androidkiller4j.log.UiConsoleHandler
5 | handlers=java.util.logging.ConsoleHandler,com.richardtang.androidkiller4j.log.DateFileHandler
6 | java.util.logging.ConsoleHandler.level=ALL
7 | java.util.logging.ConsoleHandler.formatter=logging.formatter.MySimpleFormatter
8 | # TextAreaHandler配置
9 | # 为 Handler 指定默认的级别(默认为 Level.INFO)。
10 | com.richardtang.androidkiller4j.log.UiConsoleHandler.level=ALL
11 | # DateFileHandler配置
12 | # 为 Handler 指定默认的级别(默认为 Level.ALL)。
13 | com.richardtang.androidkiller4j.log.DateFileHandler.level=WARNING
14 | # 指定要使用的 Formatter 类的名称(默认为 java.util.logging.XMLFormatter)。
15 | com.richardtang.androidkiller4j.log.DateFileHandler.formatter=java.util.logging.SimpleFormatter
16 | # 指定要写入到任意文件的近似最大量(以字节为单位)。如果该数为 0,则没有限制(默认为无限制)。
17 | com.richardtang.androidkiller4j.log.DateFileHandler.limit=1024000
18 | # 指定有多少输出文件参与循环(默认为 1)。
19 | com.richardtang.androidkiller4j.log.DateFileHandler.count=1
20 | # 指定是否应该将 FileHandler 追加到任何现有文件上(默认为 false)。
21 | com.richardtang.androidkiller4j.log.DateFileHandler.append=true
22 | # SimpleFormatter配置
23 | java.util.logging.SimpleFormatter.format=%1$tY-%1$tb-%1$td %1$tl:%1$tM:%1$tS %2$s%n%4$s: %5$s%6$s%n
--------------------------------------------------------------------------------
/startup.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | setlocal enabledelayedexpansion
3 | @set classpath=.
4 | @for %%c in (libs\*.jar) do (
5 | @set classpath=!classpath!;%%c
6 | )
7 | jre\bin\java.exe -cp %classpath% com.richardtang.androidkiller4j.Application
8 | pause
--------------------------------------------------------------------------------
/startup.sh:
--------------------------------------------------------------------------------
1 | CLASS_PATH=./
2 | for jar in ./libs/*.jar; do
3 | CLASS_PATH=$CLASS_PATH:$jar
4 | done
5 | ./jre/bin/java -cp $CLASS_PATH com.richardtang.androidkiller4j.Application
--------------------------------------------------------------------------------