和我一起開發Android應用(二)——“悅詞-i背單詞”項目功能分析


  好幾天沒有更新了,給關心這個系列的朋友們說聲抱歉。今天我們開始第二節,項目功能分析。因為這個背單詞軟件雖說功能比較簡單,但要真正實現起來也挺麻煩的。所以今天我們首先分析一下這個應用的功能,然后逐條慢慢實現。

     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的目的主要是分享一下經驗和思路,而不是分享代碼。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM