您的位置:首页 > 理论基础 > 计算机网络

浅谈android网络编程

2016-01-08 22:19 836 查看
写在前面的话:最近有点忙,终于能写个博客了,自己做了几个关于android网络编程的demo,下面跟各位分享下,请各位批评指正,下面言归正传

正文:在一个android应用中,网络部分的编程应该是整个APP里涉及到的最多的内容之一,可能也是最难的之一。说它多,毋庸置疑,当今的APP如果没有网络做支撑,任何内容将是死水一潭,就像我们在憋得不行的时候到处找卫生纸一样,现在的我们同样会因为某个地方没有WIFI而“憋得不行”,是的,我们需要上网!网络就像一道道连接世界的隐形光束,如果哪个地方没有被它照到,这个地方就是荒蛮之地,可见我们对网络的迫切地需求;说网络编程难,也不是我危言耸听:网络是变化的,是动态的,我们访问网络可能需要两拨人维护(android client和server端),需要通过OSI七层协议或TCP/IP四层协议,需要迎合各种各样的网络传输协议(ftp,telnet,http1.1,http/2 等),需要上传和下载各种类型的文件(字符串,图片流,文件流 等),不同格式的数据,打包和解析方式也不同(XML格式(需要PULL,SAX,DOM 等解析方式),JSON格式(需要JSONObject,JSONArray或GSON或JACKSON等解析方式),流文件(Httpmine解析))等。。。说了这么多,你可能很恼火:网络用处这么广,却又这么难,到底还学不学?当然要学,因为正是它比较难,一些公司推出了各种各样的第三方jar包开源框架,这个框架为我们做了很好的代码封装,让我们方便地进行网络编程,下面我结合几个demo跟各位浅谈一下android的网络编程。

网络编程的demo介绍

本文一共包含3个demo,他们分别是:

用户登录:用户在Android客户端输入用户名密码,上传至服务器,服务器通过查询数据库中的信息,给客户端返回一个正确性的提示。

用户注册:用户将自己输入的姓名和兴趣爱好,上传至服务器,服务器将新增用户添加至数据库中。

下载图片流

本文涉及的知识点:

在tomcat容器中搭建简单的Servlet,在doPost和doGet方法中通过参数HttpServletRequest对象和HttpServletResponse对象获取client端的内容或向client端发送内容。

在client端使用HttpClient对象或HttpURLConnection对象请求server,并使用这两个对象接收返回信息。

使用多线程、Handler、runOnUIThread等线程和异步知识,在主线程(UI线程)中更新UI,在子线程中访问网络,并利用Handler在线程之间传递信息。

利用JSONObject、JSONArray类封装、解析JSON格式的数据

弱引用

定制异常

IO流

demo#1: 用户登录

本demo将实现从android客户端输入用户名密码,上传至服务器,服务器通过比对,返回客户端正确性信息。

- 服务器端使用tomcat容器装载Servlet web应用程序,通过doGet方式接收请求、处理、并返回客户端。

- 客户端通过HttpClient,以get/post方式请求server端

- 通过Handler实现UI更新

server端浅析

Server端需注意的几点:

- 由于tomcat容器最终运行的classes文件位于 /WebContent/WEB-INF/classes 中,然而创建Dynamic Web Project的时候,代码默认build成class文件的存放地址默认是 /工程名/build ,所以应把该存放地址改为 /WebContent/WEB-INF/classes

- 在tomcat中新建Servlet类时,默认的该Servlet的URL地址是 /Servlet类名,如果类名过长,可以在创建Servlet时,在URL mapping中修改一个虚拟映射的URL路径,方便访问。

- 无论客户端用哪种方式请求(get/post),在Server端用doGet和doPost方式都能接收,只需要在其中一个方法中调用另一个方法即可。

- 为了能够处理client端发送的中文信息,应设置字符的编码方式

// 处理接收到的client的编码方式
request.setCharacterEncoding("UTF-8");


为了防止client端接收的消息乱码,应设置如下的编码方式

// 防止发送到client端乱码
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");


