Java 精簡Jre jar打包成exe

 

 

 

#開始

  最近幾天都在忙一個事情,那就是嘗試精簡jre,我想不明白爲何甲骨文官方不出exe打包工具... java

  網絡上精簡jre的文章不少,可是原創的彷佛沒幾個,絕大多數都是轉發同一個博客, 這裏借鑑了很多思路以及方法 不知道還算不算原創了git

  這兩天收穫也是挺大的,在這裏和你們分享一下個人成果.以及原理....github

  謝謝網絡上大神們分享的教程受益良多 : )正則表達式

  雖然這篇博文比較長 可是其實是由於我太囉嗦了........並不難作 而且建議先看一遍大致流程shell

#成果

  

  這個是我前不久寫的一個聊天室的客戶端, 源碼在這裏 : https://github.com/LonelySinging/JavaChatRoom-client.git網絡

  整個jre以及成品jar包加起來打包成exe以後只有11MB了 不過 說真的 仍是以爲很大 由於jar包自己只有13KB 那麼也就是說 jre環境是真的很大了 完整的jre是197MB了 QAQeclipse

#開始

  那麼如今就開始吧jvm

  假設如今咱們有ide

  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;

    另外 代碼中的InputOutput那個會出錯 由於缺乏那個類 在上面原做者的連接裏面能夠找到InputOutput的java文件
    原做者的 InputOutput文件裏面的main()方法會出錯 刪除便可

    2019年2月15補充: 若是不行折騰 如下代碼已經編譯成jar包 能夠直接運行: 需求類複製工具 點擊下載便可 運行就知道怎麼用了

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 

    11.5: 將ort打包的方法還有一個 將cmd工做目錄切換到 有[ort]文件夾的哪裏 而後執行命令 [jar cvf rt.jar -C ort/ .] (方括號裏面的所有內容) 便可 方法來自評論區:水瓶座鬼才

  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. 這個就比較簡單了 使用現成的工具就能夠了 就是      winrar

  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  下載winrar 安裝

  8. 全選上圖全部的文件及文件夾 壓縮爲rar格式的壓縮包

  9. 用winrar打開rar壓縮包 而後選擇自解壓格式

 

 

 

 

 

10. 而後點擊肯定 再點肯定 就能夠獲得 在上圖高級設置裏面還能夠修改生成exe的圖標

  看到了嗎 整個環境加上 jar打包成exe以後只有11MB 不到了

  最終  雙擊運行一下 TAT 驚喜的發現 成功了!!!!!!!!!!!!!!!!!!!!!!!!!! 

  若是不能打包成功 請打開  https://blog.csdn.net/fz835304205/article/details/46942589

  能夠看看這個博文 謝謝這位博主分享經驗 ^_^

#期間可能遇到的問題

  1. class文件丟失太多了 這個主要是在生成class.txt文件的時候沒有把全部程序功能使用一遍形成的 所謂的使用包括點擊窗口 以及各個組件的功能

  2. 在往rt.jar文件裏面添加class文件的時候添加不進去 這個多是由於你的程序還在運行 須要關閉控制檯窗口

  3. 有時候可能往rt.jar裏面添加class文件也不能解決問題 那麼就把相關的整個文件夾替換進去

  4. 完成以後你的程序可能會有各類bug那麼 你得讓控制檯窗口出來 而後查看異常 看看丟失了那個class文件 把他 添加到rt.jar中相同的文件夾下

  5. 打包完了發現不能雙擊運行提示丟失DLL文件 那麼 你得安裝運行庫 百度運行庫合集就能夠了

  6. 這樣精簡的jre對其餘系統(win7 winxp)的兼容性比較差 甚至不能運行 解決方案就是在目標系統上運行一次 獲得class.txt 整合全部用到的class文件應該就能夠了(其實我這個沒有嘗試 是個思路) 

  7. 若是還有問題請在下方留言 有建議也請留言 謝謝 : )

   

感謝如下幾篇文章:

  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

  https://blog.csdn.net/fz835304205/article/details/46942589 (winrar打包方式)

  還有幾個沒收藏 謝謝了 ^_^

                      -------轉發請寫明出處 謝謝

相關文章
相關標籤/搜索