欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android中使用HTTP服务的用法详解

程序员文章站 2024-03-02 12:00:40
在android中,除了使用java.net包下的api访问http服务之外,我们还可以换一种途径去完成工作。android sdk附带了apache的httpclient...

在android中,除了使用java.net包下的api访问http服务之外,我们还可以换一种途径去完成工作。android sdk附带了apache的httpclient api。apache httpclient是一个完善的http客户端,它提供了对http协议的全面支持,可以使用http get和post进行访问。下面我们就结合实例,介绍一下httpclient的使用方法。

我们新建一个http项目,项目结构如图:
Android中使用HTTP服务的用法详解
在这个项目中,我们不需要任何的activity,所有的操作都在单元测试类httptest.java中完成。

因为使用到了单元测试,所以在这里先介绍一下如何配置android中的单元测试。所有配置信息均在androidmanifest.xml中完成:

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
   package="com.scott.http" 
   android:versioncode="1" 
   android:versionname="1.0"> 
  <application android:icon="@drawable/icon" android:label="@string/app_name"> 
    <!-- 配置测试要使用的类库 --> 
    <uses-library android:name="android.test.runner"/> 
  </application> 
  <!-- 配置测试设备的主类和目标包 --> 
  <instrumentation android:name="android.test.instrumentationtestrunner" 
           android:targetpackage="com.scott.http"/> 
  <!-- 访问http服务所需的网络权限 --> 
  <uses-permission android:name="android.permission.internet"/> 
  <uses-sdk android:minsdkversion="8" /> 
</manifest>  

然后,我们的单元测试类需要继承android.test.androidtestcase类,这个类本身是继承junit.framework.testcase,并提供了getcontext()方法,用于获取android上下文环境,这个设计非常有用,因为很多android api都是需要context才能完成的。

现在让我们来看一下我们的测试用例,httptest.java代码如下:

package com.scot.http.test; 
 
import java.io.bytearrayoutputstream; 
import java.io.inputstream; 
import java.util.arraylist; 
import java.util.list; 
 
import junit.framework.assert; 
 
import org.apache.http.httpentity; 
import org.apache.http.httpresponse; 
import org.apache.http.httpstatus; 
import org.apache.http.namevaluepair; 
import org.apache.http.client.httpclient; 
import org.apache.http.client.entity.urlencodedformentity; 
import org.apache.http.client.methods.httpget; 
import org.apache.http.client.methods.httppost; 
import org.apache.http.entity.mime.multipartentity; 
import org.apache.http.entity.mime.content.inputstreambody; 
import org.apache.http.entity.mime.content.stringbody; 
import org.apache.http.impl.client.defaulthttpclient; 
import org.apache.http.message.basicnamevaluepair; 
 
import android.test.androidtestcase; 
 
public class httptest extends androidtestcase { 
   
  private static final string path = "http://192.168.1.57:8080/web"; 
   
  public void testget() throws exception { 
    httpclient client = new defaulthttpclient(); 
    httpget get = new httpget(path + "/testservlet?id=1001&name=john&age=60"); 
    httpresponse response = client.execute(get); 
    if (response.getstatusline().getstatuscode() == httpstatus.sc_ok) { 
      inputstream is = response.getentity().getcontent(); 
      string result = instream2string(is); 
      assert.assertequals(result, "get_success"); 
    } 
  } 
   
  public void testpost() throws exception { 
    httpclient client = new defaulthttpclient(); 
    httppost post = new httppost(path + "/testservlet"); 
    list<namevaluepair> params = new arraylist<namevaluepair>(); 
    params.add(new basicnamevaluepair("id", "1001")); 
    params.add(new basicnamevaluepair("name", "john")); 
    params.add(new basicnamevaluepair("age", "60")); 
    httpentity formentity = new urlencodedformentity(params); 
    post.setentity(formentity); 
    httpresponse response = client.execute(post); 
    if (response.getstatusline().getstatuscode() == httpstatus.sc_ok) { 
      inputstream is = response.getentity().getcontent(); 
      string result = instream2string(is); 
      assert.assertequals(result, "post_success"); 
    } 
  } 
   