服务器端代码如下:

@WebServlet("/login.do")
public class SecondVanpersieServletForAndroidLogin extends HttpServlet {
private static final long serialVersionUID = 1L;

/**
* @see HttpServlet#HttpServlet()
*/
public SecondVanpersieServletForAndroidLogin() {
super();
// TODO Auto-generated constructor stub
}

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub

/* //用于测试服务器无响应异常
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
*/
// response.getWriter().append("Served
// at:").append(request.getContextPath());
// 防止发送到client端乱码 response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8");
// Server端的输出以打印流的形式回传client
//PrintWriter out = null;
OutputStream out =null;

// 处理接收到的client的编码方式 request.setCharacterEncoding("UTF-8");

// 接收client的LoginName键中的值
String loginName = request.getParameter("LoginName");
// 接收client的LoginPassword键中的值
String loginPassword = request.getParameter("LoginPassword");
System.out.println(loginName + "|" + loginPassword);

try {
//out = response.getWriter();
out= response.getOutputStream();
if (loginName.equals("tom") && loginPassword.equals("123")) {
// 登录正确
out.write("success!".getBytes("UTF-8"));

} else {
//登录失败
out.write("failed!".getBytes("UTF-8"));
}
} finally {
out.flush();
out.close();
}

}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
// System.out.println("--doPost--");
doGet(request, response);
}

}


如上所示,request接收server传来的信息,为方便起见,直接判断用户名是否为tom,密码是否为123,若正确,则返回success!,否则返回failed!。

client端浅析

client端需注意的几点:

为使应用获得访问网络权限,应在AndroidManifest.xml中声明相应权限

<!-- 应用访问网络权限 -->

<uses-permission android:name="android.permission.INTERNET" />


一定不能在主线程中访问网络,否则会阻塞UI操作。

一定不能在子线程中更新UI,应使用异步请求,如Handler机制。

界面布局

界面布局很简单,就是两个输入框(EditText),一个清除button,一个注册button。不再做过多解释,代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:orientation="vertical" >

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >

<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_weight="1"
android:text="@string/text_login" />

<EditText
android:id="@+id/text_logininput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:hint="@string/text_login_hint"
android:inputType="text"
android:selectAllOnFocus="true" />
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >

<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_weight="1"
android:text="@string/text_password" />

<EditText
android:id="@+id/text_passwordinput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:hint="@string/text_password_hint"
android:inputType="textPassword"
android:selectAllOnFocus="true" />
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal" >

<Button
android:id="@+id/button_clear"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:background="#69696969"
android:text="@string/button_clear" />

<Button
android:id="@+id/button_login"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:background="#69696969"
android:text="@string/button_login" />
</LinearLayout>




activity代码

本段代码主要用于初始化布局中的控件,绑定button监听器,开启一个线程用于访问server,在访问的过程中,可能会出现各种异常,可以通过try/catch块按照异常优先级进行捕捉,再强调一点,一定要在Handler中的handlerMessage中更新UI,代码如下:

@SuppressWarnings("deprecation")
public class LoginActivity extends Activity {
private EditText mEditLogin;
private EditText mEditPassword;
private Button mButtonClear;
private Button mButtonLogin;
// 接口对象的引用=new 接口对象的实现类
private UserService mUserService = new UserServiceImplement();

// 登陆成功标志
private static final int FLAG_LOGIN_SUCCESS = 1;
// 登录异常显示的信息
private static final String MSG_LOGIN_ERROR = "登录出错!";
// 登陆成功显示的信息
private static final String MSG_LOGIN_SUCCESS = "登录成功!";
// 业务异常
public static final String MSG_LOGIN_FAILED = "登录名|密码出错";

// 接受服务器响应错误
public static final String MSG_SERVER_ERROR = "请求服务器错误";
// 连接服务器超时
public static final String MSG_REQUEST_TIMEOUT = "连接服务器超时";
// 服务器在规定时间未处理完业务
public static final String MSG_RESPONSE_TIMEOUT = "服务器处理超时";
// loading
private static ProgressDialog mDialog;

@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//初始化控件
init();
//绑定监听器
bindClickListener();
}

