Java 精简Jre jar打包成exe

#开始

  最近几天都在忙一个事情,那就是尝试精简jre,我想不明白为什么甲骨文官方不出exe打包工具... 

  网络上精简jre的文章很多,但是原创的似乎没几个,绝大多数都是转发同一个博客, 这里借鉴了不少思路以及方法 不知道还算不算原创了

  这两天收获也是挺大的,在这里和大家分享一下我的成果.以及原理....

  谢谢网络上大神们分享的教程受益良多 : )

  虽然这篇博文比较长 但是实际上是因为我太啰嗦了........并不难做 并且建议先看一遍大体流程

#成果

  

  这个是我前不久写的一个聊天室的客户端, 源码在这里 : https://github.com/LonelySinging/JavaChatRoom-client.git

  整个jre以及成品jar包加起来打包成exe之后只有13MB了 不过 说真的 还是觉得很大 因为jar包本身只有13KB 那么也就是说 jre环境是真的很大了 完整的jre是197MB了 QAQ

#开始

  那么现在就开始吧

  假设现在我们有

  1. 需要打包的jar包 (我这里是client.jar)

  2. 一台装有Java环境的win10 64位电脑 JDK是1.8版本

  3. 能运行java代码的环境 (我这里是eclipse)

  4. 火绒剑(我是内置版的)

  5. notepad++ 

#分析jre目录 (我也才刚学习java没多久 对于原理性的东西了解的也不多 所以出错了别打我 QAQ)

  这是jdk1.8里面的jre目录 最主要的就是bin和lib目录

  #bin目录

  bin目录就是java.exe等几个命令所在地了,应该也是JVM虚拟机的本体所在, 我的里面是这样的

  bin目录

  这里有 java.exe 也即是启动jar的关键所在 以及一大堆的dll文件 精简bin目录的关键就是精简这些dll文件,思路就是满足java.exe的运行就可以了

  那么 接下来就是想办法知道java.exe到底依赖那些dll了

   

  #lib目录

  我的lib目录是这样的

  

  大家打开可以看到是一堆不知用途的文件以及各种jar包 那么这里的jar包里面就是我们在写代码的时候调用的类以及方法.

  还有字体文件(font文件夹里) 图标资源(images文件夹里面) 还有一个最大的rt.jar文件 大小居然都是60多MB了 所以 精简lib目录的核心就是精简rt.jar文件

  精简的思路依旧是删除不需要的就好了

#精简开始

  #精简bin目录

  1. 把准备打包的jar包复制到bin 

  2. 新建一个批处理文件(新建文本文档) 添加内容如下 (意思是 运行jar文件 并且把所有输出信息导出到class.txt文件里)

    

java -jar -verbose:class client.jar >> class.txt

  保存 更改名字为 run.bat (注意 这是改了后缀名的 如果没有后缀名 看下面)

  

  3. 双击运行 run.bat 然后对你的应用进行各种操作 是所有的功能都使用一遍(这里指的是,即使是一个编辑框 你也要尝试输入不同的东西 使用方向键操作光标等等 )这个非常重要!!!!!!!!!!! 为的是把所有使用到的类都输出到class.txt文件 (class.txt文件是用来精简lib目录的 这里不管他) 

  4. 打开火绒剑 (其他的行为检测软件也可以 作用相同就行)  点击进程 -> 然后选择java.exe 然后就可以找到java.exe加载的所有dll文件

    

  5. 新建一个在桌面上新建一个文件夹 然后 新建两个文件夹(bin和lib) 如图

    

   6. 在上面火绒剑里面查看所用到的dll以及他们的路径 找到他们 复制到新建的文件夹里的bin文件夹 得到如下 发现瞬间少了很多 (如果不是jre目录下的dll文件不复制)

    

  7. 在上图空白处按着shift然后右键菜单 在此处打开power shell 运行得到如下结果 原因是缺少jvm.cfg 这是JVM虚拟机的配置文件 然后直接复制完整的jre的lib目录下的所有文件到自己精简的lib目录下

  然后再次运行一次./java (值得注意的是 power shell的话 必须加上./ 才会从当前目录下找文件)

  做完这些之后 应该是可以在这里运行你准备打包的jar文件的 

  在bin目录下新建一个批处理文件 内容如下

