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 [email protected]
*/
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

原文地址:https://www.cnblogs.com/timxgb/p/10659961.html

时间: 2024-10-13 01:40:16

Android如何实现茄子快传的相关文章

从”茄子快传”看应用程序怎样获取手机已安装程序的apk文件

"茄子快传"是联想开发的一款近距离文件共享软件.它通过wifi-direct(速度飞快,不须要联网)或者普通的网络(速度慢)在不同手机间传递文件. 不知为何.它就火了起来,火的也飞快.当中.共享传输已安装程序文件apk这一功能引起了我强烈的兴趣. 我们知道android对每一个应用的权限做了非常苛刻的控制,每一个应用程序有自己的用户id,每一个应用程序仅仅能訪问自己的数据,比方程序com.android.calculator计算器程序仅仅被同意訪问/data/data/com.andr

从”茄子快传”看应用程序如何获取手机已安装程序的apk文件

"茄子快传"是联想开发的一款近距离文件共享软件,它通过wifi-direct(速度飞快,不需要联网)或者普通的网络(速度慢)在不同手机间传递文件.不知为何,它就火了起来,火的也飞快.其中,共享传输已安装程序文件apk这一功能引起了我强烈的兴趣. 我们知道android对每个应用的权限做了很苛刻的控制,每个应用程序有自己的用户id,每个应用程序只能访问自己的数据,比如程序com.android.calculator计算器程序只被允许访问/data/data/com.android.cal

茄子快传数据分析之原理分析及数据清洗

茄子快传数据分析之原理分析及数据清洗 版权声明:闻道有先后,术业有专攻. https://blog.csdn.net/wlk_328909605/article/details/82227410 需求:联想集团有一款app产品叫茄子快传(有上亿的活跃用户,集中在第三世界国家) 现在需要开发一个数据分析系统,来对app的用户行为数据做各类分析: 原理: 流程如下图: 流程简单介绍: 用户通过茄子的客户端产生数据, 将使用时间,手机号,ip地址,手机的序列号,app的版本,app的下载渠道等重要信息

茄子快传数据分析(一)----数据清理

茄子快传数据分析(一)----数据清理 2018年09月03日 18:41:44 amin_hui 阅读数:117 茄子快传原理 流程图:  数据 “events”: “1473367236143\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001160907223957000

android开发之socket快传文件以及消息返回

应用场景: 两台android机器:一台自建wifi热点,另一台搜索到,连接该wifi热点.之后再通过socket传递消息,文件等,当服务器端接收到消息之后会返回对应的应答消息: 注意点:接收到消息之后不能直接拔dos关闭了,这样会造成socket关闭; socket关闭只需要一端关闭即可; 这里发送没有使用缓存发送,每一次writeInt的时候度会直接发送出去,,因此可以不需要flush(): 每一次的发送端的 write 和 接收端的read  必须对应,否则异常出错! 服务器端socket

Android连接socket服务器上传下载多个文件

android连接socket服务器上传下载多个文件1.socket服务端SocketServer.java public class SocketServer { int port = 8888;// 端口号,必须与客户端一致 // 选择进行传输的文件(测试) String path = "C:\\Temp"; String filePath = "E:\\img.png"; Socket client; public static void main(Strin

android form表单上传文件

原文地址:http://menuz.iteye.com/blog/1282097 Android程序使用http上传文件 有时,在网络编程过程中需要向服务器上传文件.Multipart/form-data是上传文件的一种方式. Multipart/form-data其实就是浏览器用表单上传文件的方式.最常见的情境是:在写邮件时,向邮件后添加附件,附件通常使用表单添加,也就是用multipart/form-data格式上传到服务器.  Html代码   <form action="/Test

android/IOS常用图片上传的两种方式

android/IOS常用图片上传的两种方式: 1.上传到服务器的文件服务器(FileServer) 原理:上传到文件服务器的方式是先在服务器端搭建文件服务器,配置好路径(url),该路径是我们待会上传图片的路径,配置成功后便通过http+post的模式上传到文件服务器,同时文件服务器将返回一个图片ID,这个ID就是图片的唯一标识,并将该ID写入数据库保存,当需要下载该图片时只需要将此ID带上即可. 两个核心问题: (1)服务端:配置FileServer,并写处理响应上传图片的代码,这个值得去网

android+spring boot 选择,上传,下载文件

1 概述 前端android,上传与下载文件,使用OkHttp处理请求,后端使用spring boot+MVC,处理android发送来的上传与下载请求.这个其实不难,就是特别多奇奇怪怪的坑,因此,希望看到的,不要像笔者这样踩的那么痛苦了... 2 环境 win10 Spring Boot 2.2.2 RELEASE IDEA 2019.3.1 Android Studio 3.6RC1 Tomcat 9.0.30 3 android 3.1 准备工作 3.1.1 新建工程 这次用一个全新的例子