谈谈android客户端和网站数据交互的实现

android客户端一般不直接访问网站数据库,而是像浏览器一样发送get或者post请求,然后网站返回客户端能理解的数据格式,客户端解析这些数据,显示在界面上,常用的数据格式是xml和json。

可以理解客户端其实是一个你自己定义标记语言的浏览器,一般浏览器能解析的是html+css的数据,而android客户端能解析的是xml和json(或者都不是而是你自己定义的火星格式),服务端为了能满足客户端输出这种数据格式的需求,不得不专门针对客户端开发不同于浏览器访问的接口。

所以要开发一个网站的客户端你需要:

1.在客户端模拟get和post请求,请求最终还是通过http协议以url的形式发送

2.在客户单解析服务器返回的数据

3.在服务端根据请求生成相应的json数据(强烈建议使用json而不是xml,相同字符的json能返回更多的有用数据而且解析方便速度快)

这里要讨论的是1、2两点.

一、发送get或者post请求

虽然java本身的HttpURLConnection类完全可以实现get和post,但是非常麻烦,我们还是使用HttpClient这个开源代码来实现。

先讲讲get方法的实现:

不管是get方法还是post方法,都需要httpclient来执行,通过HttpClient httpClient = new``HttpClient()可以获得一个httpClient对象,httpClient对象可以给联网操作设定一些预定值,比如超时时间、字符集等。下面的函数在获得httpclient对象的同时设置预定值,同时返回这个httpclient,你可以在后面的代码中直接调用这个函数来获得设置好的httpclient对象:

private static HttpClient getHttpClient() {     
    HttpClient httpClient = new HttpClient();
    // 设置 HttpClient 接收 Cookie,用与浏览器一样的策略
    //httpClient.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
    // 设置 默认的超时重试处理策略
    httpClient.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler());
    // 设置 连接超时时间
    httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(TIMEOUT_CONNECTION);
    // 设置 读数据超时时间
    httpClient.getHttpConnectionManager().getParams().setSoTimeout(TIMEOUT_SOCKET);
    // 设置 字符集
    httpClient.getParams().setContentCharset(UTF_8);
    return httpClient;
}

httpclient对象可以执行get方法(GetMethod),GetMethod对象代表这个请求是通过get发出的,它的构造方法中包含请求地址url参数,下面是我写的一个能获得GetMethod对象的方法:

private static GetMethod getHttpGet(String url,String cookie, String userAgent) {
    GetMethod httpGet = new GetMethod(url);
    // 设置 请求超时时间
    httpGet.getParams().setSoTimeout(TIMEOUT_SOCKET);
    httpGet.setRequestHeader("Host", "www.netmoon.cn");
    httpGet.setRequestHeader("Connection","Keep-Alive");
    httpGet.setRequestHeader("Cookie", cookie);
    httpGet.setRequestHeader("User-Agent", userAgent);
    return httpGet;
}

其中GetMethod httpGet = new GetMethod(url);是必须的,下面的

httpGet.setRequestHeader("Host", "www.netmoon.cn");
httpGet.setRequestHeader("Connection","Keep-Alive");
httpGet.setRequestHeader("Cookie", cookie);
httpGet.setRequestHeader("User-Agent", userAgent);

可以暂时不理会。

然后Httpclient对象调用executeMethod()方法,并且将包含了url的GetMethod对象作为executeMethod()方法的参数,executeMethod其实就是发送了一个get请求;

int statusCode = httpClient.executeMethod(httpGet);

根据executeMethod()方法的返回码statusCode判断请求是否成功,如果成功则读取返回的数据。

BufferedReader reader = new BufferedReader(new InputStreamReader(httpGet.getResponseBodyAsStream()));
StringBuffer stringBuffer = new StringBuffer();
String str = "";
while((str = reader.readLine())!=null){
    stringBuffer.append(str);
}
responseBody = stringBuffer.toString();

responseBody中保存的就是get请求所返回的数据。

结合上面getHttpClient()方法和`getHttpGet`()方法,写一个完整的http get请求实现方法:

public static String http_get(String url) throws AppException {  
    HttpClient httpClient = null;
    GetMethod httpGet = null;    
    String responseBody = "";
    int time = 0;
    do{
        try
        {
            httpClient = getHttpClient();
            httpGet = getHttpGet(url);
            int statusCode = httpClient.executeMethod(httpGet);
            Log.i("http","url="+url);
            if (statusCode != HttpStatus.SC_OK) {
                throw AppException.http(statusCode);
            }
            else if(statusCode == HttpStatus.SC_OK){
                //
            }            
            BufferedReader reader = new BufferedReader(new InputStreamReader(httpGet.getResponseBodyAsStream()));
            StringBuffer stringBuffer = new StringBuffer();
            String str = "";
            while((str = reader.readLine())!=null){
                stringBuffer.append(str);
            }
            responseBody = stringBuffer.toString();
            //System.out.println("XMLDATA=====>"+responseBody);
            break;           
        } catch (HttpException e) {
            time++;
            if(time < RETRY_TIME) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e1) {}
                continue;
            }
            e.printStackTrace();
            throw AppException.http(e);
        } catch (IOException e) {
            time++;
            if(time < RETRY_TIME) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e1) {}
                continue;
            }
            // 发生网络异常
            e.printStackTrace();
            throw AppException.http(e);
        } finally {
            // 释放连接
            httpGet.releaseConnection();
            httpClient = null;
        }
    }while(time < RETRY_TIME);
    return responseBody;
}

