1. Android网络前提
- 必须在子线程中执行。
- 需要申请INTERNET权限。
2. Android网络请求基础
2.1 HttpUrlConnection基础类
get请求
// final String PATH = "接口地址";
private void doHttpUrlGet(final String uName, final String pwd) {
new Thread() {
@Override
public void run() {
try {
URL url = new URL(PATH + "?CompCode=" + COMP_CODE + "&UserName=" + uName + "&Pwd=" + pwd);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
int code = connection.getResponseCode();
if (code == 200) {
InputStream stream = connection.getInputStream();
String content = Utils.getStringFromStream(stream);
// showToast("请求成功!");
Log.i("I",content);
} else {
// setTextCount("请求错误!错误码:" + code);
}
} catch (Exception e) {
e.printStackTrace();
// setTextCount("请求错误!异常:" + e.getMessage());
}
}
}.start();
}
POST请求
private void doHttpUrlPost(final String uName, final String pwd) {
new Thread() {
@Override
public void run() {
try {
String params = "CompCode=" + COMP_CODE + "&UserName=" + URLEncoder.encode(uName, "utf-8") + "&Pwd=" + URLEncoder.encode(pwd, "utf-8");
URL url = new URL(PATH);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setConnectTimeout(10000);
//设置跟post请求相关的请求头
//告诉服务器 要传递的数据类型
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
//告诉服务器,传递的数据长度
connection.setRequestProperty("Content-Length", String.valueOf(params.length()));
//打开输出流
connection.setDoOutput(true);
//通过流把请求体写到服务端
connection.getOutputStream().write(params.getBytes());
int code = connection.getResponseCode();
if (code == 200) {
InputStream stream = connection.getInputStream();
String count = Utils.getStringFromStream(stream);
showToast("请求成功!");
setTextCount(count);
} else {
setTextCount("请求错误!错误码:" + code);
}
} catch (Exception e) {
e.printStackTrace();
setTextCount("请求错误!异常:" + e.getMessage());
}
}
}.start();
}
2.2. android-async-http
第三方开源项目 https://github.com/android-async-http/android-async-http
GET请求
private void doAsyncHttpClientGet(String uName, String pwd) {
AsyncHttpClient client = new AsyncHttpClient();
RequestParams params = new RequestParams();
params.add("CompCode", COMP_CODE);
params.add("UserName", uName);
params.add("Pwd", pwd);
client.get(PATH, params, new MyAsyncHttpResponseHandler());
}
POST请求
private void doAsyncHttpClientPost(String uName, String pwd) {
AsyncHttpClient client = new AsyncHttpClient();
RequestParams params = new RequestParams();
params.add("CompCode", COMP_CODE);
params.add("UserName", uName);
params.add("Pwd", pwd);
client.post(PATH, params, new MyAsyncHttpResponseHandler());
}
class MyAsyncHttpResponseHandler extends AsyncHttpResponseHandler {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
if (statusCode == 200) {
try {
String count = new String(responseBody, "utf-8");
showToast("请求成功!");
setTextCount(count);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
setTextCount("请求失败:异常" + e.getMessage());
}
} else {
setTextCount("请求失败:错误码=" + statusCode);
}
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
setTextCount("请求失败:异常" + error.getMessage());
}
}
2.3. OkHttp
第三方开源项目 https://github.com/square/okhttp
GET请求
private void doOkHttpGet(String uName, String pwd) {
OkHttpClient client = new OkHttpClient();
try {
URL url = new URL(PATH + "?CompCode=" + COMP_CODE + "&UserName=" + uName + "&Pwd=" + pwd);
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new MyCallback());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
POST请求
private void doOkHttpPost(String uName, String pwd) {
OkHttpClient client = new OkHttpClient();
FormBody body = new FormBody.Builder()
.add("CompCode", COMP_CODE)
.add("UserName", uName)
.add("Pwd", pwd)
.build();
Request request = new Request.Builder().url(PATH).post(body).build();
client.newCall(request).enqueue(new MyCallback());
}
class MyCallback implements Callback {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
setTextCount("请求失败:异常" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) {
if (response.code() == 200) {
try {
String count = response.body().string();
showToast("请求成功!");
setTextCount(count);
} catch (IOException e) {
e.printStackTrace();
setTextCount("请求失败:异常" + e.getMessage());
}
} else {
setTextCount("请求失败:错误码=" + response.code());
}
}
}
3. 文件下载
3.1 多线程下载
多线程下载可以突破服务器对单个线程的速度限制。
原理
- 服务器支持 Range请求头,其响应码为206.
-
一般步骤
获取下载文件的总长度 MAX。
- 在本地创建一个一样大小的文件,并根据线程总数来计算分配给每个线程下载的取值范围。
- 开启多个线程,每个线程从计算好的起始位置下载到终止位置。
- 线程下载的同时保存对应内容。
- 所有线程结束时,文件下载完成。
public void onClick(View v) {
final String path = "文件网络路径";
final int threadCount = 4;// 线程总数
new Thread() {
@Override
public void run() {
try {
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
int code = connection.getResponseCode();
if (code == 200) {
// 获取文件总大小
int length = connection.getContentLength();
// 创建一个相同大小的本地文件
RandomAccessFile file = new RandomAccessFile(getFileName(path), "rw");
file.setLength(length);
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 DownLoadThred(startIndex, endIndex, i, path).start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
private class DownLoadThred extends Thread {
private int startIndex;
private int endIndex;
private int threadIndex;
private String path;
public DownLoadThred(int startIndex, int endIndex, int threadIndex, String path) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadIndex = threadIndex;
this.path = path;
}
@Override
public void run() {
try {
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
// 设置Range头,用于计算好的开始索引和技术索引到服务器请求数据
connection.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
if (connection.getResponseCode() == 206) {
//System.out.println("线程 " + threadIndex + "开始下载" + startIndex);
InputStream inputStream = connection.getInputStream();
int len = -1;
byte[] buffer = new byte[1024];
RandomAccessFile file = new RandomAccessFile(getFileName(path), "rw");
// 移动文件指针
file.seek(startIndex);
int count = 0;
while ((len = inputStream.read(buffer)) != -1) {
file.write(buffer, 0, len);
}
file.close();
inputStream.close();
// System.out.println("线程 " + threadIndex + " 结束下载");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public String getFileName(String path) {
String name = getCacheDir() + path.substring(path.lastIndexOf("/"));
return name;
}
3.2 断点下载
原理
通过一个本地文件,记录每个线程下载了多少数据,一旦下载中断,下次再下载的时候,读取日志内容,重新从记录的位置开始请求数据并写入文件
实现
多线程+断点下载
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_down) {
final String path = etUrl.getText().toString().trim();
final int threadCount = Integer.valueOf(etCount.getText().toString().trim());
new Thread() {
@Override
public void run() {
try {
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
int code = connection.getResponseCode();
if (code == 200) {
int length = connection.getContentLength();
RandomAccessFile file = new RandomAccessFile(getFileName(path), "rw");
file.setLength(length);
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 DownLoadThred(startIndex, endIndex, i, path).start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
}
private class DownLoadThred extends Thread {
private int startIndex;
private int endIndex;
private int threadIndex;
private String path;
public DownLoadThred(int startIndex, int endIndex, int threadIndex, String path) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadIndex = threadIndex;
this.path = path;
}
@Override
public void run() {
try {
// 读取出记录下来的位置
File temp = new File(getFileName(path) + threadIndex + ".log");
if (temp != null && temp.length() > 0) {
// 说明日志文件有内容
FileInputStream fis = new FileInputStream(temp);
BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
String result = reader.readLine();
// 读出记录下来的位置更新下载请求数据的起始位置
startIndex = Integer.parseInt(result);
}
URL url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
// 设置Range头,用于计算好的开始索引和技术索引到服务器请求数据
connection.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
if (connection.getResponseCode() == 206) {
// System.out.println("线程 " + threadIndex + "开始下载" + startIndex);
InputStream inputStream = connection.getInputStream();
int len = -1;
byte[] buffer = new byte[1024];
RandomAccessFile file = new RandomAccessFile(getFileName(path), "rw");
file.seek(startIndex);
int count = 0;
while ((len = inputStream.read(buffer)) != -1) {
file.write(buffer, 0, len);
count = count + len;
int position = count + startIndex;
// 断点下载存储位置
RandomAccessFile tempFile = new RandomAccessFile(getFileName(path) + threadIndex + ".log", "rwd");
tempFile.write(String.valueOf(position).getBytes());
// 一定要加在,因为android中,所有的IO操作最终都是需要句柄来操作文件实现的。
// 句柄数量对于每一个app都有一个上限,常见的是1024个。一旦某个app同时使用的句柄数超过了这个限制,就会看到这个异常:too many open files。
tempFile.close();
}
file.close();
inputStream.close();
// 下载完成后,删除日志
if (temp != null) {
temp.delete();
}
// System.out.println("线程 " + threadIndex + " 结束下载");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public String getFileName(String path) {
String name = getCacheDir() + path.substring(path.lastIndexOf("/"));
return name;
}
3.3 xUtils
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multi_down_file);
// 初始化
x.Ext.init(getApplication());
}
protected void downFile(String fileUrl,String saveFilePath) {
// 设置下载地址
RequestParams requestParams = new RequestParams(fileUrl);
// 设置保存地址
requestParams.setSaveFilePath(saveFilePath);
x.http().get(requestParams, new Callback.ProgressCallback<File>() {
@Override
public void onSuccess(File result) {
Log.i("xUtils3", "下载成功");
showToast("下载成功!");
}
@Override
public void onError(Throwable ex, boolean isOnCallback) {
Log.i("xUtils3", "下载失败");
showToast("下载失败!");
}
@Override
public void onCancelled(CancelledException cex) {
Log.i("xUtils3", "下载被取消");
showToast("下载被取消");
}
@Override
public void onFinished() {
Log.i("xUtils3", "下载结束");
showToast("下载结束!");
}
@Override
public void onWaiting() {
// 网络请求开始的时候调用
Log.i("xUtils3", "等待下载");
}
@Override
public void onStarted() {
// 下载的时候不断回调
Log.i("xUtils3", "onStarted()");
}
@Override
public void onLoading(long total, long current, boolean isDownloading) {
// 当前的下载进度和文件总大小
Log.i("xUtils3", "下载进度......" + current + "/" + total);
}
});
}