  public void testupload() throws exception { 
    inputstream is = getcontext().getassets().open("books.xml"); 
    httpclient client = new defaulthttpclient(); 
    httppost post = new httppost(path + "/uploadservlet"); 
    inputstreambody isb = new inputstreambody(is, "books.xml"); 
    multipartentity multipartentity = new multipartentity(); 
    multipartentity.addpart("file", isb); 
    multipartentity.addpart("desc", new stringbody("this is description.")); 
    post.setentity(multipartentity); 
    httpresponse response = client.execute(post); 
    if (response.getstatusline().getstatuscode() == httpstatus.sc_ok) { 
      is = response.getentity().getcontent(); 
      string result = instream2string(is); 
      assert.assertequals(result, "upload_success"); 
    } 
  } 
   
  //将输入流转换成字符串 
  private string instream2string(inputstream is) throws exception { 
    bytearrayoutputstream baos = new bytearrayoutputstream(); 
    byte[] buf = new byte[1024]; 
    int len = -1; 
    while ((len = is.read(buf)) != -1) { 
      baos.write(buf, 0, len); 
    } 
    return new string(baos.tobytearray()); 
  } 
} 

因为此文件包含三个测试用例,所以我将会逐个介绍一下。

首先,需要注意的是,我们定位服务器地址时使用到了ip,因为这里不能用localhost,服务端是在windows上运行,而本单元测试运行在android平台,如果使用localhost就意味着在android内部去访问服务,可能是访问不到的,所以必须用ip来定位服务。

我们先来分析一下testget测试用例。我们使用了httpget,请求参数直接附在url后面,然后由httpclient执行get请求,如果响应成功的话,取得响应内如输入流,并转换成字符串,最后判断是否为get_success。

testget测试对应服务端servlet代码如下:

@override 
  protected void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { 
    system.out.println("doget method is called."); 
    string id = request.getparameter("id"); 
    string name = request.getparameter("name"); 
    string age = request.getparameter("age"); 
    system.out.println("id:" + id + ", name:" + name + ", age:" + age); 
    response.getwriter().write("get_success"); 
  } 

然后再说testpost测试用例。我们使用了httppost,url后面并没有附带参数信息,参数信息被包装成一个由namevaluepair类型组成的集合的形式,然后经过urlencodedformentity处理后调用httppost的setentity方法进行参数设置,最后由httpclient执行。

testpost测试对应的服务端代码如下:

