Android如何实现茄子快传
Android如何实现茄子快传
茄子快传是一款文件传输应用,相信大家都很熟悉这款应用,应该很多人用过用来文件的传输。它有两个核心的功能:
端到端的文件传输
Web端的文件传输
这两个核心的功能我们具体来分析一下!
端到端的文件传输
所谓的端到端的文件传输是指应用端发送到应用端(这里的应用端指Android应用端),这种文件传输方式是文件发送端和文件接收端必须安装应用。
效果图
文件发送方
文件接收方
简单的文件传输的话,我们可以用蓝牙,wifi直连,ftp这几种方式来进行文件的传输。但是:
蓝牙传输的话,速度太慢,而且要配对。相对比较麻烦。
wifi直连差不多跟蓝牙一样,但是速率很快,也要配对。
ftp可以实现文件的批量传输,但是没有文件的缩略图。
最初分析这个项目的时候就想着通过自定义协议的Socket的通信来实现,自定义的协议包括header + body的自定义协议, header部分包括了文件的信息(长度,大小,文件路径,缩略图), body部分就是文件。现在实现这一功能。(后序:后面开发《网页传》功能的时候,可以考虑这两个核心的功能都能用在Android架设微型Http服务器来实现。这是后话了。)
流程图
编码实现
两部设备文件传输是需要在一个局域网的条件下的,只有文件发送方连接上文件接收方的热点(搭建了一个局域网),这样文件发送方和文件接收方就在一个局域网里面,我们才可以进行Socket通信。这是一个大前提!
初始化条件 – Ap(热点)和Wifi的管理, 文件的扫描
对Android的Ap(热点)和Wifi的一些操作都封装在下面两个类:
WifiMgr.java
APMgr.java
关于热点和Wifi的操作都是根据WifiManager来操作的。所以要像操作WifiManeger是必须要一些权限的。必须在AndroidManifest.xml清单文件里面声明权限:
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
1
2
3
4
5
文件接收端打开热点并且配置热点的代码:
//1.初始化热点
WifiMgr.getInstance(getContext()).disableWifi();
if(ApMgr.isApOn(getContext())){
ApMgr.disableAp(getContext());
}
//热点相关的广播
mWifiAPBroadcastReceiver = new WifiAPBroadcastReceiver() {
@Override
public void onWifiApEnabled() {
Log.i(TAG, "======>>>onWifiApEnabled !!!");
if(!mIsInitialized){
mUdpServerRuannable = createSendMsgToFileSenderRunnable();
AppContext.MAIN_EXECUTOR.execute(mUdpServerRuannable);
mIsInitialized = true;
tv_desc.setText(getResources().getString(R.string.tip_now_init_is_finish));
tv_desc.postDelayed(new Runnable() {
@Override
public void run() {
tv_desc.setText(getResources().getString(R.string.tip_is_waitting_connect));
}
}, 2*1000);
}
}
};
IntentFilter filter = new IntentFilter(WifiAPBroadcastReceiver.ACTION_WIFI_AP_STATE_CHANGED);
registerReceiver(mWifiAPBroadcastReceiver, filter);
ApMgr.isApOn(getContext()); // check Ap state :boolean
String ssid = TextUtils.isNullOrBlank(android.os.Build.DEVICE) ? Constant.DEFAULT_SSID : android.os.Build.DEVICE;
ApMgr.configApState(getContext(), ssid); // change Ap state :boolean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
对于类WifiAPBroadcastReceiver是热点的一个广播类,最后一行代码是配置指定名称的热点,这里是以设备名称作为热点的名称。
文件发送端发送文件,文件发送端首先要选择要发送的文件,然后将要选择的文件存储起来,这里我是用了一个HashMap将发送的文件存储起来,key是文件的路径,value是FileInfo对象。
以下是扫描手机存储盘上面的文件列表的代码:
/**
* 存储卡获取 指定后缀名文件
* @param context
* @param extension
* @return
*/
public static List<FileInfo> getSpecificTypeFiles(Context context, String[] extension){
List<FileInfo> fileInfoList = new ArrayList<FileInfo>();
//内存卡文件的Uri
Uri fileUri= MediaStore.Files.getContentUri("external");
//筛选列,这里只筛选了:文件路径和含后缀的文件名
String[] projection=new String[]{
MediaStore.Files.FileColumns.DATA, MediaStore.Files.FileColumns.TITLE
};
//构造筛选条件语句
String selection="";
for(int i=0;i<extension.length;i++)
{
if(i!=0)
{
selection=selection+" OR ";
}
selection=selection+ MediaStore.Files.FileColumns.DATA+" LIKE '%"+extension[i]+"'";
}
//按时间降序条件
String sortOrder = MediaStore.Files.FileColumns.DATE_MODIFIED;
Cursor cursor = context.getContentResolver().query(fileUri, projection, selection, null, sortOrder);
if(cursor != null){
while (cursor.moveToNext()){
try{
String data = cursor.getString(0);
FileInfo fileInfo = new FileInfo();
fileInfo.setFilePath(data);
long size = 0;
try{
File file = new File(data);
size = file.length();
fileInfo.setSize(size);
}catch(Exception e){
}
fileInfoList.add(fileInfo);
}catch (Exception e){
Log.i("FileUtils", "------>>>" + e.getMessage());
}
}
}
Log.i(TAG, "getSize ===>>> " + fileInfoList.size());
return fileInfoList;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
注意**:这里扫描的FileInfo对象只是扫描了文件路径filePath, 还有文件的大小size。
FileInfo的其他属性到文件传输的时候再二次获取,获取FileInfo的其他属性都在FileUtils这个工具类里面了。
文件发送端打开wifi扫描热点并且连接热点的代码:
if(!WifiMgr.getInstance(getContext()).isWifiEnable()) {//wifi未打开的情况,打开wifi
WifiMgr.getInstance(getContext()).openWifi();
}
//开始扫描
WifiMgr.getInstance(getContext()).startScan();
mScanResultList = WifiMgr.getInstance(getContext()).getScanResultList();
mScanResultList = ListUtils.filterWithNoPassword(mScanResultList);
if(mScanResultList != null){
mWifiScanResultAdapter = new WifiScanResultAdapter(getContext(),mScanResultList);
lv_result.setAdapter(mWifiScanResultAdapter);
lv_result.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//单击选中指定的网络
ScanResult scanResult = mScanResultList.get(position);
Log.i(TAG, "###select the wifi info ======>>>" + scanResult.toString());
//1.连接网络
String ssid = Constant.DEFAULT_SSID;
ssid = scanResult.SSID;
WifiMgr.getInstance(getContext()).openWifi();
WifiMgr.getInstance(getContext()).addNetwork(WifiMgr.createWifiCfg(ssid, null, WifiMgr.WIFICIPHER_NOPASS));
//2.发送UDP通知信息到 文件接收方 开启ServerSocketRunnable
mUdpServerRuannable = createSendMsgToServerRunnable(WifiMgr.getInstance(getContext()).getIpAddressFromHotspot());
AppContext.MAIN_EXECUTOR.execute(mUdpServerRuannable);
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
对于ListUtils.filterWithNoPassword是将扫描的结果进行过滤,过滤掉有密码的扫描结果。
lv_result.setOnItemClickListener回调的方法是连接指定的热点来形成一个局域网。文件传输的大前提条件就已经形成了。
到这里文件发送端和文件接收端的初始化环境也就搭建起来了。
文件传输模块
文件传输模块的核心代码就只有4个类,Transferable, BaseTransfer, FileSender, FileReceiver。
Transferable是接口。
BaseTransfer, FileSender, FileReceiver是类。
对于文件发送端,每一个文件发送对应一个FileSender,而对于文件接收端,每一个文件的接收对应一个FileReceiver。
而FileSender,FileReceiver是继承自 抽象类BaseTransfer的。 BaseTransfer是实现了Transferable接口。
下面是4个类图的关系:
在Transferable接口中定义了4个方法,分别是初始化,解析头部,解析主体,结束。解析头部和解析主体分别对应上面说的自定义协议的header和body。初始化是为每一次文件传输做初始化工作,而结束是为每一次文件传输做结束工作,比如关闭一些资源流,Socket等等。
而BaseTransfer就只是实现了Transferable, 里面封装了一些常量。没有实现具体的方法,具体的实现是FileSender,FileReceiver。
代码详情:
Transferable
BaseTransfer
FileSender
FileReceiver
总结
端到端的文件传输就分析到这里,主要是Ap热点的操作,Wifi的操作,Socket通信来实现文件的传输。但是这里的Socket用到的不是异步IO,是同步IO。所以会引起阻塞。比如在FileSender中的暂停文件传输pause方法调用之后,会引起FileReceiver中文件传输的阻塞。如果你对异步IO有兴趣,你也可以去实现一下。
对于端对端的核心代码都是在 io.github.mayubao.kuaichuan.core 包下面。
这是我在github上面的项目链接 https://github.com/mayubao/KuaiChuan
web端的文件传输
所谓的Web端的文件传输是指文件发送端作为一个Http服务器,提供文件接收端来下载。这种文件传输方式是文件发送端必须安装应用,而文件接收端只需要有浏览器即可。
效果图
文件发送端
文件接收端
在android应用端架设微型Http服务器来实现文件的传输。这里可以用ftp来实现,为什么不用ftp呢?因为没有缩略图,这是重点!
web端的文件传输的核心重点:
文件发送端热点的开启(参考端对端的热点操作类 APMgr.java)
文件发送端架设Http服务器。
Android端的Http服务器
Android上微型Http服务器(Socket实现),结合上面的效果图分析。主要解决三种Http url的请求形式就行了,由上面的文件接收端的效果图可以看出来(文件接收端是去访问文件发送端的Http服务器),大致可以分为三种链接:
Index主页链接 http://hostname:port
Image链接 http://hostname:port/image/xxx.xxx
Download链接 http://hostname:port/download/xxx.xxx
下面用Socket来实现在Android上面的微型Http服务器的。
关于Http协议,我简单的描述一下Http协议。对于Http协议,就是”请求-回复(响应)“的这种通信模式。客户端发出请求,服务器根据请求,返回一个回复(响应)给客户端。
Http请求的大致分为四个部分:
1. 请求行
2. 请求头
3. 空行
4. 请求实体
Http响应的大致分为四个部分:
1. 状态行
2. 响应头
3. 空行
4. 响应实体
Http请求(POST请求)的示例:
POST /image/index.html HTTP/1.1
Host: 127.0.0.1:7878
Connection: keep-alive
Content-Length: 247
Cache-Control: no-cache
Origin: chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
------WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Content-Disposition: form-data; name="username"
mayubao
------WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Content-Disposition: form-data; name="username"
123456
------WebKitFormBoundaryLIr5t1rdtuD8Ztuw--
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1.请求行(请求方式 + uri + http版本)
POST /image/index.html HTTP/1.1
1
2
2.请求头
Host: 127.0.0.1:7878
Connection: keep-alive
Content-Length: 247
Cache-Control: no-cache
Origin: chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
1
2
3
4
5
6
7
8
9
10
11
3.空行
4.请求实体(对于GET请求一般没有请求实体)
------WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Content-Disposition: form-data; name="username"
mayubao
------WebKitFormBoundaryLIr5t1rdtuD8Ztuw
Content-Disposition: form-data; name="username"
123456
------WebKitFormBoundaryLIr5t1rdtuD8Ztuw--
1
2
3
4
5
6
7
8
9
10
Http响应的示例:
HTTP/1.0 200 OK
Cache-Control:public, max-age=86400
Content-Length:235
Content-Type:image/png
Date:Wed, 21 Dec 2016 08:20:54 GMT
请求实体
1
2
3
4
5
6
7
8
1.状态行(Http版本 + 状态 + 描述)
HTTP/1.0 200 OK
1
2
2.响应头
HTTP/1.0 200 OK
Cache-Control:public, max-age=86400
Content-Length:235
Content-Type:image/png
Date:Wed, 21 Dec 2016 08:20:54 GMT
1
2
3
4
5
6
3.空行
4.响应实体
上面只是简单的叙述了一下Http一般的请求-响应流程,还有对应请求,响应的结构。如果你想进一步了解http协议,请私下自行了解。
回到我们的重点 AndroidMicroServer:
AndroidMicroServer是Http服务器的核心类,还有关联到其他的类,有IndexUriResHandler,ImageUriResHandler, DowloadUriResHandler。是AndroidMicroServer根据不同的Uri格式分配给指定的Handler去处理的。
UML的分析图如下:
下面是AndroidMicroServer的源码:
/**
* The micro server in Android
* Created by mayubao on 2016/12/14.
* Contact me 345269374@qq.com
*/
public class AndroidMicroServer {
private static final String TAG = AndroidMicroServer.class.getSimpleName();
/**
* the server port
*/
private int mPort;
/**
* the server socket
*/
private ServerSocket mServerSocket;
/**
* the thread pool which handle the incoming request
*/
private ExecutorService mThreadPool = Executors.newCachedThreadPool();
/**
* uri router handler
*/
private List<ResUriHandler> mResUriHandlerList = new ArrayList<ResUriHandler>();
/**
* the flag which the micro server enable
*/
private boolean mIsEnable = true;
public AndroidMicroServer(int port){
this.mPort = port;
}
/**
* register the resource uri handler
* @param resUriHandler
*/
public void resgisterResUriHandler(ResUriHandler resUriHandler){
this.mResUriHandlerList.add(resUriHandler);
}
/**
* unresigter all the resource uri hanlders
*/
public void unresgisterResUriHandlerList(){
for(ResUriHandler resUriHandler : mResUriHandlerList){
resUriHandler.destroy();
resUriHandler = null;
}
}
/**
* start the android micro server
*/
public void start(){
mThreadPool.submit(new Runnable() {
@Override
public void run() {
try {
mServerSocket = new ServerSocket(mPort);
while(mIsEnable){
Socket socket = mServerSocket.accept();
hanlderSocketAsyn(socket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
/**
* stop the android micro server
*/
public void stop(){
if(mIsEnable){
mIsEnable = false;
}
//release resource
unresgisterResUriHandlerList();
if(mServerSocket != null){
try {
// mServerSocket.accept(); //fuck ! fix the problem, block the main thread
mServerSocket.close();
mServerSocket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* handle the incoming socket
* @param socket
*/
private void hanlderSocketAsyn(final Socket socket) {
mThreadPool.submit(new Runnable() {
@Override
public void run() {
//1. auto create request object by the parameter socket
Request request = createRequest(socket);
//2. loop the mResUriHandlerList, and assign the task to the specify ResUriHandler
for(ResUriHandler resUriHandler : mResUriHandlerList){
if(!resUriHandler.matches(request.getUri())){
continue;
}
resUriHandler.handler(request);
}
}
});
}
/**
* create the requset object by the specify socket
*
* @param socket
* @return
*/
private Request createRequest(Socket socket) {
Request request = new Request();
request.setUnderlySocket(socket);
try {
//Get the reqeust line
SocketAddress socketAddress = socket.getRemoteSocketAddress();
InputStream is = socket.getInputStream();
String requestLine = IOStreamUtils.readLine(is);
SLog.i(TAG, socketAddress + "requestLine------>>>" + requestLine);
String requestType = requestLine.split(" ")[0];
String requestUri = requestLine.split(" ")[1];
// requestUri = URLDecoder.decode(requestUri, "UTF-8");
request.setUri(requestUri);
//Get the header line
String header = "";
while((header = IOStreamUtils.readLine(is)) != null){
SLog.i(TAG, socketAddress + "header------>>>" + requestLine);
String headerKey = header.split(":")[0];
String headerVal = header.split(":")[1];
request.addHeader(headerKey, headerVal);
}
} catch (IOException e) {
e.printStackTrace();
}
return request;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
AndroidMicroServer主要有两个方法:
1. start (Http服务器的开启)
2. stop (Http服务器的关闭,主要用来关闭ServerSocket和反注册UriResHandler)
start方法 是Http服务器的入口
对于start方法:
/**
* start the android micro server
*/
public void start(){
mThreadPool.submit(new Runnable() {
@Override
public void run() {
try {
mServerSocket = new ServerSocket(mPort);
while(mIsEnable){
Socket socket = mServerSocket.accept();
hanlderSocketAsyn(socket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
开启一个线程去执行ServerSocket, while循环去接收每一个进来的Socket。 而hanlderSocketAsyn(socket)是异步处理每一个进来的socket。
/**
* handle the incoming socket
* @param socket
*/
private void hanlderSocketAsyn(final Socket socket) {
mThreadPool.submit(new Runnable() {
@Override
public void run() {
//1. auto create request object by the parameter socket
Request request = createRequest(socket);
//2. loop the mResUriHandlerList, and assign the task to the specify ResUriHandler
for(ResUriHandler resUriHandler : mResUriHandlerList){
if(!resUriHandler.matches(request.getUri())){
continue;
}
resUriHandler.handler(request);
}
}
});
}
/**
* create the requset object by the specify socket
*
* @param socket
* @return
*/
private Request createRequest(Socket socket) {
Request request = new Request();
request.setUnderlySocket(socket);
try {
//Get the reqeust line
SocketAddress socketAddress = socket.getRemoteSocketAddress();
InputStream is = socket.getInputStream();
String requestLine = IOStreamUtils.readLine(is);
SLog.i(TAG, socketAddress + "requestLine------>>>" + requestLine);
String requestType = requestLine.split(" ")[0];
String requestUri = requestLine.split(" ")[1];
// //解决URL中文乱码的问题
// requestUri = URLDecoder.decode(requestUri, "UTF-8");
request.setUri(requestUri);
//Get the header line
String header = "";
while((header = IOStreamUtils.readLine(is)) != null){
SLog.i(TAG, socketAddress + "header------>>>" + requestLine);
String headerKey = header.split(":")[0];
String headerVal = header.split(":")[1];
request.addHeader(headerKey, headerVal);
}
} catch (IOException e) {
e.printStackTrace();
}
return request;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
对于每一个进来的Socket:
通过createRequest(socket)来创建一个Request对象,对应一个Http Request对象。在createRequest(socket)中如何去从socket中去读取每一行呢?对于每一个Http请求的每一行都是以’\r\n’字节结尾的。只要判断读取字节流的时候判断连续的两个字节是以’\r\n’结尾的就是一行结尾的标识。详情请查看IOStreamUtils.java
根据请求行的path,分配给对应的Uri处理对象去处理,而所对应uri如何获取,是从Socket的Inputsream读取Http Request的请求行中读取出来的。对于ResUriHandler,是一个接口。主要根据请求行的uri 分配给对应的ResUriHandler去处理。 ResUriHandler的实现类是对应给出响应的处理类。
注意:可参考上面的UML的类图分析
ResUriHandler有三个实现类分别对应上面分析的三种Uri格式:
IndexResUriHandler 处理发送文件列表的显示
ImageResUriHandler 处理文件图片
DownloadResUriHandler 处理文件下载
总结
AndroidMicroServer是架设在Android平台上面的一个微型HttpServer, 是根据快传项目的具体需求来实现的。巧妙的利用ResUriHandler来处理不同的uri。注意这不是一般通用的HttpServer, 之前有想过在Github上面去找一些Server端的代码来进行开发,发现代码关联太多,而且不容易定制,所以才会萌生自己用ServerSocket来实现符合自己需求的HttpServer。
对于HttpServer的核心代码都是在 io.github.mayubao.kuaichuan.micro_server包下面。
这是我在github上面的项目链接 https://github.com/mayubao/KuaiChuan
Android如何实现茄子快传的更多相关文章
- 从”茄子快传”看应用程序怎样获取手机已安装程序的apk文件
"茄子快传"是联想开发的一款近距离文件共享软件.它通过wifi-direct(速度飞快,不须要联网)或者普通的网络(速度慢)在不同手机间传递文件. 不知为何.它就火了起来,火的也飞 ...
- android开发之socket快传文件以及消息返回
应用场景: 两台android机器:一台自建wifi热点,另一台搜索到,连接该wifi热点.之后再通过socket传递消息,文件等,当服务器端接收到消息之后会返回对应的应答消息: 注意点:接收到消息之 ...
- Android端通过HttpURLConnection上传文件到服务器
Android端通过HttpURLConnection上传文件到服务器 一:实现原理 最近在做Android客户端的应用开发,涉及到要把图片上传到后台服务器中,自己选择了做Spring3 MVC HT ...
- Android仿微信图片上传,可以选择多张图片,缩放预览,拍照上传等
仿照微信,朋友圈分享图片功能 .可以进行图片的多张选择,拍照添加图片,以及进行图片的预览,预览时可以进行缩放,并且可以删除选中状态的图片 .很不错的源码,大家有需要可以下载看看 . 微信 微信 微信 ...
- 开源:矿Android新闻client,快、小、支持离线阅读、操作简单、内容丰富,形式多样展示、的信息量、全功能 等待(离开码邮箱)
分享:矿Android新闻client.快.小.支持离线阅读.操作简单.内容丰富,形式多样展示.的信息量.全功能 等待(离开码邮箱) 历时30天我为了开发这个新闻clientAPP,下面简称觅闻 ht ...
- android+nutz后台如何上传和下载图片
android+nutz后台如何上传和下载图片 发布于 588天前 作者 yummy222 428 次浏览 复制 上一个帖子 下一个帖子 标签: 无 最近在做一个基于android的ap ...
- Android端通过HttpURLConnection上传文件到server
Android端通过HttpURLConnection上传文件到server 一:实现原理 近期在做Androidclient的应用开发,涉及到要把图片上传到后台server中.自己选择了做Sprin ...
- Android连接socket服务器上传下载多个文件
android连接socket服务器上传下载多个文件1.socket服务端SocketServer.java public class SocketServer { ;// 端口号,必须与客户端一致 ...
- Android+Spring Boot 选择+上传+下载文件
2021.02.03更新 1 概述 前端Android,上传与下载文件,使用OkHttp处理请求,后端使用Spring Boot,处理Android发送来的上传与下载请求.这个其实不难,就是特别多奇奇 ...
随机推荐
- Python_collections_OrderedDict有序字典部分功能介绍
OrderedDict():实现字典的固定排序,是字典的子类 import collections dic = collections.OrderedDict() dic['k1'] = 3 dic[ ...
- LVM管理之减少LV的大小
LVM管理之减少LV的大小 规定动作 1.umount filesystem 2.e2fsck filesystem 3.resize2fs filesystem 4.lvredure 实例演示——— ...
- sublime text 自定义插件,自动插入署名,自定义插入日期,自动生成头部注释
自动插入署名 菜单下面的 一.工具(tool)>新代码段(new snippet…) 看到以下代码 <snippet> <content><![CDATA[ Hel ...
- 053 kafka自带的生产者与消费者测试
1.命令 2.启动生产者 bin/kafka-console-producer.sh --topic beifeng --broker-list linux-hadoop01.ibeifeng.com ...
- JavaEE 之 log4j
1.log4j a.概念:一个非常优秀的开源日志记录工具 b.配置: ①src同目录下建立log4j.properties文件,书写: log4j.rootLogger=debug,appender1 ...
- POJ 3159 Candies 【差分约束+Dijkstra】
<题目链接> 题目大意: 给n个人派糖果,给出m组数据,每组数据包含A,B,c 三个数,意思是A的糖果数比B少的个数不多于c,即B的糖果数 - A的糖果数<= c .最后求n 比 1 ...
- shell script exit if any command fails
dd this to the beginning of the script: set -e This will cause the shell to exit immediately if a si ...
- .net(二)
1.维护数据库的完整性.一致性.你喜欢用触发器还是自写业务逻辑?为什么? 答:尽可能用约束(包括CHECK.主键.唯一键.外键.非空字段)实现,这种方式的效率最好:其次用触发器,这种方式可以保证无论何 ...
- [MySQL] MySQL联表查询的执行顺序优化查询
SELECT t4.orgName, t3.projectName, t3.Partner, t1.type, COUNT(DISTINCT t1.imei) AS count FROM `t_tem ...
- AGC 002E.Candy Piles(博弈论)
题目链接 \(Description\) 给定\(n\)堆糖,数量分别为\(a_i\).Alice和Bob轮流操作.每次可以吃掉最多的一堆,也可以每堆各吃掉一个.无法操作的人输,求谁能赢. \(n\l ...