├── .gitignore ├── README.md ├── image └── WX20191207-171805@2x.png ├── pom.xml └── src └── main ├── java └── xpp │ └── apk2jar │ ├── BaseUtils.java │ ├── FxViewUtil.java │ ├── Main.java │ └── ZipUtils.java └── resources ├── apktool-2.4.1.zip ├── dex2jar-2.0.zip ├── icon.png └── jd-gui-1.6.5.jar /.gitignore: -------------------------------------------------------------------------------- 1 | ### Android ### 2 | # Built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # Files for the Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | *.db 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | target/ 18 | src/mac/ 19 | src/windows/ 20 | 21 | # Gradle files 22 | .gradle/ 23 | build/ 24 | gradle/ 25 | gradlew 26 | gradlew.bat 27 | 28 | # Local configuration file (sdk path, etc) 29 | local.properties 30 | 31 | # Proguard folder generated by Eclipse 32 | proguard/ 33 | 34 | # Log Files 35 | *.log 36 | 37 | 38 | #Android Studio 39 | .idea 40 | classes 41 | 42 | app/app.iml 43 | AppMaker.iml 44 | 45 | .DS_Store 46 | /build 47 | /captures 48 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ApkDex2jar 2 | 3 | ![ApkDex2jar](https://github.com/kadisting/ApkDex2jar/blob/master/image/WX20191207-171805%402x.png) 4 | 5 | ### 说明 6 | 基于Dex2jar和apktool、jd-gui等第三方工具,实现对apk进行快速反编译操作。 7 | 8 | 简化反编译过程,告别繁琐的文件夹切换操作,一拖即可反编译。 9 | 10 | 本工具仅用于辅助研发测试,或普通的apk反编译。 11 | 对于已使用第三方加固过的apk,可能会反编译失败,或者无法进行反编译。 12 | 13 | 14 | ### 环境 15 | * UI界面基于java8+javafx 16 | * IDEA 17 | 18 | ### 工具版本 19 | * Dex2jar:2.0 20 | * apktool:2.4.1 21 | * JD-GUI:1.6.5 22 | 23 | 以上工具已经内置到程序中,无需再设置 24 | 25 | 26 | ### 使用 27 | * 运行后将apk直接拖入到程序界面。等待反编译完成。 28 | * 反编译完成后,会在apk所在的同级文件夹下生成一个以_ApkDex2jar结尾的文件夹。反编译后的jar及资源文件保存在此文件夹中。 29 | * 点击"查看反编译文件"会打开反编译后对应的文件夹。 30 | * 点击JD-GUI图标会打开Java Decompiler,将上一步中的classes.dex.jar文件拖入其中,即可查看对应的java代码。 -------------------------------------------------------------------------------- /image/WX20191207-171805@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kadisting/ApkDex2jar/a23742108915a4661a42d64d703998954aee7a25/image/WX20191207-171805@2x.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | groupId 8 | ApkDex2jar 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 1.8 13 | 1.8 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/xpp/apk2jar/BaseUtils.java: -------------------------------------------------------------------------------- 1 | package xpp.apk2jar; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.InputStream; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.nio.file.attribute.PosixFilePermission; 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | public class BaseUtils { 15 | 16 | public static void moveDirAndDelete(String oldPath, String newPath) { 17 | File oldPathFile = new File(oldPath); 18 | if (!oldPathFile.exists()) { 19 | return; 20 | } 21 | File newPathFile = new File(newPath); 22 | if (!newPathFile.exists()) { 23 | newPathFile.mkdirs(); 24 | } 25 | try { 26 | String[] file = oldPathFile.list(); 27 | // 要注意,这个temp仅仅是一个临时文件指针 28 | // 整个程序并没有创建临时文件 29 | File temp = null; 30 | for (int i = 0; i < file.length; i++) { 31 | // 如果oldPath以路径分隔符/或者\结尾,那么则oldPath/文件名就可以了 32 | // 否则要自己oldPath后面补个路径分隔符再加文件名 33 | // 谁知道你传递过来的参数是f:/a还是f:/a/啊? 34 | if (oldPath.endsWith(File.separator)) { 35 | temp = new File(oldPath + file[i]); 36 | } else { 37 | temp = new File(oldPath + File.separator + file[i]); 38 | } 39 | 40 | // 如果游标遇到文件 41 | if (temp.isFile()) { 42 | FileInputStream input = new FileInputStream(temp); 43 | // 复制并且改名 44 | FileOutputStream output = new FileOutputStream(newPath 45 | + "/" + "rename_" + (temp.getName()).toString()); 46 | byte[] bufferarray = new byte[1024 * 64]; 47 | int prereadlength; 48 | while ((prereadlength = input.read(bufferarray)) != -1) { 49 | output.write(bufferarray, 0, prereadlength); 50 | } 51 | output.flush(); 52 | output.close(); 53 | input.close(); 54 | } 55 | // 如果游标遇到文件夹 56 | if (temp.isDirectory()) { 57 | moveDirAndDelete(oldPath + "/" + file[i], newPath + "/" + file[i]); 58 | } 59 | } 60 | } catch (Exception e) { 61 | System.out.println("复制整个文件夹内容操作出错"); 62 | e.printStackTrace(); 63 | } 64 | } 65 | 66 | public static void moveAndDelete(File oldFile, File newFile) { 67 | if (newFile.exists()) { 68 | newFile.delete(); 69 | } 70 | BaseUtils.copyFile(oldFile, newFile); 71 | oldFile.delete(); 72 | } 73 | 74 | public static void deleteDir(File dir) { 75 | if (dir == null || !dir.exists()) { 76 | return; 77 | } 78 | if (dir.isDirectory()) { 79 | File[] files = dir.listFiles(); 80 | if (files != null && files.length > 0) { 81 | for (File f : files) { 82 | if (f.isFile()) { 83 | f.delete(); 84 | } else { 85 | deleteDir(f); 86 | } 87 | } 88 | } 89 | dir.delete(); 90 | } else { 91 | dir.delete(); 92 | } 93 | } 94 | 95 | 96 | public static void copyFile(File oldFile, File newFile) { 97 | try { 98 | int bytesum = 0; 99 | int byteread = 0; 100 | InputStream inStream = new FileInputStream(oldFile); //读入原文件 101 | FileOutputStream fs = new FileOutputStream(newFile); 102 | byte[] buffer = new byte[1444]; 103 | while ((byteread = inStream.read(buffer)) != -1) { 104 | bytesum += byteread; //字节数 文件大小 105 | fs.write(buffer, 0, byteread); 106 | } 107 | inStream.close(); 108 | fs.close(); 109 | oldFile.delete(); 110 | } catch (Exception e) { 111 | e.printStackTrace(); 112 | } 113 | } 114 | 115 | public static void copyFile(InputStream inStream, File newFile) { 116 | try { 117 | int bytesum = 0; 118 | int byteread = 0; 119 | FileOutputStream fs = new FileOutputStream(newFile); 120 | byte[] buffer = new byte[1444]; 121 | while ((byteread = inStream.read(buffer)) != -1) { 122 | bytesum += byteread; //字节数 文件大小 123 | fs.write(buffer, 0, byteread); 124 | } 125 | inStream.close(); 126 | fs.close(); 127 | } catch (Exception e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | 132 | 133 | public static File getLocalBinPath() { 134 | 135 | File file = new File(System.getProperty("user.home") + "/.ApkDex2Jar/bin/"); 136 | if (!file.exists()) { 137 | file.mkdirs(); 138 | } 139 | return file; 140 | } 141 | 142 | 143 | public static void initBin() { 144 | 145 | getDex2JarShellPath(); 146 | getApktoolShellPath(); 147 | getJdJuiPath(); 148 | 149 | changeFolderPermission(BaseUtils.getLocalBinPath()); 150 | } 151 | 152 | public static String getDex2JarShellPath() { 153 | 154 | String path; 155 | 156 | File dex2jarDir = new File(BaseUtils.getLocalBinPath(), "dex2jar"); 157 | if (!dex2jarDir.exists()) { 158 | dex2jarDir.mkdirs(); 159 | } 160 | File vDir = new File(dex2jarDir, "dex2jar-2.0"); 161 | File shFile = new File(vDir, "d2j-dex2jar.sh"); 162 | if (vDir.exists() && shFile.exists()) { 163 | path = shFile.getPath(); 164 | } else { 165 | File zipFile = new File(dex2jarDir, "dex2jar-2.0.zip"); 166 | InputStream resourceAsStream = Main.class.getResourceAsStream("/dex2jar-2.0.zip"); 167 | BaseUtils.copyFile(resourceAsStream, zipFile); 168 | ZipUtils.unZip2CurrentDir(zipFile.getPath()); 169 | File filed = new File(zipFile.getPath().replace(".zip", "")); 170 | File shpath = new File(filed, "d2j-dex2jar.sh"); 171 | zipFile.delete(); 172 | path = shpath.getPath(); 173 | } 174 | 175 | return path; 176 | } 177 | 178 | public static String getApktoolShellPath() { 179 | 180 | String path; 181 | 182 | File apktoolDir = new File(BaseUtils.getLocalBinPath(), "apktool"); 183 | if (!apktoolDir.exists()) { 184 | apktoolDir.mkdirs(); 185 | } 186 | File vDir = new File(apktoolDir, "apktool-2.4.1"); 187 | File shFile = new File(vDir, "apktool"); 188 | if (vDir.exists() && shFile.exists()) { 189 | path = shFile.getPath(); 190 | } else { 191 | File zipFile = new File(apktoolDir, "apktool-2.4.1.zip"); 192 | InputStream resourceAsStream = Main.class.getResourceAsStream("/apktool-2.4.1.zip"); 193 | BaseUtils.copyFile(resourceAsStream, zipFile); 194 | ZipUtils.unZip2CurrentDir(zipFile.getPath()); 195 | File filed = new File(zipFile.getPath().replace(".zip", "")); 196 | File shpath = new File(filed, "apktool"); 197 | zipFile.delete(); 198 | path = shpath.getPath(); 199 | } 200 | 201 | return path; 202 | } 203 | 204 | public static String getJdJuiPath() { 205 | 206 | String path; 207 | 208 | File gui = new File(BaseUtils.getLocalBinPath(), "jd-gui-1.6.5.jar"); 209 | if (gui.exists()) { 210 | path = gui.getPath(); 211 | } else { 212 | File guiFile = new File(BaseUtils.getLocalBinPath(), "jd-gui-1.6.5.jar"); 213 | InputStream resourceAsStream = Main.class.getResourceAsStream("/jd-gui-1.6.5.jar"); 214 | BaseUtils.copyFile(resourceAsStream, guiFile); 215 | path = guiFile.getPath(); 216 | } 217 | 218 | return path; 219 | } 220 | 221 | 222 | private static void changeFolderPermission(File dirFile) { 223 | Set perms = new HashSet<>(); 224 | perms.add(PosixFilePermission.OWNER_READ); 225 | perms.add(PosixFilePermission.OWNER_WRITE); 226 | perms.add(PosixFilePermission.OWNER_EXECUTE); 227 | perms.add(PosixFilePermission.GROUP_READ); 228 | perms.add(PosixFilePermission.GROUP_WRITE); 229 | perms.add(PosixFilePermission.GROUP_EXECUTE); 230 | changeAllPermission(dirFile, perms); 231 | } 232 | 233 | private static void changeAllPermission(File file, Set perms) { 234 | 235 | if (file.isFile()) { 236 | changePermission(file, perms); 237 | } else { 238 | changePermission(file, perms); 239 | File[] files = file.listFiles(); 240 | for (File f : files) { 241 | if (f.isFile()) { 242 | changePermission(f, perms); 243 | } else { 244 | changeAllPermission(f, perms); 245 | } 246 | } 247 | } 248 | } 249 | 250 | 251 | private static void changePermission(File file, Set perms) { 252 | 253 | try { 254 | Path path = Paths.get(file.getAbsolutePath()); 255 | Files.setPosixFilePermissions(path, perms); 256 | } catch (Exception e) { 257 | e.printStackTrace(); 258 | } 259 | } 260 | 261 | 262 | public static boolean unPressApk(File apkFile, File dir) { 263 | String command = BaseUtils.getApktoolShellPath() + " d -f -o " + dir.getPath() + "_ApkDex2jar " + apkFile.getPath(); 264 | return execCommand(command); 265 | } 266 | 267 | 268 | public static boolean dex2jar(File dexFile) { 269 | 270 | File newFile = new File(dexFile.getParentFile(), dexFile.getName() + ".jar"); 271 | String command = BaseUtils.getDex2JarShellPath() + " -f -o " + newFile.getPath() + " " + dexFile.getPath(); 272 | return execCommand(command); 273 | } 274 | 275 | 276 | 277 | public static boolean execCommand(String command){ 278 | try { 279 | Runtime.getRuntime().exec(command).waitFor(); 280 | return true; 281 | } catch (Exception e) { 282 | e.printStackTrace(); 283 | } 284 | return false; 285 | } 286 | 287 | 288 | 289 | } 290 | -------------------------------------------------------------------------------- /src/main/java/xpp/apk2jar/FxViewUtil.java: -------------------------------------------------------------------------------- 1 | package xpp.apk2jar; 2 | 3 | import java.awt.Desktop; 4 | import java.net.URI; 5 | 6 | import javafx.geometry.Insets; 7 | import javafx.scene.control.Button; 8 | import javafx.scene.layout.Background; 9 | import javafx.scene.layout.BackgroundFill; 10 | import javafx.scene.layout.Border; 11 | import javafx.scene.layout.BorderStroke; 12 | import javafx.scene.layout.BorderStrokeStyle; 13 | import javafx.scene.layout.BorderWidths; 14 | import javafx.scene.layout.CornerRadii; 15 | import javafx.scene.layout.Region; 16 | import javafx.scene.paint.Color; 17 | import javafx.scene.paint.Paint; 18 | 19 | /** 20 | * Created by xupanpan on 3/17/17. 21 | */ 22 | public class FxViewUtil { 23 | 24 | 25 | public static Background getDefBackground() { 26 | 27 | return getBackground(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY); 28 | } 29 | 30 | public static Background getBackground(Paint color) { 31 | 32 | return getBackground(color, CornerRadii.EMPTY, Insets.EMPTY); 33 | } 34 | 35 | public static Background getBackground(Paint color, int corner) { 36 | 37 | return getBackground(color, new CornerRadii(corner), Insets.EMPTY); 38 | } 39 | 40 | public static Background getBackground(Paint color, CornerRadii corner) { 41 | 42 | return getBackground(color, corner, Insets.EMPTY); 43 | } 44 | 45 | 46 | public static Background getBackground(Paint color, CornerRadii corner, Insets insets) { 47 | return new Background(new BackgroundFill(color, corner, insets)); 48 | } 49 | 50 | 51 | public static Border getDefBorder() { 52 | 53 | return getBorder(Color.WHITE, 5, 1); 54 | } 55 | 56 | public static Border getBorder(Paint color) { 57 | 58 | return getBorder(color, 5, 1); 59 | } 60 | 61 | public static Border getBorder(Paint color, double corner, double borderWidths) { 62 | 63 | return getBorder(color, BorderStrokeStyle.SOLID, new CornerRadii(corner), new BorderWidths(borderWidths)); 64 | } 65 | 66 | 67 | public static Border getBorder(Paint color, BorderStrokeStyle style, CornerRadii corner, BorderWidths borderWidths) { 68 | return new Border(new BorderStroke(color, style, corner, borderWidths)); 69 | } 70 | 71 | 72 | public static void setDefTextFileStyle(Region textField) { 73 | textField.setBackground(FxViewUtil.getDefBackground()); 74 | textField.setBorder(FxViewUtil.getBorder(Color.WHITE, 3, 1)); 75 | } 76 | 77 | public static void setDefButtonStyle(Button btn) { 78 | setDefTextFileStyle(btn); 79 | btn.setTextFill(Color.WHITE); 80 | } 81 | 82 | public static void setButtonBlueStyle(Button btn) { 83 | btn.setBackground(FxViewUtil.getDefBackground()); 84 | btn.setBorder(FxViewUtil.getBorder(Color.LIGHTSKYBLUE, 3, 1)); 85 | btn.setTextFill(Color.LIGHTSKYBLUE); 86 | } 87 | 88 | 89 | public static void openSystemBrowser(String url) { 90 | 91 | if (Desktop.isDesktopSupported()) { 92 | try { 93 | URI uri = URI.create(url); 94 | Desktop desktop = Desktop.getDesktop(); 95 | if (desktop.isSupported(Desktop.Action.BROWSE)) { 96 | desktop.browse(uri); 97 | } 98 | 99 | } catch (Exception e) { 100 | e.printStackTrace(); 101 | } 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/xpp/apk2jar/Main.java: -------------------------------------------------------------------------------- 1 | package xpp.apk2jar; 2 | 3 | import javafx.application.Application; 4 | import javafx.application.Platform; 5 | import javafx.event.ActionEvent; 6 | import javafx.event.EventHandler; 7 | import javafx.geometry.Insets; 8 | import javafx.geometry.Pos; 9 | import javafx.scene.Scene; 10 | import javafx.scene.control.Button; 11 | import javafx.scene.control.Label; 12 | import javafx.scene.control.Separator; 13 | import javafx.scene.image.Image; 14 | import javafx.scene.image.ImageView; 15 | import javafx.scene.input.DragEvent; 16 | import javafx.scene.input.Dragboard; 17 | import javafx.scene.input.TransferMode; 18 | import javafx.scene.layout.Background; 19 | import javafx.scene.layout.BorderPane; 20 | import javafx.scene.layout.HBox; 21 | import javafx.scene.layout.VBox; 22 | import javafx.scene.paint.Color; 23 | import javafx.scene.text.Font; 24 | import javafx.stage.FileChooser; 25 | import javafx.stage.Stage; 26 | 27 | import java.io.File; 28 | import java.io.IOException; 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | import java.util.concurrent.ExecutorService; 32 | import java.util.concurrent.Executors; 33 | 34 | public class Main extends Application { 35 | private VBox centerBox; 36 | private static final double WIDTH = 600d; 37 | private static final double HIGTH = 500d; 38 | private Label label, statusLabel; 39 | private StringBuilder sb = new StringBuilder(); 40 | private ExecutorService executorService; 41 | BorderPane borderPane; 42 | private Stage mStage; 43 | 44 | public static void main(String[] args) { 45 | launch(args); 46 | } 47 | 48 | private List apkList; 49 | 50 | 51 | @Override 52 | public void start(Stage primaryStage) { 53 | mStage = primaryStage; 54 | borderPane = new BorderPane(); 55 | Scene scene = new Scene(borderPane, WIDTH, HIGTH); 56 | primaryStage.setWidth(WIDTH); 57 | primaryStage.setHeight(HIGTH); 58 | primaryStage.setResizable(false); 59 | primaryStage.setScene(scene); 60 | primaryStage.show(); 61 | 62 | initView(); 63 | BaseUtils.initBin(); 64 | } 65 | 66 | 67 | private void initView() { 68 | 69 | HBox topBox = new HBox(8); 70 | topBox.setAlignment(Pos.CENTER_LEFT); 71 | topBox.setBackground(FxViewUtil.getBackground(Color.valueOf("8F8F8F"))); 72 | topBox.setPadding(new Insets(10, 10, 10, 10)); 73 | Button chooseApk = new Button("选择apk文件"); 74 | chooseApk.setBackground(FxViewUtil.getBackground(Color.valueOf("DCDCDC"))); 75 | chooseApk.setOnAction(new EventHandler() { 76 | @Override 77 | public void handle(ActionEvent event) { 78 | FileChooser chooser = new FileChooser(); 79 | chooser.getExtensionFilters().addAll( 80 | new FileChooser.ExtensionFilter("可以选择多个apk", "*.apk") 81 | ); 82 | List files = chooser.showOpenMultipleDialog(mStage); 83 | findApk(files); 84 | } 85 | }); 86 | Button openDir = new Button("查看反编译文件"); 87 | openDir.setBackground(FxViewUtil.getBackground(Color.valueOf("DCDCDC"))); 88 | openDir.setOnAction(new EventHandler() { 89 | @Override 90 | public void handle(ActionEvent event) { 91 | if (apkList != null && apkList.size() > 0) { 92 | if (isExecute) { 93 | statusLabel.setText("请等待反编译完成!"); 94 | } else { 95 | File file = apkList.get(0); 96 | if (file.exists()) { 97 | File ff = new File(file.getParentFile(), file.getName().replace(".apk", "_ApkDex2jar")); 98 | try { 99 | statusLabel.setText(""); 100 | java.awt.Desktop.getDesktop().open(ff); 101 | } catch (IOException e) { 102 | e.printStackTrace(); 103 | statusLabel.setText("查看反编译文件,打开失败"); 104 | } 105 | } else { 106 | statusLabel.setText("apk文件不存在"); 107 | } 108 | } 109 | 110 | } else { 111 | statusLabel.setText("请先选择要反编译的apk文件!"); 112 | } 113 | } 114 | }); 115 | Button openGUI = new Button(); 116 | openGUI.setBackground(Background.EMPTY); 117 | openGUI.setOnAction(new EventHandler() { 118 | @Override 119 | public void handle(ActionEvent event) { 120 | try { 121 | java.awt.Desktop.getDesktop().open(new File(BaseUtils.getJdJuiPath())); 122 | } catch (IOException e) { 123 | e.printStackTrace(); 124 | } 125 | } 126 | }); 127 | 128 | ImageView lastBgView = new ImageView(new Image(Main.class.getResourceAsStream("/icon.png"))); 129 | lastBgView.setFitHeight(20); 130 | lastBgView.setFitWidth(20); 131 | openGUI.setGraphic(lastBgView); 132 | 133 | topBox.getChildren().addAll(chooseApk, openDir, openGUI); 134 | 135 | new Separator(); 136 | 137 | borderPane.setTop(topBox); 138 | 139 | centerBox = new VBox(); 140 | centerBox.setPadding(new Insets(10, 10, 10, 10)); 141 | centerBox.setSpacing(10); 142 | centerBox.setAlignment(Pos.TOP_LEFT); 143 | centerBox.setBackground(Background.EMPTY); 144 | 145 | label = new Label("请将apk文件拖动到此区域"); 146 | label.setFont(Font.font(16)); 147 | centerBox.getChildren().addAll(label); 148 | 149 | centerBox.setOnDragOver(new EventHandler() { 150 | public void handle(DragEvent event) { 151 | if (event.getGestureSource() != centerBox) { 152 | event.acceptTransferModes(TransferMode.ANY); 153 | } 154 | } 155 | }); 156 | 157 | centerBox.setOnDragDropped(new EventHandler() { 158 | public void handle(DragEvent event) { 159 | if (isExecute) { 160 | return; 161 | } 162 | Dragboard dragboard = event.getDragboard(); 163 | List files = dragboard.getFiles(); 164 | findApk(files); 165 | } 166 | }); 167 | borderPane.setCenter(centerBox); 168 | 169 | HBox bottomBox = new HBox(); 170 | bottomBox.setPadding(new Insets(5, 5, 5, 5)); 171 | statusLabel = new Label(); 172 | statusLabel.setFont(Font.font(12)); 173 | statusLabel.setTextFill(Color.valueOf("D81B60")); 174 | bottomBox.getChildren().addAll(statusLabel); 175 | borderPane.setBottom(bottomBox); 176 | 177 | } 178 | 179 | private void findApk(List files) { 180 | if (files != null && files.size() > 0) { 181 | if (apkList == null) { 182 | apkList = new ArrayList<>(); 183 | } else { 184 | apkList.clear(); 185 | } 186 | index = 0; 187 | apkList.addAll(files); 188 | executorService = Executors.newSingleThreadExecutor(); 189 | parseList(); 190 | } 191 | } 192 | 193 | private void showProgress() { 194 | Platform.runLater(new Runnable() { 195 | @Override 196 | public void run() { 197 | label.setText(sb.toString()); 198 | } 199 | }); 200 | } 201 | 202 | 203 | private void addProgressStr(String str) { 204 | sb.append(str + "\n"); 205 | showProgress(); 206 | } 207 | 208 | 209 | private int index = 0; 210 | 211 | private void parseList() { 212 | 213 | if (index < apkList.size()) { 214 | execute(apkList.get(index)); 215 | } else { 216 | addProgressStr("=======执行完成!========="); 217 | isExecute = false; 218 | index = 0; 219 | executorService.shutdown(); 220 | } 221 | 222 | } 223 | 224 | 225 | private boolean isExecute; 226 | 227 | private void execute(File apkFile) { 228 | sb.delete(0, sb.length()); 229 | 230 | if (!apkFile.exists()) { 231 | addProgressStr("文件不存在!"); 232 | stopExe(); 233 | return; 234 | } 235 | if (apkFile.isDirectory() || !apkFile.getName().endsWith(".apk")) { 236 | addProgressStr(apkFile.getName() + "不是apk文件!"); 237 | stopExe(); 238 | return; 239 | } 240 | 241 | isExecute = true; 242 | executorService.execute(new Executer(apkFile)); 243 | } 244 | 245 | 246 | private class Executer implements Runnable { 247 | 248 | private File apkFile; 249 | 250 | public Executer(File apkFile) { 251 | this.apkFile = apkFile; 252 | } 253 | 254 | @Override 255 | public void run() { 256 | addProgressStr("反编译:" + apkFile.getName()); 257 | 258 | addProgressStr("1.开始解压缩"); 259 | String unZipPath = ZipUtils.unZip(apkFile.getPath()); 260 | if (unZipPath == null) { 261 | addProgressStr(" 解压缩失败!"); 262 | stopExe(); 263 | return; 264 | } 265 | addProgressStr(" 解压缩完成!"); 266 | 267 | File dirFile = new File(unZipPath); 268 | if (!dirFile.exists()) { 269 | addProgressStr(" 解压后文件夹不存在!"); 270 | stopExe(); 271 | return; 272 | } 273 | 274 | File[] files = dirFile.listFiles(); 275 | if (files == null || files.length == 0) { 276 | addProgressStr(" 解压后文件夹无文件!"); 277 | stopExe(); 278 | return; 279 | } 280 | 281 | List dexFileList = new ArrayList<>(); 282 | 283 | for (File file : files) { 284 | if (file.isDirectory()) { 285 | BaseUtils.deleteDir(file); 286 | continue; 287 | } 288 | if (file.getName().endsWith(".dex")) { 289 | dexFileList.add(file); 290 | } else { 291 | file.delete(); 292 | } 293 | } 294 | if (dexFileList.size() == 0) { 295 | addProgressStr(" 解压后文件夹无dex文件!"); 296 | stopExe(); 297 | return; 298 | } else { 299 | addProgressStr(" 解压出" + dexFileList.size() + "个dex文件"); 300 | } 301 | 302 | addProgressStr("2.开始反编译......"); 303 | for (int i = 0; i < dexFileList.size(); i++) { 304 | addProgressStr(" 正在反编译第" + (i + 1) + "个dex文件"); 305 | BaseUtils.dex2jar(dexFileList.get(i)); 306 | } 307 | addProgressStr(" dex反编译完成!"); 308 | 309 | addProgressStr("3.解压res文件......"); 310 | 311 | boolean b = BaseUtils.unPressApk(apkFile, dirFile); 312 | String unPressStatus = b ? "成功" : "失败"; 313 | addProgressStr(" res解压" + unPressStatus); 314 | if (b) { 315 | BaseUtils.moveDirAndDelete(dirFile.getPath(), dirFile.getPath() + "_ApkDex2jar"); 316 | BaseUtils.deleteDir(dirFile); 317 | }else { 318 | 319 | } 320 | stopExe(); 321 | } 322 | } 323 | 324 | private void stopExe() { 325 | index++; 326 | parseList(); 327 | } 328 | 329 | } 330 | -------------------------------------------------------------------------------- /src/main/java/xpp/apk2jar/ZipUtils.java: -------------------------------------------------------------------------------- 1 | package xpp.apk2jar; 2 | 3 | import java.io.*; 4 | import java.util.Enumeration; 5 | import java.util.zip.ZipEntry; 6 | import java.util.zip.ZipFile; 7 | 8 | public class ZipUtils { 9 | 10 | private static final int buffer = 2048; 11 | 12 | /** 13 | * 解压Zip文件 14 | * 15 | * @param path 文件目录 16 | */ 17 | public static String unZip(String path) { 18 | int count = -1; 19 | String savepath = null; 20 | 21 | File file = null; 22 | InputStream is = null; 23 | FileOutputStream fos = null; 24 | BufferedOutputStream bos = null; 25 | 26 | savepath = path.substring(0, path.lastIndexOf(".")) + File.separator; //保存解压文件目录 27 | File dir = new File(savepath); 28 | if (dir.exists()) { 29 | BaseUtils.deleteDir(dir); 30 | } 31 | dir.mkdir(); //创建保存目录 32 | 33 | ZipFile zipFile = null; 34 | try { 35 | zipFile = new ZipFile(path); //解决中文乱码问题 36 | Enumeration entries = zipFile.entries(); 37 | 38 | while (entries.hasMoreElements()) { 39 | byte buf[] = new byte[buffer]; 40 | 41 | ZipEntry entry = (ZipEntry) entries.nextElement(); 42 | 43 | String filename = entry.getName(); 44 | boolean ismkdir = false; 45 | if (filename.lastIndexOf("/") != -1) { //检查此文件是否带有文件夹 46 | ismkdir = true; 47 | } 48 | filename = savepath + filename; 49 | 50 | if (entry.isDirectory()) { //如果是文件夹先创建 51 | file = new File(filename); 52 | file.mkdirs(); 53 | continue; 54 | } 55 | file = new File(filename); 56 | if (!file.exists()) { //如果是目录先创建 57 | if (ismkdir) { 58 | new File(filename.substring(0, filename.lastIndexOf("/"))).mkdirs(); //目录先创建 59 | } 60 | } 61 | file.createNewFile(); //创建文件 62 | 63 | is = zipFile.getInputStream(entry); 64 | fos = new FileOutputStream(file); 65 | bos = new BufferedOutputStream(fos, buffer); 66 | 67 | while ((count = is.read(buf)) > -1) { 68 | bos.write(buf, 0, count); 69 | } 70 | bos.flush(); 71 | bos.close(); 72 | fos.close(); 73 | 74 | is.close(); 75 | } 76 | 77 | zipFile.close(); 78 | 79 | } catch (IOException ioe) { 80 | ioe.printStackTrace(); 81 | } finally { 82 | try { 83 | if (bos != null) { 84 | bos.close(); 85 | } 86 | if (fos != null) { 87 | fos.close(); 88 | } 89 | if (is != null) { 90 | is.close(); 91 | } 92 | if (zipFile != null) { 93 | zipFile.close(); 94 | } 95 | } catch (Exception e) { 96 | e.printStackTrace(); 97 | } 98 | } 99 | 100 | return savepath; 101 | } 102 | 103 | 104 | public static String unZip2CurrentDir(String path) { 105 | int count = -1; 106 | String savepath = null; 107 | 108 | File file = null; 109 | InputStream is = null; 110 | FileOutputStream fos = null; 111 | BufferedOutputStream bos = null; 112 | 113 | savepath = new File(path).getParentFile().getPath(); 114 | 115 | ZipFile zipFile = null; 116 | try { 117 | zipFile = new ZipFile(path); //解决中文乱码问题 118 | Enumeration entries = zipFile.entries(); 119 | 120 | while (entries.hasMoreElements()) { 121 | byte buf[] = new byte[buffer]; 122 | 123 | ZipEntry entry = (ZipEntry) entries.nextElement(); 124 | 125 | String filename = entry.getName(); 126 | boolean ismkdir = false; 127 | if (filename.lastIndexOf(File.separator) != -1) { //检查此文件是否带有文件夹 128 | ismkdir = true; 129 | } 130 | filename = savepath +File.separator+ filename; 131 | 132 | if (entry.isDirectory()) { //如果是文件夹先创建 133 | file = new File(filename); 134 | file.mkdirs(); 135 | continue; 136 | } 137 | file = new File(filename); 138 | if (!file.exists()) { //如果是目录先创建 139 | if (ismkdir) { 140 | new File(filename.substring(0, filename.lastIndexOf(File.separator))).mkdirs(); //目录先创建 141 | } 142 | } 143 | file.createNewFile(); //创建文件 144 | 145 | is = zipFile.getInputStream(entry); 146 | fos = new FileOutputStream(file); 147 | bos = new BufferedOutputStream(fos, buffer); 148 | 149 | while ((count = is.read(buf)) > -1) { 150 | bos.write(buf, 0, count); 151 | } 152 | bos.flush(); 153 | bos.close(); 154 | fos.close(); 155 | 156 | is.close(); 157 | } 158 | 159 | zipFile.close(); 160 | 161 | } catch (IOException ioe) { 162 | ioe.printStackTrace(); 163 | } finally { 164 | try { 165 | if (bos != null) { 166 | bos.close(); 167 | } 168 | if (fos != null) { 169 | fos.close(); 170 | } 171 | if (is != null) { 172 | is.close(); 173 | } 174 | if (zipFile != null) { 175 | zipFile.close(); 176 | } 177 | } catch (Exception e) { 178 | e.printStackTrace(); 179 | } 180 | } 181 | 182 | return savepath; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/resources/apktool-2.4.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kadisting/ApkDex2jar/a23742108915a4661a42d64d703998954aee7a25/src/main/resources/apktool-2.4.1.zip -------------------------------------------------------------------------------- /src/main/resources/dex2jar-2.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kadisting/ApkDex2jar/a23742108915a4661a42d64d703998954aee7a25/src/main/resources/dex2jar-2.0.zip -------------------------------------------------------------------------------- /src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kadisting/ApkDex2jar/a23742108915a4661a42d64d703998954aee7a25/src/main/resources/icon.png -------------------------------------------------------------------------------- /src/main/resources/jd-gui-1.6.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kadisting/ApkDex2jar/a23742108915a4661a42d64d703998954aee7a25/src/main/resources/jd-gui-1.6.5.jar --------------------------------------------------------------------------------