最近打算實現一個功能:在Android中加載顯示Word文檔,當然這里不是使用外部程序打開。查看一些資料后,打算采用poi實現,確定了以下實現思路:
- 將ftp中的word文檔下載到本地。
- 調用poi將word文檔轉成html格式並保存到本地
- 使用WebViewer加載顯示本地html
這里略去下載word文檔到本地不談,僅僅后面兩步,看起來還是比較簡單的,網上也有相關代碼。不過在使用過程中遇到了兩個大的問題,着實讓筆者費了一番腦筋。這里給大家列出來,希望能幫助大家節省些時間。
首先,說一下POI使用方法
- 下載poi-bin-3.9-20121203.tar.gz並解壓,提取查看Office文檔所依賴的包。
- word相關操作依賴於poi-3.9-20121203.jar和poi-scratchpad-3.9-20121203.jar兩個包,將其加入到Android程序的libs文件夾中。
- 將word轉html並保存到本地,然后使用WebViewer加載顯示本地html。整個代碼如下
package com.example.office; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.List; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.converter.PicturesManager; import org.apache.poi.hwpf.converter.WordToHtmlConverter; import org.apache.poi.hwpf.usermodel.Picture; import org.apache.poi.hwpf.usermodel.PictureType; import org.w3c.dom.Document; import android.os.Bundle; import android.app.Activity; import android.webkit.WebSettings; import android.webkit.WebView; public class MainActivity extends Activity { private String docPath = "/mnt/sdcard/documents/"; private String docName = "test.doc"; private String savePath = "/mnt/sdcard/documents/"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String name = docName.substring(0, docName.indexOf(".")); try { if(!(new File(savePath+name).exists())) new File(savePath+name).mkdirs(); convert2Html(docPath+docName,savePath+name+".html"); } catch (Exception e){ e.printStackTrace(); } //WebView加載顯示本地html文件 WebView webView = (WebView)this.findViewById(R.id.office); WebSettings webSettings = webView.getSettings(); webSettings.setLoadWithOverviewMode(true); webSettings.setSupportZoom(true); webSettings.setBuiltInZoomControls(true); webView.loadUrl("file:/"+savePath+name+".html"); } /** * word文檔轉成html格式 * */ public void convert2Html(String fileName, String outPutFile) throws TransformerException, IOException, ParserConfigurationException { HWPFDocument wordDocument = new HWPFDocument(new FileInputStream(fileName)); WordToHtmlConverter wordToHtmlConverter = new WordToHtmlConverter( DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()); //設置圖片路徑 wordToHtmlConverter.setPicturesManager(new PicturesManager() { public String savePicture( byte[] content, PictureType pictureType, String suggestedName, float widthInches, float heightInches ) { String name = docName.substring(0,docName.indexOf(".")); return name+"/"+suggestedName; } } ); //保存圖片 List<Picture> pics=wordDocument.getPicturesTable().getAllPictures(); if(pics!=null){ for(int i=0;i<pics.size();i++){ Picture pic = (Picture)pics.get(i); System.out.println( pic.suggestFullFileName()); try { String name = docName.substring(0,docName.indexOf(".")); pic.writeImageContent(new FileOutputStream(savePath+ name + "/" + pic.suggestFullFileName())); } catch (FileNotFoundException e) { e.printStackTrace(); } } } wordToHtmlConverter.processDocument(wordDocument); Document htmlDocument = wordToHtmlConverter.getDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream(); DOMSource domSource = new DOMSource(htmlDocument); StreamResult streamResult = new StreamResult(out); TransformerFactory tf = TransformerFactory.newInstance(); Transformer serializer = tf.newTransformer(); serializer.setOutputProperty(OutputKeys.ENCODING, "utf-8"); serializer.setOutputProperty(OutputKeys.INDENT, "yes"); serializer.setOutputProperty(OutputKeys.METHOD, "html"); serializer.transform(domSource, streamResult); out.close(); //保存html文件 writeFile(new String(out.toByteArray()), outPutFile); } /** * 將html文件保存到sd卡 * */ public void writeFile(String content, String path) { FileOutputStream fos = null; BufferedWriter bw = null; try { File file = new File(path); if(!file.exists()){ file.createNewFile(); } fos = new FileOutputStream(file); bw = new BufferedWriter(new OutputStreamWriter(fos,"utf-8")); bw.write(content); } catch (FileNotFoundException fnfe) { fnfe.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } finally { try { if (bw != null) bw.close(); if (fos != null) fos.close(); } catch (IOException ie) { } } } }
activity_main.xml如下
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <WebView android:id = "@+id/office" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="@string/hello_world" tools:context=".MainActivity"/> </RelativeLayout>
上面代碼中convert2Html用於將word文檔轉換html。下面的代碼則是使用WebViewer加載顯示本地html文件。
WebView webView = (WebView)this.findViewById(R.id.office); WebSettings webSettings = webView.getSettings(); webSettings.setLoadWithOverviewMode(true); webSettings.setSupportZoom(true); webSettings.setBuiltInZoomControls(true); webView.loadUrl("file:/"+savePath+name+".html");
下面來詳細說說存在的兩個問題
問題一:使用時有如下報錯:
09-23 17:40:12.350: W/System.err(29954): java.lang.NullPointerException
09-23 17:40:12.350: W/System.err(29954): at org.apache.poi.hwpf.converter.AbstractWordUtils.compactChildNodesR(AbstractWordUtils.java:146)
child2.getParentNode().removeChild( child2 );
i--;
更改為
if(child2.getParentNode()!=null){ child2.getParentNode().removeChild( child2 ); i--; }
然而這里需要重新編譯AbstractWordUtils.java類,將源工程下載后,找到AbstractWordUtils.java后,試驗了以下方法。
- 直接使用javac編譯,會提示很多類庫找不到
- 使用反編譯工具,反編譯后更改個文字還可以,更改代碼就有點勉強了。
- 將整個poi導入eclipse后重新編譯,工作量太大,沒有進行嘗試。
最后絞盡腦汁還是想到了一個相當簡單的方法(高手請飄過~),為此還得瑟了幾分鍾。具體如下:
- 將AbstractWordUtils.java,poi-3.9-20121203.jar,poi-scratchpad-3.9-20121203.jar放到同一目錄下,非必需。
- 通過引用已有的兩個包進行編譯,編譯命令如下:javac -cp d:\poi-3.9-20121203.jar;d:\poi-scratchpad-3.9-20121203.jar; d:\AbstractWordUtils.java ;編譯后生成AbstractWordUtils.class文件。
- 將poi-3.9-20121203.jar的后綴改成zip,將AbstractWordUtils.class拖到zip中覆蓋掉原有文件,然后將后綴zip改成jar即可。點擊此處下載更改好的poi-3.9-20121203.jar。
問題二:找不到HWPFDocument錯誤:java.lang.NoClassDefFoundError: org.apache.poi.hwpf.HWPFDocument或者內存不足問題:Unable to execute dex: Java heap space
上述問題取決於使用poi-3.9-20121203.jar,poi-scratchpad-3.9-20121203.jar包的不同方式。
如果將兩個jar包放在libs目錄下,就不會出現類找不到的錯誤;但很可能會出現內存不足的問題。筆者開始通過更改eclipse安裝文件夾下的eclipse.ini文件增大內存到512M,解決了內存不足的問題;后來加入到另外一個更大的程序后,又出現內存不足的問題,調整到800M解決。值得注意的是,如果把最大值調整到1024M,eclipse就無法啟動了(和你的機器相關),
這實在不能算是個好的解決方案。以下為筆者機器上修改后eclipse.ini文件,注意標紅的部分。
-startup plugins/org.eclipse.equinox.launcher_1.2.0.v20110502.jar --launcher.library plugins/org.eclipse.equinox.launcher.win32.win32.x86_1.1.100.v20110502 -showsplash org.eclipse.platform --launcher.XXMaxPermSize 256m --launcher.defaultAction openFile -vmargs -Xms256m -Xmx800m
如果通過使用
Add Library的方法加載jar包,就不會出現內存的問題,但是會出現類找不到的的問題:java.lang.NoClassDefFoundError: org.apache.poi.hwpf.HWPFDocument。雖然
csdn上有人通過將新增的user lib放置到最上面的方法解決了,但我試了下沒有生效,不得已還是采用了第一種方法。
這里也希望解決了該問題的人能夠留下評論或聯系方式,方便請教。
最后,補充幾點
- 目前poi只針對2003的doc格式,不支持2007及其以上的docx格式。
- 經測試發現,偶爾會出現的問題,不知如何解決。這里建議內部程序簡單預覽,外部程序打開word文檔詳細瀏覽的方式。
- poi和android API的版本或ADT版本有關;有的在java環境下良好,在android環境下就有問題,還請多多注意。
- 整個工程實例代碼請點擊此處。