一.开发初衷:最近项目中需要用到版本升级这一块,需要用到一些基本的数据请求与文件下载功能。之前做项目都是用别人的网络框架,类似retrofit 、 okhttp、 fresco等框架,用的多了,发现这几个网络请求框架,无非都是
按解决以下几个问题为导向的:
1.怎么发请求?
2.Cookie的问题。
3.如何停止请求(好像上面提到的几个框架没有停止请求的概念,因为停止请求常用用SOcket长连接协议中,
而http是短连接,只要触发了请求,就失去了控制一样。)
4.请求的并发?
5.如何管理请求的优先级(类似http这种协议请求,几乎可以忽略,请求的优先级常用于socket协议中)
一直都想写一个自己的网络请求框架,借此项目机会,刚好用上了,现将设计思路与源码贡献出来与各位一起交流学习,如有写的不好,请各位大神,批评指正,谢谢。
先从回调接口说起:这个框架中主要有两类回调
第一类为普通的字符串请求(类似json都可以视为一种字符串,只是一种特殊的格式封装的数据)
第二类为文件类的byte流数据。
package com.example.lxb.hellohttp.httpclient.listener;
/**
* 回调基类接口
* Created by lxb on 2017/4/12.
*/
public abstract class BaseRequestListener
public abstract void onSuccess(T result);
public abstract void onFailure(T result);
public void onExcetion(T e) {
}
public void Excetion(String e){
}
public void onLoading(long total,long curProgress){
}
}
在这个类中将回调共有的的方法封装出来,如果没有特殊的回调行为可以直接用这个基类作为回调,否则可以自己去扩展。
这里我还写了一个文件的监听器:
package com.example.lxb.hellohttp.httpclient.listener;
/**
* 文件监听器
*
* Created by lxb on 2017/4/14.
*/
public class FileListener
@Override
public void onSuccess(T result) {
}
@Override
public void onFailure(T result) {
}
public void Excetion(String e){
}
public void onLoading(long total,long curProgress){
}
}
二。因网络请求本身就是一种I/O操作,并且是一种阻塞式的请求。如果直接放在主线程中进行很明显会影响主线程的运行,且android系统中不允许这样干。
鉴于此,本框架中的所有请求均在一个新的线程中进行。
先来看普通字符串的请求线程:
package com.example.lxb.hellohttp.httpclient.client;
import com.example.lxb.hellohttp.httpclient.HttpClient;
import com.example.lxb.hellohttp.httpclient.handler.MsgHandler;
import com.example.lxb.hellohttp.httpclient.listener.BaseRequestListener;
import com.example.lxb.hellohttp.httpclient.request.RequestParams;
/**
* 请求线程
*
* Created by lxb on 2017/4/12.
*/
public class RequestThread extends Thread {
private HttpClient httpClient;
private MsgHandler response;
private RequestParams requestParams;
public RequestParams getRequestParams() {
return requestParams;
}
public void setRequestParams(RequestParams requestParams) {
this.requestParams = requestParams;
}
public HttpClient getHttpClient() {
return httpClient;
}
public void setHttpClient(HttpClient httpClient) {
this.httpClient = httpClient;
}
public MsgHandler getResponse() {
return response;
}
public void setResponse(MsgHandler response) {
this.response = response;
}
@Override
public void run() {
super.run();
this.httpClient.execute(requestParams,response);
}
}
文件请求线程:
package com.example.lxb.hellohttp.httpclient.client;
import com.example.lxb.hellohttp.httpclient.HttpClient;
import com.example.lxb.hellohttp.httpclient.handler.MsgHandler;
import com.example.lxb.hellohttp.httpclient.request.FileRequest;
/**
* 文件网络请求线程
*
* Created by lxb on 2017/4/13.
*/
public class FileReuqestThread extends Thread {
private HttpClient httpClient;
private MsgHandler response;
private FileRequest fileRequest;
public FileRequest getFileRequest() {
return fileRequest;
}
public void setFileRequest(FileRequest fileRequest) {
this.fileRequest = fileRequest;
}
public HttpClient getHttpClient() {
return httpClient;
}
public void setHttpClient(HttpClient httpClient) {
this.httpClient = httpClient;
}
public MsgHandler getResponse() {
return response;
}
public void setResponse(MsgHandler response) {
this.response = response;
}
@Override
public void run() {
super.run();
this.httpClient.execute(fileRequest,response);
}
}
这里完全可以使用一个请求线程,但为区分不同的请求类型还是写了两个类,
两个类的行为完全一致:
1.保持真正干活类实例对象的引用(后面会讲,别急^_^)
2.绑定请求对象
3.消息分发句柄(主要用来解决android线程的通信问题,后面会讲)
既然说到这时直接把请求对象都给出来吧:
普通字符 串请求对象,如果是进行这类的请求,需要在这里进行设置参数:
package com.example.lxb.hellohttp.httpclient.request;
/**
* 请求基类
* Created by lxb on 2017/4/13.
*/
public class Request {
private String URL;
private String method;
public String getURL() {
return URL;
}
public void setURL(String URL) {
this.URL = URL;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
}
不好意思,上面是一个请求基类,
下面才给出真正的普通字符串请求对象:
package com.example.lxb.hellohttp.httpclient.request;
import android.text.TextUtils;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Created by lxb on 2017/4/11.
*/
public class RequestParams extends Request{
private String defaultMethod = "POST";
private Map
public Map
return params;
}
public void setParams(Map
this.params = params;
}
}
文件请求对象来喽:
package com.example.lxb.hellohttp.httpclient.request;
/**
* Created by lxb on 2017/4/13.
*/
public class FileRequest extends Request{
private String filePath;
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
}
三。再来看看直正的干活类:
方法分发接口,主要为了颗粒度:
/**
* 开始执行普通 数据网络请求操作
*
* @param requestParams
*/
public void execute(RequestParams requestParams, MsgHandler response) {
this.method = requestParams.getMethod();
this.URL = requestParams.getURL();
Map
if (!params.isEmpty()) {
paramsData = new JSONObject();
Iterator iter = params.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
try {
paramsData.put(entry.getKey().toString(), entry.getValue().toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
}
request(this.method, this.URL, paramsData.toString(), response);
}
/**
* 默认请求方法采用POST
*
* @param method
* @param url
* @param param
* @return
*/
public void request(String method, String url, String param, MsgHandler response) {
if (method.equals(MsgCode.POST)) {
doPost(url, param, response);
} else if (method.equals(MsgCode.GET)) {
doPost(url, param, response);
}
}
四。http请求方式:Post 与 Get (这两种方式的区别这里暂且不提)两种方式,主要是先设置一些请求格式,然后将http中的输入/输出流给拿出来进行相应的操作就行了。
http相对socket协议来说简单很多,它的数据包格式都已经封装好了,也就是它有固定的消息头
但如果采用socket协议的话,消息头就需要自己去定义了,不然很容易出现粘包的情况。
post 方式:代码中有注释,不详细解释了
/**
* 向指定 URL 发送POST方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
* @throws Exception
*/
public void doPost(String url, String param, MsgHandler response) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("charset", "utf-8");
conn.setUseCaches(false);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setReadTimeout(TIMEOUT_IN_MILLIONS);
conn.setConnectTimeout(TIMEOUT_IN_MILLIONS);
if (param != null && !param.trim().equals("")) {
out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.flush();
}
if (conn.getResponseCode() == 200) {
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} else {
Map
Failure.put(MsgCode.Failure_Key, conn.getResponseCode() + "");
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Failure, Failure);
}
} catch (Exception e) {
e.printStackTrace();
Map
Exception.put(MsgCode.Exection_Key, e.toString());
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Exection, Exception);
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
Map
Exception.put(MsgCode.Exection_Key, ex.toString());
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Exection, Exception);
}
}
Map
success.put(MsgCode.Success_Key, result);
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Success, success);
//return result;
}
get 方式:
/**
* Get请求,获得返回数据
*
* @param urlStr
* @return
* @throws Exception
*/
public void doGet(String urlStr, String param, MsgHandler response) {
PrintWriter out = null;
URL url = null;
HttpURLConnection conn = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
url = new URL(urlStr);
conn = (HttpURLConnection) url.openConnection();
conn.setUseCaches(false);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setReadTimeout(TIMEOUT_IN_MILLIONS);
conn.setConnectTimeout(TIMEOUT_IN_MILLIONS);
conn.setRequestMethod("GET");
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
if (param != null && !param.trim().equals("")) {
out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.flush();
}
if (conn.getResponseCode() == 200) {
is = conn.getInputStream();
baos = new ByteArrayOutputStream();
int len = -1;
byte[] buf = new byte[1024];
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
}
baos.flush();
Map
success.put(MsgCode.Success_Key, baos.toString());
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Success, success);
//return baos.toString();
} else {
Map
Failure.put(MsgCode.Failure_Key, conn.getResponseCode() + "");
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Failure, Failure);
//throw new RuntimeException(" responseCode is not 200 ... ");
}
} catch (Exception e) {
//e.printStackTrace();
Map
Exception.put(MsgCode.Exection_Key, e.toString());
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Exection, Exception);
} finally {
try {
if (is != null)
is.close();
} catch (IOException e) {
Map
Exception.put(MsgCode.Exection_Key, e.toString());
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Exection, Exception);
}
try {
if (baos != null)
baos.close();
} catch (IOException e) {
Map
Exception.put(MsgCode.Exection_Key, e.toString());
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Exection, Exception);
}
conn.disconnect();
}
//return null;
}
上传文件方法:主要是注意文件请求头的格式,其他没什么了
/**
* 上传文件时,注意http协议上传文件的协议头部格式
*
* 示例:例如向主机192.168.1.8上传图片格式如下:
*
* POST/logsys/home/uploadIspeedLog!doDefault.html HTTP/1.1
* Accept: text/plain,
* Accept-Language: zh-cn
* Host: 192.168.1.8
* Content-Type:multipart/form-data;boundary=-----------------------------7db372eb000e2 // step 1
* User-Agent: WinHttpClient
* Content-Length: 3693
* Connection: Keep-Alive
*
* -------------------------------7db372eb000e2 //step 2
* Content-Disposition: form-data; name="file"; filename="kn.jpg" //step 3
* Content-Type: image/jpeg
* (此处省略jpeg文件二进制数据...)
* -------------------------------7db372eb000e2-- //step 4
*
* @param RequestURL
* @param path
* @param response
* @return
*/
public boolean uploadFile(String RequestURL, String path, MsgHandler response) {
long uploadCount = 0;
long totalSize = 0;
Map
Uploadprogress.put(MsgCode.Loading_Total_Key, totalSize + "");
File uploadFile = new File(path);
if (!uploadFile.exists()) return false;
totalSize = uploadFile.length();
String filePostfix = path.substring(path.lastIndexOf("/") + 1, path.length());
String lineEnd = "\r\n"; //严格遵循http协议包含换行
String twoHyphens = "--"; //边界前后必须用--声明
String boundary = "*****"; //边界声明,可自定义
try {
URL url = new URL(RequestURL);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
/**
* 允许Input、Output,不使用Cache
*/
con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false);
con.setReadTimeout(TIMEOUT_IN_MILLIONS);
con.setConnectTimeout(TIMEOUT_IN_MILLIONS);
/**
* 设置http连接属性,这个是参照传输文件的头部信息来写的
*/
con.setRequestMethod("POST");
con.setRequestProperty("Connection", "Keep-Alive");
con.setRequestProperty("Charset", "UTF-8");
con.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); // step1
DataOutputStream dataOutputStream = new DataOutputStream(con.getOutputStream()); //获取输出流通过此处进行文件上传
/**
* 注意格式 即:头+边界+尾,它们的分隔可以自定义,方便分隔一张图片是否已经上传完毕
*/
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd); //step 2
dataOutputStream.writeBytes("Content-Disposition: form-data; " + "name=\"file\";filename=\"" + filePostfix + "\"" + lineEnd); //step3
dataOutputStream.writeBytes(lineEnd); //添加换行,每一句格式都须严格遵守
/**
* 处理文件
*/
FileInputStream fStream = new FileInputStream(path);
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = -1;
while ((length = fStream.read(buffer)) != -1) {
dataOutputStream.write(buffer, 0, length);
uploadCount += bufferSize;
Uploadprogress.put(MsgCode.Loading_CurProgress_Key, ((uploadCount * 100) / totalSize) + "");
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Loading, Uploadprogress);
//System.out.println("369-----------------已上传:"+((uploadCount * 100) / totalSize));
}
/**
* 写入文件流时,再写入一遍文件尾,告诉服务器我文件流上传完成可以读取了
*/
dataOutputStream.writeBytes(lineEnd);
dataOutputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
fStream.close();
dataOutputStream.flush();
/* 取得Response内容 */
/* InputStream is = con.getInputStream();
int ch;
StringBuffer b = new StringBuffer();
while ((ch = is.read()) != -1) {
b.append((char) ch);
}*/
dataOutputStream.close();
int nResponseCode = con.getResponseCode();
if (nResponseCode == 200) {
Map
success.put(MsgCode.Success_Key, path);
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Success, success);
return true;
} else {
return false;
}
} catch (Exception e) {
System.out.println("上传失败:" + e);
Map
Exception.put(MsgCode.Exection_Key, e.toString());
ThreadCrossHandler.sendMsgByHandler(response, MsgCode.Exection, Exception);
return false;
}
}
下载文件:
/**
* 下载文件,并把文件存储在制定目录(-1:下载失败,0:下载成功,1:文件已存在)
*
* @param urlStr
* @param path
* @param fileName
* @param response
* @return
*/
public int downloadFiles(String urlStr, String path, String fileName, MsgHandler response) {
try {
FileUtils fileUtils = FileUtils.getInstance();
if (fileUtils.isFileExist(fileName, path)) return 1;
else {
InputStream inputStream = getInputStreamFromUrl(urlStr);
int fileSize = getInputStreamSizeFromUrl(urlStr);
File resultFile = fileUtils.write2SDFromInput(fileSize, fileName, path, inputStream, response);
if (resultFile == null) return -1;
}
} catch (Exception e) {
System.out.println("读写数据异常:" + e);
return -1;
}
return 0;
}
/**
* 通过url获取输入流
*
* @param urlStr
* @return
* @throws IOException
*/
public InputStream getInputStreamFromUrl(String urlStr) throws IOException {
URL url = new URL(urlStr);
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
InputStream inputStream = urlConn.getInputStream();
return inputStream;
}
/**
* 获取输入流中文件大小
*
* @param urlStr
* @return
* @throws IOException
*/
private int getInputStreamSizeFromUrl(String urlStr) throws IOException {
URL url = new URL(urlStr);
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
return urlConn.getContentLength();
}
请求代理类:
package com.example.lxb.hellohttp.httpclient;
import com.example.lxb.hellohttp.httpclient.client.FileReuqestThread;
import com.example.lxb.hellohttp.httpclient.client.RequestThread;
import com.example.lxb.hellohttp.httpclient.handler.MsgHandler;
import com.example.lxb.hellohttp.httpclient.request.FileRequest;
import com.example.lxb.hellohttp.httpclient.request.RequestParams;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 网编请求的代理类
*
* Created by lxb on 2017/4/11.
*/
public class HelloHttp extends HttpClient {
private static HelloHttp helloHttp;
private RequestParams requestParams;
private FileRequest fileRequest;
private MsgHandler response;
private RequestThread requestThread;
private FileReuqestThread fileReuqestThread;
private BlockingQueue
private BlockingQueue
/**
* 单例方式构建请求实例对象
*
* @return
*/
public static HelloHttp build() {
if (helloHttp == null) {
synchronized (HelloHttp.class) {
if (helloHttp == null) {
helloHttp = new HelloHttp();
}
}
}
return helloHttp;
}
/**
* 设置普通字符串请求
*
* @param requestParams
* @return
*/
public HelloHttp setRequestParams(RequestParams requestParams) {
this.requestParams = requestParams;
return helloHttp;
}
/**
* 设置文件请求
*
* @param fileRequest
* @return
*/
public HelloHttp setFileRequest(FileRequest fileRequest){
this.fileRequest = fileRequest;
return helloHttp;
}
/**
* 设置消息转发句柄
*
* @param response
* @return
*/
public HelloHttp setResponse(MsgHandler response) {
this.response = response;
return helloHttp;
}
public HelloHttp() {
}
/**
* 执行请求逻辑
*/
public HelloHttp execute() {
requestThread = new RequestThread();
requestThread.setRequestParams(this.requestParams);
requestThread.setHttpClient(helloHttp);
requestThread.setResponse(this.response);
requestQueue.add(requestThread);
requestThread = requestQueue.poll();
requestThread.start();
return helloHttp;
}
public HelloHttp executeFile(){
fileReuqestThread = new FileReuqestThread();
fileReuqestThread.setFileRequest(fileRequest);
fileReuqestThread.setResponse(this.response);
fileReuqestThread.setHttpClient(helloHttp);
fileRequestQueue.add(fileReuqestThread);
fileReuqestThread = fileRequestQueue.poll();
fileReuqestThread.start();
return helloHttp;
}
}
使用方法:
package com.example.lxb.microhttp;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.lxb.hellohttp.httpclient.HelloHttp;
import com.example.lxb.hellohttp.httpclient.HttpClient;
import com.example.lxb.hellohttp.httpclient.handler.MsgCode;
import com.example.lxb.hellohttp.httpclient.handler.MsgHandler;
import com.example.lxb.hellohttp.httpclient.listener.BaseRequestListener;
import com.example.lxb.hellohttp.httpclient.request.FileRequest;
import com.example.lxb.hellohttp.httpclient.request.RequestParams;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
private String TestUrl = "http://192.168.1.8:8080/netTest/index.jsp";
private HelloHttp helloHttp;
private TextView txtResult;
private String uploadURL = "http://192.168.1.8:8080/netTest/servlet/UploadHandleServlet";
public static String APKPATH = Environment.getExternalStorageDirectory() + "/arapp/智视.apk";
public static String downLoadUrl = "http://192.168.1.8:8080/netTest/head/123.apk";
public static String downPath = "/aaa";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
EventClick eventClick = new EventClick();
txtResult = (TextView) findViewById(R.id.txt_result);
Button link = (Button) findViewById(R.id.btn_link);
link.setOnClickListener(eventClick);
Button upload = (Button) findViewById(R.id.btn_upload);
upload.setOnClickListener(eventClick);
Button download = (Button) findViewById(R.id.btn_download);
download.setOnClickListener(eventClick);
}
private class EventClick implements View.OnClickListener {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_link:
Map
pamrasMap.put("username", "liu");
pamrasMap.put("pwd", "123456");
RequestParams params = new RequestParams();
params.setURL(TestUrl);
params.setMethod("POST");
params.setParams(pamrasMap);
helloHttp = HelloHttp.build()
.setRequestParams(params)
.setResponse(new MsgHandler().setProxyListener(new StringRequest()))
.execute();
break;
case R.id.btn_upload:
FileRequest fileRequest = new FileRequest();
fileRequest.setURL(uploadURL);
fileRequest.setFilePath(APKPATH);
fileRequest.setMethod(MsgCode.UPLOADFILE);
helloHttp = HelloHttp.build()
.setFileRequest(fileRequest)
.setResponse(new MsgHandler().setProxyListener(new UploadListener()))
.executeFile();
break;
case R.id.btn_download:
FileRequest downRequest = new FileRequest();
downRequest.setURL(downLoadUrl);
downRequest.setFilePath(downPath);
downRequest.setMethod(MsgCode.DOWNLOADFILE);
helloHttp = HelloHttp.build()
.setFileRequest(downRequest)
.setResponse(new MsgHandler().setProxyListener(new DownLoadListener()))
.executeFile();
break;
}
}
}
/**
* 请求的回调
*/
private class StringRequest extends BaseRequestListener
@Override
public void onSuccess(String result) {
txtResult.setText("");
txtResult.setText(result);
System.out.println("70-------------------:" + result);
}
@Override
public void onFailure(String result) {
System.out.println("78-------------------:" + result);
}
@Override
public void onExcetion(String e) {
super.onExcetion(e);
System.out.println("81-------------------:" + e);
}
}
/**
* 上传文件的监听器
*/
private class UploadListener extends BaseRequestListener
@Override
public void onSuccess(String filePath) {
System.out.println("150----------------上传成功本地文件路径为:"+filePath);
}
@Override
public void onFailure(String result) {
}
@Override
public void onExcetion(String e) {
super.onExcetion(e);
}
@Override
public void onLoading(long total, long curProgress) {
super.onLoading(total, curProgress);
txtResult.setText("");
txtResult.setText(curProgress + "");
//System.out.println("152-------------------:" + curProgress);
}
}
/**
* 下载的监听器
*
*/
private class DownLoadListener extends BaseRequestListener
@Override
public void onSuccess(String filePath) {
System.out.println("184----------------下载成功本地文件路径为:"+filePath);
}
@Override
public void onFailure(String result) {
}
@Override
public void onExcetion(String e) {
super.onExcetion(e);
}
@Override
public void onLoading(long total, long curProgress) {
super.onLoading(total, curProgress);
txtResult.setText("");
txtResult.setText(curProgress + "");
System.out.println("199-------------------:" + curProgress);
}
}
}