private void bindClickListener() {
// TODO Auto-generated method stub
mButtonLogin.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
final String _editLoginName = mEditLogin.getText().toString();
final String _editPassword = mEditPassword.getText().toString();
// Toast.makeText(LoginActivity.this,
// _editLoginName + "|" + _editPassword,
// Toast.LENGTH_SHORT).show();

if (mDialog == null) {
mDialog = new ProgressDialog(LoginActivity.this);
}
mDialog.setTitle("请等待...");
mDialog.setMessage("登陆中...");
mDialog.setCancelable(false);
mDialog.show();
// 新开线程,将输入的用户名和密码提交至服务器
Thread thread = new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
try {
mUserService.userLogin(_editLoginName,
_editPassword);
// 在子线程中发消息 发给主线程的handler 让handler处理 (登陆成功)
handler.sendEmptyMessage(FLAG_LOGIN_SUCCESS);
}

// 捕获连接超时异常
catch (ConnectTimeoutException e) {
Message msg = new Message();

Bundle bundle = new Bundle();
bundle.putSerializable("ErrorMsg",
MSG_REQUEST_TIMEOUT);
msg.setData(bundle);
handler.sendMessage(msg);
}
// 服务器处理超时
catch (SocketTimeoutException e) {
// TODO: handle exception
Message msg = new Message();

Bundle bundle = new Bundle();
bundle.putSerializable("ErrorMsg",
MSG_RESPONSE_TIMEOUT);
msg.setData(bundle);
handler.sendMessage(msg);
}
// 业务异常
catch (ServiceRulesException e) {
Message msg = new Message();

Bundle bundle = new Bundle();
bundle.putSerializable("ErrorMsg", e.getMessage());
msg.setData(bundle);
handler.sendMessage(msg);
}

// 空指针异常
catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
Message msg = new Message();

Bundle bundle = new Bundle();
bundle.putSerializable("ErrorMsg", MSG_LOGIN_ERROR);
msg.setData(bundle);
handler.sendMessage(msg);
}
}
});
thread.start();
}

});
mButtonClear.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
mEditLogin.setText("");
mEditPassword.setText("");
Toast.makeText(LoginActivity.this, "cleared!",
Toast.LENGTH_SHORT).show();

}
});
}

private void showTip(String str) {
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
};

// 继承handler,目的是为了持有LoginActivity对象的引用
private static class IHandler extends Handler {
// 创建一个弱引用 可有效避免OOM
private final WeakReference<Activity> mActivity;

public IHandler(LoginActivity activity) {
mActivity = new WeakReference<Activity>(activity);
}

// 该方法可以收到子线程发出的消息,并对其处理(该方法在主线程中运行,可以更新UI)
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub

// 首先关闭ProgressDialog
if (mDialog != null) {
mDialog.dismiss();
}

// 在handler中获得主Activity的对象引用,这样可以调用Activity中的方法
// ((LoginActivity)mActivity.get()).showTip();

int flag = msg.what;
switch (flag) {
// 登录出错
case 0:
String errorMsg = (String) msg.getData().getSerializable(
"ErrorMsg");
((LoginActivity) mActivity.get()).showTip(errorMsg);

break;
// 登陆成功
case FLAG_LOGIN_SUCCESS:
((LoginActivity) mActivity.get()).showTip(MSG_LOGIN_SUCCESS);

break;

default:
break;
}
}

}

private IHandler handler = new IHandler(this);

// 初始化控件
private void init() {
// TODO Auto-generated method stub
mEditLogin = (EditText) findViewById(R.id.text_logininput);
mEditPassword = (EditText) findViewById(R.id.text_passwordinput);
mButtonClear = (Button) findViewById(R.id.button_clear);
mButtonLogin = (Button) findViewById(R.id.button_login);
}

}


