1. Android网络前提
- 必须在子线程中执行。
- 需要申请INTERNET权限。
2. Android网络请求基础
2.1 HttpUrlConnection基础类
get请求
// final String PATH = "接口地址";private void doHttpUrlGet(final String uName, final String pwd) {new Thread() {@Overridepublic 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() {@Overridepublic 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 {@Overridepublic 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);}}@Overridepublic 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 {@Overridepublic void onFailure(@NotNull Call call, @NotNull IOException e) {setTextCount("请求失败:异常" + e.getMessage());}@Overridepublic 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() {@Overridepublic 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;}@Overridepublic 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 断点下载
原理
通过一个本地文件,记录每个线程下载了多少数据,一旦下载中断,下次再下载的时候,读取日志内容,重新从记录的位置开始请求数据并写入文件
实现
多线程+断点下载
@Overridepublic 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() {@Overridepublic 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;}@Overridepublic 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
@Overrideprotected 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>() {@Overridepublic void onSuccess(File result) {Log.i("xUtils3", "下载成功");showToast("下载成功!");}@Overridepublic void onError(Throwable ex, boolean isOnCallback) {Log.i("xUtils3", "下载失败");showToast("下载失败!");}@Overridepublic void onCancelled(CancelledException cex) {Log.i("xUtils3", "下载被取消");showToast("下载被取消");}@Overridepublic void onFinished() {Log.i("xUtils3", "下载结束");showToast("下载结束!");}@Overridepublic void onWaiting() {// 网络请求开始的时候调用Log.i("xUtils3", "等待下载");}@Overridepublic void onStarted() {// 下载的时候不断回调Log.i("xUtils3", "onStarted()");}@Overridepublic void onLoading(long total, long current, boolean isDownloading) {// 当前的下载进度和文件总大小Log.i("xUtils3", "下载进度......" + current + "/" + total);}});}
