Android中的网络技术总结

来源:互联网 发布:wap淘宝流量是什么意思 编辑:程序博客网 时间:2024/05/20 15:39

Android中的网络技术总结(大部分内容借用《第一行代码》第二版):

Android网络技术知识体系图

1.使用Http协议访问网络

  如果真的说要去深入分析 HTTP 协议,可能需要花费整整一本书的篇幅。这里我当然不会这干,因为毕竟你是跟着我学习 Android 开发的,而不是网站开发。对于 HTTP 协议,你只需要稍微了解一些就足够了,它的工作原理特别的简单,就是客户端向服务器发出一条HTTP请求,服务器收到请求之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理就可以了。是不是非常简单?一个浏览器的基本工作原理也就是如此了。比如说上一节中使用到的 WebView 控件,其实也就是我们向百度的服务器发起了一条 HTTP 请求,接着服务器分析出我们想要访问的是百度的首页,于是会把该网页的 HTML 代码进行返回,然后 WebView再调用手机浏览器的内核对返回的 HTML代码进行解析,最终将页面展示出来。
  简单来说,WebView已经在后台帮我们处理好了发送HTTP请求、接收服务响应、解析返回数据,以及最终的页面展示这几步工作,不过由于它封装得实在是太好了,反而使得我们不能那么直观地看出 HTTP协议到底是如何工作的。因此,接下来就让我们通过手动发送HTTP请求的方式,来更加深入地理解一下这个过程。

1.1 使用HTTPURLConnection

  在过去,Android发送Http请求一般有两种方式:HTTPURLConnection和HttpClient。不过由于HttpClient存在API数量较多,扩展困哪等缺点,Android团队越来越不建议我们使用这种方式。终于在Android6.0系统中,HttpClient的功能被完全移除了,标志着此功能被正式弃用,因此本小节我们就学习一下现在官方建议使用的HTTPURLConnection用法。
  首先需要获取到的实例,一般只需new出一个URL对象,并传入目标的网络地址,然后调用一下openConnection()方法即可,如下所示:

URL url = new URL("http://www.baidu.com");HttpURLConnection connection = (HttpURLConnection)url.openConnection();

  在得到HttpURLConnection的实例对象之后,我们可以设置一下HTTP请求所使用的方法。常用的方法主要有两个:GET和POST。GET表示希望从服务器那里获取数据。而POST则表示希望提交数据给服务器。写法如下:

connection.setRequestMethod("GET");

  接下来就是进行一系列的自由的定制了。比如设置连接超时,读取超时的毫秒数,以及服务器希望得到的一些消息头等。这部分内容根据自己的实际情况进行编写,示例写法如下:

 connection.setConnectTimeout(8000);connection.setReadTimeout(8000);

  之后再调用getInputStream()方法就可以获取到服务器返回的数据流了,剩下的任务就是对输入流进行读取,如下所示:

InputStream in = connection.getInputStream();

  最后可以调用disconnect()方法将这个HTTP请求关闭掉,如下所示:

