好幾天沒有更新了,給關心這個系列的朋友們說聲抱歉。今天我們開始第二節,項目功能分析。因為這個背單詞軟件雖說功能比較簡單,但要真正實現起來也挺麻煩的。所以今天我們首先分析一下這個應用的功能,然后逐條慢慢實現。
PS:這款應用已經上線91助手,百度移動應用和應用寶,有興趣下來研究的可以百度搜索“悅詞i背單詞91”就可找到,我想真正用一下這個應用再看這個教程會有比較直觀的理解。好廢話不多講,進入正題。
功能分析:
功能1、查單詞。
實現方法:金山詞霸開放平台提供了一個開放API,通過Http訪問金山詞霸API提供的URL,可以獲得一個XML文檔,這個文檔就包含了要查詢的單詞的釋義、例句、音標、聲音的地址。通過解析該XML就可以獲得單詞的所有信息。
所用到的技術:
1)Http訪問網絡,並下載網絡文件
2)對SD卡進行操作,從SD卡(這里的SD卡是指手機默認的存儲卡,因為有些手機既 能能插SD卡又有內部存儲,這里不涉及這個問題)中讀取文件,和把從網絡下載的文件存進 SD卡。
3)解析XML文件
4)播放音樂(這個我后來封裝成了一個類,專門從網絡上查詢某個單詞,解析XML文 件,並且將下載的Mp3文件存在SD卡中,然后播放該Mp3文件)
5)數據庫,這里涉及到第一個數據庫,每查找一個單詞之后,就會將該單詞和釋義存儲 到一個SQLite數據庫中。這樣一來,下一次查找這個單詞時,先訪問數據庫,看看數據庫中 有沒有這個單詞,若有,就不用訪問網絡了。
功能2、背單詞。
實現方法:這里要用到第二個數據庫,背單詞的詞庫。我們需要一個存放單詞的TXT文件,通過解析這個TXT文件,將要背的單詞解析並存進數據庫中,然后根據一定的規律彈出單詞。
所用到的技術:
1)數據庫,同前面的數據庫技術相似;
2)對TXT文件中的單詞進行解析,字符串解析函數;
3)單詞狀態機,設計一定的算法,按照一定的規律彈出單詞,並進行背詞操作。(這個確實挺麻煩)
4)文件瀏覽,做一個簡易的文件瀏覽器,用於瀏覽SD卡中的單詞源文件txt,然后導入詞庫。這個屬於比較單獨的一個功能。
功能3、設置界面
用於對背詞軟件的一些參數進行設置,比如播放英音還是美音?前后兩個的單詞的背詞間隔是多少?導入詞庫設置?計划完成日期,以及當前課程的名稱設置。
功能4、金山詞霸每日一詞
實現方法:這里要用到第三個數據庫(就是數據庫的一個表table),用於記錄使用者的信息,如當前日期,當日期有更新時,應用會自動訪問網絡,獲取最新的額每日一句,並且呈現在主界面上;當前背單詞的完成進度,今天已經背的單詞,待完成的單詞,以及根據計划完成日期算出今天應該的單詞的任務,(關於這一點大家可以參考拓詞的實現效果)
所用到的技術:
1)數據庫技術;
2)Http訪問網絡,從網絡下載圖片並顯示。
注意這一個功能雖然看似簡單,但是,實現這個功能之后這個應用才顯得活起來^^
好了以上就是這款應用的主要框架。今天我們就來開始實現第一個功能:查單詞功能。
首先講一下金山詞霸API:瀏覽器輸入http://open.iciba.com/就會出現啊如下界面:
點擊詞霸查詞、並選擇文檔選項,http://open.iciba.com/?c=wiki,就可以看到如下界面:
再點擊查詞接口,就到了final page:
大家會注意到我們需要的東西:http://dict-co.iciba.com/api/dictionary.php?w=go&key=******** 這里的key是你自己申請的金山詞霸開放平台的API key,申請界面在這里:
網址隨便填即可
當你申請到金山API key之后,就可在瀏覽器輸出上面的地址:http://dict-co.iciba.com/api/dictionary.php?w=go&key=這里換成你的API key.
這里多說一句,為什么選金山詞霸API呢,其實有道詞典也有開放API,但它提供的數據遠不如金山詞霸,最重要的一點:金山提供的單詞的發音(金山真是夠仗義的)
例如我們查一個hello,就可以在瀏覽器輸入http://dict-co.iciba.com/api/dictionary.php?w=hello&key=這里換成你的API key. 就是把w=后面寫成hello,注意首字母必須小寫!
<dict num="219" id="219" name="219"> <key>hello</key> <ps>hə'ləʊ</ps> <pron> http://res-tts.iciba.com/5/d/4/5d41402abc4b2a76b9719d911017c592.mp3 </pron> <ps>hɛˈlo, hə-</ps> <pron> http://res.iciba.com/resource/amp3/1/0/5d/41/5d41402abc4b2a76b9719d911017c592.mp3 </pron> <pos>int.</pos> <acceptation>哈嘍,喂;你好,您好;表示問候;打招呼;</acceptation> <pos>n.</pos> <acceptation>“喂”的招呼聲或問候聲;</acceptation> <pos>vi.</pos> <acceptation>喊“喂”;</acceptation> <sent> <orig> This document contains Hello application components of each document summary of the contents. </orig> <trans>此文件包含組成Hello應用程序的每個文件的內容摘要.</trans> </sent> <sent> <orig> In the following example, CL produces a combined source and machine - code listing called HELLO. COD. </orig> <trans>在下面的例子中, CL將產生一個命名為HELLO. COD的源代碼與機器代碼組合的清單文件.</trans> </sent> <sent> <orig>Hello! Hello! Hello! Hello! Hel - lo!</orig> <trans>你好! 你好! 你好! 你好! 你好!</trans> </sent> <sent> <orig>Hello! Hello! Hello! Hello ! I'm glad to meet you.</orig> <trans>你好! 你好! 你好! 你好! 見到你很高興.</trans> </sent> <sent> <orig>Hello Marie. Hello Berlioz. Hello Toulouse.</orig> <trans>你好瑪麗, 你好柏里歐, 你好圖魯茲.</trans> </sent> </dict>
這個XML中含有幾個元素:key:單詞本身; ps:第一個是英音音標,第二個是美音音標; pron第一個是英音的MP3地址,第二個是美音的;pos 詞性; acception 詞義;sent 例句; orig例句英語;trans例句中文翻譯。我們要做的就是根據XML文件把這幾個元素解析出來。
另外這里就涉及到了另一個問題:能不能查中文呢?一開始我也沒搞出來,后來才發現了秘密查中文(或日文)需要在待查的詞前面加上一個下划線 _ 即如 :_你好
搜索你好:http://dict-co.iciba.com/api/dictionary.php?w=_你好&key=這里換成你的API key ,得到結果
<dict num="219" id="219" name="219"> <key>你好</key> <fy>Hello</fy> <sent> <orig>Hello! Hello! Hello! Hello! Hel - lo!</orig> <trans>你好! 你好! 你好! 你好! 你好!</trans> </sent> <sent> <orig>Hello! Hello! Hello! Hello ! I'm glad to meet you.</orig> <trans>你好! 你好! 你好! 你好! 見到你很高興.</trans> </sent> <sent> <orig>Hello Marie. Hello Berlioz. Hello Toulouse.</orig> <trans>你好瑪麗, 你好柏里歐, 你好圖魯茲.</trans> </sent> <sent> <orig> B Hi Gao. How are you doing? It's good to meet you. </orig> <trans>B你好,高. 你好 嗎 ?很高興認識你.</trans> </sent> <sent> <orig> Grant: Hi , Tess. Hi , Jenna. Are you doing your homework? </orig> <trans>格蘭特: 你好! 苔絲. 你好! 詹娜. 你們在做家庭作業 嗎 ?</trans> </sent> </dict>
注意fy元素就是查詢的意思,在解析XML文件時要考慮到這一點。
根據以上分析,我們首先需要訪問網絡,將這個xml文件下載下來並進行解析,下面我給出幾個工具類:
訪問網絡類,注意對應的要在AndroidManifest.xml文件中添加訪問網絡權限。
package com.carlos.internet; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; public class NetOperator { public final static String iCiBaURL1="http://dict-co.iciba.com/api/dictionary.php?w="; public final static String iCiBaURL2="&key=你申請的APIkey,不要忘記了替換!!"; 注意! public static InputStream getInputStreamByUrl(String urlStr){ InputStream tempInput=null; URL url=null; HttpURLConnection connection=null; //設置超時時間 try{ url=new URL(urlStr); connection=(HttpURLConnection)url.openConnection(); //別忘了強制類型轉換 connection.setConnectTimeout(8000); connection.setReadTimeout(10000); tempInput=connection.getInputStream(); }catch(Exception e){ e.printStackTrace(); } return tempInput; } }
這個類的功能就是根據給出的URL,從網絡獲得輸入流,iCiBaURL1 和iCiBaURL2是用於構成查單詞的URL的。iCiBaURL1+要查的單詞+iCiBaURL2 就構成了金山查單詞的URL
這里首先給出一個對象WordValue,該對象用來存放一個單詞的信息:
public class WordValue { public String word=null,psE=null,pronE=null,psA=null,pronA=null, interpret=null,sentOrig=null,sentTrans=null; public WordValue(String word, String psE, String pronE, String psA, String pronA, String interpret, String sentOrig, String sentTrans) { super(); this.word = ""+word; this.psE = ""+psE; this.pronE = ""+pronE; this.psA = ""+psA; this.pronA = ""+pronA; this.interpret = ""+interpret; this.sentOrig = ""+sentOrig; this.sentTrans = ""+sentTrans; } public WordValue() { super(); this.word = ""; //防止空指針異常 this.psE = ""; this.pronE = ""; this.psA = ""; this.pronA = ""; this.interpret = ""; this.sentOrig = ""; this.sentTrans = ""; } public ArrayList<String> getOrigList(){ ArrayList<String> list=new ArrayList<String>(); BufferedReader br=new BufferedReader(new StringReader(this.sentOrig)); String str=null; try{ while((str=br.readLine())!=null){ list.add(str); } }catch(Exception e){ e.printStackTrace(); } return list; } public ArrayList<String> getTransList(){ ArrayList<String> list=new ArrayList<String>(); BufferedReader br=new BufferedReader(new StringReader(this.sentTrans)); String str=null; try{ while((str=br.readLine())!=null){ list.add(str); } }catch(Exception e){ e.printStackTrace(); } return list; } public String getWord() { return word; } public void setWord(String word) { this.word = word; } public String getPsE() { return psE; } public void setPsE(String psE) { this.psE = psE; } public String getPronE() { return pronE; } public void setPronE(String pronE) { this.pronE = pronE; } public String getPsA() { return psA; } public void setPsA(String psA) { this.psA = psA; } public String getPronA() { return pronA; } public void setPronA(String pronA) { this.pronA = pronA; } public String getInterpret() { return interpret; } public void setInterpret(String interpret) { this.interpret = interpret; } public String getSentOrig() { return sentOrig; } public void setSentOrig(String sentOrig) { this.sentOrig = sentOrig; } public String getSentTrans() { return sentTrans; } public void setSentTrans(String sentTrans) { this.sentTrans = sentTrans; } public void printInfo(){ System.out.println(this.word); System.out.println(this.psE); System.out.println(this.pronE); System.out.println(this.psA); System.out.println(this.pronA); System.out.println(this.interpret); System.out.println(this.sentOrig); System.out.println(this.sentTrans); } }
大家從成員變量的名字就可以看出,這個對象中的成員就對應從XML文件中解析出來的各個元素,大家可以在上面XML的介紹中找對應。
接下來是一個ContentHandler對象,用於對XML的解析:
public class JinShanContentHandler extends DefaultHandler{ public WordValue wordValue=null; private String tagName=null; private String interpret=""; //防止空指針異常 private String orig=""; private String trans=""; private boolean isChinese=false; public JinShanContentHandler(){ wordValue=new WordValue(); isChinese=false; } public WordValue getWordValue(){ return wordValue; } @Override public void characters(char[] ch, int start, int length) throws SAXException { // TODO Auto-generated method stub super.characters(ch, start, length); if(length<=0) return; for(int i=start; i<start+length; i++){ if(ch[i]=='\n') return; } //去除莫名其妙的換行! String str=new String(ch,start,length); if(tagName=="key"){ wordValue.setWord(str); }else if(tagName=="ps"){ if(wordValue.getPsE().length()<=0){ wordValue.setPsE(str); }else{ wordValue.setPsA(str); } }else if(tagName=="pron"){ if(wordValue.getPronE().length()<=0){ wordValue.setPronE(str); }else{ wordValue.setPronA(str); } }else if(tagName=="pos"){ isChinese=false; interpret=interpret+str+" "; }else if(tagName=="acceptation"){ interpret=interpret+str+"\n"; interpret=wordValue.getInterpret()+interpret; wordValue.setInterpret(interpret); interpret=""; //初始化操作,預防有多個釋義 }else if(tagName=="orig"){ orig=wordValue.getSentOrig(); wordValue.setSentOrig(orig+str+"\n"); }else if(tagName=="trans"){ String temp=wordValue.getSentTrans()+str+"\n"; wordValue.setSentTrans(temp); }else if(tagName=="fy"){ isChinese=true; wordValue.setInterpret(str); } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { // TODO Auto-generated method stub super.endElement(uri, localName, qName); tagName=null; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { // TODO Auto-generated method stub super.startElement(uri, localName, qName, attributes); tagName=localName; } @Override public void endDocument() throws SAXException { // TODO Auto-generated method stub super.endDocument(); if(isChinese) return; String interpret=wordValue.getInterpret(); if(interpret!=null && interpret.length()>0){ char[] strArray=interpret.toCharArray(); wordValue.setInterpret(new String(strArray,0,interpret.length()-1)); //去掉解釋的最后一個換行符 } } }
這里要注意的是:關於XML解析的基本知識我不想講了,因為要講這樣細的話我三個月也完成不了這個系列。大家若有不懂的可以參考Mars 陳川老師的安卓開發視頻教程,非常基礎,入門必備。
關於XML解析基本就是用的他的的思路,但我想補充幾點細節的東西:
1)不僅在startElement()之后會調用character()方法,在endElement()之后也會調用character90方法;
2)在具有多層的元素如上面的sent元素里面有嵌套了orig 和trans元素,此時character()方法並不會嚴格地在startElement之后就立即調用;
以上兩點就會導致一個問題:會把多余的空行(換行符)也讀取進來,所以我在程序中添加了清除換行的代碼。請看注釋。
3)這里也考慮了查英文的翻譯結果是pos acception 而查中文的翻譯結果是 fy ,可以看看上面ContentHandler中character()方法,這個方法是核心。
接下來就是一個XMLParser對象,該對象把XML解析用的SAXParserFactory等獲取實例的工作封裝起來,有了這個對象,解析XML時只需創建一個XMLParser對象,調用的該對象的parseJinShanXml()方法即可。
public class XMLParser { public SAXParserFactory factory=null; public XMLReader reader=null; public XMLParser(){ try { factory=SAXParserFactory.newInstance(); reader=factory.newSAXParser().getXMLReader(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void parseJinShanXml(DefaultHandler content, InputSource inSource){ if(inSource==null) return; try { reader.setContentHandler(content); reader.parse(inSource); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void parseDailySentenceXml(DailySentContentHandler contentHandler, InputSource inSource){ if(inSource==null) return; try { reader.setContentHandler(contentHandler); reader.parse(inSource); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
大家可以看到還有一個 parseDailySentenceXml()方法,這是解析每日一句的,暫時不用管它。
那么如何根據一個單詞來獲取它的XML並進行解析呢?即如何進行調用?方法如下:
public WordValue getWordFromInternet(String searchedWord){ WordValue wordValue=null; String tempWord=searchedWord; if(tempWord==null&& tempWord.equals("")) return null; char[] array=tempWord.toCharArray(); if(array[0]>256) //是中文,或其他語言的的簡略判斷 tempWord="_"+URLEncoder.encode(tempWord); InputStream in=null; String str=null; try{ String tempUrl=NetOperator.iCiBaURL1+tempWord+NetOperator.iCiBaURL2; in=NetOperator.getInputStreamByUrl(tempUrl); //從網絡獲得輸入流 if(in!=null){ //new FileUtils().saveInputStreamToFile(in, "", "gfdgf.txt"); XMLParser xmlParser=new XMLParser(); InputStreamReader reader=new InputStreamReader(in,"utf-8"); //最終目的獲得一個InputSource對象用於傳入形參 JinShanContentHandler contentHandler=new JinShanContentHandler(); xmlParser.parseJinShanXml(contentHandler, new InputSource(reader)); wordValue=contentHandler.getWordValue(); wordValue.setWord(searchedWord); } }catch(Exception e){ e.printStackTrace(); } return wordValue; }
這是我從Dict類中截取的一個方法,注意這一個:tempWord="_"+URLEncoder.encode(tempWord); HttpURL中存在中文的話,會因為編碼的問題產生亂碼,所以先要對中文調用URLEncoder.encode()方法進行一下編碼,這樣才能得到正常的XML文件,這個問題當時困擾了我好久!
另外注意InputStream是二進制字節流,必須先經過InputStreamReader包裝成字符流在創建InputSource對象,否則會出現編碼異常,這個是我的一個經驗。
調用這個方法就可以從網上獲得要查詢的單詞的信息,並返回一個WordValue對象,然后我們再進行其它操作。
另外有一點必須強調:如果某個方法要訪問網絡,必須開辟一個子線程,在子線程里調用該方法!!!!!
今天就介紹到這里吧,這個項目要講完還得不少時間,畢竟我寫了三個星期。另外在在整個程序大體講完之前我不打算共享源代碼,希望大家能夠體諒。寫這個Blog的目的主要是分享一下經驗和思路,而不是分享代碼。