其中AppException是我自定义的一个异常累,可以不用理会。

回到安卓开发中,假如客户端需要通过get请求访问一个url地址,则可以直接调用上面的 http_get(String url) 就可以得到服务器返回的数据了,你可以直接访问www.baidu.com,或者其他任意一网址,都能得到返回数据,不过这些数据是html的。

客户端解析返回的数据

通过上述方法去请求一个普通网址得到的是一般的html,html包含了很多与数据本身无关的东西,比如决定布局的

等标签,因此我们在android客户端开发中一般都是去请求能返回xml或者json数据的url,将这些json数据解析出来,得到我们要的数据,这里以一个第三方物流查询网站返回的json数据格式为例,讲解如何用java解析。

通过抓包我获得了某第三方物流查询网站的查询接口网址(声明,我仅仅是用于讲解未经过别人允许用作商业用途是不道德的):

http://www.kuaidi100.com/query

通过这个网址查询一个快递需要至少传入两个参数type:快递类型(EMS,圆通等),postid(快递编号)

这里涉及到将请求参数组合为一个完整请求地址的过程,因为客户端请求的生成都是动态的,比如博客的客户端,每篇文章的id不一样,url是变动的,往往需要将级别较大的url在代码运行的时候动态组合请求参数之后才能用,网上已经有了一个现成的方法:

private static String _MakeURL(String p_url, Map<String, Object> params) {
    StringBuilder url = new StringBuilder(p_url);
    if(url.indexOf("?")<0)
        url.append('?');
    for(String name : params.keySet()){
        url.append('&');
        url.append(name);
        url.append('=');
        url.append(String.valueOf(params.get(name)));
        //不做URLEncoder处理
        //url.append(URLEncoder.encode(String.valueOf(params.get(name)), UTF_8));
    }
    return url.toString().replace("?&", "?");
}

使用这个方法将物流请求的完整url组合的代码如下

       url = _MakeURL("http://www.kuaidi100.com/query", new HashMap<String, Object>(){{
put("type", "ems");
put("postid", "5036983946902");

       }});

url最后等效于http://www.kuaidi100.com/query?type=ems&postid=5036983946902

你可以将上面的url输入浏览器,就可以看到返回的json数据了。

运用java解析json:

其实别想太复杂,都是调用库函数实现的,具体解析请看下面的例子(包含了请求和解析,当中的某些方法用到了上面的代码,比如http_get方法和 _MakeUR)

private void getData(){
    final  Handler handler = new Handler(){
    public void handleMessage(Message msg) {      
        if(msg.what == 1){
            try{
                JSONArray array = new JSONArray( (String)msg.obj ); 
                for(int i=0; i<array.length(); i++){
                   JSONObject obj = array.getJSONObject(i);
                   PostItem item =new PostItem(obj.getString("context") , obj.getString("time"));
                   mListDatas.add(item);
               }  
                orderList(mListDatas);
                mAdapter.notifyDataSetChanged();
                mLayer.setVisibility(View.GONE);
            }catch (Exception e){
                e.printStackTrace();
            }
        }else if(msg.what == 0){
        }else if(msg.what == -1 && msg.obj != null){
        }
    }
 };
 new Thread(){
    public void run() {
        Message msg = new Message();
        String result = null;
        try {
            result = getPostInfo();
        } catch (AppException e) {
            e.printStackTrace();
            msg.what = -1;
            msg.obj = e;
        }
        String resultcode; //查询结果代码
        String message; //反馈消息
        String datas; //反馈消息
        JSONObject jsonObj;
        try {
            jsonObj = new JSONObject(result);
            resultcode  = jsonObj.getString("state");
            message = jsonObj.getString("message");
            if(Integer.parseInt(resultcode) == 3 || message.equals("ok")){
                msg.what = 1;
                datas = jsonObj.getString("data");
                msg.obj = datas;
            }else{
                msg.what = 0;
                msg.obj = message;
            }                         
        }catch (Exception e){ 
            e.printStackTrace();
            return;
        }
        handler.sendMessage(msg);
    }
    }.start();
 }

其中,getPostInfo()代码如下:

public static String getPostInfo()  {
    String url=AppContext.POST_URL;
    url = _MakeURL(url, new HashMap<String, Object>(){{
        put("type", "ems");
        put("postid", "5036983946902");
    }});
    try{
        return http_get(url);      
    }catch(Exception e){
        e.printStackTrace();
        return "erro";
    }
}

是不是很简单,这里用到了handle来处理网络数据。我们将解析到的结果都保存在了一个list中。

如何使用post就不需要讲了吧。