java -jar client.jar
pause

  复制你的jar文件 在这里我的是client.jar 

  双击run.bat就可以 运行你的jar文件了 如果不行 就是jar文件的原因

    

    

  8. 如果都可以了 那么 bin目录终于精简完了 TAT (打字好累)

  #精简lib文件夹

  1. 这里我做的比较水...我对lib文件夹不是太了解 所以精简的方法可能比较低级

  2. 思路就是 上面不是可是已经能够运行jar文件了吗 那么 我们删除一个文件 就运行一次(这就是上面用批处理运行jar的原因 QAQ )

  3. 主要是删除jar文件 从大到小 还可以尝试删除一些其他的文件 比如图片文件 字体文件 不过可能会引起一些bug

  4.  这是我删除之后

  

  5. 这样的话 这是比较笨的方法 但是效果也还不错 然后我们发现 最大的就是rt.jar文件了 那么接下来就就是精简rt.jar

  6. 拓展方法 我在看博客的时候发现可以从class.txt文件里面找到用到的jar文件 但是,,,并不能记录不是jar的文件  有些文件可能是配置文件 丢失了就不能用了 不过有人会的话 请在下面留言一下 我更新

  #精简rt.jar文件

  1. 记得上面的到的class.txt文件吗,把他复制到与你新建的jre目录下 也即是与bin和lib目录同目录 (这里是为了方便 不复制也行)

  2. 现在 处理class.txt文件 用notepad++打开它 你会发现会有好多好多的东西TAT 我就被这个吓到了 但是没关系 接下来用notepad++的话 就会很简单

  3. 删除掉所有的 不带方括号的行 

  4. Ctrl + F 然后搜索  "[Opened "   然后把所有带"[Opened "个的行删除掉 (是删除一整行)

  5. Ctrl + F 然后搜索  "[Loaded "  点击计数,然后点击替换选项卡 全部替换 (替换的时候注意点一下计数,对比一下行数 应该是一样的,,,,)

  6. 点击"正则表达式"的单选框 在查找目标里面填上正则表达式 点击计数,然后 替换->全部替换

    

  7.这样 我们就得到了如下的东西 保存

  

  8. jar文件本质上是zip文件 那么 我们把rt.jar文件复制到class.txt同一个目录 这样的话 精简的jre目录下就有 bin、lib、class.txt、rt.jar文件或文件夹了 然后右键 "解压到rt/" (我这里用的360压缩) 这样我么就得到了一个rt文件夹 里面是rt.jar的完整内容

  9. 然后新建一个文件夹名字是ort文件夹 用来放需要的class文件

  10. 然后执行如下代码 这些代码是复制别人博客的 再次非常感谢原作者 [https://blog.csdn.net/kkkwewewaqsdfas/article/details/11829349?t=1489849302000] :

    代码可以直接运行 记得修改第一行 package ch1;

package ch1;

/**
*
*/


import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;

//import InputOutput;

/**
* @author knowyourself1010
*
*/
public class CopyUsefulClasses {
   // 文件拷贝

   private static boolean copy(String sourceFileLocation,
           String objectFileLocation, String fileName) {
       try // must try and catch,otherwise will compile error
       {
           if (sourceFileLocation.substring(sourceFileLocation.length() - 1) != "/") {
               sourceFileLocation += "/";
           }
           if ((objectFileLocation.substring(objectFileLocation.length() - 1)) != "/") {
               objectFileLocation += "/";
           }
           InputOutput inputOutput = new InputOutput();
           byte[] b = inputOutput
                   .DataOutputFully(sourceFileLocation, fileName);
           inputOutput.DataInputFully(objectFileLocation, fileName, b);
           return true; // if success then return true

       } catch (Exception e) {
           System.out.println("Error!");
           return false; // if fail then return false
       }
   }

   // 读取路径,copy
   private static int dealClass(String needfile, String sdir, String odir)
           throws IOException {
       int sn = 0; // 成功个数
       if (odir.length() > 0 && sdir.length() > 0) {

           if ((sdir.substring(sdir.length() - 1)) != "/") {
               sdir += "/";
           }
           if (odir.substring(odir.length() - 1) != "/") {
               odir += "/";
           }
           File usedclass = new File(needfile);
           if (usedclass.canRead()) {
               String line = null;
               LineNumberReader reader = new LineNumberReader(
                       new InputStreamReader(new FileInputStream(usedclass),
                               "UTF-8"));
               while ((line = reader.readLine()) != null) {
                   line = line.trim();
                   if (line.contains(".") || line.contains("/")) {
                       // format the direction from package name to path
                       String dir = line.replace(".", "/");
                       // filter the file name.
                       String tmpdir = dir.substring(0, dir.lastIndexOf("/"));
                       String sourceFileLocation = sdir + tmpdir;
                       String objectFileLocation = odir + tmpdir;
                       String fileName = dir.substring(
                               dir.lastIndexOf("/") + 1, dir.length())
                               + ".class";
                       File fdir = new File(objectFileLocation);
                       if (!fdir.exists())
                           fdir.mkdirs();
                       boolean copy_ok = copy(sourceFileLocation,
                               objectFileLocation, fileName);
                       if (copy_ok)
                           sn++;
                       else {
                           System.out.println(line);
                       }
                   } else {
                       sn = -1;
                   }
               }
           }
       }
       return sn;
   }

   /**
    * @param args
    */
   public static void main(String[] args) {
       // TODO Auto-generated method stub
       try {
           BufferedReader lineOfText = null;
           // get need classes log file direction
           System.out
                   .println("要读取的class.txt文件的绝对路径  :");
           lineOfText = new BufferedReader(new InputStreamReader(System.in));
           String needfile = lineOfText.readLine();
           // get source folder direction
           System.out
                   .println(needfile
                           + "\n输入jre/lib/rt.jar解压後的rt文件夹所在的路径 :");
           lineOfText = new BufferedReader(new InputStreamReader(System.in));
           String sdir = lineOfText.readLine();

           // get object folder direction
           System.out
                   .println(sdir
                           + "\n再输入ort(所要存放拷贝过来的有用的.class文件的文件夹):");
           lineOfText = new BufferedReader(new InputStreamReader(System.in));
           String odir = lineOfText.readLine();
           System.out.println(odir + "\n");
           int sn = dealClass(needfile, sdir, odir);
           System.out.print(sn);
       } catch (IOException e) {
           // TODO 自动生成 catch 块
           e.printStackTrace();
       }
   }
}

  运行的时候填写这样的内容

    

  首先输入usedClasses.txt的绝对路径,回车,在输入jre/lib/rt.jar解压後的rt文件夹所在的路径,回车,再输入ort(所要存放拷贝过来的有用的.class文件的文件夹)。然后等上几分中,期间会提示,你的产品程序所用到的jre不包含的类不存在,不用管,因为我们呢只拷贝rt文件中的.class文件 (这一段话复制自上面代码所在博客)

  11. 这样的话 在ort文件夹里面就有了很多文件夹 像是java、javax什么的 全选这些文件夹 右键 添加到 ort.zip 

  12. 得到ort.zip文件之后改名为rt.jar

  13. 删除lib目录下的rt.jar文件 用上面的rt.jar文件(也就是我们自己打包的)替换

  14. 在bin目录下运行run.bat文件 然后如果成功了就测试一下各种功能 确认没问题的话 jre环境就已经精简成功了 可以直接看下面的exe打包阶段了

  15 如果有错误 应该是这样的 那么应该是这样的 这里的话 明显是class文件缺失

     

  16. 在精简的rt.jar文件上面右键 用360压缩打开

  17. 打开之前解压的rt.jar文件夹(完整的rt.jar文件解包之后的rt文件夹)

  18. 根据上图中提示的丢失class文件的路径, 在360压缩窗口和rt文件夹中找到丢失的class文件 然后拖到360压缩窗口里 需要保证class文件的位置是一样的 (比如在rt文件夹里面的class文件路径是java/lang 目录下 那么在360压缩里也应该是这个目录)如果添加出错 那就关闭上图的窗口

  19.  再运行bin目录下的run.bat批处理文件 可能还会有错 有错的话 就继续上一个步骤 直到没有错误

  20. 这样的话 jre环境就已经精简成功了 : ) 接下来就是打包成exe了

