├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── speakin
│ │ └── recorder
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── speakin
│ │ │ └── recorder
│ │ │ ├── RecorderApp.java
│ │ │ ├── module
│ │ │ └── control
│ │ │ │ ├── ControlConstants.java
│ │ │ │ ├── MasterControlManager.java
│ │ │ │ ├── SlaveControlManager.java
│ │ │ │ ├── search
│ │ │ │ ├── DeviceBroadcastReceiver.java
│ │ │ │ ├── DeviceBroadcastSender.java
│ │ │ │ ├── DeviceReceiver.kt
│ │ │ │ ├── MasterSearchManager.java
│ │ │ │ └── SlaveSearchManager.java
│ │ │ │ ├── socket
│ │ │ │ ├── CommonUtil.java
│ │ │ │ ├── SocketClient.java
│ │ │ │ ├── SocketClientManager.java
│ │ │ │ ├── SocketServerManager.java
│ │ │ │ ├── data
│ │ │ │ │ ├── MsgEntity.java
│ │ │ │ │ ├── MsgQueueManager.java
│ │ │ │ │ ├── TextMsgEntity.java
│ │ │ │ │ └── VoiceMsgEntity.java
│ │ │ │ └── handle
│ │ │ │ │ ├── FileHandlerSocket.java
│ │ │ │ │ ├── MsgParam.java
│ │ │ │ │ ├── MsgReceiveHandler.java
│ │ │ │ │ ├── MsgRequest.java
│ │ │ │ │ ├── MsgSendHandler.java
│ │ │ │ │ ├── RequestQueueManager.java
│ │ │ │ │ └── TextHandlerSocket.java
│ │ │ │ └── websocket
│ │ │ │ ├── MasterWebSocketManager.java
│ │ │ │ └── SlaveWebSocketManager.java
│ │ │ ├── ui
│ │ │ └── MainActivity.java
│ │ │ └── utils
│ │ │ ├── DeviceUUID.java
│ │ │ ├── DevicesInfo.java
│ │ │ ├── IpUtil.java
│ │ │ └── NetHelper.java
│ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── speakin
│ └── recorder
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
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 | 1.8
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #手机局域网内(包括热点)组网通讯实现
2 |
3 |
4 | 最近在做一个应用,需要多个手机一起组网,一个充当主机,其他多个手机充当从机。主机跟每个从机之间可以相互通信,发送消息和文件。这不就是个CS(client-server)模式吗,只不过server不是在云端,只是在同一个局域网的手机上罢了。
5 |
6 | 这里面有两个关键的地方,一个是如何发现同一个局域网内的手机客户端,包括自建的热点,一个是知道各个手机的Ip地址后,用什么方式来建立通信。这里说一下热点,手机开启热点之后,别的手机连上这个特点,其实也是一个小型局域网,只不过这个局域网稍微有点不一样(Ip地址),其他的都一样。
7 |
8 | 局域网发现
9 | 局域网发现主要难点在于如何得到对方Ip地址。我们的做法是发送UDP包。UDP协议是无面向连接的,也就是不需要像TCP协议那样提前建立连接。所以我们向局域网内某一个特定的端口广播UDP包,接收到这个包的人,发送回包,那么我们就知道彼此的存在和地址。
10 |
11 | 那么关键地方是
12 |
13 | 有人一直在监听一个端口X,接收UDP包
14 | 有人在一直往这个端口X发送UDP包
15 | 监听端的人收到了UDP包后,就知道了发送者的Ip地址。但这时发送者是不知道接收者的,因为这是UDP包,无连接的状态的。那怎么做才让发送者也知道了。其实只要发过来再做一次就行。发送者同时也是监听者,这就行了。不过发送者A监听的是另一个端口Y,接收者B接收到包后,发送一个Y端口的包给A就行了。
16 |
17 | 总结起来:
18 |
19 | 主机监听端口X,一直发送Y端口的广播UDP包
20 | 从机监听端口Y,接收到UDP包后,往X端口发送UDP包。
21 | 这样就能双方知道对方的Ip地址了。发送的数据包也可以很讲究,例如发送json数据,可以自己定义一些字段,当接收者收到之后,解析数据,如果不符合规则的数据包直接丢弃掉。这就可以支撑一些业务逻辑了。
22 |
23 | 广播地址
24 |
25 | 广播地址是什么呢,一般都是用255.255.255.255,也有不用广播用组播地址例如192.168.43.255.这个组播地址就是热点的时候采用的地址,因为开启热点之后,那个手机的IP地址是固定的,是192.168.43.1其他端的手机都是这个地址后面的192.168.43.X。如果广播发送不了或者被禁掉了?一般来说还有一种方法,就是子网掩码里面的所有Ip地址遍历发一遍,这也是办法之一。
26 |
27 | 一对多的通信
28 | 当知道对方的Ip地址后,一切都好办了。端对端的通信有挺多方法,一对多的通信也不少。如果要同时考虑在Android和iOS都好实现的话,其实可选的不是很多。
29 |
30 | 最保守的方法就是直接采用TCP协议建立sock来通信。在iOS端和Android端都有很好的socket库,不用担心能不能实现和兼容性问题。这个方案挺好但是缺点也明显,因为sock通信只定义了最基础的,消息格式要完全自己定制和实现,还多心跳之类的,这里的工作量还不少。也许还有少的框架实现了这些,但是要同时在iOS端和安卓端都有实现,我还真不知道有哪些。而且我的情景里最重要的是有一个主机在里面,所有的从机都跟主机通信,所以必须说要有一个server。
31 |
32 | 我后来采用的的WebSocket来实现。WebSocket在Android上有很多库,不过大多数都是client端的,包括了server端的有AndroidAsync,Java-Websocket。我采用的是AndroidAysnc。而iOS端可选择的就更少了。我在github上找了很多库,都是只支持client的,有支持server的都是很久很老的项目,找到一个采用swift写的叫Telegraph库,试用了起来,这个库实现得不是很好,有挺多问题,不尽如意。最终现在iOS端只有采用了Facebook的SocketRocket来作为client端,server端暂时没有好的实现。**后来我找到了一个还算比较可靠的包含server的iOS的WebSocket库:PocketSocket。感兴趣的可以看一下**
33 |
34 | 采用WebSocket来实现双向通信有不少好处,例如它直接支持发送字符串消息,二进制数据(文件),还有连接上和断开都有相应的事件,还有ping-pong这种心跳机制,我觉得挺好的。iOS端的WebSocket server暂时没有找到可靠的第三方实现,比较遗憾。
35 |
36 | 我自己做了demo工程,上传到github。
37 |
38 | * [Android局域网发现和建立一对多通信](https://github.com/szuwest/Recorder)
39 | * [iOS局域网发现和建立一对多通信](https://github.com/szuwest/SpeakinRecorder)
40 |
41 |
42 | 如果对你有用或许你可以打赏请我喝杯咖啡☕️
43 | 
44 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 26
6 | buildToolsVersion "26.0.1"
7 | defaultConfig {
8 | applicationId "com.speakin.recorder"
9 | minSdkVersion 14
10 | targetSdkVersion 26
11 | versionCode 1
12 | versionName "1.0"
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
26 | exclude group: 'com.android.support', module: 'support-annotations'
27 | })
28 | compile 'com.android.support:appcompat-v7:26.+'
29 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
30 | testCompile 'junit:junit:4.12'
31 | compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
32 | compile 'com.nononsenseapps:filepicker:4.1.0'
33 | compile 'com.koushikdutta.async:androidasync:2.+'
34 | compile 'org.greenrobot:eventbus:3.0.0'
35 | }
36 | repositories {
37 | mavenCentral()
38 | }
39 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/west/Documents/software/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/speakin/recorder/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.speakin.recorder", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/RecorderApp.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder;
2 |
3 | import android.app.Application;
4 |
5 | /**
6 | * Copyright 2017 SpeakIn.Inc
7 | * Created by west on 2017/10/25.
8 | */
9 |
10 | public class RecorderApp extends Application {
11 |
12 | public static RecorderApp app = null;
13 |
14 | @Override
15 | public void onCreate() {
16 | super.onCreate();
17 | app = this;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/ControlConstants.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control;
2 |
3 | import android.os.Environment;
4 |
5 | /**
6 | * Copyright 2017 SpeakIn.Inc
7 | * Created by west on 2017/10/17.
8 | */
9 |
10 | public class ControlConstants {
11 |
12 | public static final String TEAMID = "Speakin";
13 | public static final String TASKID = "Test";
14 | public static final int SLAVECOUNT = 3;
15 |
16 | public static final int SERVER_SOCKET_PORT = 8080;
17 | public static final String PROTOCOL = "speakinchat";//
18 |
19 | public static final int SLAVE_LISTEN_PORT = 8883;
20 | public static final int MASTER_LISTEN_PORT = 8882;
21 |
22 | public static final String ROOT = Environment.getExternalStorageDirectory().getAbsolutePath()+"/SpeakInRecorder/";
23 | public static final String RECEIVE_DIR = ROOT + "receive/";
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/MasterControlManager.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.util.Log;
6 |
7 | import com.koushikdutta.async.AsyncNetworkSocket;
8 | import com.koushikdutta.async.http.WebSocket;
9 | import com.speakin.recorder.module.control.search.MasterSearchManager;
10 | import com.speakin.recorder.module.control.websocket.MasterWebSocketManager;
11 |
12 | import org.json.JSONObject;
13 |
14 | import java.util.List;
15 |
16 | /**
17 | * Copyright 2017 SpeakIn.Inc
18 | * Created by west on 2017/10/9.
19 | */
20 |
21 | public class MasterControlManager implements MasterSearchManager.MasterSearchManagerCallback, MasterWebSocketManager.MasterSocketManagerCallback{
22 |
23 | public interface MasterControlManagerCallback {
24 | void onServerError(Exception ex);
25 | void onClientConnected(String clientSocket);
26 | void onClientDisconnect(String clientSocket);
27 | void onMessageReceive(String clientSocket, String message);
28 | void onReceiveFile(String clientSocket, String filePath);
29 | }
30 |
31 | private static final String TAG = MasterControlManager.class.getSimpleName();
32 | private MasterSearchManager searchManager;
33 | private MasterWebSocketManager socketManager;
34 | private Handler handler = new Handler(Looper.getMainLooper());
35 | private MasterControlManagerCallback controlManagerCallback;
36 | private boolean isRunning = false;
37 |
38 | public void setControlManagerCallback(MasterControlManagerCallback controlManagerCallback) {
39 | this.controlManagerCallback = controlManagerCallback;
40 | }
41 |
42 | public void start() {
43 | start(ControlConstants.SLAVECOUNT, ControlConstants.TEAMID, ControlConstants.TASKID);
44 | }
45 |
46 | public void stop() {
47 | stopSearch();
48 | stopMaster();
49 | isRunning = false;
50 | }
51 |
52 | public void start(int slaveCount, String teamId, String taskId) {
53 | if (isRunning) {
54 | Log.d(TAG, "is running");
55 | return;
56 | }
57 | searchManager = new MasterSearchManager(slaveCount, teamId, taskId);
58 | searchManager.setSearchCallback(this);
59 | searchManager.start();
60 | socketManager = new MasterWebSocketManager(slaveCount);
61 | socketManager.setMasterSocketManager(this);
62 | socketManager.start();
63 |
64 | }
65 |
66 | public void send(String message) {
67 | if (socketManager != null) {
68 | socketManager.sendMessage(message);
69 | }
70 | }
71 |
72 | private void stopMaster() {
73 | if (socketManager != null) {
74 | socketManager.stop();
75 | socketManager.setMasterSocketManager(null);
76 | }
77 | socketManager = null;
78 | }
79 |
80 | private void stopSearch() {
81 | if (searchManager != null) {
82 | searchManager.stop();
83 | searchManager.setSearchCallback(null);
84 | }
85 | searchManager = null;
86 | }
87 |
88 | @Override
89 | public void onFoundNewSlave(String slaveIp, JSONObject slaveInfo) {
90 | Log.d(TAG, "found slave " + slaveIp + " info=" + slaveInfo);
91 | }
92 |
93 | @Override
94 | public void onFoundSlaves(List slaveIpList) {
95 | Log.d(TAG, "slave count = " + slaveIpList.size());
96 | String msg = "发现从机:\n";
97 | for (String ip : slaveIpList) {
98 | msg += "IP地址:" + ip + "\n";
99 | }
100 | Log.d(TAG, msg);
101 |
102 | handler.post(new Runnable() {
103 | @Override
104 | public void run() {
105 | stopSearch();
106 | }
107 | });
108 | }
109 |
110 | @Override
111 | public void onServerError(final Exception ex) {
112 | Log.d(TAG, "start server error " + ex.getLocalizedMessage());
113 | handler.post(new Runnable() {
114 | @Override
115 | public void run() {
116 | if (controlManagerCallback != null) {
117 | controlManagerCallback.onServerError(ex);
118 | }
119 | }
120 | });
121 |
122 | isRunning = false;
123 | }
124 |
125 | @Override
126 | public void onClientConnected(WebSocket clientSocket) {
127 | final AsyncNetworkSocket workSocket = (AsyncNetworkSocket) clientSocket.getSocket();
128 | Log.d(TAG, "client connected: " + workSocket.getRemoteAddress().getHostName());
129 | handler.post(new Runnable() {
130 | @Override
131 | public void run() {
132 | if (controlManagerCallback != null) {
133 | controlManagerCallback.onClientConnected(workSocket.getRemoteAddress().getHostName());
134 | }
135 | if (socketManager.getClientCount() == ControlConstants.SLAVECOUNT) {
136 | stopSearch();
137 | }
138 | }
139 | });
140 | }
141 |
142 | @Override
143 | public void onClientDisconnect(WebSocket clientSocket) {
144 | final AsyncNetworkSocket workSocket = (AsyncNetworkSocket) clientSocket.getSocket();
145 | Log.d(TAG, "client onClientDisconnect: " + workSocket.getRemoteAddress().getHostName());
146 | handler.post(new Runnable() {
147 | @Override
148 | public void run() {
149 | if (controlManagerCallback != null)
150 | controlManagerCallback.onClientDisconnect(workSocket.getRemoteAddress().getHostName());
151 | }
152 | });
153 |
154 | }
155 |
156 | @Override
157 | public void onMessageReceive(WebSocket clientSocket, final String message) {
158 | final AsyncNetworkSocket workSocket = (AsyncNetworkSocket) clientSocket.getSocket();
159 | Log.d(TAG, "receive: " + message + "from: " + workSocket.getRemoteAddress().getHostName());
160 | handler.post(new Runnable() {
161 | @Override
162 | public void run() {
163 | if (controlManagerCallback != null)
164 | controlManagerCallback.onMessageReceive(workSocket.getRemoteAddress().getHostName(), message);
165 | }
166 | });
167 | }
168 |
169 | @Override
170 | public void onFileReceive(WebSocket clientSocket, final String filePath) {
171 | final AsyncNetworkSocket workSocket = (AsyncNetworkSocket) clientSocket.getSocket();
172 | Log.d(TAG, "receive: " + filePath + "from: " + workSocket.getRemoteAddress().getHostName());
173 | handler.post(new Runnable() {
174 | @Override
175 | public void run() {
176 | if (controlManagerCallback != null)
177 | controlManagerCallback.onReceiveFile(workSocket.getRemoteAddress().getHostName(), filePath);
178 | }
179 | });
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/SlaveControlManager.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.util.Log;
6 |
7 | import com.koushikdutta.async.http.WebSocket;
8 | import com.speakin.recorder.module.control.search.SlaveSearchManager;
9 | import com.speakin.recorder.module.control.websocket.SlaveWebSocketManager;
10 |
11 | import org.json.JSONObject;
12 |
13 | /**
14 | * Copyright 2017 SpeakIn.Inc
15 | * Created by west on 2017/10/9.
16 | */
17 |
18 | public class SlaveControlManager implements SlaveSearchManager.SlaveSearchManagerCallback, SlaveWebSocketManager.SlaveSocketManagerCallback{
19 | private static final String TAG = SlaveControlManager.class.getSimpleName();
20 |
21 | public interface SlaveControlManagerCallback {
22 | void onFoundMaster(String masterIp, JSONObject masterInfo);
23 | void onConnectedMaster(String masterIp, Exception ex);
24 | void onDisconnectMaster(String masterIp, Exception ex);
25 | void onReceiveMessage(String message);
26 | }
27 |
28 | private SlaveSearchManager searchManager;
29 | private String masterIp;
30 | private SlaveWebSocketManager socketManager;
31 | private Handler handler = new Handler(Looper.getMainLooper());
32 | private SlaveControlManagerCallback controlManagerCallback;
33 | private boolean isConnected = false;
34 |
35 | public void setControlManagerCallback(SlaveControlManagerCallback controlManagerCallback) {
36 | this.controlManagerCallback = controlManagerCallback;
37 | }
38 |
39 | public void start() {
40 | startSearchMaster(ControlConstants.TEAMID, ControlConstants.TASKID);
41 | }
42 |
43 | public void stop() {
44 | stopSearch();
45 | stopConnect();
46 | isConnected = false;
47 | }
48 |
49 | public boolean send(String message) {
50 | if (socketManager != null) {
51 | return socketManager.sendMessage(message);
52 | }
53 | Log.d(TAG, "not connected");
54 | return false;
55 | }
56 |
57 | public void sendFile(String filePath) {
58 | if (socketManager != null) {
59 | socketManager.sendFile(filePath);
60 | }
61 | }
62 |
63 | public void startSearchMaster(String teamId, String taskId) {
64 | if (isConnected) {
65 | Log.d(TAG, "isConnected");
66 | return;
67 | }
68 | stop();
69 | isConnected = true;
70 | searchManager = new SlaveSearchManager(teamId, taskId);
71 | searchManager.setSlaveSearchCallback(this);
72 | searchManager.start();
73 | }
74 |
75 | private void stopSearch() {
76 | if (searchManager != null) {
77 | searchManager.stop();
78 | searchManager.setSlaveSearchCallback(null);
79 | }
80 | searchManager = null;
81 | }
82 |
83 | private void startConnectMaster() {
84 | socketManager = new SlaveWebSocketManager(masterIp, ControlConstants.SERVER_SOCKET_PORT);
85 | socketManager.setSlaveSocketManagerCallback(this);
86 | socketManager.startConnect();
87 | }
88 |
89 | private void stopConnect() {
90 | if (socketManager != null) {
91 | socketManager.stopConnect();
92 | socketManager.setSlaveSocketManagerCallback(null);
93 | }
94 | socketManager = null;
95 | }
96 |
97 |
98 |
99 | @Override
100 | public void onFoundMaster(final String masterIp, final JSONObject masterInfo) {
101 | Log.d(TAG, "found master" + masterIp);
102 | this.masterIp = masterIp;
103 |
104 | handler.post(new Runnable() {
105 | @Override
106 | public void run() {
107 | if (controlManagerCallback != null) {
108 | controlManagerCallback.onFoundMaster(masterIp, masterInfo);
109 | }
110 | stopSearch();
111 | startConnectMaster();
112 | }
113 | });
114 | }
115 |
116 |
117 | @Override
118 | public void onMasterConnected(WebSocket socket, final Exception ex) {
119 | if (socket != null) {
120 | Log.d(TAG, "master connected");
121 | isConnected = true;
122 | } else {
123 | Log.d(TAG, "connect to master fail error = " + ex.getLocalizedMessage());
124 | isConnected = false;
125 | }
126 | handler.post(new Runnable() {
127 | @Override
128 | public void run() {
129 | if (controlManagerCallback != null) {
130 | controlManagerCallback.onConnectedMaster(SlaveControlManager.this.masterIp, ex);
131 | }
132 | }
133 | });
134 |
135 | }
136 |
137 | @Override
138 | public void onMasterDisconnect(WebSocket socket, final Exception ex) {
139 | isConnected = false;
140 | Log.d(TAG, "master disconnected");
141 | handler.post(new Runnable() {
142 | @Override
143 | public void run() {
144 | if (controlManagerCallback != null) {
145 | controlManagerCallback.onDisconnectMaster(SlaveControlManager.this.masterIp, ex);
146 | }
147 | }
148 | });
149 |
150 | }
151 |
152 | @Override
153 | public void onReceiveMessage(final String message) {
154 | Log.d(TAG, "receive: " + message);
155 | handler.post(new Runnable() {
156 | @Override
157 | public void run() {
158 | if (controlManagerCallback != null) {
159 | controlManagerCallback.onReceiveMessage(message);
160 | }
161 | }
162 | });
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/search/DeviceBroadcastReceiver.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.search;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 |
6 | import com.speakin.recorder.module.control.ControlConstants;
7 | import com.speakin.recorder.utils.IpUtil;
8 |
9 | import java.io.IOException;
10 | import java.net.DatagramPacket;
11 | import java.net.DatagramSocket;
12 | import java.util.Arrays;
13 |
14 | /**
15 | * Copyright 2017 SpeakIn.Inc
16 | * Created by west on 2017/9/26.
17 | */
18 |
19 | public class DeviceBroadcastReceiver {
20 |
21 |
22 | private static final int BUFFER_LEN = 1024*4;
23 | private volatile boolean needListen = true;
24 |
25 | public interface BroadcastReceiverCallback {
26 | void onError(String errMsg);
27 | void onReceive(String senderIp, String message);
28 | }
29 |
30 | private int port = 0;
31 | private Handler handler;
32 | private DatagramSocket server = null;
33 |
34 | public DeviceBroadcastReceiver(boolean isSlave) {
35 | if (!isSlave) {
36 | port = ControlConstants.MASTER_LISTEN_PORT;
37 | } else {
38 | port = ControlConstants.SLAVE_LISTEN_PORT;
39 | }
40 | handler = new Handler(Looper.getMainLooper());
41 | needListen = true;
42 | }
43 |
44 | private BroadcastReceiverCallback callback;
45 |
46 | public void setBroadcastReceiveCallback(BroadcastReceiverCallback callback) {
47 | this.callback = callback;
48 | }
49 |
50 | private void startReceive() throws IOException {
51 | final DatagramPacket receive = new DatagramPacket(new byte[BUFFER_LEN], BUFFER_LEN);
52 |
53 | server = new DatagramSocket(port);
54 |
55 | System.out.println("---------------------------------");
56 | System.out.println("start listen ......");
57 | System.out.println("---------------------------------");
58 |
59 | while (needListen) {
60 | server.receive(receive);
61 | byte[] recvByte = Arrays.copyOfRange(receive.getData(), 0, receive.getLength());
62 | final String receiveMsg = new String(recvByte);
63 | System.out.println("receive msg:" + receiveMsg);
64 |
65 | final String senderIp = receive.getAddress().getHostAddress();
66 | String localIP = IpUtil.getHostIP();
67 | if (senderIp.equals(localIP)) {
68 | System.out.println("myself,ignore");
69 | continue;
70 | }
71 | System.out.println("hostIp" + receive.getAddress().toString());
72 | System.out.println("port" + receive.getPort());
73 |
74 | handler.post(new Runnable() {
75 | @Override
76 | public void run() {
77 | if (callback != null) {
78 | callback.onReceive(senderIp, receiveMsg);
79 | }
80 | }
81 | });
82 | }
83 | server.disconnect();
84 | server.close();
85 | System.out.println("end listen ......");
86 | }
87 |
88 | public void startBroadcastReceive() {
89 |
90 | if (server != null) {
91 | server.close();
92 | server = null;
93 | }
94 | new Thread(new Runnable() {
95 | @Override
96 | public void run() {
97 | try {
98 | startReceive();
99 | } catch (final IOException e) {
100 | e.printStackTrace();
101 | handler.post(new Runnable() {
102 | @Override
103 | public void run() {
104 | if (callback != null) {
105 | callback.onError(e.getLocalizedMessage());
106 | }
107 | }
108 | });
109 | if (server != null) {
110 | server.close();
111 | server = null;
112 | }
113 | }
114 | }
115 | }).start();
116 | }
117 |
118 | public void stopReceive() {
119 | needListen = false;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/search/DeviceBroadcastSender.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.search;
2 |
3 | import android.content.Context;
4 | import android.net.DhcpInfo;
5 | import android.net.wifi.WifiManager;
6 |
7 | import com.speakin.recorder.RecorderApp;
8 | import com.speakin.recorder.module.control.ControlConstants;
9 | import com.speakin.recorder.utils.IpUtil;
10 | import com.speakin.recorder.utils.NetHelper;
11 |
12 | import java.io.IOException;
13 | import java.net.DatagramPacket;
14 | import java.net.DatagramSocket;
15 | import java.net.InetAddress;
16 | import java.net.UnknownHostException;
17 |
18 | /**
19 | * Copyright 2017 SpeakIn.Inc
20 | * Created by west on 2017/9/26.
21 | */
22 |
23 | public class DeviceBroadcastSender {
24 |
25 | private static final String BROADCAST_IP = "255.255.255.255";
26 | // private static final String MSG_TO_SEND = "{\"from\":\"speakin\",\"type\":\"slave\",\"port\":9001}";
27 | // private static final String MSG_TO_SEND2 = "{\"from\":\"speakin\",\"type\":\"master\"},\"port\":9002";
28 |
29 |
30 | private int port = 0;
31 | private boolean isSlave = true;
32 |
33 | public DeviceBroadcastSender(boolean isSlave) {
34 | this.isSlave = isSlave;
35 | if (isSlave) port = ControlConstants.MASTER_LISTEN_PORT;
36 | else port = ControlConstants.SLAVE_LISTEN_PORT;
37 | }
38 |
39 | // private String getMsgToSend() {
40 | // if (isSlave) return MSG_TO_SEND;
41 | // else return MSG_TO_SEND2;
42 | // }
43 |
44 | private void sendBroadcast(String message) throws IOException {
45 |
46 | String ipAddr = BROADCAST_IP;
47 | if (IpUtil.isWifiApEnabled(RecorderApp.app)) {
48 | System.out.println("已开启热点");
49 | ipAddr = "192.168.43.255";
50 | }
51 |
52 | byte[] msg = new String(message).getBytes();
53 | /*
54 | * 在Java UDP中单播与广播的代码是相同的,要实现具有广播功能的程序只需要使用广播地址即可, 例如:这里使用了本地的广播地址
55 | */
56 | InetAddress inetAddr = InetAddress.getByName(ipAddr);
57 | DatagramSocket client = new DatagramSocket();
58 |
59 | DatagramPacket sendPack = new DatagramPacket(msg, msg.length, inetAddr, port);
60 |
61 | client.send(sendPack);
62 | System.out.println("send msg complete");
63 | client.close();
64 | }
65 |
66 |
67 | public void sendBroadcastData(final String message) {
68 | new Thread(new Runnable() {
69 | @Override
70 | public void run() {
71 | try {
72 | sendBroadcast(message);
73 | } catch (IOException e) {
74 | e.printStackTrace();
75 | }
76 | }
77 | }).start();
78 | }
79 |
80 | public static InetAddress getBroadcastAddress(Context context) throws UnknownHostException {
81 | WifiManager wifi = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
82 | DhcpInfo dhcp = wifi.getDhcpInfo();
83 | if(dhcp==null) {
84 | return InetAddress.getByName("255.255.255.255");
85 | }
86 | int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask;
87 | byte[] quads = new byte[4];
88 | for (int k = 0; k < 4; k++)
89 | quads[k] = (byte) ((broadcast >> k * 8) & 0xFF);
90 | return InetAddress.getByAddress(quads);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/search/DeviceReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.search
2 |
3 | /**
4 | * Copyright 2017 SpeakIn.Inc
5 | * Created by west on 2017/9/26.
6 | */
7 |
8 | class DeviceReceiver {
9 |
10 | fun startReceiver() {
11 | print("log")
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/search/MasterSearchManager.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.search;
2 |
3 | import android.os.Build;
4 | import android.os.Handler;
5 | import android.os.Looper;
6 | import android.text.TextUtils;
7 | import android.util.Log;
8 |
9 | import com.speakin.recorder.module.control.ControlConstants;
10 |
11 | import org.json.JSONException;
12 | import org.json.JSONObject;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * Copyright 2017 SpeakIn.Inc
19 | * Created by west on 2017/9/27.
20 | */
21 |
22 | public class MasterSearchManager implements DeviceBroadcastReceiver.BroadcastReceiverCallback {
23 |
24 | private static final String TAG = "MasterSearchManager";
25 | private DeviceBroadcastSender broadcastSender;
26 | private DeviceBroadcastReceiver broadcastReceiver;
27 | private static final int BROADCAST_INTERVAL = 5*1000;
28 |
29 | public interface MasterSearchManagerCallback {
30 | void onFoundNewSlave(String slaveIp, JSONObject slaveInfo);
31 | void onFoundSlaves(List slaveIpList);
32 | }
33 |
34 | private int minDeviceCount = 1;
35 | private Handler handler;
36 | private volatile boolean stop = false;
37 | private List ipList;
38 | private String teamId;
39 | private String taskId;
40 |
41 | public MasterSearchManager(int minDeviceCount, String teamId, String taskId) {
42 | ipList = new ArrayList<>(5);
43 | handler = new Handler(Looper.getMainLooper());
44 | this.teamId = teamId;
45 | this.taskId = taskId;
46 | this.minDeviceCount = minDeviceCount;
47 | }
48 |
49 | private MasterSearchManagerCallback callback;
50 |
51 | public void setSearchCallback(MasterSearchManagerCallback callback) {
52 | this.callback = callback;
53 | }
54 |
55 | private String getSendMsg() {
56 | JSONObject jsonObject = new JSONObject();
57 | try {
58 | jsonObject.put("type", "master");
59 | jsonObject.put("teamId", this.teamId);
60 | jsonObject.put("port", ControlConstants.SERVER_SOCKET_PORT);
61 | jsonObject.put("taskId", this.taskId);
62 | jsonObject.put("deviceName", Build.MODEL);
63 | } catch (JSONException e) {
64 | e.printStackTrace();
65 | }
66 | return jsonObject.toString();
67 | }
68 |
69 | private Runnable broadcastRunnable = new Runnable() {
70 | @Override
71 | public void run() {
72 | if (stop) return;
73 | if (broadcastSender != null) {
74 | broadcastSender.sendBroadcastData(getSendMsg());
75 | handler.postDelayed(this, BROADCAST_INTERVAL);
76 | }
77 | }
78 | };
79 |
80 | public void start() {
81 | stop = false;
82 | broadcastReceiver = new DeviceBroadcastReceiver(false);
83 | broadcastReceiver.setBroadcastReceiveCallback(this);
84 | broadcastSender = new DeviceBroadcastSender(false);
85 | broadcastReceiver.startBroadcastReceive();
86 | broadcastSender.sendBroadcastData(getSendMsg());
87 | handler.postDelayed(broadcastRunnable, BROADCAST_INTERVAL);
88 | }
89 |
90 | public void stop() {
91 | Log.d(TAG, "stop search");
92 | stop = true;
93 | broadcastReceiver.stopReceive();
94 | broadcastReceiver.setBroadcastReceiveCallback(null);
95 | broadcastReceiver = null;
96 |
97 | broadcastSender = null;
98 | handler.removeCallbacks(broadcastRunnable);
99 | }
100 |
101 | @Override
102 | public void onError(String errMsg) {
103 | Log.d(TAG, "errMsg =" + errMsg);
104 | }
105 |
106 | @Override
107 | public void onReceive(String senderIp, String message) {
108 | Log.d(TAG, "message:" + message);
109 | try {
110 | JSONObject jsonObj = new JSONObject(message);
111 | String type = jsonObj.optString("type");
112 | if (type != null && type.equals("slave")) {
113 | String team = jsonObj.optString("teamId");
114 | String task = jsonObj.optString("taskId");
115 | if (!TextUtils.isEmpty(team) && task.equals(teamId) && !TextUtils.isEmpty(task) && task.equals(taskId)) {
116 | if (!ipList.contains(senderIp)) {
117 | ipList.add(senderIp);
118 | if (callback != null) callback.onFoundNewSlave(senderIp, jsonObj);
119 | }
120 | if (ipList.size() == this.minDeviceCount) {
121 | if (callback != null) callback.onFoundSlaves(ipList);
122 | }
123 | }
124 | }
125 | } catch (JSONException e) {
126 | e.printStackTrace();
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/search/SlaveSearchManager.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.search;
2 |
3 | import android.os.Build;
4 | import android.text.TextUtils;
5 | import android.util.Log;
6 |
7 | import org.json.JSONException;
8 | import org.json.JSONObject;
9 |
10 | /**
11 | * Copyright 2017 SpeakIn.Inc
12 | * Created by west on 2017/9/27.
13 | */
14 |
15 | public class SlaveSearchManager implements DeviceBroadcastReceiver.BroadcastReceiverCallback{
16 |
17 | private static final String TAG = "SlaveSearchManager";
18 | private DeviceBroadcastSender broadcastSender;
19 | private DeviceBroadcastReceiver broadcastReceiver;
20 |
21 | public interface SlaveSearchManagerCallback {
22 | void onFoundMaster(String masterIp, JSONObject masterInfo);
23 | }
24 |
25 | private SlaveSearchManagerCallback callback;
26 |
27 | public void setSlaveSearchCallback(SlaveSearchManagerCallback callback) {
28 | this.callback = callback;
29 | }
30 |
31 | private String teamId;
32 | private String taskId;
33 |
34 | public SlaveSearchManager(String teamId, String taskId) {
35 | this.teamId = teamId;
36 | this.taskId = taskId;
37 | }
38 |
39 | private String getSendMsg() {
40 | JSONObject jsonObject = new JSONObject();
41 | try {
42 | jsonObject.put("type", "slave");
43 | jsonObject.put("teamId", this.teamId);
44 | jsonObject.put("taskId", this.taskId);
45 | jsonObject.put("deviceName", Build.MODEL);
46 | } catch (JSONException e) {
47 | e.printStackTrace();
48 | }
49 | return jsonObject.toString();
50 | }
51 |
52 | public void start() {
53 | broadcastReceiver = new DeviceBroadcastReceiver(true);
54 | broadcastReceiver.setBroadcastReceiveCallback(this);
55 |
56 | broadcastSender = new DeviceBroadcastSender(true);
57 | broadcastReceiver.startBroadcastReceive();
58 | }
59 |
60 | public void stop() {
61 | broadcastReceiver.stopReceive();
62 | broadcastReceiver.setBroadcastReceiveCallback(null);
63 | broadcastReceiver = null;
64 | broadcastSender = null;
65 | }
66 |
67 | @Override
68 | public void onError(String errMsg) {
69 | Log.d(TAG, "errMsg=" + errMsg);
70 | }
71 |
72 | @Override
73 | public void onReceive(String senderIp, String message) {
74 | Log.d(TAG, "message:" + message);
75 | try {
76 | JSONObject jsonObj = new JSONObject(message);
77 | String type = jsonObj.optString("type");
78 | if (type != null && type.equals("master")) {
79 | String team = jsonObj.optString("teamId");
80 | String task = jsonObj.optString("taskId");
81 | broadcastSender.sendBroadcastData(getSendMsg());
82 | if (!TextUtils.isEmpty(team) && team.equals(teamId) && !TextUtils.isEmpty(task) && task.equals(taskId)) {
83 | if (callback != null) callback.onFoundMaster(senderIp, jsonObj);
84 | }
85 | }
86 | } catch (JSONException e) {
87 | e.printStackTrace();
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/CommonUtil.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket;
2 |
3 | import android.os.Environment;
4 |
5 | import java.io.File;
6 | import java.util.Calendar;
7 |
8 | /**
9 | * @author keshuangjie
10 | * @date 2014-12-1 下午7:41:28
11 | * @package com.jimmy.im.client.util
12 | * @version 1.0 常用工具类
13 | */
14 | public class CommonUtil {
15 |
16 | public static final String PATH_ROOT = "SpeakInRecorder";
17 | public static final String PATH_VOICE = "voice";
18 | public static final String PATH_SEPARATOR = File.separator;
19 | public static final String FILE_SUFFIX = ".wav";
20 |
21 | public static String getAmrFilePath(String name) {
22 | String path = "";
23 | if (isSdcardMounted()) {
24 | path = Environment.getExternalStorageDirectory().getAbsolutePath()
25 | + PATH_SEPARATOR + PATH_ROOT
26 | + PATH_SEPARATOR + PATH_VOICE
27 | + PATH_SEPARATOR + name
28 | + FILE_SUFFIX;
29 | }
30 | return path;
31 | }
32 |
33 | /**
34 | * 获取.amr文件名
35 | * @param path
36 | * @return
37 | */
38 | public static String getAmrFileName(String path){
39 | return getFileName(path, FILE_SUFFIX);
40 | }
41 |
42 | /**
43 | * 获取文件名
44 | * @param path
45 | * @return
46 | */
47 | public static String getFileName(String path, String suffix){
48 | String name = "";
49 | int index = path.lastIndexOf(CommonUtil.PATH_SEPARATOR);
50 | name = path.substring(index+1).replace(suffix, "");
51 | return name;
52 | }
53 |
54 | /**
55 | * 创建目录(不存在则创建)
56 | * @param dir
57 | * @return
58 | */
59 | public static boolean CreateDir(String dir) {
60 | boolean isSuccess = false;
61 | File file = new File(dir);
62 | File parentFile = file.getParentFile();
63 | if (parentFile != null) {
64 | if(!parentFile.exists()){
65 | isSuccess = file.getParentFile().mkdirs();
66 | }else{
67 | isSuccess = true;
68 | }
69 | }
70 | return isSuccess;
71 | }
72 |
73 | public static boolean isSdcardMounted() {
74 | return Environment.getExternalStorageState().equals(
75 | Environment.MEDIA_MOUNTED);
76 | }
77 |
78 |
79 | /**
80 | * 获取当前时间
81 | *
82 | * @return
83 | */
84 | public static String getDate() {
85 | Calendar c = Calendar.getInstance();
86 |
87 | String year = String.valueOf(c.get(Calendar.YEAR));
88 | String month = String.valueOf(c.get(Calendar.MONTH));
89 | String day = String.valueOf(c.get(Calendar.DAY_OF_MONTH) + 1);
90 | String hour = String.valueOf(c.get(Calendar.HOUR_OF_DAY));
91 | String mins = String.valueOf(c.get(Calendar.MINUTE));
92 |
93 | StringBuffer sbBuffer = new StringBuffer();
94 | sbBuffer.append(year + "-" + month + "-" + day + " " + hour + ":"
95 | + mins);
96 |
97 | return sbBuffer.toString();
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/SocketClient.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket;
2 |
3 | import com.speakin.recorder.module.control.socket.handle.MsgReceiveHandler;
4 | import com.speakin.recorder.module.control.socket.handle.MsgSendHandler;
5 |
6 | import java.net.Socket;
7 |
8 | /**
9 | * Copyright 2017 SpeakIn.Inc
10 | * Created by west on 2017/10/24.
11 | */
12 |
13 | public class SocketClient {
14 |
15 | private Socket socket;
16 | private MsgReceiveHandler receiveHandler;
17 | private MsgSendHandler sendHandler;
18 |
19 | public SocketClient(Socket socket) {
20 | this.socket = socket;
21 | }
22 |
23 | public void start() {
24 | receiveHandler = new MsgReceiveHandler(socket);
25 | receiveHandler.start();
26 | sendHandler = new MsgSendHandler(socket);
27 | sendHandler.start();
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/SocketClientManager.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket;
2 |
3 | import java.io.IOException;
4 | import java.net.InetSocketAddress;
5 | import java.net.Socket;
6 | import java.net.SocketException;
7 |
8 | import com.speakin.recorder.module.control.socket.handle.MsgReceiveHandler;
9 | import com.speakin.recorder.module.control.socket.handle.MsgSendHandler;
10 |
11 | /**
12 | * @author keshuangjie
13 | * @date 2014-12-1 下午7:40:29
14 | * @package com.jimmy.im.client.socket
15 | * @version 1.0
16 | * 连接管理类
17 | */
18 | public class SocketClientManager {
19 |
20 | private static final SocketClientManager sINSTANCE = new SocketClientManager();
21 |
22 | private SocketClientManager() {}
23 |
24 | public static SocketClientManager getInstance() {
25 | return sINSTANCE;
26 | }
27 |
28 | private String serverIP;
29 | int serverPort;
30 |
31 | /**
32 | * 启动一个socket长连接
33 | */
34 | public void startSocket(String serverIp, int serverPort){
35 | this.serverIP = serverIp;
36 | this.serverPort = serverPort;
37 | new Launcher().start();
38 | }
39 |
40 | // @Deprecated
41 | // public void startTextHandlerSocket(){
42 | // new TextHandlerSocket().start();
43 | // }
44 | //
45 | // @Deprecated
46 | // public void startFileSendSocket() {
47 | // new FileHandlerSocket(FileHandlerSocket.TYPE_SEND).start();
48 | // }
49 | //
50 | // @Deprecated
51 | // public void startFileReceivelerSocket() {
52 | // new FileHandlerSocket(FileHandlerSocket.TYPE_RECEIVE).start();
53 | // }
54 |
55 | private class Launcher extends Thread{
56 |
57 | @Override
58 | public void run() {
59 | try {
60 | Socket socket = new Socket();
61 | socket.connect(new InetSocketAddress(serverIP, serverPort), 5000);
62 | socket.setKeepAlive(true);
63 |
64 | new MsgReceiveHandler(socket).start();
65 | new MsgSendHandler(socket).start();
66 |
67 | } catch (SocketException e) {
68 | e.printStackTrace();
69 | } catch (IOException e) {
70 | e.printStackTrace();
71 | }
72 | }
73 |
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/SocketServerManager.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket;
2 |
3 | import java.io.IOException;
4 | import java.net.ServerSocket;
5 | import java.net.Socket;
6 |
7 | import android.util.Log;
8 |
9 | import com.speakin.recorder.module.control.socket.handle.MsgReceiveHandler;
10 | import com.speakin.recorder.module.control.socket.handle.MsgSendHandler;
11 |
12 |
13 | /**
14 | * @author keshuangjie
15 | * @date 2014-12-1 下午7:46:04
16 | * @package com.jimmy.im.server.socket
17 | * @version 1.0
18 | * socket连接处理
19 | */
20 | public class SocketServerManager implements Runnable {
21 | private static final String TAG = SocketServerManager.class.getSimpleName();
22 |
23 | public static final int CONNET_PORT = 5013;
24 |
25 | ServerSocket serverSocket;
26 |
27 | private static final SocketServerManager sINSTANCE = new SocketServerManager();
28 |
29 | private SocketServerManager() {
30 | };
31 |
32 | public static SocketServerManager getInstance() {
33 | return sINSTANCE;
34 | }
35 |
36 | public void startConnect() {
37 | new Thread(this).start();
38 | }
39 |
40 | public void run() {
41 | try {
42 | Log.i(TAG, "1.创建ServerSocket");
43 | serverSocket = new ServerSocket(CONNET_PORT);
44 |
45 | int num = 0;//socket connect num
46 |
47 | while (true) {
48 | Socket client = serverSocket.accept();
49 | num++;
50 | log("run() -> craete client success " + num);
51 |
52 | new MsgReceiveHandler(client).start();
53 | new MsgSendHandler(client).start();
54 |
55 | }
56 | } catch (IOException e) {
57 | e.printStackTrace();
58 | }finally{
59 | close();
60 | }
61 | }
62 |
63 | public void close() {
64 | if (serverSocket != null) {
65 | try {
66 | serverSocket.close();
67 | } catch (IOException e) {
68 | e.printStackTrace();
69 | }
70 | }
71 | }
72 |
73 | private void log(String message) {
74 | Log.i(TAG, message);
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/data/MsgEntity.java:
--------------------------------------------------------------------------------
1 |
2 | package com.speakin.recorder.module.control.socket.data;
3 |
4 | /**
5 | * @author keshuangjie
6 | * @date 2014-12-1 下午7:44:51
7 | * @package com.jimmy.im.server.data
8 | * @version 1.0
9 | * 实体类
10 | */
11 | public class MsgEntity {
12 |
13 | public static final int TYPE_TEXT = 1;
14 | public static final int TYPE_VOICE = 2;
15 |
16 | /** 用户名 */
17 | public String userName;
18 |
19 | /** 日期 */
20 | public String date;
21 |
22 | /** 是否是自己发送 */
23 | public boolean isSelf;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/data/MsgQueueManager.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket.data;
2 |
3 | import java.util.concurrent.LinkedBlockingQueue;
4 |
5 |
6 | /**
7 | * @author keshuangjie
8 | * @date 2014-12-4 下午2:02:03
9 | * @package com.jimmy.im.client.data
10 | * @version 1.0
11 | * 待发送消息队列
12 | */
13 | public class MsgQueueManager {
14 |
15 | LinkedBlockingQueue mQueueList;
16 |
17 | private static final class Holder{
18 | public static final MsgQueueManager sINSTANCE = new MsgQueueManager();
19 | }
20 |
21 | private MsgQueueManager(){
22 | mQueueList = new LinkedBlockingQueue();
23 | }
24 |
25 | public static MsgQueueManager getInstance(){
26 | return Holder.sINSTANCE;
27 | }
28 |
29 | public MsgEntity poll(){
30 | MsgEntity entity = null;
31 | if(mQueueList != null){
32 | entity = mQueueList.poll();
33 | }
34 | return entity;
35 | }
36 |
37 | public void push(MsgEntity entity){
38 | try {
39 | if(mQueueList != null){
40 | mQueueList = new LinkedBlockingQueue();
41 | }
42 | mQueueList.put(entity);
43 | } catch (InterruptedException e) {
44 | e.printStackTrace();
45 | }
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/data/TextMsgEntity.java:
--------------------------------------------------------------------------------
1 |
2 | package com.speakin.recorder.module.control.socket.data;
3 |
4 | /**
5 | * @author keshuangjie
6 | * @date 2014-12-1 下午7:45:01
7 | * @package com.jimmy.im.server.data
8 | * @version 1.0
9 | * 文本实体类
10 | */
11 | public class TextMsgEntity extends MsgEntity{
12 |
13 | /** 文本内容 */
14 | public String msgContent;
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/data/VoiceMsgEntity.java:
--------------------------------------------------------------------------------
1 |
2 | package com.speakin.recorder.module.control.socket.data;
3 |
4 | /**
5 | * @author keshuangjie
6 | * @date 2014-12-1 下午7:45:17
7 | * @package com.jimmy.im.server.data
8 | * @version 1.0
9 | * 语音文件实体类
10 | */
11 | public class VoiceMsgEntity extends MsgEntity{
12 |
13 | /** 文件名称 */
14 | public String fileName;
15 |
16 | /** 文件路径 */
17 | public String filePath;
18 |
19 | /** 文件时长,以秒为单位 */
20 | public int time;
21 |
22 | /** 文件大小 */
23 | public long size;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/handle/FileHandlerSocket.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket.handle;
2 |
3 | import java.io.BufferedInputStream;
4 | import java.io.BufferedOutputStream;
5 | import java.io.DataInputStream;
6 | import java.io.DataOutputStream;
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.io.FileNotFoundException;
10 | import java.io.FileOutputStream;
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.io.OutputStream;
14 | import java.net.Socket;
15 | import java.net.SocketException;
16 |
17 | import android.text.TextUtils;
18 | import android.util.Log;
19 |
20 |
21 | import com.speakin.recorder.module.control.socket.CommonUtil;
22 | import com.speakin.recorder.module.control.socket.data.VoiceMsgEntity;
23 |
24 | import org.greenrobot.eventbus.EventBus;
25 |
26 |
27 | /**
28 | * @author keshuangjie
29 | * @date 2014-12-1 下午7:45:40
30 | * @package com.jimmy.im.server.socket
31 | * @version 1.0
32 | * 文件传输接收处理,单通道通信
33 | */
34 | @Deprecated
35 | public class FileHandlerSocket extends Thread {
36 | private static final String TAG = FileHandlerSocket.class.getSimpleName();
37 |
38 | public static final int TYPE_SEND = 1;
39 | public static final int TYPE_RECEIVE = 2;
40 |
41 | private Socket mSocket;
42 |
43 | private VoiceMsgEntity latestEntity;
44 |
45 | private int mType;
46 |
47 | public FileHandlerSocket(Socket socket, int type) {
48 | this.mSocket = socket;
49 | this.mType = type;
50 | }
51 |
52 | public void run() {
53 |
54 | if (mSocket == null)
55 | return;
56 |
57 | InputStream mInputString = null;
58 | OutputStream mOutputString = null;
59 | DataInputStream mDataInputStream = null;
60 | DataOutputStream dataOutputStream = null;
61 |
62 | try {
63 | mSocket.setKeepAlive(true);
64 | mInputString = mSocket.getInputStream();
65 | mOutputString = mSocket.getOutputStream();
66 | // 1、访问Socket对象的getInputStream方法取得客户端发送过来的数据流
67 | mDataInputStream = new DataInputStream(new BufferedInputStream(
68 | mInputString));
69 | dataOutputStream = new DataOutputStream(new BufferedOutputStream(
70 | mOutputString));
71 |
72 | while (true) {
73 |
74 | try {
75 | Thread.sleep(1000);
76 | } catch (InterruptedException e) {
77 | e.printStackTrace();
78 | }
79 |
80 | if(mSocket == null || mSocket.isClosed()){
81 | Log.i(TAG, "run() -> socket close");
82 | break;
83 | }
84 |
85 | if(mType == TYPE_SEND){
86 | // 发送文件
87 | doSend(dataOutputStream);
88 |
89 | }else if(mType == TYPE_RECEIVE){
90 |
91 | if(mSocket.isInputShutdown()){
92 | Log.i(TAG, "run() -> socket input shutdown");
93 | continue;
94 | }
95 |
96 | // 接收文件
97 | doReceive(mDataInputStream);
98 | }
99 |
100 | }
101 |
102 | } catch (SocketException e1) {
103 | e1.printStackTrace();
104 | } catch (IOException e1) {
105 | e1.printStackTrace();
106 | }finally{
107 | close();
108 | }
109 |
110 | }
111 |
112 | /**
113 | * 接收文件处理
114 | * @param dis
115 | */
116 | private void doReceive(DataInputStream dis) {
117 | if (dis == null) {
118 | return;
119 | }
120 | Log.i(TAG, "doReceive()");
121 | BufferedOutputStream fo = null;
122 |
123 | try {
124 | Log.i(TAG, "doReceive() -> before readUTF()");
125 | VoiceMsgEntity entity = new VoiceMsgEntity();
126 | // 文件名
127 | entity.fileName = dis.readUTF();
128 |
129 | Log.i(TAG, "doReceive() -> after readUTF()");
130 |
131 | // 存储路径
132 | if (TextUtils.isEmpty(entity.fileName)) {
133 | // fileName = System.currentTimeMillis() + "";
134 | Log.e(TAG, "run() -> name is null");
135 | return;
136 | }
137 |
138 | Log.i(TAG, "run() -> 文件名: " + entity.fileName);
139 |
140 | entity.size = dis.readLong();
141 | if(entity.size <= 0){
142 | Log.e(TAG, "run() -> 文件大小为0");
143 | return;
144 | }
145 | Log.i(TAG, "run() -> 文件大小: " + entity.size);
146 |
147 | entity.time = dis.readInt();
148 |
149 | entity.filePath = CommonUtil.getAmrFilePath(entity.fileName);
150 |
151 | Log.i(TAG, "run() -> 保存路径:" + entity.filePath);
152 | // 创建目录
153 | CreateDir(entity.filePath);
154 |
155 | // 2、将数据流写到文件中
156 | fo = new BufferedOutputStream(new FileOutputStream(new File(
157 | entity.filePath)));
158 |
159 | int bytesRead = 0;
160 | byte[] buffer = new byte[2048];
161 | int writeLens = 0;
162 | while ((bytesRead = dis.read(buffer, 0, buffer.length)) != -1) {
163 | writeLens += bytesRead;
164 | Log.i(TAG, "doReceive() -> 接收文件完成,文件长度:" + writeLens);
165 | fo.write(buffer, 0, bytesRead);
166 | //如果文件读取完,退出循环,避免阻塞
167 | if(writeLens >= entity.size){
168 | break;
169 | }
170 | }
171 | fo.flush();
172 |
173 | Log.i(TAG, "doReceive() -> 数据接收完毕");
174 |
175 | EventBus.getDefault().post(entity);
176 |
177 | } catch (FileNotFoundException e) {
178 | e.printStackTrace();
179 | } catch (IOException e) {
180 | // e.printStackTrace();
181 | Log.i(TAG, "doReceive() -> IOException error");
182 | } finally {
183 | try {
184 | if (fo != null) {
185 | fo.close();
186 | }
187 | } catch (IOException e) {
188 | e.printStackTrace();
189 | }
190 | }
191 | }
192 |
193 | /**
194 | * 发送文件处理
195 | * @param dos
196 | */
197 | private void doSend(DataOutputStream dos) {
198 | if (dos == null) {
199 | return;
200 | }
201 |
202 | if (newSendRequest()) {
203 | String name = latestEntity.fileName;
204 | Log.i(TAG, "doSend() -> new file name: " + name);
205 |
206 | File file = new File(latestEntity.filePath);
207 | if(!file.exists()){
208 | return;
209 | }
210 |
211 | FileInputStream reader = null;
212 | byte[] buf = null;
213 |
214 | try {
215 | // 1. 读取文件输入流
216 | reader = new FileInputStream(file);
217 | // 2. 将文件内容写到Socket的输出流中
218 | // out.writeInt(UPLOAD);
219 | Log.i(TAG, "doSend() -> before writeUTF()");
220 | dos.writeUTF(name);
221 | Log.i(TAG, "doSend() -> after writeUTF()");
222 | dos.flush();
223 | dos.writeLong(file.length());
224 | dos.flush();
225 | dos.writeInt(latestEntity.time);
226 | dos.flush();
227 |
228 | int bufferSize = 20480; // 20K
229 | buf = new byte[bufferSize];
230 | int read = 0;
231 | // 将文件输入流 循环 读入 Socket的输出流中
232 | while ((read = reader.read(buf, 0, buf.length)) != -1) {
233 | dos.write(buf, 0, read);
234 | }
235 | // dos.write(null);
236 | Log.i(TAG, "socket执行完成");
237 | dos.flush();
238 | // mSocket.shutdownOutput();
239 | } catch (FileNotFoundException e) {
240 | e.printStackTrace();
241 | } catch (IOException e) {
242 | e.printStackTrace();
243 | } finally {
244 | try {
245 | if (reader != null) {
246 | reader.close();
247 | }
248 | } catch (IOException e) {
249 | e.printStackTrace();
250 | }
251 | }
252 | }
253 |
254 | }
255 |
256 | private boolean newSendRequest() {
257 | VoiceMsgEntity entity = (VoiceMsgEntity) EventBus.getDefault().getStickyEvent(
258 | VoiceMsgEntity.class);
259 | if (entity == null) {
260 | return false;
261 | }
262 |
263 | File file = new File(entity.filePath);
264 |
265 | if(!file.exists()){
266 | return false;
267 | }
268 |
269 | if (latestEntity == entity) {
270 | return false;
271 | }
272 |
273 | latestEntity = entity;
274 |
275 | return true;
276 | }
277 |
278 | private void close() {
279 | try {
280 | if (mSocket != null) {
281 | mSocket.close();
282 | }
283 | } catch (IOException e) {
284 | Log.e(TAG, "run() -> io error");
285 | }
286 | }
287 |
288 | // 创建目录(不存在则创建)
289 | public File CreateDir(String dir) {
290 | Log.i(TAG, "CreateDir()");
291 | File file = new File(dir);
292 | if (!file.getParentFile().exists()) {
293 | file.getParentFile().mkdirs();
294 | }
295 | return file;
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/handle/MsgParam.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket.handle;
2 |
3 |
4 | import com.speakin.recorder.module.control.socket.data.MsgEntity;
5 |
6 | public class MsgParam {
7 |
8 | private MsgEntity mMsgEntity;
9 |
10 | public void setMsgEntity(MsgEntity entity){
11 | this.mMsgEntity = entity;
12 | }
13 |
14 | public MsgEntity getMsgEntity(){
15 | return mMsgEntity;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/handle/MsgReceiveHandler.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket.handle;
2 |
3 | import java.io.BufferedInputStream;
4 | import java.io.BufferedOutputStream;
5 | import java.io.DataInputStream;
6 | import java.io.File;
7 | import java.io.FileNotFoundException;
8 | import java.io.FileOutputStream;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.net.Socket;
12 | import java.net.SocketException;
13 |
14 | import android.text.TextUtils;
15 | import android.util.Log;
16 |
17 | import com.speakin.recorder.module.control.socket.CommonUtil;
18 | import com.speakin.recorder.module.control.socket.data.MsgEntity;
19 | import com.speakin.recorder.module.control.socket.data.TextMsgEntity;
20 | import com.speakin.recorder.module.control.socket.data.VoiceMsgEntity;
21 |
22 | import org.greenrobot.eventbus.EventBus;
23 |
24 |
25 | /**
26 | *
27 | * 双通道通信(接收和发送在不同的线程)
28 | */
29 | public class MsgReceiveHandler extends Thread {
30 |
31 | private static final String TAG = MsgReceiveHandler.class.getSimpleName();
32 |
33 | private InputStream mInputString = null;
34 | private DataInputStream mDataInputStream = null;
35 | private volatile boolean isCancel = false;
36 |
37 | private Socket mSocket;
38 |
39 | public MsgReceiveHandler(Socket socket) {
40 | this.mSocket = socket;
41 | }
42 |
43 | public void run() {
44 |
45 | try {
46 | mInputString = mSocket.getInputStream();
47 | // 1、访问Socket对象的getInputStream方法取得客户端发送过来的数据流
48 | mDataInputStream = new DataInputStream(new BufferedInputStream(
49 | mInputString));
50 |
51 | while (!isCancel) {
52 |
53 | try {
54 | Thread.sleep(1000);
55 | } catch (InterruptedException e) {
56 | e.printStackTrace();
57 | }
58 |
59 | if (mSocket == null || mDataInputStream == null) {
60 | return;
61 | }
62 |
63 | Log.i(TAG, "before readInt");
64 |
65 | int type = mDataInputStream.readInt();
66 |
67 | Log.i(TAG, "run() -> type: " + type);
68 |
69 | Log.i(TAG, "after readInt");
70 |
71 | if (type == MsgEntity.TYPE_VOICE) {
72 | // 接收文件
73 | doFileReceive(mDataInputStream);
74 | } else if(type == MsgEntity.TYPE_TEXT){
75 | doTextReceive(mDataInputStream);
76 | }
77 |
78 | }
79 |
80 | } catch (SocketException e1) {
81 | e1.printStackTrace();
82 | } catch (IOException e1) {
83 | e1.printStackTrace();
84 | }
85 |
86 | }
87 |
88 | public void stopMsgRecevie() {
89 | isCancel = true;
90 | }
91 |
92 | /**
93 | * 接收文本信息
94 | * @param dis
95 | * @throws IOException
96 | */
97 | private void doTextReceive(DataInputStream dis) throws IOException {
98 | String msg = null;
99 | int length = (int)dis.readInt();
100 | Log.i(TAG, "doTextReceive() -> length: " + length);
101 | byte[] buffer = new byte[length];
102 | dis.read(buffer, 0, buffer.length);
103 | msg = new String(buffer);
104 | Log.i(TAG, "doTextReceive() -> msg: " + msg);
105 | if (!TextUtils.isEmpty(msg.trim())) {
106 | TextMsgEntity entity = new TextMsgEntity();
107 | entity.msgContent = msg;
108 | EventBus.getDefault().post(entity);
109 | }
110 | }
111 |
112 | /**
113 | * 接收文件处理
114 | *
115 | * @param dis
116 | */
117 | private void doFileReceive(DataInputStream dis){
118 |
119 | if (dis == null) {
120 | return;
121 | }
122 | Log.i(TAG, "doReceive()");
123 | BufferedOutputStream fo = null;
124 |
125 | try {
126 | Log.i(TAG, "doReceive() -> before readUTF()");
127 | VoiceMsgEntity entity = new VoiceMsgEntity();
128 | // 文件名
129 | entity.fileName = dis.readUTF();
130 |
131 | Log.i(TAG, "doReceive() -> after readUTF()");
132 |
133 | // 存储路径
134 | if (TextUtils.isEmpty(entity.fileName)) {
135 | // fileName = System.currentTimeMillis() + "";
136 | Log.e(TAG, "run() -> name is null");
137 | return;
138 | }
139 |
140 | Log.i(TAG, "run() -> 文件名: " + entity.fileName);
141 |
142 | entity.size = dis.readLong();
143 | if (entity.size <= 0) {
144 | Log.e(TAG, "run() -> 文件大小为0");
145 | return;
146 | }
147 | Log.i(TAG, "run() -> 文件大小: " + entity.size);
148 |
149 | entity.time = dis.readInt();
150 |
151 | entity.filePath = CommonUtil.getAmrFilePath(entity.fileName);
152 |
153 | Log.i(TAG, "run() -> 保存路径:" + entity.filePath);
154 | // 创建目录
155 | boolean isCreateSuccess = CommonUtil.CreateDir(entity.filePath);
156 | if(isCreateSuccess){
157 | // 将数据流写到文件中,如果创建目录失败,为了不破坏数据流机构,需要读取但是不写入
158 | fo = new BufferedOutputStream(new FileOutputStream(new File(
159 | entity.filePath)));
160 | }else{
161 | Log.e(TAG, "create dir error " + entity.filePath);
162 | }
163 |
164 | int bytesRead = 0;
165 | byte[] buffer = new byte[2048];
166 | int writeLens = 0;
167 | while ((bytesRead = dis.read(buffer, 0, buffer.length)) != -1) {
168 | writeLens += bytesRead;
169 | Log.i(TAG, "doReceive() -> 接收文件完成,文件长度:" + writeLens);
170 | if(fo != null){
171 | fo.write(buffer, 0, bytesRead);
172 | }
173 | // 如果文件读取完,退出循环,避免阻塞
174 | if (writeLens >= entity.size) {
175 | break;
176 | }
177 | }
178 | if(fo != null){
179 | fo.flush();
180 |
181 | Log.i(TAG, "doReceive() -> 数据接收完毕");
182 |
183 | EventBus.getDefault().post(entity);
184 | }
185 |
186 | } catch (FileNotFoundException e) {
187 | e.printStackTrace();
188 | } catch (IOException e) {
189 | // e.printStackTrace();
190 | Log.i(TAG, "doReceive() -> IOException error");
191 | } finally {
192 | try {
193 | if (fo != null) {
194 | fo.close();
195 | }
196 | } catch (IOException e) {
197 | e.printStackTrace();
198 | }
199 | }
200 | }
201 |
202 | }
203 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/handle/MsgRequest.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket.handle;
2 |
3 | /**
4 | * @author keshuangjie
5 | * @date 2014-12-4 下午7:41:14
6 | * @package com.jimmy.im.client.socket
7 | * @version 1.0
8 | * 消息发送请求
9 | */
10 | public class MsgRequest {
11 |
12 | private MsgParam mParam;
13 | private SendCallback mSendCallBack;
14 |
15 | public MsgRequest(MsgParam param, SendCallback callBack){
16 | this.mParam = param;
17 | this.mSendCallBack = callBack;
18 | }
19 |
20 | public SendCallback getSendCallBack() {
21 | return mSendCallBack;
22 | }
23 |
24 | public void setSendCallBack(SendCallback mSendCallBack) {
25 | this.mSendCallBack = mSendCallBack;
26 | }
27 |
28 | public void setMsgParam(MsgParam param){
29 | this.mParam = param;
30 | }
31 |
32 | public MsgParam getMsgParam(){
33 | return mParam;
34 | }
35 |
36 | public interface SendCallback{
37 | void onFinish();
38 |
39 | void onError();
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/handle/MsgSendHandler.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket.handle;
2 |
3 | import java.io.BufferedOutputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.File;
6 | import java.io.FileInputStream;
7 | import java.io.IOException;
8 | import java.io.OutputStream;
9 | import java.net.Socket;
10 | import java.net.SocketException;
11 |
12 | import android.text.TextUtils;
13 | import android.util.Log;
14 |
15 | import com.speakin.recorder.module.control.socket.data.MsgEntity;
16 | import com.speakin.recorder.module.control.socket.data.TextMsgEntity;
17 | import com.speakin.recorder.module.control.socket.data.VoiceMsgEntity;
18 |
19 |
20 | public class MsgSendHandler extends Thread {
21 |
22 | private static final String TAG = MsgSendHandler.class.getSimpleName();
23 |
24 | private Socket mSocket;
25 | private OutputStream mOutputString = null;
26 | private DataOutputStream mDataOutputStream = null;
27 | private volatile boolean isCancel = false;
28 |
29 | /** 会话唯一标识 */
30 | // private String mKey;
31 |
32 | public MsgSendHandler(Socket socket) {
33 | this.mSocket = socket;
34 | }
35 |
36 | public void run() {
37 |
38 | try {
39 | mOutputString = mSocket.getOutputStream();
40 | mDataOutputStream = new DataOutputStream(new BufferedOutputStream(
41 | mOutputString));
42 |
43 | while (!isCancel) {
44 |
45 | try {
46 | Thread.sleep(1000);
47 | } catch (InterruptedException e) {
48 | e.printStackTrace();
49 | }
50 |
51 | MsgRequest request = RequestQueueManager.getInstance().poll();
52 | if(request == null){
53 | continue;
54 | }
55 | MsgRequest.SendCallback callback = request.getSendCallBack();
56 | MsgParam param = request.getMsgParam();
57 | MsgEntity entity = param.getMsgEntity();
58 |
59 | if(entity == null){
60 | continue;
61 | }
62 |
63 | if(entity instanceof VoiceMsgEntity){
64 | //发送语音消息
65 | doFileSend((VoiceMsgEntity) entity, callback);
66 | }else if(entity instanceof TextMsgEntity){
67 | //发送文字消息
68 | doTextSend((TextMsgEntity) entity, callback);
69 | }
70 | }
71 |
72 | } catch (SocketException e1) {
73 | e1.printStackTrace();
74 | } catch (IOException e1) {
75 | e1.printStackTrace();
76 | }
77 |
78 | }
79 |
80 | public void stopSendMsg() {
81 | isCancel = true;
82 | }
83 |
84 | /**
85 | * 发送文件处理
86 | *
87 | * @param dos
88 | */
89 | private void doFileSend(VoiceMsgEntity entity, MsgRequest.SendCallback callback) {
90 | if (mDataOutputStream == null || entity == null) {
91 | return;
92 | }
93 |
94 | String name = entity.fileName;
95 | Log.i(TAG, "doSend() -> new file name: " + name);
96 |
97 | File file = new File(entity.filePath);
98 | if (!file.exists()) {
99 | return;
100 | }
101 |
102 | FileInputStream reader = null;
103 | byte[] buf = null;
104 |
105 | try {
106 |
107 | //写入类型:语音
108 | mDataOutputStream.writeInt(MsgEntity.TYPE_VOICE);
109 | mDataOutputStream.flush();
110 |
111 | // 1. 读取文件输入流
112 | reader = new FileInputStream(file);
113 | // 2. 将文件内容写到Socket的输出流中
114 | // out.writeInt(UPLOAD);
115 | Log.i(TAG, "doSend() -> before writeUTF()");
116 | mDataOutputStream.writeUTF(name);
117 | Log.i(TAG, "doSend() -> after writeUTF()");
118 | mDataOutputStream.flush();
119 | mDataOutputStream.writeLong(file.length());
120 | mDataOutputStream.flush();
121 | mDataOutputStream.writeInt(entity.time);
122 | mDataOutputStream.flush();
123 |
124 | int bufferSize = 20480; // 20K
125 | buf = new byte[bufferSize];
126 | int read = 0;
127 | // 将文件输入流 循环 读入 Socket的输出流中
128 | while ((read = reader.read(buf, 0, buf.length)) != -1) {
129 | mDataOutputStream.write(buf, 0, read);
130 | }
131 | Log.i(TAG, "socket执行完成");
132 | mDataOutputStream.flush();
133 |
134 | if(callback != null){
135 | callback.onFinish();
136 | }
137 |
138 | reader.close();
139 |
140 | } catch (IOException e) {
141 | // e.printStackTrace();
142 |
143 | if(callback != null){
144 | callback.onError();
145 | }
146 | }
147 | }
148 |
149 | /**
150 | * 发送文本消息
151 | * @param entity
152 | */
153 | private void doTextSend(TextMsgEntity entity, MsgRequest.SendCallback callback) {
154 | if (mDataOutputStream == null || entity == null || TextUtils.isEmpty(entity.msgContent)) {
155 | return;
156 | }
157 |
158 | Log.i(TAG, "send to client: " + entity.msgContent);
159 | try {
160 |
161 | byte[] buffer = entity.msgContent.getBytes();
162 |
163 | //写入类型:文本
164 | mDataOutputStream.writeInt(MsgEntity.TYPE_TEXT);
165 | mDataOutputStream.flush();
166 |
167 | mDataOutputStream.writeInt(buffer.length);
168 | mDataOutputStream.flush();
169 |
170 | mDataOutputStream.write(buffer);
171 | mDataOutputStream.flush();
172 |
173 | if(callback != null){
174 | callback.onFinish();
175 | }
176 |
177 | } catch (IOException e) {
178 | // e.printStackTrace();
179 |
180 | if(callback != null){
181 | callback.onError();
182 | }
183 | }
184 | }
185 |
186 | }
187 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/handle/RequestQueueManager.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket.handle;
2 |
3 | import java.util.concurrent.LinkedBlockingQueue;
4 |
5 | /**
6 | *
7 | * 待发送消息队列
8 | */
9 | public class RequestQueueManager {
10 |
11 | LinkedBlockingQueue mQueueList;
12 |
13 | private static final class Holder{
14 | public static final RequestQueueManager sINSTANCE = new RequestQueueManager();
15 | }
16 |
17 | private RequestQueueManager(){
18 | mQueueList = new LinkedBlockingQueue();
19 | }
20 |
21 | public static RequestQueueManager getInstance(){
22 | return Holder.sINSTANCE;
23 | }
24 |
25 | public MsgRequest poll(){
26 | MsgRequest entity = null;
27 | if(mQueueList != null){
28 | entity = mQueueList.poll();
29 | }
30 | return entity;
31 | }
32 |
33 | public void push(MsgRequest entity){
34 | try {
35 | if(mQueueList != null){
36 | mQueueList = new LinkedBlockingQueue();
37 | }
38 | mQueueList.put(entity);
39 | } catch (InterruptedException e) {
40 | e.printStackTrace();
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/socket/handle/TextHandlerSocket.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.socket.handle;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.io.OutputStream;
6 | import java.net.Socket;
7 | import java.net.SocketException;
8 |
9 | import android.text.TextUtils;
10 | import android.util.Log;
11 |
12 | import com.speakin.recorder.module.control.socket.data.TextMsgEntity;
13 |
14 | import org.greenrobot.eventbus.EventBus;
15 |
16 |
17 | /**
18 | * @author keshuangjie
19 | * @date 2014-12-1 下午7:46:21
20 | * @package com.jimmy.im.server.socket
21 | * @version 1.0
22 | * 字符串传输接收处理,双通道,但只能处理文本传输
23 | */
24 | @Deprecated
25 | public class TextHandlerSocket extends Thread {
26 | private static final String TAG = TextHandlerSocket.class.getSimpleName();
27 |
28 | private Socket mSocket;
29 |
30 | TextMsgEntity latestEntity;
31 |
32 | InputStream in = null;
33 | OutputStream out = null;
34 |
35 | public TextHandlerSocket(Socket socket) {
36 | mSocket = socket;
37 | }
38 |
39 | @Override
40 | public void run() {
41 |
42 | try {
43 | mSocket.setKeepAlive(true);
44 | in = mSocket.getInputStream();
45 | out = mSocket.getOutputStream();
46 |
47 | // 使用循环的方式,不停的与客户端交互会话
48 | while (true) {
49 | try {
50 | Thread.sleep(1000);
51 | } catch (InterruptedException e) {
52 | e.printStackTrace();
53 | }
54 |
55 | if (mSocket != null && mSocket.isConnected()) {
56 | // 发送数据回客户端
57 | doSend(out);
58 | // 处理客户端发来的数据
59 | doReceive(in);
60 | }
61 | }
62 | } catch (SocketException e1) {
63 | e1.printStackTrace();
64 | } catch (IOException e1) {
65 | e1.printStackTrace();
66 | } finally {
67 | try {
68 | if (in != null) {
69 | in.close();
70 | }
71 | if (out != null) {
72 | out.close();
73 | }
74 | if (mSocket != null) {
75 | mSocket.close();
76 | }
77 | } catch (IOException e) {
78 | e.printStackTrace();
79 | }
80 | }
81 | }
82 |
83 | private void doReceive(InputStream in) {
84 | String msg = null;
85 | try {
86 | byte[] bytes = new byte[in.available()];
87 | in.read(bytes);
88 | msg = new String(bytes);
89 | if (!TextUtils.isEmpty(msg.trim())) {
90 | TextMsgEntity entity = new TextMsgEntity();
91 | entity.msgContent = msg;
92 | EventBus.getDefault().post(entity);
93 | }
94 | } catch (IOException e) {
95 | e.printStackTrace();
96 | }
97 | }
98 |
99 | private void doSend(OutputStream out) {
100 | TextMsgEntity entity = (TextMsgEntity) EventBus.getDefault()
101 | .getStickyEvent(TextMsgEntity.class);
102 | if (entity == null || TextUtils.isEmpty(entity.msgContent)) {
103 | return;
104 | }
105 | if (latestEntity == entity) {
106 | return;
107 | }
108 | latestEntity = entity;
109 | Log.i(TAG, "send to client: " + latestEntity.msgContent);
110 | try {
111 | out.write(latestEntity.msgContent.getBytes());
112 | out.flush();
113 | } catch (IOException e) {
114 | e.printStackTrace();
115 | }
116 | }
117 |
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/websocket/MasterWebSocketManager.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.websocket;
2 |
3 | import android.util.Log;
4 |
5 | import com.koushikdutta.async.AsyncNetworkSocket;
6 | import com.koushikdutta.async.ByteBufferList;
7 | import com.koushikdutta.async.DataEmitter;
8 | import com.koushikdutta.async.callback.CompletedCallback;
9 | import com.koushikdutta.async.callback.DataCallback;
10 | import com.koushikdutta.async.http.WebSocket;
11 | import com.koushikdutta.async.http.server.AsyncHttpServer;
12 | import com.koushikdutta.async.http.server.AsyncHttpServerRequest;
13 | import com.speakin.recorder.module.control.ControlConstants;
14 |
15 | import java.io.File;
16 | import java.io.FileNotFoundException;
17 | import java.io.FileOutputStream;
18 | import java.io.IOException;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | /**
23 | * Copyright 2017 SpeakIn.Inc
24 | * Created by west on 2017/9/28.
25 | */
26 |
27 | public class MasterWebSocketManager {
28 |
29 | public interface MasterSocketManagerCallback {
30 | void onServerError(Exception ex);
31 | void onClientConnected(WebSocket clientSocket);
32 | void onClientDisconnect(WebSocket clientSocket);
33 | void onMessageReceive(WebSocket clientSocket, String message);
34 | void onFileReceive(WebSocket clientSocket, String filePath);
35 | }
36 |
37 |
38 | private static final String TAG = MasterWebSocketManager.class.getSimpleName();
39 | private int clientMaxCount = 3;
40 |
41 | private AsyncHttpServer server = new AsyncHttpServer();
42 | private final List socketClients = new ArrayList<>();
43 | private MasterSocketManagerCallback masterSocketManagerCallback;
44 |
45 | public MasterWebSocketManager(int count) {
46 | clientMaxCount = count;
47 | }
48 |
49 | public void setMasterSocketManager(MasterSocketManagerCallback callback) {
50 | masterSocketManagerCallback = callback;
51 | }
52 |
53 | public void start() {
54 | server.websocket("/", ControlConstants.PROTOCOL, callback);
55 | server.listen(ControlConstants.SERVER_SOCKET_PORT);
56 | server.setErrorCallback(new CompletedCallback() {
57 | @Override
58 | public void onCompleted(Exception ex) {
59 | if (ex != null) {
60 | ex.printStackTrace();
61 | Log.e(TAG, "Error");
62 | if (masterSocketManagerCallback != null) masterSocketManagerCallback.onServerError(ex);
63 | }
64 | }
65 | });
66 | Log.d(TAG, "start server and listen");
67 | }
68 |
69 | public void stop() {
70 | Log.d(TAG, "stop");
71 |
72 | for (SlaveClientSocket clientSocket : socketClients) {
73 | clientSocket.mSocket.close();
74 | }
75 | socketClients.clear();
76 |
77 | if (server != null) {
78 | server.stop();
79 | }
80 | }
81 |
82 | public void sendMessage(String message) {
83 | for (SlaveClientSocket clientSocket : socketClients) {
84 | clientSocket.mSocket.send(message);
85 | }
86 | }
87 |
88 | public int getClientCount() {
89 | return socketClients.size();
90 | }
91 |
92 | private AsyncHttpServer.WebSocketRequestCallback callback = new AsyncHttpServer.WebSocketRequestCallback() {
93 | @Override
94 | public void onConnected(final WebSocket webSocket, AsyncHttpServerRequest request) {
95 | Log.d(TAG, "request" + request.toString());
96 | AsyncNetworkSocket asyncNetworkSocket = (AsyncNetworkSocket) webSocket.getSocket();
97 | Log.d(TAG, asyncNetworkSocket.getRemoteAddress().getAddress() + ":" + asyncNetworkSocket.getRemoteAddress().getPort() + " connected");
98 | if (socketClients.size() >= clientMaxCount) {
99 | webSocket.close();
100 | return;
101 | }
102 |
103 | SlaveClientSocket found = null;
104 | for (SlaveClientSocket ws : socketClients ) {
105 | AsyncNetworkSocket anws = (AsyncNetworkSocket) ws.mSocket.getSocket();
106 | if (asyncNetworkSocket.getRemoteAddress().getHostName().equals(anws.getRemoteAddress().getHostName())) {
107 | found = ws;
108 | break;
109 | }
110 | }
111 | if (found == null) {
112 | socketClients.add(new SlaveClientSocket(webSocket));
113 | if (masterSocketManagerCallback != null)
114 | masterSocketManagerCallback.onClientConnected(webSocket);
115 | } else {
116 | Log.d(TAG, "slave already connect");
117 | }
118 | }
119 | };
120 |
121 |
122 | private class SlaveClientSocket implements CompletedCallback, WebSocket.StringCallback, DataCallback {
123 | WebSocket mSocket;
124 |
125 | SlaveClientSocket(WebSocket clientSocket) {
126 | mSocket = clientSocket;
127 | mSocket.setStringCallback(this);
128 | mSocket.setClosedCallback(this);
129 | mSocket.setDataCallback(this);
130 | }
131 |
132 | @Override
133 | public void onCompleted(Exception ex) {
134 | try {
135 | if (ex != null) {
136 | Log.e(TAG, "Error");
137 | ex.printStackTrace();
138 | } else {
139 | Log.d(TAG, "WebSocketClient closed normally.");
140 | }
141 | } finally {
142 | socketClients.remove(this);
143 | if (masterSocketManagerCallback != null)
144 | masterSocketManagerCallback.onClientDisconnect(mSocket);
145 | }
146 | }
147 |
148 | @Override
149 | public void onStringAvailable(String s) {
150 | AsyncNetworkSocket asyncNetworkSocket = (AsyncNetworkSocket) mSocket.getSocket();
151 | Log.d(TAG, "from:" + asyncNetworkSocket.getRemoteAddress().getAddress() + ":" + asyncNetworkSocket.getRemoteAddress().getPort());
152 | Log.d(TAG, "msg = " + s);
153 | if (masterSocketManagerCallback != null) {
154 | masterSocketManagerCallback.onMessageReceive(mSocket, s);
155 | }
156 | }
157 |
158 | private static final int BUFFER_LEN = 1024*8;
159 | @Override
160 | public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
161 | Log.d(TAG, "onDataAvailable");
162 | String filePath = getFilePath();
163 | try {
164 | FileOutputStream outputStream = new FileOutputStream(new File(filePath));
165 | long start = System.currentTimeMillis();
166 | while (bb.hasRemaining()) {
167 | int remain = bb.remaining();
168 | if (remain > BUFFER_LEN) {
169 | outputStream.write(bb.getBytes(BUFFER_LEN));
170 | } else {
171 | outputStream.write(bb.getBytes(remain));
172 | }
173 | }
174 | outputStream.flush();
175 | outputStream.close();
176 | Log.d(TAG, "receive file:" + filePath);
177 | Log.d(TAG, "spend " + (System.currentTimeMillis() - start));
178 | if (masterSocketManagerCallback != null) {
179 | masterSocketManagerCallback.onFileReceive(mSocket, filePath);
180 | }
181 | } catch (FileNotFoundException e) {
182 | e.printStackTrace();
183 | } catch (IOException e) {
184 | e.printStackTrace();
185 | }
186 | }
187 |
188 | }
189 |
190 | private String getFilePath() {
191 | String fileName = System.currentTimeMillis() + ".wav";
192 | String filePath = ControlConstants.RECEIVE_DIR;
193 | boolean ret = new File(filePath).mkdirs();
194 | if (!ret) {
195 | // Log.e(TAG, "make dir error");
196 | }
197 | return filePath+fileName;
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/module/control/websocket/SlaveWebSocketManager.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.module.control.websocket;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 |
6 | import com.koushikdutta.async.ByteBufferList;
7 | import com.koushikdutta.async.DataEmitter;
8 | import com.koushikdutta.async.callback.CompletedCallback;
9 | import com.koushikdutta.async.callback.DataCallback;
10 | import com.koushikdutta.async.http.AsyncHttpClient;
11 | import com.koushikdutta.async.http.WebSocket;
12 | import com.speakin.recorder.module.control.ControlConstants;
13 |
14 | import java.io.ByteArrayOutputStream;
15 | import java.io.File;
16 | import java.io.FileInputStream;
17 | import java.io.FileNotFoundException;
18 | import java.io.IOException;
19 |
20 | /**
21 | * Copyright 2017 SpeakIn.Inc
22 | * Created by west on 2017/9/29.
23 | */
24 |
25 | public class SlaveWebSocketManager {
26 |
27 | private static final String TAG = SlaveWebSocketManager.class.getSimpleName();
28 |
29 | public interface SlaveSocketManagerCallback {
30 | void onMasterConnected(WebSocket socket, Exception ex);
31 | void onMasterDisconnect(WebSocket socket, Exception ex);
32 | void onReceiveMessage(String message);
33 | }
34 |
35 | private SlaveSocketManagerCallback slaveSocketManagerCallback;
36 | private String serverIp;
37 | private int serverPort;
38 | private WebSocket socket;
39 |
40 | public SlaveWebSocketManager(String serverIp, int serverPort) {
41 | this.serverIp = serverIp;
42 | this.serverPort = serverPort;
43 | }
44 |
45 | public void setSlaveSocketManagerCallback(SlaveSocketManagerCallback callback) {
46 | slaveSocketManagerCallback = callback;
47 | }
48 |
49 | public void startConnect() {
50 | if (isConnected()) {
51 | return;
52 | }
53 | String url = "ws://" + serverIp + ":" + serverPort;
54 | Log.d(TAG, "start connect to url=" + url);
55 | AsyncHttpClient.getDefaultInstance().websocket(url, ControlConstants.PROTOCOL, new AsyncHttpClient.WebSocketConnectCallback() {
56 | @Override
57 | public void onCompleted(Exception ex, final WebSocket webSocket) {
58 | if (ex != null) {
59 | ex.printStackTrace();
60 | Log.e(TAG, "connect error = " + ex.getMessage());
61 | if (slaveSocketManagerCallback != null) slaveSocketManagerCallback.onMasterConnected(null, ex);
62 | return;
63 | }
64 | Log.d(TAG, "connected to server");
65 |
66 | socket = webSocket;
67 | socket.setStringCallback(stringCallback);
68 | socket.setClosedCallback(closeCallback);
69 | socket.setDataCallback(dataCallback);
70 | socket.send("confirm from client");
71 |
72 | if (slaveSocketManagerCallback != null) slaveSocketManagerCallback.onMasterConnected(socket, null);
73 | }
74 | });
75 | }
76 |
77 | private WebSocket.StringCallback stringCallback = new WebSocket.StringCallback() {
78 | @Override
79 | public void onStringAvailable(String s) {
80 | Log.d(TAG, "msg from server: " + s);
81 | if (slaveSocketManagerCallback != null) slaveSocketManagerCallback.onReceiveMessage(s);
82 | }
83 | };
84 |
85 | private DataCallback dataCallback = new DataCallback() {
86 | @Override
87 | public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
88 | Log.d(TAG, "onDataAvailable");
89 | }
90 | };
91 |
92 | private CompletedCallback closeCallback = new CompletedCallback() {
93 | @Override
94 | public void onCompleted(Exception ex) {
95 | if (ex != null) {
96 | ex.printStackTrace();
97 | Log.e(TAG, "close error = " + ex.getMessage());
98 | }
99 | if (slaveSocketManagerCallback != null) slaveSocketManagerCallback.onMasterDisconnect(socket, ex);
100 | socket = null;
101 | }
102 | };
103 |
104 | public boolean sendMessage(String message) {
105 | if (!isConnected()) {
106 | Log.d(TAG, "not connected");
107 | return false;
108 | }
109 | if (TextUtils.isEmpty(message)) return false;
110 |
111 | socket.send(message);
112 | return true;
113 | }
114 |
115 | private static final int BUFFER_LEN = 8*1024;
116 | public void sendFile(final String filePath) {
117 | if (filePath == null || !(new File(filePath).exists())) return;
118 | new Thread(new Runnable() {
119 | @Override
120 | public void run() {
121 | Log.d(TAG, "send file " +filePath);
122 | long start = System.currentTimeMillis();
123 | try {
124 | File file = new File(filePath);
125 | Log.d(TAG, "file len=" + file.length());
126 | FileInputStream inputStream = new FileInputStream(file);
127 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
128 | byte[] buffer = new byte[BUFFER_LEN];
129 | int read ;
130 | while ((read = inputStream.read(buffer)) != -1 ) {
131 | if (read == BUFFER_LEN) {
132 | baos.write(buffer);
133 | } else {
134 | byte[] bs2 = new byte[read];
135 | System.arraycopy(buffer, 0, bs2, 0, read);
136 | baos.write(bs2);
137 | }
138 | }
139 |
140 | byte[] payload = baos.toByteArray();
141 |
142 | socket.send(payload);
143 | Log.d(TAG, "send file done");
144 | Log.d(TAG, "spend " + (System.currentTimeMillis() - start));
145 | } catch (FileNotFoundException e) {
146 | e.printStackTrace();
147 | } catch (IOException e) {
148 | e.printStackTrace();
149 | }
150 | }
151 | }).start();
152 |
153 | }
154 |
155 | public void stopConnect() {
156 | if (socket != null) {
157 | socket.close();
158 | socket = null;
159 | }
160 | }
161 |
162 | public boolean isConnected() {
163 | return socket != null;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.ui;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.widget.TextView;
7 | import android.widget.Toast;
8 |
9 | import com.speakin.recorder.R;
10 | import com.speakin.recorder.module.control.MasterControlManager;
11 | import com.speakin.recorder.module.control.SlaveControlManager;
12 | import com.speakin.recorder.utils.IpUtil;
13 |
14 | import org.json.JSONObject;
15 |
16 |
17 | public class MainActivity extends AppCompatActivity {
18 |
19 |
20 | private TextView textView;
21 | private TextView textView2;
22 |
23 | private MasterControlManager masterControlManager;
24 | private SlaveControlManager slaveControlManager;
25 | private boolean isMaster = false;
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState) {
29 | super.onCreate(savedInstanceState);
30 | setContentView(R.layout.activity_main);
31 | initData();
32 | initView();
33 | refreshIP();
34 | }
35 |
36 | private void initData() {
37 | masterControlManager = new MasterControlManager();
38 | slaveControlManager = new SlaveControlManager();
39 |
40 | slaveControlManager.setControlManagerCallback(new SlaveControlManager.SlaveControlManagerCallback() {
41 | @Override
42 | public void onFoundMaster(String masterIp, JSONObject masterInfo) {
43 | textView2.setText(masterIp + " " + masterInfo.toString());
44 | }
45 |
46 | @Override
47 | public void onConnectedMaster(String masterIp, Exception ex) {
48 | textView2.setText(masterIp + " connected");
49 | }
50 |
51 | @Override
52 | public void onDisconnectMaster(String masterIp, Exception ex) {
53 | textView2.setText(masterIp + " disconnected");
54 | }
55 |
56 | @Override
57 | public void onReceiveMessage(String message) {
58 | textView2.setText("message: " + message);
59 | }
60 | });
61 |
62 | masterControlManager.setControlManagerCallback(new MasterControlManager.MasterControlManagerCallback() {
63 | @Override
64 | public void onServerError(Exception ex) {
65 | textView2.setText("server error: " + ex.getLocalizedMessage());
66 | }
67 |
68 | @Override
69 | public void onClientConnected(String clientSocket) {
70 | textView2.setText("client: " + clientSocket);
71 | }
72 |
73 | @Override
74 | public void onClientDisconnect(String clientSocket) {
75 | textView2.setText("disconnected: " + clientSocket);
76 | }
77 |
78 | @Override
79 | public void onMessageReceive(String clientSocket, String message) {
80 | textView2.setText("message: " + message);
81 | }
82 |
83 | @Override
84 | public void onReceiveFile(String clientSocket, String filePath) {
85 | Toast.makeText(MainActivity.this, "receive file" + filePath, Toast.LENGTH_SHORT).show();
86 | }
87 | });
88 | }
89 |
90 | private void initView() {
91 | findViewById(R.id.masterBtn).setOnClickListener(new View.OnClickListener() {
92 | @Override
93 | public void onClick(View view) {
94 | masterControlManager.start();
95 | isMaster = true;
96 | }
97 | });
98 |
99 | findViewById(R.id.slaveBtn).setOnClickListener(new View.OnClickListener() {
100 | @Override
101 | public void onClick(View view) {
102 | slaveControlManager.start();
103 | }
104 | });
105 |
106 | textView = (TextView) findViewById(R.id.text1);
107 | textView.setOnClickListener(new View.OnClickListener() {
108 | @Override
109 | public void onClick(View view) {
110 | refreshIP();
111 | }
112 | });
113 |
114 | textView2 = (TextView) findViewById(R.id.text2);
115 |
116 | findViewById(R.id.sendBtn).setOnClickListener(new View.OnClickListener() {
117 | @Override
118 | public void onClick(View view) {
119 | if (isMaster) {
120 | masterControlManager.send("Hello, I am server :" + System.currentTimeMillis());
121 | } else {
122 | slaveControlManager.send("Hello, I am client :" + System.currentTimeMillis());
123 | }
124 | }
125 | });
126 | findViewById(R.id.stopBtn).setOnClickListener(new View.OnClickListener() {
127 | @Override
128 | public void onClick(View view) {
129 | if (isMaster) {
130 | masterControlManager.stop();
131 | } else {
132 | slaveControlManager.stop();
133 | }
134 | }
135 | });
136 | findViewById(R.id.sendeFile).setOnClickListener(new View.OnClickListener() {
137 | @Override
138 | public void onClick(View view) {
139 | slaveControlManager.sendFile("/storage/emulated/0/wakeupIn/record/detected_1505991013.wav");
140 | }
141 | });
142 | }
143 |
144 | private void refreshIP() {
145 | String ip1 = IpUtil.getHostIP();
146 |
147 | textView.setText("本机IP: " + ip1 );
148 | }
149 |
150 |
151 | @Override
152 | protected void onDestroy() {
153 | super.onDestroy();
154 | slaveControlManager.stop();
155 | masterControlManager.stop();
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/utils/DeviceUUID.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.utils;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.telephony.TelephonyManager;
6 | import android.text.TextUtils;
7 |
8 | import java.util.UUID;
9 |
10 | import static android.text.TextUtils.isEmpty;
11 |
12 | /**
13 | * Copyright 2017 SpeakIn.Inc
14 | * Created by west on 2017/10/26.
15 | */
16 |
17 | public class DeviceUUID {
18 |
19 | private static final String _UUID = "DeviceUUID";
20 | private static final String KEY_UUID = "KEY_UUID";
21 |
22 | public static String getDeviceUUID(Context context){
23 |
24 | //IMEI(imei)
25 | TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
26 | String imei = tm.getDeviceId();
27 | if(!isEmpty(imei) && !imei.startsWith("00000")){
28 | return imei;
29 | }
30 |
31 | String sn = tm.getSimSerialNumber();
32 | if(!isEmpty(sn) && !sn.startsWith("00000")){
33 | return sn;
34 | }
35 |
36 | SharedPreferences preferences = context.getSharedPreferences(_UUID, Context.MODE_PRIVATE);
37 | String uuid = preferences.getString(KEY_UUID, "");
38 | if (TextUtils.isEmpty(uuid)) {
39 | uuid = UUID.randomUUID().toString();
40 | preferences.edit().putString(KEY_UUID,uuid).apply();
41 | }
42 | return uuid;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/utils/DevicesInfo.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.utils;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.os.Build;
6 | import android.telephony.TelephonyManager;
7 |
8 | import com.speakin.recorder.RecorderApp;
9 |
10 |
11 | public class DevicesInfo {
12 |
13 | private static DevicesInfo info = null;
14 | public String imei;
15 | public String deviceName;
16 | private String systemVer;
17 |
18 | public DevicesInfo(Context ctx) {
19 | imei = DeviceUUID.getDeviceUUID(ctx);
20 | deviceName = Build.MODEL;
21 | systemVer = Build.VERSION.SDK;
22 | }
23 |
24 | public static DevicesInfo getDeviceInfo() {
25 | if (info == null) {
26 | info = new DevicesInfo(RecorderApp.app);
27 | }
28 | return info;
29 | }
30 |
31 | public String getIMEI(){
32 | return imei;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/utils/IpUtil.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.utils;
2 |
3 | import android.content.Context;
4 | import android.net.DhcpInfo;
5 | import android.net.wifi.WifiManager;
6 | import android.util.Log;
7 |
8 | import java.lang.reflect.InvocationTargetException;
9 | import java.lang.reflect.Method;
10 | import java.net.Inet6Address;
11 | import java.net.InetAddress;
12 | import java.net.NetworkInterface;
13 | import java.net.SocketException;
14 | import java.util.Enumeration;
15 |
16 | /**
17 | * Copyright 2017 SpeakIn.Inc
18 | * Created by west on 2017/9/27.
19 | */
20 |
21 | public class IpUtil {
22 |
23 | /**
24 | * 获取开启便携热点后自身热点IP地址
25 | * @param context
26 | * @return
27 | */
28 | public static String getHotspotLocalIpAddress(Context context) {
29 | WifiManager wifimanager = (WifiManager) context.getSystemService(context.WIFI_SERVICE);
30 | DhcpInfo dhcpInfo = wifimanager.getDhcpInfo();
31 | if(dhcpInfo != null) {
32 | int address = dhcpInfo.serverAddress;
33 | return ((address & 0xFF)
34 | + "." + ((address >> 8) & 0xFF)
35 | + "." + ((address >> 16) & 0xFF)
36 | + "." + ((address >> 24) & 0xFF));
37 | }
38 | return null;
39 | }
40 |
41 | /**
42 | * 获取连接WiFi后的IP地址
43 | * @return
44 | */
45 | public static String getIpAddressFromHotspot(Context context) {
46 | WifiManager wifimanager = (WifiManager) context.getSystemService(context.WIFI_SERVICE);
47 | DhcpInfo dhcpInfo = wifimanager.getDhcpInfo();
48 | if(dhcpInfo != null) {
49 | int address = dhcpInfo.gateway;
50 | return ((address & 0xFF)
51 | + "." + ((address >> 8) & 0xFF)
52 | + "." + ((address >> 16) & 0xFF)
53 | + "." + ((address >> 24) & 0xFF));
54 | }
55 | return null;
56 | }
57 |
58 | public static Boolean isWifiApEnabled(Context context) {
59 | try {
60 | WifiManager manager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
61 | Method method = manager.getClass().getMethod("isWifiApEnabled");
62 | return (Boolean)method.invoke(manager);
63 | }
64 | catch (NoSuchMethodException e) {
65 | e.printStackTrace();
66 | }
67 | catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
68 | e.printStackTrace();
69 | }
70 | return false;
71 | }
72 |
73 | /**
74 | * 获取ip地址
75 | * @return
76 | */
77 | public static String getHostIP() {
78 |
79 | String hostIp = null;
80 | try {
81 | Enumeration nis = NetworkInterface.getNetworkInterfaces();
82 | InetAddress ia = null;
83 | while (nis.hasMoreElements()) {
84 | NetworkInterface ni = (NetworkInterface) nis.nextElement();
85 | Enumeration ias = ni.getInetAddresses();
86 | while (ias.hasMoreElements()) {
87 | ia = ias.nextElement();
88 | if (ia instanceof Inet6Address) {
89 | continue;// skip ipv6
90 | }
91 | String ip = ia.getHostAddress();
92 | if (!"127.0.0.1".equals(ip)) {
93 | hostIp = ia.getHostAddress();
94 | break;
95 | }
96 | }
97 | }
98 | } catch (SocketException e) {
99 | Log.i("yao", "SocketException");
100 | e.printStackTrace();
101 | }
102 | return hostIp;
103 |
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/app/src/main/java/com/speakin/recorder/utils/NetHelper.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.net.ConnectivityManager;
6 | import android.net.DhcpInfo;
7 | import android.net.NetworkInfo;
8 | import android.net.wifi.WifiManager;
9 | import android.os.Build;
10 | import android.telephony.TelephonyManager;
11 |
12 | import java.net.Inet4Address;
13 | import java.net.InetAddress;
14 | import java.net.NetworkInterface;
15 | import java.net.SocketException;
16 | import java.util.Enumeration;
17 |
18 |
19 | /**
20 | * 判断网络的的工具类
21 | * @author liuzongyao
22 | *
23 | */
24 | public abstract class NetHelper
25 | {
26 |
27 | public static boolean isWifi(Context c)
28 | {
29 | boolean bRet = false;
30 | ConnectivityManager cm = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
31 | if (cm != null) {
32 | NetworkInfo wifiInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
33 | if (null != wifiInfo && wifiInfo.isConnectedOrConnecting()) {
34 | bRet = true;
35 | }
36 | }
37 | return bRet;
38 | }
39 |
40 | public static boolean isWifiConnected(Context c)
41 | {
42 | boolean bRet = false;
43 | ConnectivityManager cm = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
44 | if (cm != null) {
45 | NetworkInfo wifiInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
46 | if (null != wifiInfo && wifiInfo.isConnected()) {
47 | bRet = true;
48 | }
49 | }
50 | return bRet;
51 | }
52 |
53 | public static boolean isMobileNet(final Context c)
54 | {
55 | boolean ret = false;
56 | ConnectivityManager cm = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
57 | if (null != cm)
58 | {
59 | NetworkInfo mobileInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
60 | if (null != mobileInfo && mobileInfo.isConnectedOrConnecting())
61 | {
62 | ret = true;
63 | }
64 | }
65 | return ret;
66 | }
67 |
68 | public static boolean isNetworkAvailable(Context context)
69 | {
70 | Context ct = context.getApplicationContext();
71 | ConnectivityManager cm = (ConnectivityManager) ct.getSystemService(Context.CONNECTIVITY_SERVICE);
72 | if (null == cm)
73 | {
74 | return false;
75 | }
76 | else
77 | {
78 | NetworkInfo[] info = cm.getAllNetworkInfo();
79 | if (null != info)
80 | {
81 | for (int i = 0; i < info.length; i++)
82 | {
83 | if (info[i].getState() == NetworkInfo.State.CONNECTED)
84 | {
85 | return true;
86 | }
87 | }
88 | }
89 | }
90 | return false;
91 | }
92 |
93 |
94 | // 以网络编码格式返回,如 3gnet/3gwap/uninet/uniwap/cmnet/cmwap/ctnet/ctwap
95 | @SuppressLint("DefaultLocale")
96 | public static String getNetTypeName(Context context)
97 | {
98 | String typeName = "null";
99 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
100 | NetworkInfo info = cm.getActiveNetworkInfo();
101 |
102 | if (null == info)
103 | {
104 | typeName = "null";
105 | }
106 | else if (info.getTypeName() != null)
107 | {
108 | typeName = info.getTypeName().toLowerCase(); // WIFI/MOBILE
109 |
110 | if (!typeName.equals("wifi"))
111 | {
112 | if (info.getExtraInfo() != null)
113 | {
114 | typeName = info.getExtraInfo().toLowerCase(); //3gnet/3gwap/uninet/uniwap/cmnet/cmwap/ctnet/ctwap
115 | }
116 |
117 | if (typeName.equals("#777") && info.getSubtypeName() != null)
118 | typeName = info.getSubtypeName();
119 | }
120 | }
121 |
122 | return typeName;
123 | }
124 |
125 | // wuwenhua add: 以wifi、2g、3g、4g作为返回格式
126 | public static String getNetTypeNameEx(Context context) {
127 |
128 | String type = "null";
129 | ConnectivityManager cm=(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
130 |
131 | NetworkInfo mobileInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
132 | NetworkInfo wifiInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
133 |
134 | if(null != mobileInfo && mobileInfo.isConnected()) {
135 | TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
136 | int netType = tm.getNetworkType();
137 | switch (netType) {
138 | case TelephonyManager.NETWORK_TYPE_GPRS:
139 | case TelephonyManager.NETWORK_TYPE_EDGE:
140 | case TelephonyManager.NETWORK_TYPE_CDMA:
141 | case TelephonyManager.NETWORK_TYPE_1xRTT:
142 | case TelephonyManager.NETWORK_TYPE_IDEN:
143 | type = "2g";
144 | break;
145 | case TelephonyManager.NETWORK_TYPE_UMTS:
146 | case TelephonyManager.NETWORK_TYPE_EVDO_0:
147 | case TelephonyManager.NETWORK_TYPE_EVDO_A:
148 | case TelephonyManager.NETWORK_TYPE_HSDPA:
149 | case TelephonyManager.NETWORK_TYPE_HSUPA:
150 | case TelephonyManager.NETWORK_TYPE_HSPA:
151 | case TelephonyManager.NETWORK_TYPE_EVDO_B:
152 | case TelephonyManager.NETWORK_TYPE_EHRPD:
153 | case TelephonyManager.NETWORK_TYPE_HSPAP:
154 | type = "3g";
155 | break;
156 | case TelephonyManager.NETWORK_TYPE_LTE:
157 | type = "4g";
158 | break;
159 | default:
160 | type = "null";
161 | break;
162 | }
163 |
164 | }
165 | if(null != wifiInfo && wifiInfo.isConnected()) {
166 | type = "wifi";
167 | }
168 | return type;
169 | }
170 |
171 |
172 | public static int getNetType(Context context)
173 | {
174 | int type = -1;
175 |
176 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
177 |
178 | NetworkInfo mobileInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
179 | NetworkInfo wifiInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
180 |
181 | if (null != mobileInfo && mobileInfo.isConnected())
182 | {
183 | type = ConnectivityManager.TYPE_MOBILE;
184 | }
185 | if (null != wifiInfo && wifiInfo.isConnected())
186 | {
187 | type = ConnectivityManager.TYPE_WIFI;
188 | }
189 |
190 | return type;
191 | }
192 |
193 | public static int getCurrentNetType(Context ctx)
194 | {
195 | ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
196 | NetworkInfo ni = cm.getActiveNetworkInfo();
197 | return ni == null ? -1 : ni.getType();
198 | }
199 |
200 | public static String getNetworkSubType(int subType)
201 | {
202 | String type = null;
203 | switch (subType)
204 | {
205 | case TelephonyManager.NETWORK_TYPE_GPRS:
206 | case TelephonyManager.NETWORK_TYPE_EDGE:
207 | case TelephonyManager.NETWORK_TYPE_CDMA:
208 | // 2G网络
209 | type = "2g";
210 | break;
211 | case TelephonyManager.NETWORK_TYPE_EVDO_0:
212 | case TelephonyManager.NETWORK_TYPE_EVDO_A:
213 | case TelephonyManager.NETWORK_TYPE_HSDPA:
214 | case TelephonyManager.NETWORK_TYPE_UMTS:
215 | // 3G网络
216 | type = "3g";
217 | break;
218 | default:
219 | type = "other";
220 | break;
221 | }
222 |
223 | return type;
224 | }
225 |
226 | /**
227 | * 获得ip地址【Old版本,在MX351手机上获取的ip地址有错】,修改by GeXianglin
228 | * @param context
229 | * @return
230 | * @throws SocketException
231 | */
232 | public static String getIPAddress(Context context) throws SocketException {
233 | String ret = null;
234 | if ((ret = getIPAddressPrivate(context)) != null) {
235 | return ret;
236 | }
237 |
238 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
239 | for (Enumeration en = NetworkInterface.getNetworkInterfaces();
240 | en.hasMoreElements();) {
241 |
242 | NetworkInterface intf = en.nextElement();
243 | for (Enumeration enumIpAddr = intf.getInetAddresses();
244 | enumIpAddr.hasMoreElements();) {
245 |
246 | InetAddress inetAddress = enumIpAddr.nextElement();
247 | if (!inetAddress.isLoopbackAddress() && (inetAddress instanceof Inet4Address)) {
248 | return inetAddress.getHostAddress().toString();
249 | }
250 | }
251 | }
252 | } else {
253 | for (Enumeration en = NetworkInterface.getNetworkInterfaces();
254 | en.hasMoreElements();) {
255 |
256 | NetworkInterface intf = en.nextElement();
257 | for (Enumeration enumIpAddr = intf.getInetAddresses();
258 | enumIpAddr.hasMoreElements();) {
259 |
260 | InetAddress inetAddress = enumIpAddr.nextElement();
261 | if (!inetAddress.isLoopbackAddress()) {
262 | return inetAddress.getHostAddress().toString();
263 | }
264 | }
265 | }
266 | }
267 | return null;
268 | }
269 |
270 | /*
271 | * 获取Ip地址,原来的ip地址获取,在魅族MX351手机上获取ip地址有错,增加这个在老方法里头。
272 | * @param context
273 | * @return
274 | * @author GeXianglin
275 | */
276 | private static String getIPAddressPrivate(Context context) {
277 | WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
278 | DhcpInfo dhcp = wifiManager.getDhcpInfo();
279 | if (dhcp != null) {
280 | int ipAddress = dhcp.ipAddress;
281 | return (ipAddress & 0xff) + "." + ((ipAddress >> 8) & 0xff) + "." +
282 | ((ipAddress >> 16) & 0xff) + "." + ((ipAddress >> 24) & 0xff);
283 | }
284 |
285 | return null;
286 | }
287 |
288 | /**
289 | * 获取当前wifi的ssid
290 | *
291 | */
292 | public static String getSsid(Context ctx)
293 | {
294 | WifiManager mWifiManager = (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE);
295 | if (mWifiManager.getConnectionInfo() != null)
296 | {
297 | return mWifiManager.getConnectionInfo().getSSID();
298 | }
299 | return null;
300 | }
301 |
302 |
303 | /**
304 | * 判断任务的SSID是否和当前网络的SSID相同(和taskInfo解耦)
305 | *
306 | * @param context
307 | * @param ssid
308 | * @return
309 | */
310 | public static boolean isSSIDSame(Context context, String ssid)
311 | {
312 | boolean ret = false;
313 | if (context != null && ssid != null)
314 | {
315 | if (ssid != null && !ssid.equals(""))
316 | {
317 | ret = ssid.equals(getSsid(context));
318 | }
319 | else
320 | {
321 | ret = true;
322 | }
323 | }
324 | else
325 | {
326 | if (ssid == null || ssid.equals(""))
327 | {
328 | ret = true;
329 | }
330 | }
331 | return ret;
332 | }
333 |
334 | /**
335 | * 判断传入的IP地址与手机的wifi是否在同一局域网中。
336 | *
337 | * @param context
338 | * @param PCIP
339 | * @return
340 | */
341 | public static boolean isNetSame(Context context, String PCIP)
342 | {
343 | WifiManager mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
344 | DhcpInfo dhcp = null;
345 | if (null != mWifiManager && null != (dhcp = mWifiManager.getDhcpInfo()))
346 | {
347 | int networkIP = dhcp.ipAddress;
348 | int networkMask = dhcp.netmask;
349 | int networkId = networkIP & networkMask;
350 | int pcaddress = ipAddrToInt(PCIP);
351 | if (0 != pcaddress)
352 | {
353 | int pcNetWorkId = pcaddress & networkMask;
354 | if (pcNetWorkId == networkId)
355 | {
356 | return true;
357 | }
358 | }
359 | }
360 | return false;
361 | }
362 |
363 | @SuppressLint("DefaultLocale")
364 | public static String getLocalIpAddress()
365 | {
366 | try
367 | {
368 | for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();)
369 | {
370 | NetworkInterface intf = en.nextElement();
371 | for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();)
372 | {
373 | InetAddress inetAddress = enumIpAddr.nextElement();
374 | if (!inetAddress.isLoopbackAddress())
375 | {
376 | return inetAddress.getHostAddress().toString();
377 | }
378 | }
379 | }
380 | }
381 | catch (SocketException ex)
382 | {
383 | }
384 | return null;
385 | }
386 |
387 | /**
388 | * 将String类型的IP地址转换成int32类型。将192.168.11.101转换为1812703424 注:int32 高位在前,地位在后
389 | *
390 | * @param IPAddress
391 | * String类型的IP地址,如:192.168.11.101
392 | * @return int32类型的IP地址,如:1812703424, 如果IPAddress为null或者不合法,则返回0
393 | */
394 | public static int ipAddrToInt(String IPAddress) {
395 | if (null == IPAddress) {
396 | // throw new NullPointerException("IPAddress is null.");
397 | // NullPointerException 如果IPAddress为null
398 | return 0;
399 | }
400 | if (!IPAddress
401 | .matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$")) {
402 | // throw new
403 | // RuntimeException("IPAddress is not valid...");RuntimeException
404 | // 如果String类型的IP地址格式不合法。
405 | return 0;
406 | }
407 | String[] array = IPAddress.split("\\.");
408 | int a = Integer.parseInt(array[0]);
409 | int b = Integer.parseInt(array[1]) << 8;
410 | int c = Integer.parseInt(array[2]) << 16;
411 | int d = Integer.parseInt(array[3]) << 24;
412 | return a | b | c | d;
413 | }
414 | }
415 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
25 |
26 |
33 |
34 |
41 |
48 |
55 |
56 |
62 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/szuwest/Recorder/babd04b6ef2f2f7cab89e1d35aa63ea3897477cd/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/szuwest/Recorder/babd04b6ef2f2f7cab89e1d35aa63ea3897477cd/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/szuwest/Recorder/babd04b6ef2f2f7cab89e1d35aa63ea3897477cd/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/szuwest/Recorder/babd04b6ef2f2f7cab89e1d35aa63ea3897477cd/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/szuwest/Recorder/babd04b6ef2f2f7cab89e1d35aa63ea3897477cd/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/szuwest/Recorder/babd04b6ef2f2f7cab89e1d35aa63ea3897477cd/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/szuwest/Recorder/babd04b6ef2f2f7cab89e1d35aa63ea3897477cd/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/szuwest/Recorder/babd04b6ef2f2f7cab89e1d35aa63ea3897477cd/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/szuwest/Recorder/babd04b6ef2f2f7cab89e1d35aa63ea3897477cd/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/szuwest/Recorder/babd04b6ef2f2f7cab89e1d35aa63ea3897477cd/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Recorder
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/speakin/recorder/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.speakin.recorder;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.1.50'
5 | repositories {
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:2.3.3'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | jcenter()
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/szuwest/Recorder/babd04b6ef2f2f7cab89e1d35aa63ea3897477cd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Sep 26 16:39:52 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------