├── .gitignore ├── README.md ├── libs └── packer-helper-1.0.5.jar ├── market_template.txt └── src ├── META-INF └── MANIFEST.MF └── com └── micro └── mcpt ├── ToolGui.form ├── ToolGui.java └── util ├── ConfigUtil.java └── FileUtil.java /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.gradle 3 | /build 4 | /captures 5 | /opt 6 | /out 7 | /apks 8 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **version1.0** 2 | 3 | ### 功能:打开指定的apk文件和需要构建的渠道列表,输出多渠道包。 4 | ### 导入项目:本项目是java 的IntelliJ IDEA工程,导入运行main方法即可打开主界面 5 | 6 | ### 使用:运行MultiChannelPackerTools.jar,根据提示操作即可。 7 | 命令行下调用(需要jre): 8 | ```shell 9 | java -jar MultiChannelPackerTools.jar 10 | ``` 11 | 12 | ###注意: 13 | - 读写渠道名称需要配合此工具原理来实现 14 | - 渠道列表文件规则:一行代表一个,#表示注释 15 | 16 | #### 参考文章: 17 | - [https://github.com/seven456/MultiChannelPackageTool](https://github.com/seven456/MultiChannelPackageTool) 18 | - [https://github.com/mcxiaoke/packer-ng-plugin](https://github.com/mcxiaoke/packer-ng-plugin) 19 | -------------------------------------------------------------------------------- /libs/packer-helper-1.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/act262/MultiChannelPackerTool/f262c4e2baf19b7a53f90322230b152455083806/libs/packer-helper-1.0.5.jar -------------------------------------------------------------------------------- /market_template.txt: -------------------------------------------------------------------------------- 1 | #官网下载 2 | official 3 | #360手机助手 4 | qihu360 5 | #百度手机助手 6 | baidu 7 | #腾讯应用宝 8 | yingyongbao 9 | #豌豆荚 10 | wandoujia 11 | #淘宝手机助手 12 | taobao 13 | #小米应用商店 14 | xiaomi 15 | #华为应用商店 16 | huawei 17 | #魅族应用商店 18 | meizu 19 | #联想乐商店 20 | lianxiang 21 | #oppo 22 | oppo 23 | #vivo 24 | vivo 25 | #金立手机助手 26 | jinli 27 | #乐视应用开放平台 28 | leshi 29 | #机锋市场 30 | jifeng 31 | #安智市场 32 | anzhi 33 | #搜狗手机助手 34 | sougou 35 | #三星 36 | samsung 37 | -------------------------------------------------------------------------------- /src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: com.micro.mcpt.ToolGui 3 | 4 | -------------------------------------------------------------------------------- /src/com/micro/mcpt/ToolGui.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 | -------------------------------------------------------------------------------- /src/com/micro/mcpt/ToolGui.java: -------------------------------------------------------------------------------- 1 | package com.micro.mcpt; 2 | 3 | import com.mcxiaoke.packer.helper.PackerNg; 4 | import com.micro.mcpt.util.ConfigUtil; 5 | 6 | import javax.swing.*; 7 | import javax.swing.filechooser.FileNameExtensionFilter; 8 | import java.awt.*; 9 | import java.awt.event.ActionEvent; 10 | import java.awt.event.ActionListener; 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.util.List; 14 | import java.util.zip.ZipFile; 15 | 16 | /** 17 | * APK多渠道读写工具 18 | * 19 | * @author act262@gmail.com 20 | */ 21 | public class ToolGui { 22 | private JButton mApkFileButton; 23 | private JPanel mRootPannel; 24 | private JTextField mApkFilePath; 25 | private JTextArea mConsoleArea; 26 | private JButton mBeginButton; 27 | private JTextField mMarketFilePath; 28 | private JButton mMarketButton; 29 | private JProgressBar progressBar1; 30 | private JScrollPane mScrollText; 31 | 32 | public ToolGui() { 33 | // 初始化上次记录的文件位置 34 | mApkFilePath.setText(getLastApkFilePath()); 35 | mMarketFilePath.setText(getLastMarketFilePath()); 36 | 37 | mApkFileButton.addActionListener(new ActionListener() { 38 | @Override 39 | public void actionPerformed(ActionEvent actionEvent) { 40 | openApkFile(); 41 | } 42 | }); 43 | 44 | mMarketButton.addActionListener(new ActionListener() { 45 | @Override 46 | public void actionPerformed(ActionEvent actionEvent) { 47 | openMarketFile(); 48 | } 49 | }); 50 | 51 | mBeginButton.addActionListener(new ActionListener() { 52 | @Override 53 | public void actionPerformed(ActionEvent actionEvent) { 54 | begin(); 55 | } 56 | }); 57 | } 58 | 59 | // 开始处理 60 | private void begin() { 61 | String apkFilePath = mApkFilePath.getText(); 62 | // 未选择文件 63 | if (apkFilePath == null || apkFilePath.isEmpty()) { 64 | prompt("APK文件不存在"); 65 | return; 66 | } 67 | String marketFilePath = mMarketFilePath.getText(); 68 | if (marketFilePath == null || marketFilePath.isEmpty()) { 69 | prompt("Market文件不存在"); 70 | return; 71 | } 72 | 73 | try { 74 | boolean check = check(apkFilePath); 75 | if (!check) { 76 | return; 77 | } 78 | } catch (IOException e) { 79 | e.printStackTrace(); 80 | // 文件操作发生错误 81 | mConsoleArea.setText("文件不存在"); 82 | return; 83 | } 84 | 85 | // 保存上次有效目录 86 | save(apkFilePath, marketFilePath); 87 | 88 | try { 89 | build(apkFilePath, marketFilePath); 90 | } catch (Exception e) { 91 | e.printStackTrace(); 92 | prompt("构建失败"); 93 | } 94 | } 95 | 96 | // 打开市场文件 97 | private void openMarketFile() { 98 | JFileChooser chooser = new JFileChooser(); 99 | chooser.setFileFilter(new FileNameExtensionFilter("Market Text Files", "txt")); 100 | // 记录上次选择位置 101 | File file = new File(mMarketFilePath.getText()); 102 | if (file.exists()) { 103 | chooser.setCurrentDirectory(file); 104 | } 105 | int returnVal = chooser.showOpenDialog(mRootPannel); 106 | if (returnVal == JFileChooser.APPROVE_OPTION) { 107 | File selectedFile = chooser.getSelectedFile(); 108 | mMarketFilePath.setText(selectedFile.getAbsolutePath()); 109 | } 110 | } 111 | 112 | /** 113 | * 打开Apk文件 114 | */ 115 | private void openApkFile() { 116 | // 打开APK文件 117 | JFileChooser chooser = new JFileChooser(); 118 | chooser.setFileFilter(new FileNameExtensionFilter("Android Apk Files", "apk")); 119 | // 记录上次选择位置 120 | File file = new File(mApkFilePath.getText()); 121 | if (file.exists()) { 122 | chooser.setCurrentDirectory(file); 123 | } 124 | 125 | int returnVal = chooser.showOpenDialog(mRootPannel); 126 | if (returnVal == JFileChooser.APPROVE_OPTION) { 127 | File selectedFile = chooser.getSelectedFile(); 128 | mApkFilePath.setText(selectedFile.getAbsolutePath()); 129 | } 130 | } 131 | 132 | /** 133 | * 弹出提示框 134 | * 135 | * @param message  提示信息 136 | */ 137 | private void prompt(String message) { 138 | Toolkit.getDefaultToolkit().beep(); 139 | JOptionPane.showMessageDialog(null, message, "WARNING", JOptionPane.ERROR_MESSAGE); 140 | } 141 | 142 | // 检查APK文件是否已经写入过数据 143 | private boolean check(String filePath) throws IOException { 144 | ZipFile zipFile = null; 145 | try { 146 | zipFile = new ZipFile(filePath); 147 | String name = zipFile.getName(); 148 | String comment = zipFile.getComment(); 149 | int size = zipFile.size(); 150 | System.out.println("name = " + name); 151 | System.out.println("size = " + size); 152 | System.out.println("comment = " + comment); 153 | 154 | if (comment != null) { 155 | prompt("该APK文件已经写入过信息"); 156 | return false; 157 | } 158 | } catch (IOException e) { 159 | throw e; 160 | } finally { 161 | if (zipFile != null) { 162 | zipFile.close(); 163 | } 164 | } 165 | return true; 166 | } 167 | 168 | /** 169 | * 开始构建 170 | */ 171 | private void build(String apkFilePath, String marketFilePath) throws Exception { 172 | mConsoleArea.setText(""); 173 | mConsoleArea.setText("build start\n"); 174 | File apkFile = new File(apkFilePath); 175 | if (!apkFile.exists()) { 176 | prompt("APK 文件不存在"); 177 | return; 178 | } 179 | 180 | File marketFile = new File(marketFilePath); 181 | if (!marketFile.exists()) { 182 | prompt("Market 文件不存在"); 183 | return; 184 | } 185 | 186 | List markets = PackerNg.Helper.parseMarkets(marketFile); 187 | if (markets == null || markets.isEmpty()) { 188 | prompt("Market NULL"); 189 | return; 190 | } 191 | 192 | // output dir 193 | final File outputDir = new File("apks"); 194 | if (!outputDir.exists()) { 195 | outputDir.mkdirs(); 196 | } else { 197 | PackerNg.Helper.deleteDir(outputDir); 198 | } 199 | 200 | new Thread(new Runnable() { 201 | @Override 202 | public void run() { 203 | String baseName = PackerNg.Helper.getBaseName(apkFile.getName()); 204 | String extName = PackerNg.Helper.getExtension(apkFile.getName()); 205 | 206 | // 处理进度 207 | int processed = 0; 208 | progressBar1.setMaximum(markets.size()); 209 | progressBar1.setValue(processed); 210 | for (final String market : markets) { 211 | final String apkName = baseName + "-" + market + "." + extName; 212 | File destFile = new File(outputDir, apkName); 213 | try { 214 | PackerNg.Helper.copyFile(apkFile, destFile); 215 | PackerNg.Helper.writeMarket(destFile, market); 216 | if (PackerNg.Helper.verifyMarket(destFile, market)) { 217 | ++processed; 218 | mConsoleArea.append("processed apk " + apkName); 219 | } else { 220 | destFile.delete(); 221 | mConsoleArea.append("failed to process " + apkName); 222 | } 223 | } catch (IOException e) { 224 | e.printStackTrace(); 225 | } 226 | progressBar1.setValue(processed); 227 | long percentage = Math.round(progressBar1.getValue() * 100.0 / progressBar1.getMaximum()); 228 | progressBar1.setString(percentage + "%"); 229 | mConsoleArea.append("\n"); 230 | } 231 | mConsoleArea.append("all " + processed + " processed apks saved to " + outputDir); 232 | mConsoleArea.append("\n"); 233 | mConsoleArea.append("build successful"); 234 | } 235 | }).start(); 236 | } 237 | 238 | private void handleProcess() { 239 | 240 | } 241 | 242 | // 保存配置信息 243 | private void save(String apkFilePath, String marketFilePath) { 244 | ConfigUtil.put(APK_PATH, apkFilePath); 245 | ConfigUtil.put(MARKET_PATH, marketFilePath); 246 | } 247 | 248 | private String getLastApkFilePath() { 249 | return ConfigUtil.get(APK_PATH); 250 | } 251 | 252 | private String getLastMarketFilePath() { 253 | return ConfigUtil.get(MARKET_PATH); 254 | } 255 | 256 | public static void main(String[] args) { 257 | SwingUtilities.invokeLater(new Runnable() { 258 | @Override 259 | public void run() { 260 | invokeUI(); 261 | } 262 | }); 263 | } 264 | 265 | private static void invokeUI() { 266 | JFrame frame = new JFrame("ToolGui"); 267 | frame.setContentPane(new ToolGui().mRootPannel); 268 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 269 | frame.setTitle("Android 多渠道构建工具"); 270 | frame.pack(); 271 | frame.setResizable(false); 272 | frame.setSize(400, 400); 273 | frame.setLocationRelativeTo(null); 274 | frame.setVisible(true); 275 | } 276 | 277 | public static final String APK_PATH = "apkFilePath"; 278 | public static final String MARKET_PATH = "marketFilePath"; 279 | } 280 | -------------------------------------------------------------------------------- /src/com/micro/mcpt/util/ConfigUtil.java: -------------------------------------------------------------------------------- 1 | package com.micro.mcpt.util; 2 | 3 | import java.io.*; 4 | import java.util.Properties; 5 | 6 | /** 7 | * @author act262@gmail.com 8 | */ 9 | public class ConfigUtil { 10 | public static final String FILE_NAME = "config.prop"; 11 | 12 | /** 13 | * 获取Properties对象 14 | * 15 | * @throws IOException 16 | */ 17 | public static Properties getProperties() throws IOException { 18 | File file = new File(FILE_NAME); 19 | if (!file.exists()) 20 | file.createNewFile(); 21 | 22 | Properties properties = new Properties(); 23 | InputStream inputStream = new BufferedInputStream(new FileInputStream(file)); 24 | properties.load(inputStream); 25 | return properties; 26 | } 27 | 28 | private static Properties properties; 29 | 30 | /** 31 | * 保存指定 key-value 32 | */ 33 | public static void put(String key, String value) { 34 | if (properties == null) { 35 | try { 36 | properties = getProperties(); 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | properties.setProperty(key, value); 42 | 43 | try { 44 | OutputStream outputStream = new FileOutputStream(FILE_NAME); 45 | properties.store(outputStream, null); 46 | } catch (FileNotFoundException e) { 47 | e.printStackTrace(); 48 | } catch (IOException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | 53 | /** 54 | * 获取指定key的value 55 | */ 56 | public static String get(String key) { 57 | if (properties == null) { 58 | try { 59 | properties = getProperties(); 60 | } catch (IOException e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | return properties.getProperty(key); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/com/micro/mcpt/util/FileUtil.java: -------------------------------------------------------------------------------- 1 | package com.micro.mcpt.util; 2 | 3 | import java.io.*; 4 | import java.nio.channels.FileChannel; 5 | 6 | /** 7 | * 文件工具类 8 | * 9 | * @author act262@gmail.com 10 | */ 11 | public class FileUtil { 12 | 13 | /** 14 | * 高效文件复制 15 | * 16 | * @param source  源文件 17 | * @param target  拷贝文件 18 | * @throws IOException  操作失败 19 | */ 20 | public static void fileCopy(File source, File target) throws IOException { 21 | FileChannel inChannel = null; 22 | FileChannel outChannel = null; 23 | try { 24 | inChannel = new FileInputStream(source).getChannel(); 25 | outChannel = new FileOutputStream(target).getChannel(); 26 | inChannel.transferTo(0, inChannel.size(), outChannel); 27 | } catch (IOException e) { 28 | throw e; 29 | } finally { 30 | closeStream(inChannel); 31 | closeStream(outChannel); 32 | } 33 | } 34 | 35 | /** 36 | * 关闭流 37 | */ 38 | public static void closeStream(Closeable closeable) { 39 | if (closeable != null) { 40 | try { 41 | closeable.close(); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | } 47 | } 48 | --------------------------------------------------------------------------------