connection.disconnect();  

  下面进行一个详细的例子进行学习,如何获取百度首页返回的HTML信息呢?代码如下所示:

    class MyThread extends  Thread{        @Override        public void run() {            String html = "";            BufferedReader br = null;            HttpURLConnection connection = null;            try {                URL url = new URL(str);///str为网址URI字符串                connection = (HttpURLConnection) url.openConnection();                connection.setRequestMethod("GET");                connection.setConnectTimeout(8000);                connection.setReadTimeout(8000);                InputStream in = connection.getInputStream();                br = new BufferedReader(new InputStreamReader(in));                String data = null;                while((data = br.readLine())!=null){                    html = html + data;                }            }catch (IOException e){            }finally {                if(br != null){                    try {                        br.close();                    } catch (IOException e) {                        e.printStackTrace();                    }                }                if(connection!=null){                    connection.disconnect();                }                //之后是执行得到服务器发送的数据html的处理逻辑            }        }    }

  由于网络请求是耗时的操作,所以网络请求逻辑应该放在线程当中去执行。

1.2 使用OkHttp

  当然我们并不是只能使用HTTPURLConnection,完全没有任何其他选择,事实上开源盛行的今天,有许多出色的网络通信库都可以替代原生的HTTPURLConnection,而其中OkHttp无疑是做得最出色的一个。
  OkHttp是由鼎鼎大名的Square()公司开发的,这个公司在开源事业上面贡献良多,除了OkHttp之外,还开发了像Picasso,Retrofit等著名的开源项目。OkHttp不仅在接口封装上做得简单易用,就连在底层实现上也是自成一派,比起原生的HTTPURLConnection,可以说有过之而无不及,现在已经成了广大Android开发者首选的网络通信库。那么本节我们就来学习一下OkHttp的用法,OkHttp的项目主页地址是:https://github.com/square/okhttp。
  在使用OkHttp之前,我们需要现在项目中添加OkHttp库的依赖。编辑app/build.gradle文件,在dependencies闭包中添加一下内容:

dependencies {    ...    compile 'com.squareup.okhttp3:okhttp:3.7.0'    ...}

  添加以上依赖会自动下载两个库,一个是OkHttp库,一个是Okio库,后者是前者的通信基础,下面我们来看看OkHttp的具体用法,首先需要创建一个OkHttpClient实例对象,如下所示:

OkHttpClient client = new OkHttpClient();  

  接下来,我们如果想要发起一条HTTP请求,就需要创建一个Request对象:

Request request = new Request.Builder.builder();  

  当然,上述的代码只是创建一个空的Request对象,并没有什么实际作用,我们可以在最终的build()方法之前连缀很多其他的方法来丰富这个Request对象。比如可以通过url()方法来设置目标的网络地址,如下所示:

        Request request = new Request.Builder()                .url("https://www.baidu.com")                .build();

  之后再调用OkHttpClient的newCall()方法创建一个Call对象,并调用它的execute()方法来发送请求并获取服务器返回的数据,如下所示:

Response response = client.newCall(request).execute();

  其中Response对象就是服务器返回的数据了,我们可以使用如下写法来得到返回的具体内容:

String responseData = response.body().string();

  如果是发起一条POST,请求会比GET请求稍微复杂一点,我们需要先构建出一个RequestBody对象来存放待提交的参数,如下所示:

RequestBody requestBody = new FormBody().Builder()                                .add("username","admin")                                .add("password",123456)                                .build();

  然后在Request.Builder中调用一下post()方法,并将ResquestBody对象传入:

Request request = new Request.Builder()                        .url("https://www.baidu.com")                        .post(requestBody)                        .build();

  接下来的操作就跟跟一样,调用execute()方法来发送请求并获取服务器返回的数据即可。示例代码如下所示:

class MyThread extends  Thread{    @Override    public void run() {        String html = "";        OkHttpClient okHttpClient = new OkHttpClient();        Request request = new Request.Builder()                .url(str)//str为网址URI字符串                .build();        try {            Response response = okHttpClient.newCall(request).execute();            html = response.body().string();        } catch (IOException e) {            e.printStackTrace();        }        Intent intent = new Intent(MainActivity.this,SecondActivity.class);        intent.putExtra("html",html);        startActivity(intent);    }}

2.解析网络数据

2.1 解析XML格式数据

  解析XML数据其实有很多种方式,本节我们学习比较常用的两种,Pull解析和SAX解析。Pull解析的步骤如下所示:

  Pull方式解析XML数据:

  首先,要获取一个XmlPullParseFactory实例对象,并借助这个实例对象得到XmlPullParser对象,然后调用XmlPullParser的setInput()方法将服务器返回的XML数据设置进去就可以开始解析了。解析的过程也非常简单,通过getEventType()方法可以得到当前的解析事件,然后在一个while循环中不断地进行解析,如果当前的解析事件不等于XmlPullParser.END_DOCUMENT,说明解析工作还没完成,调用next()方法得到下一个解析事件。
  在while循环中,我们通过getName()得到当前节点的名字,如果发现节点名等于id,name,或version,就调用nextText()方法来获取节点内具体的内容,每当解析完一个app节点后就把所有的数据记录下来。

        String XML_DATA = "<apps>\n" +                    "<app>\n" +                    "<id>1</id>\n" +                    "<name>Google Maps</name>\n" +                    "<version>1.0</version>\n" +                    "</app>\n" +                    "<app>\n" +                    "<id>2</id>\n" +                    "<name>Chrome</name>\n" +                    "<version>2.1</version>\n" +                    "</app>\n" +                    "<app>\n" +                    "<id>3</id>\n" +                    "<name>Google Play</name>\n" +                    "<version>2.3</version>\n" +                    "</app>\n" +                    "</apps>";            String data = "";            try {                XmlPullParserFactory xmlPullParserFactory = XmlPullParserFactory.newInstance();                XmlPullParser xmlPullParser = xmlPullParserFactory.newPullParser();                xmlPullParser.setInput(new StringReader(XML_DATA));                int eventType = xmlPullParser.getEventType();                String name= "";                String id = "";                String version = "";                while(eventType != XmlPullParser.END_DOCUMENT ){                    String nodeName = xmlPullParser.getName();                    switch (eventType){                        //开始解析某个节点                        case XmlPullParser.START_TAG:                            if("id".equals(nodeName)){                                id = xmlPullParser.nextText();                            }else if("name".equals(nodeName)){                                name = xmlPullParser.nextText();                            }else if("version".equals(nodeName)){                                version = xmlPullParser.nextText();                            }                            break;                            case XmlPullParser.END_TAG:                                if("app".equals(nodeName)) {                                    data = data + "节点id:" + id + ",节点name:" + name + ",节点version" + version + "\n";                                }                                break;                        default:                            break;                    }                    eventType = xmlPullParser.next();                }            } catch (XmlPullParserException|IOException e) {                e.printStackTrace();            } //解析成功的数据全部存放于data中了

  使用SAX方式解析XML数据

  Pull解析方式虽然好用,但是它不是唯一的选择,SAX解析也是一种非常好用的XML格式数据解析
方式,虽然它比Pull解析方式要复杂一些,但在语义方面更加清楚。
  通常情况下,我们都会去创建一个类去继承DefaultHandler,并重写父类的5个方法,如下所示:

public class MyHandler extends DefaultHandler {    @Override    public void startDocument() throws SAXException {        super.startDocument();    }    @Override    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {        super.startElement(uri, localName, qName, attributes);    }    @Override    public void characters(char[] ch, int start, int length) throws SAXException {        super.characters(ch, start, length);    }    @Override    public void endElement(String uri, String localName, String qName) throws SAXException {    }    @Override    public void endDocument() throws SAXException {        super.endDocument();    }}

  这五个方法一看就很清楚吧?startDocument()方法会在开始 XML 解析的时候调用,startElement()方法会在开始解析某个结点的时候调用,characters()方法会在获取结点中内容的时候调用,endElement()方法会在完成解析某个结点的时候调用,endDocument()方法会在完成整个 XML 解析的时候调用。其中,startElement()、characters()和 endElement()这三个方法是有参数的,从 XML 中解析出的数据就会以参数的形式传入到这些方法中。需要注意的是,在获取结点中的内容时,characters()方法可能会被调用多次,一些换行符也被当作内容解析出来,我们需要针对这种情况在代码中做好控制。

  SAX解析需要的DefaultHandler子类:

public class MyHandler extends DefaultHandler {    private Context mContext;    private String nodeName = "";//记录当前节点名    private StringBuilder id ;    private StringBuilder name ;    private StringBuilder version;    private String data = "";    MyHandler(Context context){        mContext = context;        id = new StringBuilder();        name = new StringBuilder();        version = new StringBuilder();    }    @Override    public void startDocument() throws SAXException {        //刚开始解析的时候调用        super.startDocument();    }    @Override    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {        super.startElement(uri, localName, qName, attributes);        //开始解析某个节点的时候调用        nodeName = localName;//记录当前的节点    }    @Override    public void characters(char[] ch, int start, int length) throws SAXException {        super.characters(ch, start, length);        if("id".equals(nodeName)){            id .append(ch, start, length);        }else if("name".equals(nodeName)){            name.append(ch, start, length);        }else if("version".equals(nodeName)){            version.append(ch, start, length);        }    }    @Override    public void endElement(String uri, String localName, String qName) throws SAXException {        //结束当前节点的时候调用        if(localName.equals("app")){            data = data + "节点id:" + id.toString().trim() + ",节点name:" + name.toString().trim() + ",节点version" + version.toString().trim() + "\n";            //清空id,name,version            id.setLength(0);            name.setLength(0);            version.setLength(0);        }    }    @Override    public void endDocument() throws SAXException {        super.endDocument();        Intent intent = new Intent(mContext,SecondActivity.class);        intent.putExtra("html",data);        mContext.startActivity(intent);    }}

  解析时的代码:

           String XML_DATA = "<apps>\n" +                    "<app>\n" +                    "<id>1</id>\n" +                    "<name>Google Maps</name>\n" +                    "<version>1.0</version>\n" +                    "</app>\n" +                    "<app>\n" +                    "<id>2</id>\n" +                    "<name>Chrome</name>\n" +                    "<version>2.1</version>\n" +                    "</app>\n" +                    "<app>\n" +                    "<id>3</id>\n" +                    "<name>Google Play</name>\n" +                    "<version>2.3</version>\n" +                    "</app>\n" +                    "</apps>";            try {                SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();                XMLReader xmlReader = saxParserFactory.newSAXParser().getXMLReader();                MyHandler myHandler = new MyHandler(MainActivity.this);                xmlReader.setContentHandler(myHandler);                xmlReader.parse(new InputSource(new StringReader(XML_DATA)));            }catch (SAXException|ParserConfigurationException e){            } catch (IOException e) {                e.printStackTrace();            }

2.2 解析JSON格式数据

  现在你已经掌握了XML格式数据的解析,那么接下来,我们再来学习一下JSON数据格式的解析,与XML格式数据相比,JSON格式数据的主要优势在于它的体积更小,在网络传输的时候可以更省流量,但缺点在于,它的语义性比较差,看起来不如XML格式数据直观。JSON格式数据解析的常用的解析方式由JSONObject(官方提供)和GSON(谷歌开源库)两种,当然还有些其他诸如:Jackson,FastJSON也不错,下面我们来学习一下JSONObject(官方提供)和GSON(谷歌开源库)这两种解析方式吧!

  JSONObject方式解析JSON格式数据:

  String JSON_DATA = "[{\"id\":\"5\",\"version\":\"5.5\",\"name\":\"Angry Birds\"},\n" +                    "{\"id\":\"6\",\"version\":\"7.0\",\"name\":\"Clash of Clans\"},\n" +                    "{\"id\":\"7\",\"version\":\"3.5\",\"name\":\"Hey Day\"}]";            String data = "";            try {                JSONArray jsonArray = new JSONArray(JSON_DATA);把JSON格式数据交给JSONArray进行解析                for(int i=0;i<jsonArray.length();i++){                    //通过JSONArray的getJSONObject()方法获取具体的JSONObject,可以说一个JSONObject就是一条数据                     JSONObject jsonObject = jsonArray.getJSONObject(i);                    //解析每一条数据的具体属性                    String id = jsonObject.getString("id");                    String name = jsonObject.getString("name");                    String version = jsonObject.getString("version");                    data = data + "节点id:" + id + ",节点name:" + name + ",节点version" + version + "\n";                }            }catch (Exception e){            }            Intent intent = new Intent(MainActivity.this,SecondActivity.class);            intent.putExtra("html",data);            startActivity(intent);

  使用GSON方式解析JSON格式数据:

  如果你认为JSONObject来解析JSON数据已经非常简单了,那你就太容易满足了。谷歌提供的GSON开源库可以让你对JSON格式数据的解析简单到你不敢想象的地步,那我们是肯定不能错过这次学习的机会。
  首先第一步就是在项目的依赖库中添加GSON的依赖库:

dependencies {    ...    compile 'com.google.code.gson:gson:2.8.0'    ...}

  那么GSON库究竟神奇在哪里?其实它主要就是可以把一段JSON格式数据的字符串自动映射成一个对象,从而不需要我们再手动去编写代码进行解析了。
  比如说一段 JSON 格式的数据如下所示:

{"name":"Tom","age":20}

  那我们就可以定义一个 Person类,并加入 name 和 age 这两个字段,然后只需简单地调
用如下代码就可以将 JSON 数据自动解析成一个 Person 对象了:

Gson gson = new Gson();Person person = gson.fromJson(jsonData, Person.class);

  如果需要解析的是一段 JSON数组会稍微麻烦一点,我们需要借助 TypeToken 将期望解
析成的数据类型传入到 fromJson()方法中,如下所示:

List<Person> people = gson.fromJson(jsonData, new TypeToken<List<Person>>(){}.getType());

  好了,基本的用法就是这样,下面就让我们来真正地尝试一下吧。首先新增一个App类,并加入 id、name 和 version 这三个字段,有些复杂的JSON数据格式手动封装一个类出来是相当耗时又耗力的,这里使用一个Android Studio中带的一个工具就可以把JSON数据格式自动解析成一个封装类,如何安装和使用这个工具,看以下链接:

http://www.cnblogs.com/tianmanyi/p/6028624.html

  通过此工具,我们成功得到一个与我们要解析的JSON格式数据对应的App类,其代码如下所示:

public class App {    /**     * id : 5     * version : 5.5     * name : Angry Birds     */    private String id;    private String version;    private String name;    public String getId() {        return id;    }    public void setId(String id) {        this.id = id;    }    public String getVersion() {        return version;    }    public void setVersion(String version) {        this.version = version;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}

  接下来就是进行GSON解析的代码的编写了:

String JSON_DATA = "[{\"id\":\"5\",\"version\":\"5.5\",\"name\":\"Angry Birds\"},\n" +                    "{\"id\":\"6\",\"version\":\"7.0\",\"name\":\"Clash of Clans\"},\n" +                    "{\"id\":\"7\",\"version\":\"3.5\",\"name\":\"Hey Day\"}]";            String data = "";            Gson gson = new Gson();            List<App> appList = gson.fromJson(JSON_DATA,new TypeToken<List<App>>(){}.getType());            for(App app:appList){                data = data + "节点id:" + app.getId() + ",节点name:" + app.getName() + ",节点version" + app.getVersion() + "\n";            }            Intent intent = new Intent(MainActivity.this,SecondActivity.class);            intent.putExtra("html",data);            startActivity(intent);

3.WebView的用法

  有时候我们可能碰到一些比较特殊的需求,比如说要求在应用程序中显示一些网页。相信每个人都知道,加载和显示网页通常都是浏览器的任务,但是需求里又明确指出,不允许打开系统浏览器,而我们当然也不可能自己去编写一个浏览器出来,这时应该怎么办呢?
  不用担心,Android早就已经考虑了这种需求,并提供了一个WebView控件,借助它我们就可以在自己的应用程序里嵌入一个浏览器,从而非常轻松的展示各种各样的网页。
  WebView的用法也是非常的简单,下面我们通过一个例子来学习一下吧!比如:让我们的应用显示百度首页搜索引擎的例子:

  布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"              android:layout_width="match_parent"              android:layout_height="match_parent" >             <WebView                 android:id="@+id/web_view"                 android:layout_width="match_parent"                 android:layout_height="match_parent" /></LinearLayout>

  Java代码部分:

public class MainActivity extends AppCompatActivity {    private WebView webView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        webView = (WebView) findViewById(R.id.web_view);        webView.getSettings().setJavaScriptEnabled(true);//让WebView支持JavaScript脚本        webView.setWebViewClient(new WebViewClient());//这段代码的作用是,当需要从一个网页跳转到另一个网页时,我们希望目标网页仍然在当前WebView中显示,而不是打开系统浏览器。        webView.loadUrl("https://www.baidu.com/");//展示百度首页    }    @Override    public boolean onKeyDown(int keyCode, KeyEvent event) {        if(event.getKeyCode() == KeyEvent.KEYCODE_BACK ){            if(webView.getUrl().equals("https://www.baidu.com/")){                finish();            }else {                webView.loadUrl("https://www.baidu.com/");            }        }        return true;    }}

最后别忘记了添加网络访问的权限:< uses-permission android:name=”android.permission.INTERNET” />

原创粉丝点击