在上段代码中,继承了一个定制的Handler类,定义了一个弱引用类型的activity,用于处理当内存出现OOM时(Out Of Memory)时,系统及时调用GC机制(Garbage Collection),方便垃圾回收;另外程序会根据优先级对异常进行捕捉,如先捕捉连接服务器超时的异常(在规定的时间内没有连接到服务器,用ConnectTimeoutException类捕捉),在捕捉服务器响应超时异常(在规定的时间内服务器无响应,用SocketTimeoutException类捕捉),接着是业务异常(用户名或密码输入错误,用定制Exception类捕捉),最后是其他异常(用Exception类捕捉)。下面是定制的Exception类定义:

public class ServiceRulesException extends Exception {

/**
* 定制Exception类,捕捉业务异常
*/
private static final long serialVersionUID = 1L;

public ServiceRulesException(String message) {
super(message);
}

}


连接server端的client业务代码 (get请求)

定义一个接口,用于声明访问网络的方法并抛出异常,代码如下:

public interface UserService {
public void userLogin(String loginName, String loginPassword)
throws Exception;


程序使用HttpClient访问网络,用get方式请求,特别说明一点,HttpClient在Android2.3版本后就不推荐使用了,在Android6.0中更是直接被废弃了,若想访问网络可以使用HttpURLConnection,该类会在后面介绍,虽然被废弃了,不过还是有必要介绍一下,毕竟访问方式差不多,步骤如下:

定义HttpClient对象(HttpClient是个接口,只能new它的实现类DefaultHttpClient);

创建HttpGet/HttpPost对象,传入String类型的URL地址参数;

调用HttpClient类的execute方法,传入参数HttpGet/HttpPst对象,返回HttpResponse对象,该对象就是Server端返回给client端的信息;

判断返回的信息中携带的响应码是否为200,若不是200,说明出错,抛出异常;

通过entity实体解析HttpResponse对象,处理返回结果。

具体访问网络代码如下:

public class UserServiceImplement implements UserService {
@Override
public void userLogin(String loginName, String loginPassword)
throws Exception {
@SuppressWarnings("deprecation")
HttpClient client = new DefaultHttpClient();
String uri = "http://192.168.1.103:8080/test/login.do?LoginName="+ loginName + "&LoginPassword=" + loginPassword;
HttpGet get = new HttpGet(uri);
HttpResponse response = client.execute(get);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK)
{
throw new ServiceRulesException(LoginActivity.MSG_SERVER_ERROR);
}
String result = EntityUtils.toString(response.getEntity(), HTTP.UTF_8);
if (result.equals("success!")) {

} else {
throw new ServiceRulesException(LoginActivity.MSG_LOGIN_FAILED);
}

}


注意一点:http协议的get请求方式如下

http://localhost:8080/test/login.do/?参数键=参数值&参数键=参数值 . . . . . .

每个参数的参数键都应与Server端接收到的键一致。

连接server端的client业务代码 (post请求)

与get请求不同,post请求将不会把参数键和参数值直接写在URL之后,而是写在请求的内容中,看得出来,**当传递数据的隐秘性不高、数据量比较小时,适合使用get请求访问server,当数据量比较大(>256bytes),且数据隐秘性比较高时(包含用户的密码等内容),应当考虑使用post请求。**post请求代码如下:

// 以post方式请求
HttpParams params = new BasicHttpParams();
// 设置请求的字符集
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
// 设置请求的超时时限为3秒,若loading3秒以上则抛异常 ConnectionTimeoutExeption异常
HttpConnectionParams.setConnectionTimeout(params, 3000);
// 设置服务器的响应超时时限,即收到了client的请求但在3秒内没完成操作 SocketTimeoutException
HttpConnectionParams.setSoTimeout(params, 3000);

SchemeRegistry registry = new SchemeRegistry();
// 设置请求协议,以http或HTTPS方式请求(HTTPS对应433端口,http对应80端口)
registry.register(new Scheme("https", PlainSocketFactory
.getSocketFactory(), 433));
registry.register(new Scheme("http", PlainSocketFactory
.getSocketFactory(), 80));
ClientConnectionManager conman = new ThreadSafeClientConnManager(
params, registry);

HttpClient client = new DefaultHttpClient(conman, params);
String url = "http://192.168.1.103:8080/test/login.do";
HttpPost post = new HttpPost(url);
// 用post方式传递参数 通过NameValuePair对象以键值对的方式传递
NameValuePair paramLoginName = new BasicNameValuePair("LoginName",loginName);
NameValuePair paramLoginPassword = new BasicNameValuePair(
"LoginPassword", loginPassword);
// 把参数放在List中
List<NameValuePair> postParams = new ArrayList<NameValuePair>();
postParams.add(paramLoginName);
postParams.add(paramLoginPassword);
// 把封装好的参数放在post中
post.setEntity(new UrlEncodedFormEntity(postParams, HTTP.UTF_8));
// 通过HttpClient的execute方法,将post作为参数 发送到server端
HttpResponse response = client.execute(post);


之后的代码与get方式相同。由于参数不能跟在URL后面,post请求使用NameValuePair对象存储需要传递的键值对,接着把这些对象存储在ArrayList中,最后调用GetPost的setEntity方法将list封装成一个实体,这样就可以将post作为execute方法中的参数传递出去了。而无论使用get请求还是post请求,Server端都不用修改代码。

至此第一个demo完成。

demo#2:用户注册

本demo用于将用户名和兴趣爱好数组上传至Server端,大部分内容与demo1相仿,不同点主要数据采用了JSON格式封装,server端需要用json-lib解析,server端所需json-lib.jar包及其依赖包如下:

commons-beanutils-1.8.0.jar

commons-collections-3.2.1.jar

commons-lang-2.5.jar

commons-logging-1.1.1.jar

ezmorph-1.0.6.jar

json-lib-2.4-jdk15.jar

界面布局

一个用户输入(EditText),一个兴趣爱好选择组(RaidioGroup),代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:orientation="vertical" >

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >

<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_weight="1"
android:text="@string/text_register" />

<EditText
android:id="@+id/text_register_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:hint="@string/text_register_hint"
android:inputType="text"
android:selectAllOnFocus="true" />
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="vertical" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="@string/text_interesting" />

<CheckBox
android:id="@+id/checkbox_music"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/checkbox_music" />

<CheckBox
android:id="@+id/checkbox_game"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/checkbox_game" />

<CheckBox
android:id="@+id/checkbox_swim"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:text="@string/checkbox_swim" />
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_vertical"
android:orientation="horizontal" >

<Button
android:id="@+id/button_register_clear"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:background="#69696969"
android:text="@string/button_register_clear" />

<Button
android:id="@+id/button_register"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:background="#69696969"
android:text="@string/button_register" />
</LinearLayout>

</LinearLayout>


所见即所得



由于本demo的activity和demo1相仿,故不再给出。

client端发送请求封装JSON数据、接收响应解析JSON数据

client端采用HttpClient访问网络,使用post请求。

代码如下:

@Override
public void userRegister(String registerName, List<String> interestingList)
throws Exception {
// TODO Auto-generated method stub

// JSON格式封装数据
HttpClient client = new DefaultHttpClient();
String uri = "http://localhost:8080/test/getstudent.do";
HttpPost post = new HttpPost(uri);

JSONObject obj = new JSONObject();
obj.put("RegisterName", registerName);
JSONArray arr = new JSONArray();
for (String _string : interestingList) {
arr.put(_string);
}
obj.put("Interesting", arr);
NameValuePair pair = new BasicNameValuePair("Data", obj.toString());
List<NameValuePair> data = new ArrayList<NameValuePair>();
data.add(pair);
post.setEntity(new UrlEncodedFormEntity(data, "UTF-8"));

HttpResponse response = client.execute(post);

int status = response.getStatusLine().getStatusCode();

if (status != HttpStatus.SC_OK) {
throw new ServiceRulesException(RegisterActivity.MSG_SERVER_ERROR);

}

String result = EntityUtils.toString(response.getEntity(), HTTP.UTF_8);
// 解析从server返回的Json数据
JSONObject _obj = new JSONObject(result);
String _result = _obj.getString("result");
if (result.equals("success!")) {
// 注册成功
} else {
// 注册失败
String errorMsg = _obj.getString("errorMsg");
throw new ServiceRulesException(errorMsg);
}

}


用户在界面上输入用户名,并勾选兴趣爱好,用户名以字符串的形式传入该方法,兴趣爱好以list的形式传入方法,则JSON格式的数据为如下形式:

var data={"RegisterName":"tom","Interesting":["swim","music","game"]};


JSON格式的数据也是以键值对的形式存在,对象中可以包含对象(JSONObject),也可以包含集合(JSONArray),反过来,集合中可以包含单个对象,也可以包含集合,即集合和对象可以相互嵌套。本例中,最外层是一个对象,里面包含了一个对象和一个简单集合,故封装JSON数据如上面代码所示。

client端接收server端的相应结果,首先用工具类UtilEntity对象把JSON数据解析成字符串,然后从Server端可知,封装的JSON数据格式为

var return={"result":"success!","errorMsg":"register success!" } {"result":"failed!","errorMsg":"register failed!"}


server端接收请求解析JSON数据、返回响应封装JSON数据

下面是Server端的代码:

protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
request.setCharacterEncoding("UTF-8");

// 解析client端传过来的json数据
String data = request.getParameter("Data");
// System.out.println(data);

JSONObject obj = JSONObject.fromObject(data);
String registerName = obj.getString("RegisterName");
System.out.println(registerName);
JSONArray arr = obj.getJSONArray("Interesting");
if (arr != null) {
for (Object object : arr) {
System.out.println(object);
}
}

// 封装json数据 向client端发送
/*
* { "result":"success!","errorMsg":"register success!" } {
* "result":"failed!","errorMsg":"register failed!"}
*/
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");

PrintWriter out = null;
try {
out = response.getWriter();
ResultJSONBean jsonBean = new ResultJSONBean();
// success
jsonBean.setResult("success!");
jsonBean.setErrorMsg("register success!");
// failed
// jsonBean.setResult("failed!");
// jsonBean.setErrorMsg("register failed!");

JSONObject _obj = JSONObject.fromObject(jsonBean);
System.out.println(_obj);
out.write(_obj.toString());

} finally {
out.flush();
out.close();
}

}