@override 
  protected void dopost(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { 
    system.out.println("dopost method is called."); 
    string id = request.getparameter("id"); 
    string name = request.getparameter("name"); 
    string age = request.getparameter("age"); 
    system.out.println("id:" + id + ", name:" + name + ", age:" + age); 
    response.getwriter().write("post_success"); 
  } 

上面两个是最基本的get请求和post请求,参数都是文本数据类型,能满足普通的需求,不过在有的场合例如我们要用到上传文件的时候,就不能使用基本的get请求和post请求了,我们要使用多部件的post请求。下面介绍一下如何使用多部件post操作上传一个文件到服务端。

由于android附带的httpclient版本暂不支持多部件post请求,所以我们需要用到一个httpmime开源项目,该组件是专门处理与mime类型有关的操作。因为httpmime是包含在httpcomponents 项目中的,所以我们需要去apache官方网站下载httpcomponents,然后把其中的httpmime.jar包放到项目中去,如图:
Android中使用HTTP服务的用法详解
然后,我们观察testupload测试用例,我们用httpmime提供的inputstreambody处理文件流参数,用stringbody处理普通文本参数,最后把所有类型参数都加入到一个multipartentity的实例中,并将这个multipartentity设置为此次post请求的参数实体,然后执行post请求。服务端servlet代码如下:

package com.scott.web.servlet; 
 
import java.io.fileoutputstream; 
import java.io.ioexception; 
import java.util.iterator; 
import java.util.list; 
 
import javax.servlet.servletexception; 
import javax.servlet.http.httpservlet; 
import javax.servlet.http.httpservletrequest; 
import javax.servlet.http.httpservletresponse; 
 
import org.apache.commons.fileupload.fileitem; 
import org.apache.commons.fileupload.fileitemfactory; 
import org.apache.commons.fileupload.fileuploadexception; 
import org.apache.commons.fileupload.disk.diskfileitemfactory; 
import org.apache.commons.fileupload.servlet.servletfileupload; 
 
@suppresswarnings("serial") 
public class uploadservlet extends httpservlet { 
   
  @override 
  @suppresswarnings("rawtypes") 
  protected void dopost(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { 
    boolean ismultipart = servletfileupload.ismultipartcontent(request); 
    if (ismultipart) { 
      fileitemfactory factory = new diskfileitemfactory(); 
      servletfileupload upload = new servletfileupload(factory); 
      try { 
        list items = upload.parserequest(request); 
        iterator iter = items.iterator(); 
        while (iter.hasnext()) { 
          fileitem item = (fileitem) iter.next(); 
          if (item.isformfield()) { 
            //普通文本信息处理 
            string paramname = item.getfieldname(); 
            string paramvalue = item.getstring(); 
            system.out.println(paramname + ":" + paramvalue); 
          } else { 
            //上传文件信息处理 
            string filename = item.getname(); 
            byte[] data = item.get(); 
            string filepath = getservletcontext().getrealpath("/files") + "/" + filename; 
            fileoutputstream fos = new fileoutputstream(filepath); 
            fos.write(data); 
            fos.close(); 
          } 
        } 
      } catch (fileuploadexception e) { 
        e.printstacktrace(); 
      } 
    } 
    response.getwriter().write("upload_success"); 
  } 
} 

服务端使用apache开源项目fileupload进行处理,所以我们需要commons-fileupload和commons-io这两个项目的jar包,对服务端开发不太熟悉的朋友可以到网上查找一下相关资料。

介绍完上面的三种不同的情况之后,我们需要考虑一个问题,在实际应用中,我们不能每次都新建httpclient,而是应该只为整个应用创建一个httpclient,并将其用于所有http通信。此外,还应该注意在通过一个httpclient同时发出多个请求时可能发生的多线程问题。针对这两个问题,我们需要改进一下我们的项目:

1.扩展系统默认的application,并应用在项目中。

2.使用httpclient类库提供的threadsafeclientmanager来创建和管理httpclient。

改进后的项目结构如图:
Android中使用HTTP服务的用法详解
其中myapplication扩展了系统的application,代码如下:

package com.scott.http; 
 
import org.apache.http.httpversion; 
import org.apache.http.client.httpclient; 
import org.apache.http.conn.clientconnectionmanager; 
import org.apache.http.conn.scheme.plainsocketfactory; 
import org.apache.http.conn.scheme.scheme; 
import org.apache.http.conn.scheme.schemeregistry; 
import org.apache.http.conn.ssl.sslsocketfactory; 
import org.apache.http.impl.client.defaulthttpclient; 
import org.apache.http.impl.conn.tsccm.threadsafeclientconnmanager; 
import org.apache.http.params.basichttpparams; 
import org.apache.http.params.httpparams; 
import org.apache.http.params.httpprotocolparams; 
import org.apache.http.protocol.http; 
 
import android.app.application; 
 
public class myapplication extends application { 
 
  private httpclient httpclient; 
   
  @override 
  public void oncreate() { 
    super.oncreate(); 
    httpclient = this.createhttpclient(); 
  } 
   
  @override 
  public void onlowmemory() { 
    super.onlowmemory(); 
    this.shutdownhttpclient(); 
  } 
   
  @override 
  public void onterminate() { 
    super.onterminate(); 
    this.shutdownhttpclient(); 
  } 
   
  //创建httpclient实例 
  private httpclient createhttpclient() { 
    httpparams params = new basichttpparams(); 
    httpprotocolparams.setversion(params, httpversion.http_1_1); 
    httpprotocolparams.setcontentcharset(params, http.default_content_charset); 
    httpprotocolparams.setuseexpectcontinue(params, true); 
     
    schemeregistry schreg = new schemeregistry(); 
    schreg.register(new scheme("http", plainsocketfactory.getsocketfactory(), 80)); 
    schreg.register(new scheme("https", sslsocketfactory.getsocketfactory(), 443)); 
     
    clientconnectionmanager connmgr = new threadsafeclientconnmanager(params, schreg); 
     
    return new defaulthttpclient(connmgr, params); 
  } 
   
  //关闭连接管理器并释放资源 
  private void shutdownhttpclient() { 
    if (httpclient != null && httpclient.getconnectionmanager() != null) { 
      httpclient.getconnectionmanager().shutdown(); 
    } 
  } 
   
  //对外提供httpclient实例 
  public httpclient gethttpclient() { 
    return httpclient; 
  } 
} 

我们重写了oncreate()方法,在系统启动时就创建一个httpclient;重写了onlowmemory()和onterminate()方法,在内存不足和应用结束时关闭连接,释放资源。需要注意的是,当实例化defaulthttpclient时,传入一个由threadsafeclientconnmanager创建的一个clientconnectionmanager实例,负责管理httpclient的http连接。

然后,想要让我们这个加强版的“application”生效,需要在androidmanifest.xml中做如下配置:

<application android:name=".myapplication" ...> 
... 
</application> 

如果我们没有配置,系统默认会使用android.app.application,我们添加了配置,系统就会使用我们的com.scott.http.myapplication,然后就可以在context中调用getapplication()来获取myapplication实例。

有了上面的配置,我们就可以在活动中应用了,httpactivity.java代码如下:

package com.scott.http; 
 
import java.io.bytearrayoutputstream; 
import java.io.inputstream; 
 
import org.apache.http.httpresponse; 
import org.apache.http.httpstatus; 
import org.apache.http.client.httpclient; 
import org.apache.http.client.methods.httpget; 
 
import android.app.activity; 
import android.os.bundle; 
import android.view.view; 
import android.widget.button; 
import android.widget.toast; 
 
public class httpactivity extends activity { 
  @override 
  protected void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.main); 
    button btn = (button) findviewbyid(r.id.btn); 
    btn.setonclicklistener(new view.onclicklistener() { 
      @override 
      public void onclick(view v) { 
        execute(); 
      } 
    }); 
     
  } 
   
  private void execute() { 
    try { 
      myapplication app = (myapplication) this.getapplication(); //获取myapplication实例 
      httpclient client = app.gethttpclient();  //获取httpclient实例 
      httpget get = new httpget("http://192.168.1.57:8080/web/testservlet?id=1001&name=john&age=60"); 
      httpresponse response = client.execute(get); 
      if (response.getstatusline().getstatuscode() == httpstatus.sc_ok) { 
        inputstream is = response.getentity().getcontent(); 
        string result = instream2string(is); 
        toast.maketext(this, result, toast.length_long).show(); 
      } 
    } catch (exception e) { 
      e.printstacktrace(); 
    } 
  } 
   
  //将输入流转换成字符串 
  private string instream2string(inputstream is) throws exception { 
    bytearrayoutputstream baos = new bytearrayoutputstream(); 
    byte[] buf = new byte[1024]; 
    int len = -1; 
    while ((len = is.read(buf)) != -1) { 
      baos.write(buf, 0, len); 
    } 
    return new string(baos.tobytearray()); 
  } 
} 

点击“execute”按钮,执行结果如下:
Android中使用HTTP服务的用法详解

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。