1、httpURLConnection 方式把数据提交到服务器
1、搭建服务器端程序
2、使用get方法和post服务方法提交数据
1、使用get方法提交数据
//获取用户名,密码
String username = et_username.getText().toString().trim();
String pwd = et_password.getText().toString().trim();
// 定义访问的URL
final String path = "http://192.168.128.2:8080/login/servlet/LoginServelet?username="+username+"&password="+pwd+"";
//错误点:将协议名称忘了
//开启子线程,进行网络访问
new Thread(){
public void run(){
try {
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setReadTimeout(5000);
int responseCode = connection.getResponseCode();
if(responseCode == 200){ //200请求资源全部成功 206请求资源部分成功
InputStream inputStream = connection.getInputStream();
String res = StreamUtils.fromStream(inputStream);
showToast(res);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
2、使用post方法提交数据
//获取用户名和密码
String username = et_username.getText().toString().trim();
String pwd = et_password.getText().toString().trim();
//get和post不同点一★★★★★★★设置访问路径
final String path = "http://192.168.128.2:8080/login/servlet/LoginServelet";
//访问网络资源,开启子线程
new Thread(){
public void run(){
try {
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
//get和post不同点二★★★★★★★组拼请求体
String data = "username=abc&password=123";
//设置访问属性
connection.setRequestMethod("POST");
connection.setReadTimeout(5000);
//get和post不同点三★★★★★★★另外设置的两个请求属性
//错误点:忘记将Content-Type:和Content-Length:的":"去掉,因而服务器返回了500状态码
connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length",data.length()+"");
//get和post不同点四★★★★★★★设置允许输出,并且将数据输出
connection.setDoOutput(true);
connection.getOutputStream().write(data.getBytes());
//获取状态码
int responseCode = connection.getResponseCode();
if(responseCode == 200){
InputStream inputStream = connection.getInputStream();
String s = StreamUtils.fromStream(inputStream);
showToast(s);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
3、get方式和post方式不同点
1、请求的url地址上不同 在get方式上是通过组拼url地址的方式,将数据全部都存到url地址上,但是大小是有限制的,有两种限制一种是最大数据请求量为1k另外一个是4k,1k是由浏览器规定,4k是由http协议规定。而post方式,请求信息放在请求体中
2、post方式多了两个属性和一个请求体
属性是
Content-Type
和
Content-Length 两个属性,
多了一个请求体
String
data
=
"username=abc&password=123"
;
2、乱码解决
乱码原因:
* 从android客户端提交上来的数据默认使用android的utf-8编码方式,在传输到接受的过程中是先在android客户端使用utf-8码表编码,到达 tomcat服务器,使用iso8859-1解码,由于码表不对应
* 所以出现乱码,
* String username = req.getParameter("username"); 使用iso8859-1进行解码,所以要再进行构造,然后使用utf-8进行编码
* new String(username.getBytes("iso8859-1"),"utf-8") 现使用iso8859-1进行解码,然后使用utf-8进行编码
* 整个过程是utf-8编码--->iso8859-1解码---->iso8859-1进行编码为原来的二进制--->utf-8进行解码就是原来的数据
解决代码
//首先将数据先使用URLEncode进行一下url编码
String data = "username="+URLEncoder.encode(username,"UTF-8")+"&password="+URLEncoder.encode(pwd,"UTF-8");
//再在服务器端进行解码编码再解码
new String(username.getBytes("iso8859-1"),"utf-8")
3、以HttpClient方式把数据提交到服务器
[1] HttpClient是apache的一个开源项目,然后由谷歌封装,作为一个类出现在android的开发中
[2] HttpClient的使用
在android api level23之后,google将DefaultHttpClient废弃了,然后鼓励使用HttpURLConnection进行开发,如果想要使用,那么需要去23以后的
optional
中找到org.apache.http.legacy.jar,然后导入库文件,就是粘到lib文件夹下,然后才能使用
//使用get请求发送数据
// [1] 获取用户名和密码
String username = et_username.getText().toString().trim();
String password = et_password.getText().toString().trim();
// [2] 组拼URL地址
final String path = "http://192.168.128.2:8080/login/servlet/LoginServelet?username="+username+"&password="+password+"";
// [3] 开启子线程进行网络访问
new Thread(){
public void run() {
//获取执行端
DefaultHttpClient client = new DefaultHttpClient();
//获取get执行方法
final HttpGet get = new HttpGet(path);
//开启子线程执行网络请求
try {
HttpResponse response = client.execute(get);
int code = response.getStatusLine().getStatusCode();
if(code == 200){
InputStream inputStream = response.getEntity().getContent();
String s = StreamUtils.fromStream(inputStream);
showToast(s);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
// [4] 加上网络访问权限
<uses-permission android:name="android.permission.INTERNET"/>
//使用post请求进行服务器访问
// [1] 准备用户名和密码
String username = et_username.getText().toString().trim();
String password = et_password.getText().toString().trim();
// [2] 准备路径
String path = "http://192.168.128.2:8080/login/servlet/LoginServelet";
// [3] 准备提交端
DefaultHttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(path);
//[3.1] 准备提交的数据
List<NameValuePair> lists = new ArrayList<NameValuePair>();
BasicNameValuePair nameValuePair = new BasicNameValuePair("username", username);
BasicNameValuePair pwdValuePair1 = new BasicNameValuePair("password", password);
// [3.2] 将数据添加到list中
lists.add(nameValuePair);
lists.add(pwdValuePair1);
// [3.3] 生成数据实体
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(lists);
// [3.4] 设置访问数据
post.setEntity(entity);
// [3.5] 执行语句
client.execute(post);
HttpResponse response = client.execute(post);
int code = response.getStatusLine().getStatusCode();
if(code == 200){
InputStream inputStream = response.getEntity().getContent();
String s = StreamUtils.fromStream(inputStream);
showToast(s);
}
[3] 使用post方式提交数据的代码编写思路
[1] 由于需要上传数据 ,所以需要设置entity
post
.
setEntity
(
entity
);
[2] 实体需要Entity,所以要实例化一下
UrlEncodedFormEntity
entity
=
new
UrlEncodedFormEntity
(
lists
);
[3] 需要加载数据,这个数据是一个list的nameValuePair的,
[4]
由于NameValuePair是一个interface,所以需要实例化它的子类
BasicNameValuePair
pwdValuePair1
=
new
BasicNameValuePair
(
"password"
,
password
);
[5] 然后将这些数据放到list中就可以开始访问了
4、开源项目方式把数据提交到服务器
AsyncHttpClient方式提交数据
[1] get方式提交数据
// [1] 获取用户名和密码
String username = et_username.getText().toString().trim();
String password = et_password.getText().toString().trim();
// [2] 组拼URL地址
String path = "http://192.168.128.2:8080/login/servlet/LoginServelet?username="+username+"&password="+password+"";
AsyncHttpClient client = new AsyncHttpClient();
client.get(path, new AsyncHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
Toast.makeText(getApplicationContext(),new String(responseBody),Toast.LENGTH_LONG).show();
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
}
});
[2] post方式提交数据
// [1] 准备用户名和密码
String username = et_username.getText().toString().trim();
String password = et_password.getText().toString().trim();
// [2] 准备路径
String path = "http://192.168.128.2:8080/login/servlet/LoginServelet";
AsyncHttpClient client = new AsyncHttpClient();
RequestParams params = new RequestParams();
params.put("username",username);
params.put("password",password);
client.post(path, params, new AsyncHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
Toast.makeText(getApplicationContext(),new String(responseBody),Toast.LENGTH_LONG).show();
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
}
});
总结
[1] HttpURLConnection 可以人随意封装信息
[2] HttpClient
[3] AsyncHttpClient 在只上传一些简单的信息的时候使用,例如账号密码等
5、javase多线程下载
概念:开辟多个线程,精确的分段下载文件,然后将文件合并
元数据
(
Metadata
),又称
中介数据
、
中继数据
,为描述
数据
的数据(data about data),主要是描述数据
属性
(property)的
信息
,用来支持如指示存储位置、
历史
数据、
资源
查找、文件记录等功能
步骤与实现代码
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("GET");
connection.setReadTimeout(5000);
int resuCode = connection.getResponseCode();
if(resuCode == 200){
//获取服务器文件大小,以便计算线程的开始和结束的位置
int length = connection.getContentLength();
//创建一个文件大小和服务器文件一样,提前把空间申请出来
/*System.out.println(length); //9173224*/
RandomAccessFile raf = new RandomAccessFile("temp.exe", "rw");
//设置文件大小
raf.setLength(length);
raf.close();
//计算出每个下载文件大约大小
int blockSize = length / THREADCOUNT;
//找出每个文件大开始位置和结束位置
for(int i = 0;i < THREADCOUNT;i++){
int startIndex = i * blockSize;
int endIndex = (i+1) * blockSize - 1;
//这是特殊情况,是最后一个线程,那么就将剩下的内容全部下载下来
if(i == THREADCOUNT - 1){
endIndex = length - 1;
}
//开线程去下载文件
new DownloadThread(startIndex, endIndex, i).start();
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("GET");
connection.setReadTimeout(5000);
//设置一个头信息,代表分段下载
connection.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
int resultCode = connection.getResponseCode();
if(resultCode == 206){ //206代表部分资源请求成功
RandomAccessFile raf = new RandomAccessFile("temp.exe", "rw");
raf.seek(startIndex);
InputStream inputStream = connection.getInputStream();
int len = -1;
byte[] buff = new byte[1024];
while((len = inputStream.read(buff)) != -1){
raf.write(buff,0,len);
}
raf.close();
System.out.println("线程"+threadId+"--下载完成");
6、断点续传实现
这个是在多线程下载的基础上,添加上了这几个逻辑
[1] 在重新下载的时候读取临时记录文件,看是不是存在,如果存在,那么将这个值作为开始下载的值继续进行
//如果以前出了意外导致文件没有下载完,那么就可以读取记录文件,进行继续下载
File file = new File(getFileName(path)+"."+threadId+".downloadTempFile");
if(file.exists() && file.length() > 0){
BufferedReader bfr = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String lastPosition = bfr.readLine();
System.out.println("上次"+threadId+"下载到"+lastPosition);
startIndex = Integer.valueOf(lastPosition);
}
[2] 在每次获取并写入后,将读取到的位置写到临时记录文件中
//获取当前线程的下载到的位置
total += len;
long currentThreadPosition = startIndex + total;
//将下载的位置存起来,以备断点后继续下载 并且文件的存储必须是每次更新都写到底层的设备,使用mode=rwd
RandomAccessFile raff = new RandomAccessFile(getFileName(path)+"."+threadId+".downloadTempFile", "rwd");
//这里犯了一个错误raf.write(int len) 是将从文件指针位置开始计算写len个字节进文件,并不是将len写进去
raff.write(String.valueOf(currentThreadPosition).getBytes());
raff.close();
[3] 如果全部下载完成,那么将临时记录文件全部删除
//一旦全部下载完成就将所有的临时文件删除
/*
* 这里的锁加不加都无所谓,因为最后是一个判断,无论怎么减都不会出现-1,最后结果都是0而且减的结果
* 都是唯一的,
* 但是如果是在每个线程中都在使用,例如卖票,先打印票剩余的张数再进行--
* System.out.println(runningThread);
* runningThread--;
* 这样就会出问题,因为如果thread-0先打印了,然后cpu切了出去,那么还会打印一遍这个值,就会出问题
* 这个时候就需要使用锁了
*/
synchronized(DownloadThread.class){
runningThread--;
if(runningThread == 0){
for(int i = 0;i < THREADCOUNT;i++){
File deleteFile = new File(getFileName(path)+"."+i+".downloadTempFile");
deleteFile.delete();
}
}
}
7、断点续传逻辑移植到android上
8、开源项目实现多线程下载