特别说明一下,Server端的JSONObject和JSONArray来自第三方框架json-lib.jar,client端来自org.json包。

至此第二个demo结束。

demo#3: 从server下载一个图片流

本demo将以HttpURLConnection请求server上的一个图片流,

采用HttpURLConnection访问网络步骤如下:

创建HttpURLConnection对象;

创建URL对象,传入URL对象地址参数;

调用URL对象的openConnection方法,打开连接(openConnection);

设置连接参数;

连接(设置本次连接的参数);

接收返回数据 对数据进行操作(connect)。

client端

@Override
public Bitmap getImage() throws Exception {
// TODO Auto-generated method stub
Bitmap _bitmap = null;
URL url = null;

// 该类是Java SDK里的类 而不是android里的类
// 首先 声明一个HttpsURLConnection对象
HttpURLConnection _httpURLConnection = null;
InputStream _inputStream = null;
// post请求方式
OutputStream out = null;
byte[] data = null;

try {

// 封装向server端发送的数据
Map<String, String> _params = new HashMap<String, String>();
_params.put("id", "1");
data = setPostPassParams(_params).toString().getBytes();

url = new URL("http://127.0.0.1:8080/test/getImage.jpg");
// 打开连接
_httpURLConnection = (HttpURLConnection) url.openConnection();
// 设置参数
// 设置请求服务器超时时间
_httpURLConnection.setConnectTimeout(5000);
// 设置服务器响应超时时间
_httpURLConnection.setReadTimeout(5000);

// 设置允许读取Server端信息权限
_httpURLConnection.setDoInput(true);
// 设置允许向server端发送信息权限
_httpURLConnection.setDoOutput(true);
// 不使用缓冲(希望每次都能从服务器端获取最新数据)
_httpURLConnection.setDefaultUseCaches(false);
// 设置请求方式
_httpURLConnection.setRequestMethod("POST");

// 连接
_httpURLConnection.connect();
// 获取服务器返回的响应状态码
int responceCode = _httpURLConnection.getResponseCode();
if (responceCode != HttpURLConnection.HTTP_OK) {
throw new ServiceRulesException("post请求服务器异常");
}
// 接收server发来的数据流
_inputStream = new BufferedInputStream(
_httpURLConnection.getInputStream());
// _inputStream = _httpURLConnection.getInputStream();
if (_inputStream != null)
// 把Server端传过来的inputstream转化为bitmap格式
_bitmap = BitmapFactory.decodeStream(_inputStream);

// 向Server端发送信息
out = _httpURLConnection.getOutputStream();
out.write(data);
out.flush();

} finally {
if (_inputStream != null) {
_inputStream.close();
}
if (_httpURLConnection != null) {
_httpURLConnection.disconnect();
}
}
return _bitmap;
}
// 通过httpURLConnection访问网络,并用post请求向服务器传递的参数
private static StringBuffer setPostPassParams(Map<String, String> params) {
StringBuffer string = new StringBuffer();
// k1=v1&k2=v2...
for (Map.Entry<String, String> entry : params.entrySet()) {
try {
string.append(entry.getKey()).append("=")
.append(URLEncoder.encode(entry.getValue(), "UTF-8"))
.append("&");

} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 去掉最后一个"&"号
string.deleteCharAt(string.length() - 1);
}

return string;
}


