├── .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 | ![WX20221021-152753@2x](https://user-images.githubusercontent.com/30547741/197141553-15926a55-93d4-4f8a-94d0-63d8d19b6e0f.png) 23 | 24 | ![WX20221021-152857@2x](https://user-images.githubusercontent.com/30547741/197141616-cc483c16-4d15-498c-af1a-d727786edf4f.png) 25 | 26 | ![WX20221021-152928@2x](https://user-images.githubusercontent.com/30547741/197141642-6da4020a-48ca-476f-97dd-2875aa2ced2e.png) 27 | 28 | ![WX20221021-153050@2x](https://user-images.githubusercontent.com/30547741/197141652-96d91af3-08c4-4579-b559-2b9e64e08c05.png) 29 | 30 | ## Windows 31 | 32 | ![1666337697481](https://user-images.githubusercontent.com/30547741/197141796-87ee56a4-1f7a-4717-b12c-0c3840d0eda5.jpg) 33 | 34 | ![WX20221021-153546@2x](https://user-images.githubusercontent.com/30547741/197141818-9ae31f10-9db6-4838-aeba-aeccc5007d1e.png) 35 | 36 | ![WX20221021-153721@2x](https://user-images.githubusercontent.com/30547741/197141831-9455fa52-2d11-4a7f-8ff6-d4f13aa60a6e.png) 37 | 38 | # 编译 39 | 40 | 安装好Jdk17,导入到IDEA后运行Maven的Install,会在target目录下生成编译后的工具文件夹。 41 | 42 | ![image](https://user-images.githubusercontent.com/30547741/197142225-f38cc861-929a-434f-ba86-eb2639f1ec84.png) 43 | 44 | image 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 | image 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 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 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 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 | 2 | 3 | 4 | 5 | 6 | background 7 | 8 | 9 | 10 | Layer 1 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /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 --------------------------------------------------------------------------------