#打包成exe

  1. 这个就比较简单了 使用现成的工具就可以了 就是       虚拟文件打包工具(Enigma Virtual Box)8.10   http://www.greenxf.com/soft/166563.html

  2. 不过呢 这个软件似乎是没有执行命令行的作用的 解决方法就是用C语言写一个启动器 QAQ  (其他语言也行 只要是个exe就行了) 但是呢 直接用C语言执行 java -jar client.jar 这个命令会有一个问题 就是会有一个控制台窗口 (我这里是图形界面 如果没有图形界面的话 那就不用考虑了)  这个据说用javaw 运行就没问题 不会出现控制台窗口 但是我失败了/QAQ 不知道为啥 所以接下来需要这样做 

  3. 编译一个exe文件 C语言代码如下  说明一下 C语言如果是控制台程序的话 运行的时候还是有控制台窗口的 所有建立的工程是win32工程 也就是默认带窗口的那种 如果有小伙伴不知道怎么弄 请在下面留言 我会补充一下

#include "stdafx.h"
#include "无窗口的vbs运行.h"

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)

{
    system("run.vbs");
    return 0;
}

  核心代码就是是system("run.vbs");

  4. 那么接下来就编写run.vbs 内容如下 作用是运行一个名为run.bat的批处理 并且把窗口隐藏起来