利用HttpURLConnection访问网络的几点注意事项:

该方式的get请求形式与HttpClient类似,都是在URL后加”?”并拼接键值对,中间用”&”隔开。

post请求的实质也是拼接键值对,只是不能跟在URL后,将键值对拼接后转换成字节数组的形式传递。

HttpURLConnection对象可以在打开连接后对本次连接做一些配置,比如设置连接server时限,server响应的时限,请求方式,读写server端的权限,是否使用缓冲等。

Server端

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
// response.getWriter().append("Served at:
// ").append(request.getContextPath());
System.out.println("---get---");
String id = request.getParameter("id");

/*
* 用输入流将磁盘上的图片读到Servlet中
*/
InputStream in = null;
/*
* 将读到的图片流写到response中发送给请求端
*/
OutputStream out = null;
try {
in = new FileInputStream(new File("F://图片素材/" + id + ".jpg"));

// 设置响应头 设置相应内容的长度
response.setContentLength(in.available());
// 设置响应头 设置MIME——标识向client端发送的文件类型 以便client端能够识别该类型文件

response.setContentType("image/jpeg");
out = response.getOutputStream();
// 输入流in和输出流out不能直接交换数据 需要先用in的read把图片以字符数组的形式读进来
// 用byte[]座位中间转换的介质不是一个好方法

/*
* //获得图片的字节大小 分配一个byte数组空间用于存放图片 byte[] b = new
* byte[in.available()]; //用in的read方法把图片的字节数组形式读进来 in.read(b);
* //再用out的write方法把字节数组形式的图片写到客户端 out.write(b);
*/
// 用边读边发的形式
// 先分配1024个字节(1KB)
byte[] b = new byte[1024];
// 只要这1024个字节没读完 就一直读 读一点 写一点
int read=0;
while((read=in.read(b))!=-1)
{
out.write(b, 0, read);
}

} catch (Exception _e) {
// TODO: handle exception
_e.printStackTrace();
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.flush();
out.close();
}
}


代码首先从服务器磁盘中读取一张图片流到Servlet中,接着传到client端,由于是以流的形式传递,故不能一次传完,否则会比较占用空间,而应使用边存边发的形式。即先从server端读取1kb,再向client传递1kb,由于该图片流可能无法被1kb等分,故应调用outputStream含有三个参数的write方法,该方法的第三个参数将计算读到文件大小的偏移量,所以最后不会出现最后一段流按1kb计算的情况。

至此,demo#3结束。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息