好几天没有更新了,给关心这个系列的朋友们说声抱歉。今天我们开始第二节,项目功能分析。因为这个背单词软件虽说功能比较简单,但要真正实现起来也挺麻烦的。所以今天我们首先分析一下这个应用的功能,然后逐条慢慢实现。
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的目的主要是分享一下经验和思路,而不是分享代码。