Set ws = CreateObject("Wscript.Shell")   
ws.run "cmd /c run.bat",vbhide

  5. 修改bin目录下的run.bat文件 修改内容如下 然后复制到与bin、lib文件夹同目录

cd bin
java -jar client.jar

  6. 上面两步完成了GUI界面Java程序 隐藏控制台窗口运行 然后把上面编译的启动器exe放在与bin、lib文件夹同目录 最终是这样的 双击run.vbs或者启动器.exe应该都是可以无控制台窗口运行的

  7. 终于到了最终打包的时候了 TAT  启动 打包工具 

    

  8. a. 主程序选择启动器 b.添加 c.添加文件夹递归 d. 找到你精简的jre目录是包含bin、lib、run.bat、run.vbs、启动器.exe的目录 e. 选择文件夹就默认的就行了点击确定

  9. 选择文件夹选项 勾上 压缩文件 复选框 以及 退出时删除文件 其实退出删除不太重要 不过如果文件比较多的话 不删除可能能够加快下次打开速度

  10. 然后就是点击 打包 然后等着就好了

#最终成果 QAQ 我激动的流泪了 

  

  看到了吗 整个环境加上 jar打包成exe之后只有13MB 不到了

  最终  双击运行一下 TAT 惊喜的发现 成功了!!!!!!!!!!!!!!!!!!!!!!!!!! 

#期间可能遇到的问题

  1. class文件丢失太多了 这个主要是在生成class.txt文件的时候没有把所有程序功能使用一遍造成的 所谓的使用包括点击窗口 以及各个组件的功能

  2. 在往rt.jar文件里面添加class文件的时候添加不进去 这个可能是因为你的程序还在运行 需要关闭控制台窗口

  3. 有时候可能往rt.jar里面添加class文件也不能解决问题 那么就把相关的整个文件夹替换进去

  4. 完成之后你的程序可能会有各种bug那么 你得让控制台窗口出来 然后查看异常 看看丢失了那个class文件 把他 添加到rt.jar中相同的文件夹下

  5. 打包完了发现不能双击运行提示丢失DLL文件 那么 你得安装运行库 百度运行库合集就可以了

  6. 如果还有问题请在下方留言 有建议也请留言 谢谢 : ) 

   

感谢以下几篇文章:

  https://blog.csdn.net/kkkwewewaqsdfas/article/details/11829349?t=1489849302000

  https://blog.csdn.net/xiaoping8411/article/details/6973887

  https://blog.csdn.net/ema1995cylove/article/details/52792361

  还有几个没收藏 谢谢了 ^_^

                      -------转发请写明出处 谢谢

  

猜你喜欢

转载自www.cnblogs.com/cjdty/p/9057845.html