├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── dimen.xml │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── layout │ │ │ ├── item_log.xml │ │ │ └── activity_main.xml │ │ ├── values-night │ │ │ └── themes.xml │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ └── drawable │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ └── com │ │ │ └── eurigo │ │ │ └── websocketutils │ │ │ ├── LogAdapter.java │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── .idea ├── .name ├── .gitignore ├── compiler.xml ├── kotlinc.xml ├── vcs.xml ├── migrations.xml ├── deploymentTargetSelector.xml ├── gradle.xml ├── runConfigurations.xml ├── misc.xml └── inspectionProfiles │ └── Project_Default.xml ├── websocketlib ├── .gitignore ├── jitpack.yml ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── eurigo │ │ └── websocketlib │ │ ├── util │ │ ├── WsLogUtil.java │ │ ├── AppUtils.java │ │ └── ThreadUtils.java │ │ ├── IWebSocketServerListener.java │ │ ├── DisConnectReason.java │ │ ├── ReconnectGuardianTask.java │ │ ├── IWebSocketListener.java │ │ ├── ReconnectTask.java │ │ ├── WsClient.java │ │ └── WsManager.java ├── proguard-rules.pro └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | websocketUtils -------------------------------------------------------------------------------- /websocketlib/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /websocketlib/jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | websocketUtils 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eurigo/WebsocketUtils/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eurigo/WebsocketUtils/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eurigo/WebsocketUtils/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eurigo/WebsocketUtils/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eurigo/WebsocketUtils/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eurigo/WebsocketUtils/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eurigo/WebsocketUtils/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eurigo/WebsocketUtils/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eurigo/WebsocketUtils/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eurigo/WebsocketUtils/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eurigo/WebsocketUtils/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 72dp 5 | 6 | 50dp 7 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 14 08:54:12 CST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /websocketlib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF666699 4 | #FF0099FF 5 | #FF336699 6 | #FF336666 7 | #FF669999 8 | #FF000000 9 | #FFFFFFFF 10 | #FF757575 11 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_log.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /websocketlib/src/main/java/com/eurigo/websocketlib/util/WsLogUtil.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketlib.util; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * @author Eurigo 7 | * Created on 2022/3/29 16:11 8 | * desc : 9 | */ 10 | public class WsLogUtil { 11 | 12 | private static final String TAG = "WsLogUtil"; 13 | 14 | private static boolean isClose = true; 15 | 16 | public static void closeLog(boolean print) { 17 | isClose = print; 18 | } 19 | 20 | public static void d(String msg) { 21 | if (isClose) { 22 | return; 23 | } 24 | Log.d(TAG, "d: " + msg); 25 | } 26 | 27 | public static void e(String msg) { 28 | if (isClose) { 29 | return; 30 | } 31 | Log.e(TAG, "e: " + msg); 32 | } 33 | 34 | public static void w(String msg) { 35 | if (isClose) { 36 | return; 37 | } 38 | Log.w(TAG, "w: " + msg); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /websocketlib/src/main/java/com/eurigo/websocketlib/IWebSocketServerListener.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketlib; 2 | 3 | import org.java_websocket.WebSocket; 4 | import org.java_websocket.handshake.ClientHandshake; 5 | import org.java_websocket.server.WebSocketServer; 6 | 7 | /** 8 | * @author eurigo 9 | * Created on 2025/3/6 17:16 10 | * desc : WebServerListener 11 | */ 12 | public interface IWebSocketServerListener { 13 | 14 | /** 15 | * 服务器已启动 16 | */ 17 | void onWsOpen(WebSocket conn, ClientHandshake handshake); 18 | 19 | /** 20 | * 服务器已关闭 21 | */ 22 | void onWsClose(WebSocket conn, int code, String reason, boolean remote); 23 | 24 | /** 25 | * 接收到消息 26 | */ 27 | void onWsMessage(WebSocket conn, String message); 28 | 29 | /** 30 | * 连接异常 31 | * 32 | * @param conn 客户端 33 | * @param ex 异常 34 | */ 35 | void onWsError(WebSocket conn, Exception ex); 36 | 37 | /** 38 | * 已启动 39 | */ 40 | void onWsStart(WebSocketServer server); 41 | } 42 | -------------------------------------------------------------------------------- /websocketlib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep public class com.eurigo.websocketlib.**{*;} 24 | -keep public class org.slf4j.**{*;} 25 | -dontwarn org.slf4j.** 26 | -keep public class * extends com.eurigo.websocketlib.IWebSocketListener 27 | -keep public class org.java_websocket.** {*;} -------------------------------------------------------------------------------- /app/src/main/java/com/eurigo/websocketutils/LogAdapter.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketutils; 2 | 3 | import com.blankj.utilcode.util.TimeUtils; 4 | import com.chad.library.adapter.base.BaseQuickAdapter; 5 | import com.chad.library.adapter.base.viewholder.BaseViewHolder; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * @author Eurigo 14 | * Created on 2021/6/30 10:38 15 | * desc : 16 | */ 17 | public class LogAdapter extends BaseQuickAdapter { 18 | 19 | public LogAdapter(@Nullable List data) { 20 | super(R.layout.item_log, data); 21 | } 22 | 23 | @Override 24 | protected void convert(@NotNull BaseViewHolder helper, String s) { 25 | helper.setText(R.id.tv_item_log, s); 26 | } 27 | 28 | public void addDataAndScroll(@NotNull String data, boolean isClient) { 29 | String regex = isClient ? "客户端:" : "服务端收到:"; 30 | addData(TimeUtils.getNowString() + "\n" + regex + data); 31 | getRecyclerView().scrollToPosition(getData().size() - 1); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { url 'https://jitpack.io' } 4 | maven { url 'https://plugins.gradle.org/m2/'} 5 | maven { url 'https://maven.aliyun.com/repository/google' } 6 | maven { url 'https://maven.aliyun.com/repository/public' } 7 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 8 | // 华为开源镜像:https://mirrors.huaweicloud.com 9 | maven { url 'https://repo.huaweicloud.com/repository/maven' } 10 | } 11 | } 12 | dependencyResolutionManagement { 13 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 14 | repositories { 15 | maven { url 'https://jitpack.io' } 16 | maven { url 'https://plugins.gradle.org/m2/'} 17 | maven { url 'https://maven.aliyun.com/repository/google' } 18 | maven { url 'https://maven.aliyun.com/repository/public' } 19 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 20 | // 华为开源镜像:https://mirrors.huaweicloud.com 21 | maven { url 'https://repo.huaweicloud.com/repository/maven' } 22 | } 23 | } 24 | rootProject.name = "websocketUtils" 25 | include ':app' 26 | include ':websocketlib' 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /websocketlib/src/main/java/com/eurigo/websocketlib/DisConnectReason.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketlib; 2 | 3 | import org.java_websocket.framing.CloseFrame; 4 | 5 | /** 6 | * @author Eurigo 7 | * Created on 2022/3/29 10:11 8 | * desc : 断线原因 9 | */ 10 | public class DisConnectReason { 11 | 12 | public DisConnectReason(int code, String reason, boolean remote) { 13 | this.code = code; 14 | this.reason = reason; 15 | this.remote = remote; 16 | } 17 | 18 | /** 19 | * 断线码{@link CloseFrame} 20 | */ 21 | private final int code; 22 | 23 | /** 24 | * 断线原因 25 | */ 26 | private final String reason; 27 | 28 | /** 29 | * 是否由服务端发起的断线 30 | */ 31 | private final boolean remote; 32 | 33 | public int getCode() { 34 | return code; 35 | } 36 | 37 | public String getReason() { 38 | return reason; 39 | } 40 | 41 | public boolean isRemote() { 42 | return remote; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "DisConnectReason{" + 48 | "code=" + code + 49 | ", reason='" + reason + '\'' + 50 | ", remote=" + remote + 51 | '}'; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | -------------------------------------------------------------------------------- /websocketlib/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'maven-publish' 4 | } 5 | 6 | afterEvaluate { 7 | publishing { 8 | publications { 9 | release(MavenPublication) { 10 | from components.release 11 | 12 | groupId = 'com.eurigo' 13 | artifactId = 'websocketutil' 14 | version = '1.0.0' 15 | } 16 | } 17 | } 18 | } 19 | 20 | android { 21 | namespace 'com.eurigo.websocketutilslib' 22 | compileSdk 35 23 | 24 | defaultConfig { 25 | minSdk 21 26 | targetSdk 35 27 | versionCode 16 28 | versionName "1.3.0" 29 | } 30 | 31 | publishing { 32 | singleVariant('release') { 33 | withSourcesJar() 34 | } 35 | } 36 | 37 | buildTypes { 38 | release { 39 | minifyEnabled false 40 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 41 | } 42 | } 43 | compileOptions { 44 | sourceCompatibility JavaVersion.VERSION_17 45 | targetCompatibility JavaVersion.VERSION_17 46 | } 47 | } 48 | 49 | dependencies { 50 | implementation 'androidx.appcompat:appcompat:1.0.0' 51 | api 'org.java-websocket:Java-WebSocket:1.6.0' 52 | } -------------------------------------------------------------------------------- /websocketlib/src/main/java/com/eurigo/websocketlib/ReconnectGuardianTask.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketlib; 2 | 3 | import com.eurigo.websocketlib.util.ThreadUtils; 4 | 5 | import java.util.concurrent.TimeUnit; 6 | 7 | /** 8 | * @author eurigo 9 | * Created on 2025/3/7 08:19 10 | * desc : WebSocket守护任务 11 | */ 12 | public class ReconnectGuardianTask extends ThreadUtils.SimpleTask { 13 | 14 | @Override 15 | public Void doInBackground() { 16 | WsManager.getInstance().start(); 17 | return null; 18 | } 19 | 20 | @Override 21 | public void onSuccess(Void result) { 22 | 23 | } 24 | 25 | public void execute(){ 26 | long reconnectInterval = WsManager.getInstance().getGuardianTaskInterval(); 27 | if (reconnectInterval <= 0) { 28 | throw new IllegalArgumentException("reconnectInterval must be greater than 0"); 29 | } 30 | if (reconnectInterval < WsManager.getInstance().getMaxReconnectCount() * WsManager.getInstance().getMaxReconnectInterval() * 2) { 31 | throw new IllegalArgumentException("reconnectInterval must be greater than maxReconnectCount * maxReconnectInterval * 2"); 32 | } 33 | ThreadUtils.executeByCachedAtFixRate(this, WsManager.getInstance().getGuardianTaskInterval(), TimeUnit.SECONDS); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace 'com.eurigo.websocketutils' 8 | compileSdk 35 9 | 10 | defaultConfig { 11 | applicationId "com.eurigo.websocketutils" 12 | minSdk 21 13 | targetSdk 35 14 | versionCode 1 15 | versionName "1.0" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_17 26 | targetCompatibility JavaVersion.VERSION_17 27 | } 28 | kotlinOptions { 29 | jvmTarget = '17' 30 | } 31 | 32 | // 在module build.gradle android内 33 | buildFeatures { 34 | viewBinding = true 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation project (':websocketlib') 40 | implementation 'androidx.core:core-ktx:1.13.0' 41 | implementation 'androidx.appcompat:appcompat:1.7.0' 42 | implementation 'com.google.android.material:material:1.12.0' 43 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 44 | 45 | // 工具类 https://github.com/Blankj/AndroidUtilCode/blob/master/lib/utilcode/README-CN.md 46 | implementation 'com.blankj:utilcodex:1.31.1' 47 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4' 48 | } -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /websocketlib/src/main/java/com/eurigo/websocketlib/IWebSocketListener.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketlib; 2 | 3 | import org.java_websocket.framing.Framedata; 4 | 5 | import java.nio.ByteBuffer; 6 | 7 | /** 8 | * @author Eurigo 9 | * Created on 2022/3/29 10:07 10 | * desc : 11 | */ 12 | public interface IWebSocketListener { 13 | 14 | /** 15 | * 连接成功 16 | * 17 | * @param client 客户端 18 | */ 19 | void onConnected(WsClient client); 20 | 21 | /** 22 | * 连接已断开 23 | * 24 | * @param client 客户端 25 | * @param reason 断开原因 26 | */ 27 | void onDisconnect(WsClient client, DisConnectReason reason); 28 | 29 | /** 30 | * 正在关闭连接 31 | * 32 | * @param client 客户端 33 | * @param reason 关闭原因 34 | */ 35 | void onClosing(WsClient client, DisConnectReason reason); 36 | 37 | /** 38 | * 连接失败,调用后会调用onClose 39 | * 40 | * @param client 客户端 41 | * @param ex 异常 42 | */ 43 | default void onError(WsClient client, Exception ex) { 44 | 45 | } 46 | 47 | /** 48 | * 接收到消息 49 | * 50 | * @param client 客户端 51 | * @param message 消息 52 | */ 53 | void onMessage(WsClient client, String message); 54 | 55 | /** 56 | * 接收到消息 57 | * 58 | * @param client 客户端 59 | * @param bytes 消息 60 | */ 61 | default void onMessage(WsClient client, ByteBuffer bytes) { 62 | 63 | } 64 | 65 | /** 66 | * 响应服务端ping时触发 67 | * 此默认实现将发送一个 pong 以响应收到的 ping。 pong 帧将具有与 ping 帧相同的有效负载 68 | * 69 | * @param wsClient 客户端 70 | * @param frameData 帧数据 71 | */ 72 | void onPing(WsClient wsClient, Framedata frameData); 73 | 74 | /** 75 | * 响应服务端pong时触发 76 | * 77 | * @param client 客户端 78 | * @param frameData 帧数据 79 | */ 80 | void onPong(WsClient client, Framedata frameData); 81 | 82 | /** 83 | * 发送消息成功 84 | * 85 | * @param client 客户端 86 | * @param message 发送的消息 87 | */ 88 | void onSendMessage(WsClient client, String message); 89 | 90 | default void onSendMessage(WsClient client, byte[] message) { 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /websocketlib/src/main/java/com/eurigo/websocketlib/ReconnectTask.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketlib; 2 | 3 | import com.eurigo.websocketlib.util.ThreadUtils; 4 | import com.eurigo.websocketlib.util.WsLogUtil; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * @author Eurigo 10 | * Created on 2022/3/30 15:07 11 | * desc : 12 | */ 13 | public class ReconnectTask extends ThreadUtils.SimpleTask { 14 | 15 | private final String wsKey; 16 | private int reconnectCount; 17 | private final long reconnectInterval; 18 | private int count = 1; 19 | 20 | public ReconnectTask(String wsKey) { 21 | this.wsKey = wsKey; 22 | // 重置全局重连次数 23 | reconnectCount = WsManager.getInstance().getWsClient(wsKey).getReconnectCount(); 24 | reconnectInterval = WsManager.getInstance().getWsClient(wsKey).getReconnectInterval(); 25 | } 26 | 27 | @Override 28 | public Void doInBackground() { 29 | if (WsManager.getInstance().getWsClient(wsKey).isOpen()) { 30 | ThreadUtils.cancel(this); 31 | return null; 32 | } 33 | WsLogUtil.e("执行第" + count + "次重连"); 34 | WsManager.getInstance().setTaskReconnectCount(count); 35 | WsManager.getInstance().safeConnect(WsManager.getInstance().getWsClient(wsKey)); 36 | // 每次执行任务,重连次数递减,直到为0不再发起重连 37 | reconnectCount--; 38 | count++; 39 | return null; 40 | } 41 | 42 | @Override 43 | public void onSuccess(Void result) { 44 | if (reconnectCount == 0) { 45 | ThreadUtils.cancel(this); 46 | } 47 | } 48 | 49 | @Override 50 | public void onCancel() { 51 | WsLogUtil.e("重连任务执行完毕"); 52 | WsManager.getInstance().setReconnectTaskRun(false); 53 | } 54 | 55 | public void execute() { 56 | if (WsManager.getInstance().isReconnectTaskRun()) { 57 | WsLogUtil.e("重连任务已执行"); 58 | return; 59 | } 60 | if (!WsManager.getInstance().isNetworkAvailable()) { 61 | WsLogUtil.e("网络不可用, 不执行重连"); 62 | return; 63 | } 64 | if (WsManager.getInstance().getWsClient(wsKey).isOpen()) { 65 | WsLogUtil.e("Socket已连接"); 66 | return; 67 | } 68 | WsManager.getInstance().setReconnectTaskRun(true); 69 | ThreadUtils.executeByIoAtFixRate(this, reconnectInterval, TimeUnit.SECONDS); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 21 | 22 | 30 | 31 | 38 | 39 | 50 | 51 | 59 | 60 | 68 | 69 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /websocketlib/src/main/java/com/eurigo/websocketlib/util/AppUtils.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketlib.util; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import java.lang.reflect.Field; 7 | 8 | /** 9 | * @author Eurigo 10 | * Created on 2022/3/31 10:15 11 | * desc : 12 | */ 13 | public class AppUtils { 14 | 15 | public AppUtils() { 16 | } 17 | 18 | public static AppUtils getInstance() { 19 | return SingletonHelper.INSTANCE; 20 | } 21 | 22 | private static class SingletonHelper { 23 | private final static AppUtils INSTANCE = new AppUtils(); 24 | } 25 | 26 | private Application sApp; 27 | 28 | public Application getApp() { 29 | if (sApp != null) { 30 | return sApp; 31 | } 32 | sApp = getApplicationByReflect(); 33 | if (sApp == null) { 34 | throw new NullPointerException("reflect failed."); 35 | } 36 | return sApp; 37 | } 38 | 39 | private Application getApplicationByReflect() { 40 | try { 41 | Class activityThreadClass = Class.forName("android.app.ActivityThread"); 42 | Object thread = getActivityThread(); 43 | if (thread == null) { 44 | return null; 45 | } 46 | Object app = activityThreadClass.getMethod("getApplication").invoke(thread); 47 | if (app == null) { 48 | return null; 49 | } 50 | return (Application) app; 51 | } catch (Exception e) { 52 | e.printStackTrace(); 53 | } 54 | return null; 55 | } 56 | 57 | private Object getActivityThread() { 58 | Object activityThread = getActivityThreadInActivityThreadStaticField(); 59 | if (activityThread != null) { 60 | return activityThread; 61 | } 62 | return getActivityThreadInActivityThreadStaticMethod(); 63 | } 64 | 65 | private Object getActivityThreadInActivityThreadStaticField() { 66 | try { 67 | Class activityThreadClass = Class.forName("android.app.ActivityThread"); 68 | Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread"); 69 | sCurrentActivityThreadField.setAccessible(true); 70 | return sCurrentActivityThreadField.get(null); 71 | } catch (Exception e) { 72 | Log.e("UtilsActivityLifecycle", "getActivityThreadInActivityThreadStaticField: " + e.getMessage()); 73 | return null; 74 | } 75 | } 76 | 77 | private Object getActivityThreadInActivityThreadStaticMethod() { 78 | try { 79 | Class activityThreadClass = Class.forName("android.app.ActivityThread"); 80 | return activityThreadClass.getMethod("currentActivityThread").invoke(null); 81 | } catch (Exception e) { 82 | Log.e("UtilsActivityLifecycle", "getActivityThreadInActivityThreadStaticMethod: " + e.getMessage()); 83 | return null; 84 | } 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Websocket [![](https://jitpack.io/v/eurigo/WebsocketUtils.svg)](https://jitpack.io/#eurigo/WebsocketUtils) 2 | 3 | 基于Java-websocket,在Android下的websocket。 4 | 5 | * 具有断网重连和自动重连功能。 6 | * 支持设置连接保护 7 | * 支持管理多个Websocket 8 | * 支持搭建本地Websocket调试 9 | * 支持发送ping帧和pong帧 10 | 11 | --- 12 | 13 | ### 快速使用 14 | 15 | #### 1.在项目级 `build.gradle`添加: 16 | 17 | ```java 18 | allprojects { 19 | repositories { 20 | maven { url 'https://jitpack.io' } 21 | } 22 | } 23 | ``` 24 | 25 | #### 2.在app模块下的`build.gradle`文件中加入: 26 | 27 | ```java 28 | dependencies { 29 | implementation 'com.github.eurigo:WebsocketUtils:1.3.0' 30 | } 31 | ``` 32 | 33 | #### 3.在Manifest添加权限: 34 | 35 | ```xml 36 | 37 | 38 | ``` 39 | 40 | #### 4.初始化 41 | 42 | ```java 43 | // 构造一个默认WebSocket客户端 44 | WsClient wsClient = new WsClient.Builder() 45 | .setServerUrl(address) 46 | .setReconnectInterval(1) 47 | .setReconnectCount(10) 48 | .setPingInterval(30) 49 | .setListener(new IWsListener() { 50 | @Override 51 | public void onConnected(WsClient client) { 52 | // 连接成功 53 | } 54 | 55 | @Override 56 | public void onDisconnect(WsClient client, DisConnectReason reason) { 57 | // 连接断开 58 | } 59 | 60 | @Override 61 | public void onMessage(WsClient client, String message) { 62 | // 接收到消息 63 | } 64 | 65 | @Override 66 | public void onPing(WsClient wsClient, Framedata frameData) { 67 | // ping frame 68 | } 69 | 70 | @Override 71 | public void onPong(WsClient client, Framedata frameData) { 72 | // pong frame 73 | } 74 | 75 | @Override 76 | public void onSendMessage(WsClient client, String message) { 77 | // 发送数据 78 | } 79 | }) 80 | .setPingInterval(30) 81 | .build(); 82 | 83 | // 初始化并启动连接 84 | WsManager.getInstance() 85 | .init(wsClient) 86 | .start(); 87 | ``` 88 | 89 | #### 5.在onDestory销毁 90 | 91 | ```java 92 | @Override 93 | protected void onDestroy() { 94 | super.onDestroy(); 95 | WsManager.getInstance().destroy(); 96 | } 97 | ``` 98 | 99 | --- 100 | 101 | ### WsManager API介绍 102 | 103 | | WsManager.getInstance() | 说明 | 104 | |:---------------------------------|:-----------------------:| 105 | | isNetworkAvailable() | 网络是否可用 | 106 | | startGuardianTaskInterval() | 设置保护间隔并启动保护任务 | 107 | | startWsServer() | 启动一个WebSocketServer | 108 | | closeLog() | 是否显示内部日志,默认true | 109 | | getDefault() | 获取默认的websocket | 110 | | send() | 用(指定的)websocket发送消息 | 111 | | sendPing() | 用(指定的)websocket发送心跳ping | 112 | | disconnect() | 断开(指定的)websocket连接 | 113 | | destroy() | 销毁所有websocket资源 | 114 | 115 | ### WsClient 属性 116 | 117 | | 属性 | 说明 | 118 | |-------------------------------|:-----------------------------:| 119 | | serverUrl(必须) | 服务端地址 | 120 | | IWebSocketListener(必须) | 回调 | 121 | | wsKey | 初始化时设置的标识,不设置,自动使用默认websocket | 122 | | draft | Websocket协议,默认6455 | 123 | | connectTimeout | 连接超时时间,默认值:0 | 124 | | pingInterval | 心跳时间,单位秒,默认60。小于等于0,则关闭心跳功能 | 125 | | reConnectCount | 重连次数,默认10,大于0才开启重连功能 | 126 | | reconnectInterval | 自动重连间隔, 单位毫秒,默认值1000 | 127 | | isReconnectTaskRun | 是否正在执行重连任务 | 128 | | reConnectWhenNetworkAvailable | 网络可用时是否自动重连,默认值true | 129 | | httpHeaders | 要使用的附加标头 | 130 | 131 | ### 更多 132 | 133 | 请参考demo 134 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /app/src/main/java/com/eurigo/websocketutils/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketutils; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Bundle; 5 | import android.text.TextUtils; 6 | import android.view.View; 7 | import android.widget.EditText; 8 | 9 | import androidx.activity.ComponentActivity; 10 | import androidx.activity.EdgeToEdge; 11 | import androidx.annotation.Nullable; 12 | import androidx.recyclerview.widget.DividerItemDecoration; 13 | import androidx.recyclerview.widget.LinearLayoutManager; 14 | 15 | import com.blankj.utilcode.util.LogUtils; 16 | import com.blankj.utilcode.util.NetworkUtils; 17 | import com.blankj.utilcode.util.ToastUtils; 18 | import com.eurigo.websocketlib.DisConnectReason; 19 | import com.eurigo.websocketlib.IWebSocketListener; 20 | import com.eurigo.websocketlib.IWebSocketServerListener; 21 | import com.eurigo.websocketlib.WsClient; 22 | import com.eurigo.websocketlib.WsManager; 23 | import com.eurigo.websocketutils.databinding.ActivityMainBinding; 24 | 25 | import org.java_websocket.WebSocket; 26 | import org.java_websocket.framing.Framedata; 27 | import org.java_websocket.handshake.ClientHandshake; 28 | import org.java_websocket.server.WebSocketServer; 29 | 30 | import java.net.InetSocketAddress; 31 | import java.util.ArrayList; 32 | 33 | /** 34 | * @author Eurigo 35 | * Created on 2022/3/28 10:35 36 | * desc : 37 | */ 38 | public class MainActivity extends ComponentActivity implements View.OnClickListener 39 | , IWebSocketListener, IWebSocketServerListener { 40 | 41 | private LogAdapter mAdapter; 42 | 43 | private String ipAddress; 44 | private static final int PORT = 8800; 45 | private static final String REGEX = "ws:/"; 46 | 47 | private ActivityMainBinding mBinding; 48 | 49 | @Override 50 | protected void onCreate(@Nullable Bundle savedInstanceState) { 51 | EdgeToEdge.enable(this); 52 | super.onCreate(savedInstanceState); 53 | mBinding = ActivityMainBinding.inflate(getLayoutInflater()); 54 | setContentView(mBinding.getRoot()); 55 | initView(); 56 | ipAddress = NetworkUtils.getIpAddressByWifi(); 57 | WsManager.getInstance().startWsServer(new InetSocketAddress(ipAddress, PORT), this); 58 | connectWebSocket("ws://".concat(ipAddress).concat(":" + PORT)); 59 | } 60 | 61 | private void initView() { 62 | mAdapter = new LogAdapter(new ArrayList<>()); 63 | mBinding.rcvApLog.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); 64 | mBinding.rcvApLog.setLayoutManager(new LinearLayoutManager(this)); 65 | mBinding.rcvApLog.setAdapter(mAdapter); 66 | mBinding.btnConnect.setOnClickListener(this); 67 | mBinding.btnClose.setOnClickListener(this); 68 | mBinding.btnSend.setOnClickListener(this); 69 | } 70 | 71 | @SuppressLint("NonConstantResourceId") 72 | @Override 73 | public void onClick(View view) { 74 | if (view == mBinding.btnConnect) { 75 | connectWebSocket(getEditText(mBinding.etAddress)); 76 | return; 77 | } 78 | if (view == mBinding.btnClose) { 79 | WsManager.getInstance().disConnect(); 80 | return; 81 | } 82 | if (view == mBinding.btnSend) { 83 | String msg = getEditText(mBinding.etMessage); 84 | if (TextUtils.isEmpty(msg)) { 85 | ToastUtils.showShort("请输入消息"); 86 | return; 87 | } 88 | if (TextUtils.equals("reset", mBinding.etMessage.getText().toString().trim())) { 89 | WsManager.getInstance().resetTaskReconnectCount(); 90 | return; 91 | } 92 | WsManager.getInstance().send(getEditText(mBinding.etMessage)); 93 | } 94 | } 95 | 96 | private String getEditText(EditText editText) { 97 | return editText.getText().toString().trim(); 98 | } 99 | 100 | /** 101 | * 连接WebSocket 102 | */ 103 | private void connectWebSocket(String address) { 104 | if (TextUtils.isEmpty(ipAddress)) { 105 | ToastUtils.showShort("请先连接WIFI"); 106 | return; 107 | } 108 | mBinding.etAddress.setText(address); 109 | // 构造一个默认WebSocket客户端 110 | WsClient wsClient = new WsClient.Builder() 111 | .setServerUrl(address) 112 | .setListener(this) 113 | .setReconnectInterval(1) 114 | .setReconnectCount(10) 115 | .setPingInterval(30) 116 | .build(); 117 | // 初始化并启动连接 118 | WsManager.getInstance() 119 | .init(wsClient) 120 | .closeLog(false) 121 | .start(); 122 | // 启动3分钟的重连保护 123 | WsManager.getInstance().startGuardianTaskInterval(60*3); 124 | } 125 | 126 | @Override 127 | public void onWsOpen(WebSocket conn, ClientHandshake handshake) { 128 | LogUtils.e("服务端日志", "服务器已启动,地址:" + conn.getLocalSocketAddress().toString()); 129 | } 130 | 131 | @Override 132 | public void onWsClose(WebSocket conn, int code, String reason, boolean remote) { 133 | LogUtils.e("服务端日志", conn + "已关闭" 134 | , new DisConnectReason(code, reason, remote).toString()); 135 | } 136 | 137 | @Override 138 | public void onWsMessage(WebSocket conn, String message) { 139 | runOnUiThread(() -> mAdapter.addDataAndScroll(message, false)); 140 | } 141 | 142 | @Override 143 | public void onWsError(WebSocket conn, Exception ex) { 144 | LogUtils.e("服务端日志", "异常", ex); 145 | runOnUiThread(() -> mAdapter.addDataAndScroll(ex.getMessage(), false)); 146 | } 147 | 148 | @Override 149 | public void onWsStart(WebSocketServer server) { 150 | LogUtils.e("服务端日志", "服务器已启动", "地址:" + ipAddress + ":" + PORT); 151 | } 152 | 153 | /** 154 | * 客户端回调start 155 | */ 156 | @Override 157 | public void onConnected(WsClient client) { 158 | runOnUiThread(() -> { 159 | mBinding.etAddress.setText(REGEX.concat(client.getRemoteSocketAddress().toString())); 160 | mAdapter.addDataAndScroll(client.getLocalSocketAddress().toString().replace("/", "").concat(" 连接成功"), true); 161 | mBinding.btnConnect.setEnabled(false); 162 | mBinding.btnClose.setEnabled(true); 163 | }); 164 | } 165 | 166 | @Override 167 | public void onClosing(WsClient client, DisConnectReason reason) { 168 | runOnUiThread(() -> mAdapter.addDataAndScroll("连接断开中...", true)); 169 | } 170 | 171 | @Override 172 | public void onDisconnect(WsClient client, DisConnectReason reason) { 173 | runOnUiThread(() -> { 174 | mAdapter.addDataAndScroll("连接已断开", true); 175 | mBinding.btnConnect.setEnabled(true); 176 | mBinding.btnClose.setEnabled(false); 177 | }); 178 | } 179 | 180 | @Override 181 | public void onError(WsClient webSocketClient, Exception ex) { 182 | LogUtils.e("客户端日志", "连接失败", ex.getMessage()); 183 | runOnUiThread(() -> mAdapter.addDataAndScroll("连接失败:" + ex.getCause(), true)); 184 | } 185 | 186 | @Override 187 | public void onMessage(WsClient webSocketClient, String message) { 188 | runOnUiThread(() -> mAdapter.addDataAndScroll(message, true)); 189 | } 190 | 191 | @Override 192 | public void onPing(WsClient webSocketClient, Framedata frameData) { 193 | LogUtils.e("客户端日志", "收到Ping"); 194 | } 195 | 196 | @Override 197 | public void onPong(WsClient webSocketClient, Framedata frameData) { 198 | LogUtils.e("客户端日志", "收到Pong"); 199 | } 200 | 201 | @Override 202 | public void onSendMessage(WsClient client, String message) { 203 | LogUtils.e("客户端日志", "发送消息", message); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /websocketlib/src/main/java/com/eurigo/websocketlib/WsClient.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketlib; 2 | 3 | import static com.eurigo.websocketlib.WsManager.DEFAULT_WEBSOCKET; 4 | 5 | import com.eurigo.websocketlib.util.ThreadUtils; 6 | import com.eurigo.websocketlib.util.WsLogUtil; 7 | 8 | import org.java_websocket.WebSocket; 9 | import org.java_websocket.client.WebSocketClient; 10 | import org.java_websocket.drafts.Draft; 11 | import org.java_websocket.drafts.Draft_6455; 12 | import org.java_websocket.framing.Framedata; 13 | import org.java_websocket.handshake.ServerHandshake; 14 | 15 | import java.net.URI; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | /** 20 | * @author Eurigo 21 | * Created on 2022/3/29 17:14 22 | * Update on 2025/3/6 23 | * desc : WebSocket客户端 24 | */ 25 | public class WsClient extends WebSocketClient { 26 | 27 | public WsClient(URI serverUri, Draft protocolDraft, Map httpHeaders, int connectTimeout 28 | , Builder builder) { 29 | super(serverUri, protocolDraft, httpHeaders, connectTimeout); 30 | if (builder.listener == null) { 31 | throw new IllegalArgumentException("WsListener must not be null"); 32 | } 33 | if (builder.serverUrl == null) { 34 | throw new IllegalArgumentException("serverUrl must not be null"); 35 | } 36 | this.listener = builder.listener; 37 | this.serverUrl = builder.serverUrl; 38 | this.draft = builder.draft; 39 | this.connectTimeout = builder.connectTimeout; 40 | this.httpHeaders = builder.httpHeaders; 41 | this.pingInterval = builder.pingInterval; 42 | this.wsKey = builder.wsKey; 43 | this.reconnectCount = builder.reconnectCount; 44 | this.reconnectInterval = builder.reconnectInterval; 45 | this.reConnectWhenNetworkAvailable = builder.reConnectWhenNetworkAvailable; 46 | setConnectionLostTimeout(pingInterval); 47 | } 48 | 49 | /** 50 | * WebSocket回调 51 | */ 52 | private final IWebSocketListener listener; 53 | 54 | /** 55 | * 服务端地址 56 | */ 57 | private final String serverUrl; 58 | 59 | /** 60 | * Websocket协议,默认6455 61 | */ 62 | private final Draft draft; 63 | 64 | /** 65 | * 连接超时时间,默认值:0 66 | */ 67 | private final int connectTimeout; 68 | 69 | /** 70 | * 初始化时设置的标识,不设置,自动使用默认WebSocket 71 | */ 72 | private final String wsKey; 73 | 74 | /** 75 | * 心跳时间,单位秒,默认60。 76 | * 如果小于等于0,则关闭心跳功能。 77 | * 如果开启,若1.5倍间隔时间未收到服务端的pong响应,则自动断开连接 78 | */ 79 | private final int pingInterval; 80 | 81 | /** 82 | * 重连次数,默认10,大于0开启重连功能 83 | */ 84 | private final int reconnectCount; 85 | 86 | /** 87 | * 自动重连间隔, 单位毫秒,默认值1000 88 | */ 89 | private final long reconnectInterval; 90 | 91 | /** 92 | * 重连任务 93 | */ 94 | private ReconnectTask task; 95 | 96 | public ReconnectTask getTask() { 97 | return task; 98 | } 99 | 100 | public synchronized void runReconnectTask() { 101 | if (WsManager.getInstance().getTaskReconnectCount() >= reconnectCount) { 102 | WsLogUtil.e("已达到最大重连次数,如需重连请调用reset"); 103 | return; 104 | } 105 | if (WsManager.getInstance().isReconnectTaskRun()){ 106 | WsLogUtil.e("重连任务已正在运行"); 107 | return; 108 | } 109 | ThreadUtils.cancel(task); 110 | task = new ReconnectTask(wsKey); 111 | task.execute(); 112 | } 113 | 114 | /** 115 | * 网络可用时是否自动重连,默认值true 116 | */ 117 | private final boolean reConnectWhenNetworkAvailable; 118 | 119 | private final Map httpHeaders; 120 | 121 | public IWebSocketListener getListener() { 122 | return listener; 123 | } 124 | 125 | public String getServerUrl() { 126 | return serverUrl; 127 | } 128 | 129 | public String getWsKey() { 130 | return wsKey; 131 | } 132 | 133 | public int getPingInterval() { 134 | return pingInterval; 135 | } 136 | 137 | public int getConnectTimeout() { 138 | return connectTimeout; 139 | } 140 | 141 | public int getReconnectCount() { 142 | return reconnectCount; 143 | } 144 | 145 | public long getReconnectInterval() { 146 | return reconnectInterval; 147 | } 148 | 149 | public boolean isReConnectWhenNetworkAvailable() { 150 | return reConnectWhenNetworkAvailable; 151 | } 152 | 153 | public Map getHttpHeaders() { 154 | return httpHeaders; 155 | } 156 | 157 | @Override 158 | public void send(String text) { 159 | super.send(text); 160 | listener.onSendMessage(this, text); 161 | } 162 | 163 | @Override 164 | public void send(byte[] data) { 165 | super.send(data); 166 | listener.onSendMessage(this, data); 167 | } 168 | 169 | @Override 170 | public void onOpen(ServerHandshake handshakedata) { 171 | ThreadUtils.cancel(task); 172 | listener.onConnected(this); 173 | } 174 | 175 | @Override 176 | public void onMessage(String message) { 177 | listener.onMessage(this, message); 178 | } 179 | 180 | @Override 181 | public void onClose(int code, String reason, boolean remote) { 182 | listener.onDisconnect(this, new DisConnectReason(code, reason, remote)); 183 | runReconnectTask(); 184 | } 185 | 186 | @Override 187 | public void onError(Exception ex) { 188 | listener.onError(this, ex); 189 | } 190 | 191 | @Override 192 | public void onWebsocketPing(WebSocket conn, Framedata frameData) { 193 | super.onWebsocketPing(conn, frameData); 194 | listener.onPing(this, frameData); 195 | } 196 | 197 | @Override 198 | public void onWebsocketPong(WebSocket conn, Framedata frameData) { 199 | listener.onPong(this, frameData); 200 | } 201 | 202 | @Override 203 | public Draft getDraft() { 204 | return draft; 205 | } 206 | 207 | public static final class Builder { 208 | 209 | private String serverUrl; 210 | 211 | private IWebSocketListener listener; 212 | 213 | private String wsKey = DEFAULT_WEBSOCKET; 214 | 215 | private Draft draft = new Draft_6455(); 216 | 217 | private int connectTimeout = 0; 218 | 219 | private int pingInterval = 60; 220 | 221 | private int reconnectCount = 10; 222 | 223 | private long reconnectInterval = 1000; 224 | 225 | private boolean reConnectWhenNetworkAvailable = true; 226 | 227 | private Map httpHeaders = new HashMap<>(); 228 | 229 | public Builder setServerUrl(String serverUrl) { 230 | this.serverUrl = serverUrl; 231 | return this; 232 | } 233 | 234 | public Builder setListener(IWebSocketListener listener) { 235 | this.listener = listener; 236 | return this; 237 | } 238 | 239 | public Builder setDraft(Draft draft) { 240 | this.draft = draft; 241 | return this; 242 | } 243 | 244 | public Builder setWsKey(String wsKey) { 245 | this.wsKey = wsKey; 246 | return this; 247 | } 248 | 249 | public Builder setConnectTimeout(int connectTimeout) { 250 | this.connectTimeout = connectTimeout; 251 | return this; 252 | } 253 | 254 | public Builder setPingInterval(int pingInterval) { 255 | this.pingInterval = pingInterval; 256 | return this; 257 | } 258 | 259 | public Builder setReconnectCount(int reconnectCount) { 260 | this.reconnectCount = reconnectCount; 261 | return this; 262 | } 263 | 264 | public Builder setReconnectInterval(long reconnectInterval) { 265 | this.reconnectInterval = reconnectInterval; 266 | return this; 267 | } 268 | 269 | public Builder setHttpHeaders(Map httpHeaders) { 270 | this.httpHeaders = httpHeaders; 271 | return this; 272 | } 273 | 274 | public Builder setReConnectWhenNetworkAvailable(boolean reConnectWhenNetworkAvailable) { 275 | this.reConnectWhenNetworkAvailable = reConnectWhenNetworkAvailable; 276 | return this; 277 | } 278 | 279 | public WsClient build() { 280 | return new WsClient(URI.create(serverUrl), draft 281 | , httpHeaders, connectTimeout, this); 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /websocketlib/src/main/java/com/eurigo/websocketlib/WsManager.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketlib; 2 | 3 | import static android.Manifest.permission.ACCESS_NETWORK_STATE; 4 | 5 | import android.Manifest; 6 | import android.content.Context; 7 | import android.content.pm.PackageManager; 8 | import android.net.ConnectivityManager; 9 | import android.net.Network; 10 | import android.net.NetworkCapabilities; 11 | import android.net.NetworkRequest; 12 | import android.os.Build; 13 | 14 | import androidx.annotation.NonNull; 15 | 16 | import com.eurigo.websocketlib.util.AppUtils; 17 | import com.eurigo.websocketlib.util.ThreadUtils; 18 | import com.eurigo.websocketlib.util.WsLogUtil; 19 | 20 | import org.java_websocket.WebSocket; 21 | import org.java_websocket.handshake.ClientHandshake; 22 | import org.java_websocket.server.WebSocketServer; 23 | 24 | import java.net.BindException; 25 | import java.net.InetSocketAddress; 26 | import java.util.concurrent.ConcurrentHashMap; 27 | import java.util.concurrent.atomic.AtomicInteger; 28 | 29 | /** 30 | * @author Eurigo 31 | * Created on 2022/3/29 9:58 32 | * desc : WebSocket管理器 33 | */ 34 | public class WsManager { 35 | 36 | /** 37 | * 所有WebSocket的合集 38 | */ 39 | private final ConcurrentHashMap clientMap = new ConcurrentHashMap<>(); 40 | 41 | public ConcurrentHashMap getClientMap() { 42 | return clientMap; 43 | } 44 | 45 | public static final String DEFAULT_WEBSOCKET = "DEFAULT_WEBSOCKET"; 46 | public static final String NO_INIT = "没有初始化"; 47 | 48 | private WebSocketServer webSocketServer; 49 | 50 | private ConnectivityManager connectivityManager; 51 | private ConnectivityManager.NetworkCallback networkCallback; 52 | private boolean isNetworkAvailable; 53 | 54 | private boolean isReconnectTaskRun = false; 55 | 56 | private int attempt = 0; 57 | 58 | private AtomicInteger taskReconnectCount = new AtomicInteger(0); 59 | 60 | private ReconnectGuardianTask guardianTask; 61 | 62 | /** 63 | * 重连保护进程的间隔,单位秒,默认60 64 | */ 65 | private int guardianTaskInterval = 60; 66 | 67 | /** 68 | * 设置重连保护的间隔并启动保护任务 69 | * 70 | * @param guardianTaskInterval 保护检测间隔,单位秒,要求大于0且大于【2* reconnectCount * reconnectInterval】 71 | */ 72 | public void startGuardianTaskInterval(int guardianTaskInterval) { 73 | this.guardianTaskInterval = guardianTaskInterval; 74 | ThreadUtils.cancel(guardianTask); 75 | guardianTask = new ReconnectGuardianTask(); 76 | guardianTask.execute(); 77 | 78 | } 79 | 80 | public int getGuardianTaskInterval() { 81 | return guardianTaskInterval; 82 | } 83 | 84 | public WebSocketServer getWebSocketServer() { 85 | return webSocketServer; 86 | } 87 | 88 | public synchronized void startWsServer(InetSocketAddress address, IWebSocketServerListener listener) { 89 | attempt = 0; 90 | webSocketServer = new WebSocketServer(address) { 91 | @Override 92 | public void onOpen(WebSocket conn, ClientHandshake handshake) { 93 | listener.onWsOpen(conn, handshake); 94 | } 95 | 96 | @Override 97 | public void onClose(WebSocket conn, int code, String reason, boolean remote) { 98 | listener.onWsClose(conn, code, reason, remote); 99 | } 100 | 101 | @Override 102 | public void onMessage(WebSocket conn, String message) { 103 | listener.onWsMessage(conn, message); 104 | } 105 | 106 | @Override 107 | public void onError(WebSocket conn, Exception ex) { 108 | if (ex instanceof BindException) { 109 | attempt++; 110 | WsLogUtil.e("端口被占用, 尝试端口:" + (address.getPort() + attempt)); 111 | } 112 | listener.onWsError(conn, ex); 113 | } 114 | 115 | @Override 116 | public void onStart() { 117 | listener.onWsStart(webSocketServer); 118 | } 119 | }; 120 | webSocketServer.setReuseAddr(true); 121 | webSocketServer.start(); 122 | // 添加JVM关闭钩子,当应用退出时,关闭WebSocket服务 123 | Runtime.getRuntime().addShutdownHook(new Thread(() -> WsManager.getInstance().stopWsServer())); 124 | } 125 | 126 | public synchronized void stopWsServer() { 127 | if (webSocketServer != null) { 128 | try { 129 | webSocketServer.stop(); 130 | } catch (InterruptedException e) { 131 | WsLogUtil.e(e.getMessage()); 132 | } 133 | webSocketServer = null; 134 | } 135 | } 136 | 137 | /** 138 | * 设置重连次数 139 | */ 140 | public void setTaskReconnectCount(int taskReconnectCount) { 141 | this.taskReconnectCount = new AtomicInteger(taskReconnectCount); 142 | } 143 | 144 | /** 145 | * 获取重连次数 146 | */ 147 | public int getTaskReconnectCount() { 148 | return taskReconnectCount.get(); 149 | } 150 | 151 | /** 152 | * 重置重连次数 153 | */ 154 | public synchronized void resetTaskReconnectCount() { 155 | taskReconnectCount = new AtomicInteger(0); 156 | } 157 | 158 | public synchronized void setReconnectTaskRun(boolean reconnectTaskRun) { 159 | isReconnectTaskRun = reconnectTaskRun; 160 | } 161 | 162 | public synchronized boolean isReconnectTaskRun() { 163 | return isReconnectTaskRun; 164 | } 165 | 166 | /** 167 | * 获取连接池中最大的连接次数 168 | */ 169 | public int getMaxReconnectCount() { 170 | int tempCount = 0; 171 | for (WsClient ws : clientMap.values()) { 172 | if (ws.getReconnectCount() > tempCount) { 173 | tempCount = ws.getReconnectCount(); 174 | } 175 | } 176 | return tempCount; 177 | } 178 | 179 | public long getMaxReconnectInterval() { 180 | long tempInterval = 0; 181 | for (WsClient ws : clientMap.values()) { 182 | if (ws.getReconnectInterval() > tempInterval) { 183 | tempInterval = ws.getReconnectInterval(); 184 | } 185 | } 186 | return tempInterval; 187 | } 188 | 189 | public WsManager() { 190 | } 191 | 192 | public static WsManager getInstance() { 193 | return SingletonHelper.INSTANCE; 194 | } 195 | 196 | private static class SingletonHelper { 197 | private final static WsManager INSTANCE = new WsManager(); 198 | } 199 | 200 | public boolean isNetworkAvailable() { 201 | return isNetworkAvailable; 202 | } 203 | 204 | public void updateNetworkAvailable(Network network) { 205 | NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network); 206 | isNetworkAvailable = networkCapabilities != null && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 207 | } 208 | 209 | /** 210 | * 注册网络变化监听广播,网络由不可用变为可用时会重新连接 WebSocket 211 | * 调用后会立即触发一次OnReceive 212 | */ 213 | public void registerNetworkChangedCallback() { 214 | if (networkCallback != null) { 215 | WsLogUtil.e("网络状态监听已注册"); 216 | return; 217 | } 218 | if (!checkPermission()) { 219 | WsLogUtil.e("未获取到网络状态权限,广播监听器无法注册"); 220 | return; 221 | } 222 | connectivityManager = (ConnectivityManager) AppUtils 223 | .getInstance() 224 | .getApp() 225 | .getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE); 226 | NetworkRequest.Builder builder = new NetworkRequest.Builder(); 227 | networkCallback = new ConnectivityManager.NetworkCallback() { 228 | @Override 229 | public void onAvailable(@NonNull Network network) { 230 | updateNetworkAvailable(network); 231 | for (WsClient ws : WsManager.getInstance().getClientMap().values()) { 232 | if (ws.isReConnectWhenNetworkAvailable() && !ws.isOpen() && !WsManager.getInstance().isReconnectTaskRun()) { 233 | ws.runReconnectTask(); 234 | } 235 | } 236 | WsLogUtil.e("网络状态:" + isNetworkAvailable); 237 | } 238 | 239 | @Override 240 | public void onLost(@NonNull Network network) { 241 | updateNetworkAvailable(network); 242 | WsLogUtil.e("网络状态:" + isNetworkAvailable); 243 | } 244 | }; 245 | connectivityManager.registerNetworkCallback(builder.build(), networkCallback); 246 | // 添加JVM关闭钩子,当应用退出时,关闭WebSocket服务 247 | Runtime.getRuntime().addShutdownHook(new Thread(() -> WsManager.getInstance().destroy())); 248 | } 249 | 250 | /** 251 | * 解除网络状态广播 252 | */ 253 | private void unRegisterNetworkChangedCallback() { 254 | if (networkCallback == null) { 255 | WsLogUtil.d("网络状态广播未注册"); 256 | return; 257 | } 258 | connectivityManager.unregisterNetworkCallback(networkCallback); 259 | networkCallback = null; 260 | } 261 | 262 | /** 263 | * 判断是否有网络权限{@link Manifest.permission#ACCESS_NETWORK_STATE} 264 | */ 265 | private boolean checkPermission() { 266 | Context context = AppUtils.getInstance().getApp().getApplicationContext(); 267 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 268 | return PackageManager.PERMISSION_GRANTED == context.getPackageManager() 269 | .checkPermission(ACCESS_NETWORK_STATE, context.getPackageName()); 270 | } 271 | return true; 272 | } 273 | 274 | private void addClient(WsClient wsClient) { 275 | // 移除旧的WebSocket,再重新添加 276 | if (clientMap.containsKey(wsClient.getWsKey())) { 277 | WsClient oldClient = clientMap.get(wsClient.getWsKey()); 278 | if (oldClient != null) { 279 | oldClient.closeConnection(-1, "addClient, close old"); 280 | } else { 281 | WsLogUtil.e("Tried to close old connection, but it was null"); 282 | } 283 | } 284 | clientMap.put(wsClient.getWsKey(), wsClient); 285 | } 286 | 287 | /** 288 | * 是否关闭日志输出,默认开启 289 | * 290 | * @param isClose 是否关闭 291 | */ 292 | public WsManager closeLog(boolean isClose) { 293 | WsLogUtil.closeLog(isClose); 294 | return this; 295 | } 296 | 297 | /** 298 | * 获取默认的WebSocket 299 | */ 300 | public WsManager init(WsClient wsClient) { 301 | addClient(wsClient); 302 | return this; 303 | } 304 | 305 | /** 306 | * 开始执行连接,每个WebSocket都会创建对应的重连任务 307 | */ 308 | public void start() { 309 | registerNetworkChangedCallback(); 310 | try { 311 | for (WsClient ws : clientMap.values()) { 312 | if (ws.isOpen()) { 313 | WsLogUtil.e("请勿重复连接, key = " + ws.getWsKey()); 314 | continue; 315 | } 316 | if (ws.isClosed()) { 317 | ws = reCreateClient(ws); 318 | clientMap.put(ws.getWsKey(), ws); 319 | ws.connect(); 320 | } else { 321 | ws.connectBlocking(); 322 | } 323 | } 324 | } catch (Exception e) { 325 | WsLogUtil.e(e.getMessage()); 326 | } 327 | } 328 | 329 | public void safeConnect(WsClient ws) { 330 | if (ws.isOpen()) { 331 | WsLogUtil.e("请勿重复连接, key = " + ws.getWsKey()); 332 | return; 333 | } 334 | if (ws.isClosed()) { 335 | ws = reCreateClient(ws); 336 | clientMap.put(ws.getWsKey(), ws); 337 | } 338 | try { 339 | ws.connect(); 340 | } catch (IllegalStateException e) { 341 | if (e.getMessage() != null && e.getMessage().contains("WebSocketClient objects are not reuseable")) { 342 | ws = reCreateClient(ws); 343 | clientMap.put(ws.getWsKey(), ws); 344 | ws.connect(); 345 | } 346 | } catch (Exception e) { 347 | WsLogUtil.e(e.getMessage()); 348 | } 349 | } 350 | 351 | private WsClient reCreateClient(WsClient oldWsClient) { 352 | return new WsClient.Builder() 353 | .setServerUrl(oldWsClient.getServerUrl()) 354 | .setWsKey(oldWsClient.getWsKey()) 355 | .setPingInterval(oldWsClient.getPingInterval()) 356 | .setDraft(oldWsClient.getDraft()) 357 | .setHttpHeaders(oldWsClient.getHttpHeaders()) 358 | .setConnectTimeout(oldWsClient.getConnectTimeout()) 359 | .setReconnectCount(oldWsClient.getReconnectCount()) 360 | .setReconnectInterval(oldWsClient.getReconnectInterval()) 361 | .setReConnectWhenNetworkAvailable(oldWsClient.isReConnectWhenNetworkAvailable()) 362 | .setListener(oldWsClient.getListener()) 363 | .build(); 364 | } 365 | 366 | public WsClient getDefault() { 367 | return getWsClient(DEFAULT_WEBSOCKET); 368 | } 369 | 370 | public WsClient getWsClient(String wsKey) { 371 | if (!clientMap.containsKey(wsKey)) { 372 | WsLogUtil.e(NO_INIT + wsKey); 373 | return null; 374 | } 375 | return clientMap.get(wsKey); 376 | } 377 | 378 | /** 379 | * 使用默认WebSocket发送信息 380 | * 381 | * @param message 消息 382 | */ 383 | public void send(String message) { 384 | send(DEFAULT_WEBSOCKET, message); 385 | } 386 | 387 | /** 388 | * 使用指定的WebSocket发送信息 389 | * 390 | * @param wsKey webSocket Key 391 | * @param message 消息 392 | */ 393 | public void send(String wsKey, String message) { 394 | getWsClient(wsKey).send(message); 395 | } 396 | 397 | /** 398 | * 使用默认WebSocket发送ping 399 | */ 400 | public void sendPing() { 401 | send(DEFAULT_WEBSOCKET); 402 | } 403 | 404 | /** 405 | * 使用指定的WebSocket发送ping 406 | * 407 | * @param wsKey webSocket Key 408 | */ 409 | public void sendPing(String wsKey) { 410 | getWsClient(wsKey).sendPing(); 411 | } 412 | 413 | /** 414 | * @return 默认WebSocket是否连接 415 | */ 416 | public boolean isConnected() { 417 | if (getDefault() == null) { 418 | return false; 419 | } 420 | return isConnected(DEFAULT_WEBSOCKET); 421 | } 422 | 423 | /** 424 | * @param wsKey wsKey 425 | * @return WebSocket是否连接 426 | */ 427 | public boolean isConnected(String wsKey) { 428 | if (getWsClient(wsKey) == null) { 429 | return false; 430 | } 431 | return getWsClient(wsKey).isOpen(); 432 | } 433 | 434 | /** 435 | * 断开默认WebSocket连接 436 | */ 437 | public void disConnect() { 438 | disConnect(DEFAULT_WEBSOCKET); 439 | } 440 | 441 | /** 442 | * 断开指定WebSocket 443 | * 444 | * @param wsKey wsKey 445 | */ 446 | public void disConnect(String wsKey) { 447 | getWsClient(wsKey).close(); 448 | } 449 | 450 | /** 451 | * 销毁资源, 销毁后Websocket需要重新初始化 452 | */ 453 | public void destroy() { 454 | // 解除广播 455 | unRegisterNetworkChangedCallback(); 456 | if (webSocketServer != null){ 457 | webSocketServer = null; 458 | } 459 | // 关闭连接 460 | for (WsClient ws : clientMap.values()) { 461 | if (!ws.isFlushAndClose()) { 462 | ws.closeConnection(-1, "destroy"); 463 | } 464 | } 465 | clientMap.clear(); 466 | } 467 | 468 | } 469 | -------------------------------------------------------------------------------- /websocketlib/src/main/java/com/eurigo/websocketlib/util/ThreadUtils.java: -------------------------------------------------------------------------------- 1 | package com.eurigo.websocketlib.util; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.util.Log; 6 | 7 | import androidx.annotation.CallSuper; 8 | import androidx.annotation.IntRange; 9 | import androidx.annotation.NonNull; 10 | 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Timer; 15 | import java.util.TimerTask; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | import java.util.concurrent.CountDownLatch; 18 | import java.util.concurrent.Executor; 19 | import java.util.concurrent.ExecutorService; 20 | import java.util.concurrent.LinkedBlockingQueue; 21 | import java.util.concurrent.RejectedExecutionException; 22 | import java.util.concurrent.ThreadFactory; 23 | import java.util.concurrent.ThreadPoolExecutor; 24 | import java.util.concurrent.TimeUnit; 25 | import java.util.concurrent.atomic.AtomicBoolean; 26 | import java.util.concurrent.atomic.AtomicInteger; 27 | import java.util.concurrent.atomic.AtomicLong; 28 | 29 | /** 30 | *
  31 |  *     author: Blankj
  32 |  *     blog  : http://blankj.com
  33 |  *     time  : 2018/05/08
  34 |  *     desc  : utils about thread
  35 |  * 
36 | */ 37 | public final class ThreadUtils { 38 | 39 | private static final Handler HANDLER = new Handler(Looper.getMainLooper()); 40 | 41 | private static final Map> TYPE_PRIORITY_POOLS = new HashMap<>(); 42 | 43 | private static final Map TASK_POOL_MAP = new ConcurrentHashMap<>(); 44 | 45 | private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); 46 | private static final Timer TIMER = new Timer(); 47 | 48 | private static final byte TYPE_SINGLE = -1; 49 | private static final byte TYPE_CACHED = -2; 50 | private static final byte TYPE_IO = -4; 51 | private static final byte TYPE_CPU = -8; 52 | 53 | private static Executor sDeliver; 54 | 55 | /** 56 | * Return whether the thread is the main thread. 57 | * 58 | * @return {@code true}: yes
{@code false}: no 59 | */ 60 | public static boolean isMainThread() { 61 | return Looper.myLooper() == Looper.getMainLooper(); 62 | } 63 | 64 | public static Handler getMainHandler() { 65 | return HANDLER; 66 | } 67 | 68 | public static void runOnUiThread(final Runnable runnable) { 69 | if (Looper.myLooper() == Looper.getMainLooper()) { 70 | runnable.run(); 71 | } else { 72 | HANDLER.post(runnable); 73 | } 74 | } 75 | 76 | public static void runOnUiThreadDelayed(final Runnable runnable, long delayMillis) { 77 | HANDLER.postDelayed(runnable, delayMillis); 78 | } 79 | 80 | /** 81 | * Return a thread pool that reuses a fixed number of threads 82 | * operating off a shared unbounded queue, using the provided 83 | * ThreadFactory to create new threads when needed. 84 | * 85 | * @param size The size of thread in the pool. 86 | * @return a fixed thread pool 87 | */ 88 | public static ExecutorService getFixedPool(@IntRange(from = 1) final int size) { 89 | return getPoolByTypeAndPriority(size); 90 | } 91 | 92 | /** 93 | * Return a thread pool that reuses a fixed number of threads 94 | * operating off a shared unbounded queue, using the provided 95 | * ThreadFactory to create new threads when needed. 96 | * 97 | * @param size The size of thread in the pool. 98 | * @param priority The priority of thread in the poll. 99 | * @return a fixed thread pool 100 | */ 101 | public static ExecutorService getFixedPool(@IntRange(from = 1) final int size, 102 | @IntRange(from = 1, to = 10) final int priority) { 103 | return getPoolByTypeAndPriority(size, priority); 104 | } 105 | 106 | /** 107 | * Return a thread pool that uses a single worker thread operating 108 | * off an unbounded queue, and uses the provided ThreadFactory to 109 | * create a new thread when needed. 110 | * 111 | * @return a single thread pool 112 | */ 113 | public static ExecutorService getSinglePool() { 114 | return getPoolByTypeAndPriority(TYPE_SINGLE); 115 | } 116 | 117 | /** 118 | * Return a thread pool that uses a single worker thread operating 119 | * off an unbounded queue, and uses the provided ThreadFactory to 120 | * create a new thread when needed. 121 | * 122 | * @param priority The priority of thread in the poll. 123 | * @return a single thread pool 124 | */ 125 | public static ExecutorService getSinglePool(@IntRange(from = 1, to = 10) final int priority) { 126 | return getPoolByTypeAndPriority(TYPE_SINGLE, priority); 127 | } 128 | 129 | /** 130 | * Return a thread pool that creates new threads as needed, but 131 | * will reuse previously constructed threads when they are 132 | * available. 133 | * 134 | * @return a cached thread pool 135 | */ 136 | public static ExecutorService getCachedPool() { 137 | return getPoolByTypeAndPriority(TYPE_CACHED); 138 | } 139 | 140 | /** 141 | * Return a thread pool that creates new threads as needed, but 142 | * will reuse previously constructed threads when they are 143 | * available. 144 | * 145 | * @param priority The priority of thread in the poll. 146 | * @return a cached thread pool 147 | */ 148 | public static ExecutorService getCachedPool(@IntRange(from = 1, to = 10) final int priority) { 149 | return getPoolByTypeAndPriority(TYPE_CACHED, priority); 150 | } 151 | 152 | /** 153 | * Return a thread pool that creates (2 * CPU_COUNT + 1) threads 154 | * operating off a queue which size is 128. 155 | * 156 | * @return a IO thread pool 157 | */ 158 | public static ExecutorService getIoPool() { 159 | return getPoolByTypeAndPriority(TYPE_IO); 160 | } 161 | 162 | /** 163 | * Return a thread pool that creates (2 * CPU_COUNT + 1) threads 164 | * operating off a queue which size is 128. 165 | * 166 | * @param priority The priority of thread in the poll. 167 | * @return a IO thread pool 168 | */ 169 | public static ExecutorService getIoPool(@IntRange(from = 1, to = 10) final int priority) { 170 | return getPoolByTypeAndPriority(TYPE_IO, priority); 171 | } 172 | 173 | /** 174 | * Return a thread pool that creates (CPU_COUNT + 1) threads 175 | * operating off a queue which size is 128 and the maximum 176 | * number of threads equals (2 * CPU_COUNT + 1). 177 | * 178 | * @return a cpu thread pool for 179 | */ 180 | public static ExecutorService getCpuPool() { 181 | return getPoolByTypeAndPriority(TYPE_CPU); 182 | } 183 | 184 | /** 185 | * Return a thread pool that creates (CPU_COUNT + 1) threads 186 | * operating off a queue which size is 128 and the maximum 187 | * number of threads equals (2 * CPU_COUNT + 1). 188 | * 189 | * @param priority The priority of thread in the poll. 190 | * @return a cpu thread pool for 191 | */ 192 | public static ExecutorService getCpuPool(@IntRange(from = 1, to = 10) final int priority) { 193 | return getPoolByTypeAndPriority(TYPE_CPU, priority); 194 | } 195 | 196 | /** 197 | * Executes the given task in a fixed thread pool. 198 | * 199 | * @param size The size of thread in the fixed thread pool. 200 | * @param task The task to execute. 201 | * @param The type of the task's result. 202 | */ 203 | public static void executeByFixed(@IntRange(from = 1) final int size, final Task task) { 204 | execute(getPoolByTypeAndPriority(size), task); 205 | } 206 | 207 | /** 208 | * Executes the given task in a fixed thread pool. 209 | * 210 | * @param size The size of thread in the fixed thread pool. 211 | * @param task The task to execute. 212 | * @param priority The priority of thread in the poll. 213 | * @param The type of the task's result. 214 | */ 215 | public static void executeByFixed(@IntRange(from = 1) final int size, 216 | final Task task, 217 | @IntRange(from = 1, to = 10) final int priority) { 218 | execute(getPoolByTypeAndPriority(size, priority), task); 219 | } 220 | 221 | /** 222 | * Executes the given task in a fixed thread pool after the given delay. 223 | * 224 | * @param size The size of thread in the fixed thread pool. 225 | * @param task The task to execute. 226 | * @param delay The time from now to delay execution. 227 | * @param unit The time unit of the delay parameter. 228 | * @param The type of the task's result. 229 | */ 230 | public static void executeByFixedWithDelay(@IntRange(from = 1) final int size, 231 | final Task task, 232 | final long delay, 233 | final TimeUnit unit) { 234 | executeWithDelay(getPoolByTypeAndPriority(size), task, delay, unit); 235 | } 236 | 237 | /** 238 | * Executes the given task in a fixed thread pool after the given delay. 239 | * 240 | * @param size The size of thread in the fixed thread pool. 241 | * @param task The task to execute. 242 | * @param delay The time from now to delay execution. 243 | * @param unit The time unit of the delay parameter. 244 | * @param priority The priority of thread in the poll. 245 | * @param The type of the task's result. 246 | */ 247 | public static void executeByFixedWithDelay(@IntRange(from = 1) final int size, 248 | final Task task, 249 | final long delay, 250 | final TimeUnit unit, 251 | @IntRange(from = 1, to = 10) final int priority) { 252 | executeWithDelay(getPoolByTypeAndPriority(size, priority), task, delay, unit); 253 | } 254 | 255 | /** 256 | * Executes the given task in a fixed thread pool at fix rate. 257 | * 258 | * @param size The size of thread in the fixed thread pool. 259 | * @param task The task to execute. 260 | * @param period The period between successive executions. 261 | * @param unit The time unit of the period parameter. 262 | * @param The type of the task's result. 263 | */ 264 | public static void executeByFixedAtFixRate(@IntRange(from = 1) final int size, 265 | final Task task, 266 | final long period, 267 | final TimeUnit unit) { 268 | executeAtFixedRate(getPoolByTypeAndPriority(size), task, 0, period, unit); 269 | } 270 | 271 | /** 272 | * Executes the given task in a fixed thread pool at fix rate. 273 | * 274 | * @param size The size of thread in the fixed thread pool. 275 | * @param task The task to execute. 276 | * @param period The period between successive executions. 277 | * @param unit The time unit of the period parameter. 278 | * @param priority The priority of thread in the poll. 279 | * @param The type of the task's result. 280 | */ 281 | public static void executeByFixedAtFixRate(@IntRange(from = 1) final int size, 282 | final Task task, 283 | final long period, 284 | final TimeUnit unit, 285 | @IntRange(from = 1, to = 10) final int priority) { 286 | executeAtFixedRate(getPoolByTypeAndPriority(size, priority), task, 0, period, unit); 287 | } 288 | 289 | /** 290 | * Executes the given task in a fixed thread pool at fix rate. 291 | * 292 | * @param size The size of thread in the fixed thread pool. 293 | * @param task The task to execute. 294 | * @param initialDelay The time to delay first execution. 295 | * @param period The period between successive executions. 296 | * @param unit The time unit of the initialDelay and period parameters. 297 | * @param The type of the task's result. 298 | */ 299 | public static void executeByFixedAtFixRate(@IntRange(from = 1) final int size, 300 | final Task task, 301 | long initialDelay, 302 | final long period, 303 | final TimeUnit unit) { 304 | executeAtFixedRate(getPoolByTypeAndPriority(size), task, initialDelay, period, unit); 305 | } 306 | 307 | /** 308 | * Executes the given task in a fixed thread pool at fix rate. 309 | * 310 | * @param size The size of thread in the fixed thread pool. 311 | * @param task The task to execute. 312 | * @param initialDelay The time to delay first execution. 313 | * @param period The period between successive executions. 314 | * @param unit The time unit of the initialDelay and period parameters. 315 | * @param priority The priority of thread in the poll. 316 | * @param The type of the task's result. 317 | */ 318 | public static void executeByFixedAtFixRate(@IntRange(from = 1) final int size, 319 | final Task task, 320 | long initialDelay, 321 | final long period, 322 | final TimeUnit unit, 323 | @IntRange(from = 1, to = 10) final int priority) { 324 | executeAtFixedRate(getPoolByTypeAndPriority(size, priority), task, initialDelay, period, unit); 325 | } 326 | 327 | /** 328 | * Executes the given task in a single thread pool. 329 | * 330 | * @param task The task to execute. 331 | * @param The type of the task's result. 332 | */ 333 | public static void executeBySingle(final Task task) { 334 | execute(getPoolByTypeAndPriority(TYPE_SINGLE), task); 335 | } 336 | 337 | /** 338 | * Executes the given task in a single thread pool. 339 | * 340 | * @param task The task to execute. 341 | * @param priority The priority of thread in the poll. 342 | * @param The type of the task's result. 343 | */ 344 | public static void executeBySingle(final Task task, 345 | @IntRange(from = 1, to = 10) final int priority) { 346 | execute(getPoolByTypeAndPriority(TYPE_SINGLE, priority), task); 347 | } 348 | 349 | /** 350 | * Executes the given task in a single thread pool after the given delay. 351 | * 352 | * @param task The task to execute. 353 | * @param delay The time from now to delay execution. 354 | * @param unit The time unit of the delay parameter. 355 | * @param The type of the task's result. 356 | */ 357 | public static void executeBySingleWithDelay(final Task task, 358 | final long delay, 359 | final TimeUnit unit) { 360 | executeWithDelay(getPoolByTypeAndPriority(TYPE_SINGLE), task, delay, unit); 361 | } 362 | 363 | /** 364 | * Executes the given task in a single thread pool after the given delay. 365 | * 366 | * @param task The task to execute. 367 | * @param delay The time from now to delay execution. 368 | * @param unit The time unit of the delay parameter. 369 | * @param priority The priority of thread in the poll. 370 | * @param The type of the task's result. 371 | */ 372 | public static void executeBySingleWithDelay(final Task task, 373 | final long delay, 374 | final TimeUnit unit, 375 | @IntRange(from = 1, to = 10) final int priority) { 376 | executeWithDelay(getPoolByTypeAndPriority(TYPE_SINGLE, priority), task, delay, unit); 377 | } 378 | 379 | /** 380 | * Executes the given task in a single thread pool at fix rate. 381 | * 382 | * @param task The task to execute. 383 | * @param period The period between successive executions. 384 | * @param unit The time unit of the period parameter. 385 | * @param The type of the task's result. 386 | */ 387 | public static void executeBySingleAtFixRate(final Task task, 388 | final long period, 389 | final TimeUnit unit) { 390 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_SINGLE), task, 0, period, unit); 391 | } 392 | 393 | /** 394 | * Executes the given task in a single thread pool at fix rate. 395 | * 396 | * @param task The task to execute. 397 | * @param period The period between successive executions. 398 | * @param unit The time unit of the period parameter. 399 | * @param priority The priority of thread in the poll. 400 | * @param The type of the task's result. 401 | */ 402 | public static void executeBySingleAtFixRate(final Task task, 403 | final long period, 404 | final TimeUnit unit, 405 | @IntRange(from = 1, to = 10) final int priority) { 406 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_SINGLE, priority), task, 0, period, unit); 407 | } 408 | 409 | /** 410 | * Executes the given task in a single thread pool at fix rate. 411 | * 412 | * @param task The task to execute. 413 | * @param initialDelay The time to delay first execution. 414 | * @param period The period between successive executions. 415 | * @param unit The time unit of the initialDelay and period parameters. 416 | * @param The type of the task's result. 417 | */ 418 | public static void executeBySingleAtFixRate(final Task task, 419 | long initialDelay, 420 | final long period, 421 | final TimeUnit unit) { 422 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_SINGLE), task, initialDelay, period, unit); 423 | } 424 | 425 | /** 426 | * Executes the given task in a single thread pool at fix rate. 427 | * 428 | * @param task The task to execute. 429 | * @param initialDelay The time to delay first execution. 430 | * @param period The period between successive executions. 431 | * @param unit The time unit of the initialDelay and period parameters. 432 | * @param priority The priority of thread in the poll. 433 | * @param The type of the task's result. 434 | */ 435 | public static void executeBySingleAtFixRate(final Task task, 436 | long initialDelay, 437 | final long period, 438 | final TimeUnit unit, 439 | @IntRange(from = 1, to = 10) final int priority) { 440 | executeAtFixedRate( 441 | getPoolByTypeAndPriority(TYPE_SINGLE, priority), task, initialDelay, period, unit 442 | ); 443 | } 444 | 445 | /** 446 | * Executes the given task in a cached thread pool. 447 | * 448 | * @param task The task to execute. 449 | * @param The type of the task's result. 450 | */ 451 | public static void executeByCached(final Task task) { 452 | execute(getPoolByTypeAndPriority(TYPE_CACHED), task); 453 | } 454 | 455 | /** 456 | * Executes the given task in a cached thread pool. 457 | * 458 | * @param task The task to execute. 459 | * @param priority The priority of thread in the poll. 460 | * @param The type of the task's result. 461 | */ 462 | public static void executeByCached(final Task task, 463 | @IntRange(from = 1, to = 10) final int priority) { 464 | execute(getPoolByTypeAndPriority(TYPE_CACHED, priority), task); 465 | } 466 | 467 | /** 468 | * Executes the given task in a cached thread pool after the given delay. 469 | * 470 | * @param task The task to execute. 471 | * @param delay The time from now to delay execution. 472 | * @param unit The time unit of the delay parameter. 473 | * @param The type of the task's result. 474 | */ 475 | public static void executeByCachedWithDelay(final Task task, 476 | final long delay, 477 | final TimeUnit unit) { 478 | executeWithDelay(getPoolByTypeAndPriority(TYPE_CACHED), task, delay, unit); 479 | } 480 | 481 | /** 482 | * Executes the given task in a cached thread pool after the given delay. 483 | * 484 | * @param task The task to execute. 485 | * @param delay The time from now to delay execution. 486 | * @param unit The time unit of the delay parameter. 487 | * @param priority The priority of thread in the poll. 488 | * @param The type of the task's result. 489 | */ 490 | public static void executeByCachedWithDelay(final Task task, 491 | final long delay, 492 | final TimeUnit unit, 493 | @IntRange(from = 1, to = 10) final int priority) { 494 | executeWithDelay(getPoolByTypeAndPriority(TYPE_CACHED, priority), task, delay, unit); 495 | } 496 | 497 | /** 498 | * Executes the given task in a cached thread pool at fix rate. 499 | * 500 | * @param task The task to execute. 501 | * @param period The period between successive executions. 502 | * @param unit The time unit of the period parameter. 503 | * @param The type of the task's result. 504 | */ 505 | public static void executeByCachedAtFixRate(final Task task, 506 | final long period, 507 | final TimeUnit unit) { 508 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CACHED), task, 0, period, unit); 509 | } 510 | 511 | /** 512 | * Executes the given task in a cached thread pool at fix rate. 513 | * 514 | * @param task The task to execute. 515 | * @param period The period between successive executions. 516 | * @param unit The time unit of the period parameter. 517 | * @param priority The priority of thread in the poll. 518 | * @param The type of the task's result. 519 | */ 520 | public static void executeByCachedAtFixRate(final Task task, 521 | final long period, 522 | final TimeUnit unit, 523 | @IntRange(from = 1, to = 10) final int priority) { 524 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CACHED, priority), task, 0, period, unit); 525 | } 526 | 527 | /** 528 | * Executes the given task in a cached thread pool at fix rate. 529 | * 530 | * @param task The task to execute. 531 | * @param initialDelay The time to delay first execution. 532 | * @param period The period between successive executions. 533 | * @param unit The time unit of the initialDelay and period parameters. 534 | * @param The type of the task's result. 535 | */ 536 | public static void executeByCachedAtFixRate(final Task task, 537 | long initialDelay, 538 | final long period, 539 | final TimeUnit unit) { 540 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CACHED), task, initialDelay, period, unit); 541 | } 542 | 543 | /** 544 | * Executes the given task in a cached thread pool at fix rate. 545 | * 546 | * @param task The task to execute. 547 | * @param initialDelay The time to delay first execution. 548 | * @param period The period between successive executions. 549 | * @param unit The time unit of the initialDelay and period parameters. 550 | * @param priority The priority of thread in the poll. 551 | * @param The type of the task's result. 552 | */ 553 | public static void executeByCachedAtFixRate(final Task task, 554 | long initialDelay, 555 | final long period, 556 | final TimeUnit unit, 557 | @IntRange(from = 1, to = 10) final int priority) { 558 | executeAtFixedRate( 559 | getPoolByTypeAndPriority(TYPE_CACHED, priority), task, initialDelay, period, unit 560 | ); 561 | } 562 | 563 | /** 564 | * Executes the given task in an IO thread pool. 565 | * 566 | * @param task The task to execute. 567 | * @param The type of the task's result. 568 | */ 569 | public static void executeByIo(final Task task) { 570 | execute(getPoolByTypeAndPriority(TYPE_IO), task); 571 | } 572 | 573 | /** 574 | * Executes the given task in an IO thread pool. 575 | * 576 | * @param task The task to execute. 577 | * @param priority The priority of thread in the poll. 578 | * @param The type of the task's result. 579 | */ 580 | public static void executeByIo(final Task task, 581 | @IntRange(from = 1, to = 10) final int priority) { 582 | execute(getPoolByTypeAndPriority(TYPE_IO, priority), task); 583 | } 584 | 585 | /** 586 | * Executes the given task in an IO thread pool after the given delay. 587 | * 588 | * @param task The task to execute. 589 | * @param delay The time from now to delay execution. 590 | * @param unit The time unit of the delay parameter. 591 | * @param The type of the task's result. 592 | */ 593 | public static void executeByIoWithDelay(final Task task, 594 | final long delay, 595 | final TimeUnit unit) { 596 | executeWithDelay(getPoolByTypeAndPriority(TYPE_IO), task, delay, unit); 597 | } 598 | 599 | /** 600 | * Executes the given task in an IO thread pool after the given delay. 601 | * 602 | * @param task The task to execute. 603 | * @param delay The time from now to delay execution. 604 | * @param unit The time unit of the delay parameter. 605 | * @param priority The priority of thread in the poll. 606 | * @param The type of the task's result. 607 | */ 608 | public static void executeByIoWithDelay(final Task task, 609 | final long delay, 610 | final TimeUnit unit, 611 | @IntRange(from = 1, to = 10) final int priority) { 612 | executeWithDelay(getPoolByTypeAndPriority(TYPE_IO, priority), task, delay, unit); 613 | } 614 | 615 | /** 616 | * Executes the given task in an IO thread pool at fix rate. 617 | * 618 | * @param task The task to execute. 619 | * @param period The period between successive executions. 620 | * @param unit The time unit of the period parameter. 621 | * @param The type of the task's result. 622 | */ 623 | public static void executeByIoAtFixRate(final Task task, 624 | final long period, 625 | final TimeUnit unit) { 626 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_IO), task, 0, period, unit); 627 | } 628 | 629 | /** 630 | * Executes the given task in an IO thread pool at fix rate. 631 | * 632 | * @param task The task to execute. 633 | * @param period The period between successive executions. 634 | * @param unit The time unit of the period parameter. 635 | * @param priority The priority of thread in the poll. 636 | * @param The type of the task's result. 637 | */ 638 | public static void executeByIoAtFixRate(final Task task, 639 | final long period, 640 | final TimeUnit unit, 641 | @IntRange(from = 1, to = 10) final int priority) { 642 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_IO, priority), task, 0, period, unit); 643 | } 644 | 645 | /** 646 | * Executes the given task in an IO thread pool at fix rate. 647 | * 648 | * @param task The task to execute. 649 | * @param initialDelay The time to delay first execution. 650 | * @param period The period between successive executions. 651 | * @param unit The time unit of the initialDelay and period parameters. 652 | * @param The type of the task's result. 653 | */ 654 | public static void executeByIoAtFixRate(final Task task, 655 | long initialDelay, 656 | final long period, 657 | final TimeUnit unit) { 658 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_IO), task, initialDelay, period, unit); 659 | } 660 | 661 | /** 662 | * Executes the given task in an IO thread pool at fix rate. 663 | * 664 | * @param task The task to execute. 665 | * @param initialDelay The time to delay first execution. 666 | * @param period The period between successive executions. 667 | * @param unit The time unit of the initialDelay and period parameters. 668 | * @param priority The priority of thread in the poll. 669 | * @param The type of the task's result. 670 | */ 671 | public static void executeByIoAtFixRate(final Task task, 672 | long initialDelay, 673 | final long period, 674 | final TimeUnit unit, 675 | @IntRange(from = 1, to = 10) final int priority) { 676 | executeAtFixedRate( 677 | getPoolByTypeAndPriority(TYPE_IO, priority), task, initialDelay, period, unit 678 | ); 679 | } 680 | 681 | /** 682 | * Executes the given task in a cpu thread pool. 683 | * 684 | * @param task The task to execute. 685 | * @param The type of the task's result. 686 | */ 687 | public static void executeByCpu(final Task task) { 688 | execute(getPoolByTypeAndPriority(TYPE_CPU), task); 689 | } 690 | 691 | /** 692 | * Executes the given task in a cpu thread pool. 693 | * 694 | * @param task The task to execute. 695 | * @param priority The priority of thread in the poll. 696 | * @param The type of the task's result. 697 | */ 698 | public static void executeByCpu(final Task task, 699 | @IntRange(from = 1, to = 10) final int priority) { 700 | execute(getPoolByTypeAndPriority(TYPE_CPU, priority), task); 701 | } 702 | 703 | /** 704 | * Executes the given task in a cpu thread pool after the given delay. 705 | * 706 | * @param task The task to execute. 707 | * @param delay The time from now to delay execution. 708 | * @param unit The time unit of the delay parameter. 709 | * @param The type of the task's result. 710 | */ 711 | public static void executeByCpuWithDelay(final Task task, 712 | final long delay, 713 | final TimeUnit unit) { 714 | executeWithDelay(getPoolByTypeAndPriority(TYPE_CPU), task, delay, unit); 715 | } 716 | 717 | /** 718 | * Executes the given task in a cpu thread pool after the given delay. 719 | * 720 | * @param task The task to execute. 721 | * @param delay The time from now to delay execution. 722 | * @param unit The time unit of the delay parameter. 723 | * @param priority The priority of thread in the poll. 724 | * @param The type of the task's result. 725 | */ 726 | public static void executeByCpuWithDelay(final Task task, 727 | final long delay, 728 | final TimeUnit unit, 729 | @IntRange(from = 1, to = 10) final int priority) { 730 | executeWithDelay(getPoolByTypeAndPriority(TYPE_CPU, priority), task, delay, unit); 731 | } 732 | 733 | /** 734 | * Executes the given task in a cpu thread pool at fix rate. 735 | * 736 | * @param task The task to execute. 737 | * @param period The period between successive executions. 738 | * @param unit The time unit of the period parameter. 739 | * @param The type of the task's result. 740 | */ 741 | public static void executeByCpuAtFixRate(final Task task, 742 | final long period, 743 | final TimeUnit unit) { 744 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CPU), task, 0, period, unit); 745 | } 746 | 747 | /** 748 | * Executes the given task in a cpu thread pool at fix rate. 749 | * 750 | * @param task The task to execute. 751 | * @param period The period between successive executions. 752 | * @param unit The time unit of the period parameter. 753 | * @param priority The priority of thread in the poll. 754 | * @param The type of the task's result. 755 | */ 756 | public static void executeByCpuAtFixRate(final Task task, 757 | final long period, 758 | final TimeUnit unit, 759 | @IntRange(from = 1, to = 10) final int priority) { 760 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CPU, priority), task, 0, period, unit); 761 | } 762 | 763 | /** 764 | * Executes the given task in a cpu thread pool at fix rate. 765 | * 766 | * @param task The task to execute. 767 | * @param initialDelay The time to delay first execution. 768 | * @param period The period between successive executions. 769 | * @param unit The time unit of the initialDelay and period parameters. 770 | * @param The type of the task's result. 771 | */ 772 | public static void executeByCpuAtFixRate(final Task task, 773 | long initialDelay, 774 | final long period, 775 | final TimeUnit unit) { 776 | executeAtFixedRate(getPoolByTypeAndPriority(TYPE_CPU), task, initialDelay, period, unit); 777 | } 778 | 779 | /** 780 | * Executes the given task in a cpu thread pool at fix rate. 781 | * 782 | * @param task The task to execute. 783 | * @param initialDelay The time to delay first execution. 784 | * @param period The period between successive executions. 785 | * @param unit The time unit of the initialDelay and period parameters. 786 | * @param priority The priority of thread in the poll. 787 | * @param The type of the task's result. 788 | */ 789 | public static void executeByCpuAtFixRate(final Task task, 790 | long initialDelay, 791 | final long period, 792 | final TimeUnit unit, 793 | @IntRange(from = 1, to = 10) final int priority) { 794 | executeAtFixedRate( 795 | getPoolByTypeAndPriority(TYPE_CPU, priority), task, initialDelay, period, unit 796 | ); 797 | } 798 | 799 | /** 800 | * Executes the given task in a custom thread pool. 801 | * 802 | * @param pool The custom thread pool. 803 | * @param task The task to execute. 804 | * @param The type of the task's result. 805 | */ 806 | public static void executeByCustom(final ExecutorService pool, final Task task) { 807 | execute(pool, task); 808 | } 809 | 810 | /** 811 | * Executes the given task in a custom thread pool after the given delay. 812 | * 813 | * @param pool The custom thread pool. 814 | * @param task The task to execute. 815 | * @param delay The time from now to delay execution. 816 | * @param unit The time unit of the delay parameter. 817 | * @param The type of the task's result. 818 | */ 819 | public static void executeByCustomWithDelay(final ExecutorService pool, 820 | final Task task, 821 | final long delay, 822 | final TimeUnit unit) { 823 | executeWithDelay(pool, task, delay, unit); 824 | } 825 | 826 | /** 827 | * Executes the given task in a custom thread pool at fix rate. 828 | * 829 | * @param pool The custom thread pool. 830 | * @param task The task to execute. 831 | * @param period The period between successive executions. 832 | * @param unit The time unit of the period parameter. 833 | * @param The type of the task's result. 834 | */ 835 | public static void executeByCustomAtFixRate(final ExecutorService pool, 836 | final Task task, 837 | final long period, 838 | final TimeUnit unit) { 839 | executeAtFixedRate(pool, task, 0, period, unit); 840 | } 841 | 842 | /** 843 | * Executes the given task in a custom thread pool at fix rate. 844 | * 845 | * @param pool The custom thread pool. 846 | * @param task The task to execute. 847 | * @param initialDelay The time to delay first execution. 848 | * @param period The period between successive executions. 849 | * @param unit The time unit of the initialDelay and period parameters. 850 | * @param The type of the task's result. 851 | */ 852 | public static void executeByCustomAtFixRate(final ExecutorService pool, 853 | final Task task, 854 | long initialDelay, 855 | final long period, 856 | final TimeUnit unit) { 857 | executeAtFixedRate(pool, task, initialDelay, period, unit); 858 | } 859 | 860 | /** 861 | * Cancel the given task. 862 | * 863 | * @param task The task to cancel. 864 | */ 865 | public static void cancel(final Task task) { 866 | if (task == null) return; 867 | task.cancel(); 868 | } 869 | 870 | /** 871 | * Cancel the given tasks. 872 | * 873 | * @param tasks The tasks to cancel. 874 | */ 875 | public static void cancel(final Task... tasks) { 876 | if (tasks == null || tasks.length == 0) return; 877 | for (Task task : tasks) { 878 | if (task == null) continue; 879 | task.cancel(); 880 | } 881 | } 882 | 883 | /** 884 | * Cancel the given tasks. 885 | * 886 | * @param tasks The tasks to cancel. 887 | */ 888 | public static void cancel(final List tasks) { 889 | if (tasks == null || tasks.size() == 0) return; 890 | for (Task task : tasks) { 891 | if (task == null) continue; 892 | task.cancel(); 893 | } 894 | } 895 | 896 | /** 897 | * Cancel the tasks in pool. 898 | * 899 | * @param executorService The pool. 900 | */ 901 | public static void cancel(ExecutorService executorService) { 902 | if (executorService instanceof ThreadPoolExecutor4Util) { 903 | for (Map.Entry taskTaskInfoEntry : TASK_POOL_MAP.entrySet()) { 904 | if (taskTaskInfoEntry.getValue() == executorService) { 905 | cancel(taskTaskInfoEntry.getKey()); 906 | } 907 | } 908 | } else { 909 | Log.e("ThreadUtils", "The executorService is not ThreadUtils's pool."); 910 | } 911 | } 912 | 913 | /** 914 | * Set the deliver. 915 | * 916 | * @param deliver The deliver. 917 | */ 918 | public static void setDeliver(final Executor deliver) { 919 | sDeliver = deliver; 920 | } 921 | 922 | private static void execute(final ExecutorService pool, final Task task) { 923 | execute(pool, task, 0, 0, null); 924 | } 925 | 926 | private static void executeWithDelay(final ExecutorService pool, 927 | final Task task, 928 | final long delay, 929 | final TimeUnit unit) { 930 | execute(pool, task, delay, 0, unit); 931 | } 932 | 933 | private static void executeAtFixedRate(final ExecutorService pool, 934 | final Task task, 935 | long delay, 936 | final long period, 937 | final TimeUnit unit) { 938 | execute(pool, task, delay, period, unit); 939 | } 940 | 941 | private static void execute(final ExecutorService pool, final Task task, 942 | long delay, final long period, final TimeUnit unit) { 943 | synchronized (TASK_POOL_MAP) { 944 | if (TASK_POOL_MAP.get(task) != null) { 945 | Log.e("ThreadUtils", "Task can only be executed once."); 946 | return; 947 | } 948 | TASK_POOL_MAP.put(task, pool); 949 | } 950 | if (period == 0) { 951 | if (delay == 0) { 952 | pool.execute(task); 953 | } else { 954 | TimerTask timerTask = new TimerTask() { 955 | @Override 956 | public void run() { 957 | pool.execute(task); 958 | } 959 | }; 960 | TIMER.schedule(timerTask, unit.toMillis(delay)); 961 | } 962 | } else { 963 | task.setSchedule(true); 964 | TimerTask timerTask = new TimerTask() { 965 | @Override 966 | public void run() { 967 | pool.execute(task); 968 | } 969 | }; 970 | TIMER.scheduleAtFixedRate(timerTask, unit.toMillis(delay), unit.toMillis(period)); 971 | } 972 | } 973 | 974 | private static ExecutorService getPoolByTypeAndPriority(final int type) { 975 | return getPoolByTypeAndPriority(type, Thread.NORM_PRIORITY); 976 | } 977 | 978 | private static ExecutorService getPoolByTypeAndPriority(final int type, final int priority) { 979 | synchronized (TYPE_PRIORITY_POOLS) { 980 | ExecutorService pool; 981 | Map priorityPools = TYPE_PRIORITY_POOLS.get(type); 982 | if (priorityPools == null) { 983 | priorityPools = new ConcurrentHashMap<>(); 984 | pool = ThreadPoolExecutor4Util.createPool(type, priority); 985 | priorityPools.put(priority, pool); 986 | TYPE_PRIORITY_POOLS.put(type, priorityPools); 987 | } else { 988 | pool = priorityPools.get(priority); 989 | if (pool == null) { 990 | pool = ThreadPoolExecutor4Util.createPool(type, priority); 991 | priorityPools.put(priority, pool); 992 | } 993 | } 994 | return pool; 995 | } 996 | } 997 | 998 | static final class ThreadPoolExecutor4Util extends ThreadPoolExecutor { 999 | 1000 | private static ExecutorService createPool(final int type, final int priority) { 1001 | switch (type) { 1002 | case TYPE_SINGLE: 1003 | return new ThreadPoolExecutor4Util(1, 1, 1004 | 0L, TimeUnit.MILLISECONDS, 1005 | new LinkedBlockingQueue4Util(), 1006 | new UtilsThreadFactory("single", priority) 1007 | ); 1008 | case TYPE_CACHED: 1009 | return new ThreadPoolExecutor4Util(0, 128, 1010 | 60L, TimeUnit.SECONDS, 1011 | new LinkedBlockingQueue4Util(true), 1012 | new UtilsThreadFactory("cached", priority) 1013 | ); 1014 | case TYPE_IO: 1015 | return new ThreadPoolExecutor4Util(2 * CPU_COUNT + 1, 2 * CPU_COUNT + 1, 1016 | 30, TimeUnit.SECONDS, 1017 | new LinkedBlockingQueue4Util(), 1018 | new UtilsThreadFactory("io", priority) 1019 | ); 1020 | case TYPE_CPU: 1021 | return new ThreadPoolExecutor4Util(CPU_COUNT + 1, 2 * CPU_COUNT + 1, 1022 | 30, TimeUnit.SECONDS, 1023 | new LinkedBlockingQueue4Util(true), 1024 | new UtilsThreadFactory("cpu", priority) 1025 | ); 1026 | default: 1027 | return new ThreadPoolExecutor4Util(type, type, 1028 | 0L, TimeUnit.MILLISECONDS, 1029 | new LinkedBlockingQueue4Util(), 1030 | new UtilsThreadFactory("fixed(" + type + ")", priority) 1031 | ); 1032 | } 1033 | } 1034 | 1035 | private final AtomicInteger mSubmittedCount = new AtomicInteger(); 1036 | 1037 | private final LinkedBlockingQueue4Util mWorkQueue; 1038 | 1039 | ThreadPoolExecutor4Util(int corePoolSize, int maximumPoolSize, 1040 | long keepAliveTime, TimeUnit unit, 1041 | LinkedBlockingQueue4Util workQueue, 1042 | ThreadFactory threadFactory) { 1043 | super(corePoolSize, maximumPoolSize, 1044 | keepAliveTime, unit, 1045 | workQueue, 1046 | threadFactory 1047 | ); 1048 | workQueue.mPool = this; 1049 | mWorkQueue = workQueue; 1050 | } 1051 | 1052 | private int getSubmittedCount() { 1053 | return mSubmittedCount.get(); 1054 | } 1055 | 1056 | @Override 1057 | protected void afterExecute(Runnable r, Throwable t) { 1058 | mSubmittedCount.decrementAndGet(); 1059 | super.afterExecute(r, t); 1060 | } 1061 | 1062 | @Override 1063 | public void execute(@NonNull Runnable command) { 1064 | if (this.isShutdown()) { 1065 | return; 1066 | } 1067 | mSubmittedCount.incrementAndGet(); 1068 | try { 1069 | super.execute(command); 1070 | } catch (RejectedExecutionException ignore) { 1071 | Log.e("ThreadUtils", "This will not happen!"); 1072 | mWorkQueue.offer(command); 1073 | } catch (Throwable t) { 1074 | mSubmittedCount.decrementAndGet(); 1075 | } 1076 | } 1077 | } 1078 | 1079 | private static final class LinkedBlockingQueue4Util extends LinkedBlockingQueue { 1080 | 1081 | private volatile ThreadPoolExecutor4Util mPool; 1082 | 1083 | private int mCapacity = Integer.MAX_VALUE; 1084 | 1085 | LinkedBlockingQueue4Util() { 1086 | super(); 1087 | } 1088 | 1089 | LinkedBlockingQueue4Util(boolean isAddSubThreadFirstThenAddQueue) { 1090 | super(); 1091 | if (isAddSubThreadFirstThenAddQueue) { 1092 | mCapacity = 0; 1093 | } 1094 | } 1095 | 1096 | LinkedBlockingQueue4Util(int capacity) { 1097 | super(); 1098 | mCapacity = capacity; 1099 | } 1100 | 1101 | @Override 1102 | public boolean offer(@NonNull Runnable runnable) { 1103 | if (mCapacity <= size() && 1104 | mPool != null && mPool.getPoolSize() < mPool.getMaximumPoolSize()) { 1105 | // create a non-core thread 1106 | return false; 1107 | } 1108 | return super.offer(runnable); 1109 | } 1110 | } 1111 | 1112 | static final class UtilsThreadFactory extends AtomicLong 1113 | implements ThreadFactory { 1114 | private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); 1115 | private static final long serialVersionUID = -9209200509960368598L; 1116 | private final String namePrefix; 1117 | private final int priority; 1118 | private final boolean isDaemon; 1119 | 1120 | UtilsThreadFactory(String prefix, int priority) { 1121 | this(prefix, priority, false); 1122 | } 1123 | 1124 | UtilsThreadFactory(String prefix, int priority, boolean isDaemon) { 1125 | namePrefix = prefix + "-pool-" + 1126 | POOL_NUMBER.getAndIncrement() + 1127 | "-thread-"; 1128 | this.priority = priority; 1129 | this.isDaemon = isDaemon; 1130 | } 1131 | 1132 | @Override 1133 | public Thread newThread(@NonNull Runnable r) { 1134 | Thread t = new Thread(r, namePrefix + getAndIncrement()) { 1135 | @Override 1136 | public void run() { 1137 | try { 1138 | super.run(); 1139 | } catch (Throwable t) { 1140 | Log.e("ThreadUtils", "Request threw uncaught throwable", t); 1141 | } 1142 | } 1143 | }; 1144 | t.setDaemon(isDaemon); 1145 | t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 1146 | @Override 1147 | public void uncaughtException(Thread t, Throwable e) { 1148 | System.out.println(e); 1149 | } 1150 | }); 1151 | t.setPriority(priority); 1152 | return t; 1153 | } 1154 | } 1155 | 1156 | public abstract static class SimpleTask extends Task { 1157 | 1158 | @Override 1159 | public void onCancel() { 1160 | Log.e("ThreadUtils", "onCancel: " + Thread.currentThread()); 1161 | } 1162 | 1163 | @Override 1164 | public void onFail(Throwable t) { 1165 | Log.e("ThreadUtils", "onFail: ", t); 1166 | } 1167 | 1168 | } 1169 | 1170 | public abstract static class Task implements Runnable { 1171 | 1172 | private static final int NEW = 0; 1173 | private static final int RUNNING = 1; 1174 | private static final int EXCEPTIONAL = 2; 1175 | private static final int COMPLETING = 3; 1176 | private static final int CANCELLED = 4; 1177 | private static final int INTERRUPTED = 5; 1178 | private static final int TIMEOUT = 6; 1179 | 1180 | private final AtomicInteger state = new AtomicInteger(NEW); 1181 | 1182 | private volatile boolean isSchedule; 1183 | private volatile Thread runner; 1184 | 1185 | private Timer mTimer; 1186 | private long mTimeoutMillis; 1187 | private OnTimeoutListener mTimeoutListener; 1188 | 1189 | private Executor deliver; 1190 | 1191 | public abstract T doInBackground() throws Throwable; 1192 | 1193 | public abstract void onSuccess(T result); 1194 | 1195 | public abstract void onCancel(); 1196 | 1197 | public abstract void onFail(Throwable t); 1198 | 1199 | @Override 1200 | public void run() { 1201 | if (isSchedule) { 1202 | if (runner == null) { 1203 | if (!state.compareAndSet(NEW, RUNNING)) { 1204 | return; 1205 | } 1206 | runner = Thread.currentThread(); 1207 | if (mTimeoutListener != null) { 1208 | Log.w("ThreadUtils", "Scheduled task doesn't support timeout."); 1209 | } 1210 | } else { 1211 | if (state.get() != RUNNING) { 1212 | return; 1213 | } 1214 | } 1215 | } else { 1216 | if (!state.compareAndSet(NEW, RUNNING)) { 1217 | return; 1218 | } 1219 | runner = Thread.currentThread(); 1220 | if (mTimeoutListener != null) { 1221 | mTimer = new Timer(); 1222 | mTimer.schedule(new TimerTask() { 1223 | @Override 1224 | public void run() { 1225 | if (!isDone() && mTimeoutListener != null) { 1226 | timeout(); 1227 | mTimeoutListener.onTimeout(); 1228 | onDone(); 1229 | } 1230 | } 1231 | }, mTimeoutMillis); 1232 | } 1233 | } 1234 | try { 1235 | final T result = doInBackground(); 1236 | if (isSchedule) { 1237 | if (state.get() != RUNNING) { 1238 | return; 1239 | } 1240 | getDeliver().execute(new Runnable() { 1241 | @Override 1242 | public void run() { 1243 | onSuccess(result); 1244 | } 1245 | }); 1246 | } else { 1247 | if (!state.compareAndSet(RUNNING, COMPLETING)) { 1248 | return; 1249 | } 1250 | getDeliver().execute(new Runnable() { 1251 | @Override 1252 | public void run() { 1253 | onSuccess(result); 1254 | onDone(); 1255 | } 1256 | }); 1257 | } 1258 | } catch (InterruptedException ignore) { 1259 | state.compareAndSet(CANCELLED, INTERRUPTED); 1260 | } catch (final Throwable throwable) { 1261 | if (!state.compareAndSet(RUNNING, EXCEPTIONAL)) { 1262 | return; 1263 | } 1264 | getDeliver().execute(new Runnable() { 1265 | @Override 1266 | public void run() { 1267 | onFail(throwable); 1268 | onDone(); 1269 | } 1270 | }); 1271 | } 1272 | } 1273 | 1274 | public void cancel() { 1275 | cancel(true); 1276 | } 1277 | 1278 | public void cancel(boolean mayInterruptIfRunning) { 1279 | synchronized (state) { 1280 | if (state.get() > RUNNING) { 1281 | return; 1282 | } 1283 | state.set(CANCELLED); 1284 | } 1285 | if (mayInterruptIfRunning) { 1286 | if (runner != null) { 1287 | runner.interrupt(); 1288 | } 1289 | } 1290 | 1291 | getDeliver().execute(new Runnable() { 1292 | @Override 1293 | public void run() { 1294 | onCancel(); 1295 | onDone(); 1296 | } 1297 | }); 1298 | } 1299 | 1300 | private void timeout() { 1301 | synchronized (state) { 1302 | if (state.get() > RUNNING) { 1303 | return; 1304 | } 1305 | state.set(TIMEOUT); 1306 | } 1307 | if (runner != null) { 1308 | runner.interrupt(); 1309 | } 1310 | } 1311 | 1312 | 1313 | public boolean isCanceled() { 1314 | return state.get() >= CANCELLED; 1315 | } 1316 | 1317 | public boolean isDone() { 1318 | return state.get() > RUNNING; 1319 | } 1320 | 1321 | public Task setDeliver(Executor deliver) { 1322 | this.deliver = deliver; 1323 | return this; 1324 | } 1325 | 1326 | /** 1327 | * Scheduled task doesn't support timeout. 1328 | */ 1329 | public Task setTimeout(final long timeoutMillis, final OnTimeoutListener listener) { 1330 | mTimeoutMillis = timeoutMillis; 1331 | mTimeoutListener = listener; 1332 | return this; 1333 | } 1334 | 1335 | private void setSchedule(boolean isSchedule) { 1336 | this.isSchedule = isSchedule; 1337 | } 1338 | 1339 | private Executor getDeliver() { 1340 | if (deliver == null) { 1341 | return getGlobalDeliver(); 1342 | } 1343 | return deliver; 1344 | } 1345 | 1346 | @CallSuper 1347 | protected void onDone() { 1348 | TASK_POOL_MAP.remove(this); 1349 | if (mTimer != null) { 1350 | mTimer.cancel(); 1351 | mTimer = null; 1352 | mTimeoutListener = null; 1353 | } 1354 | } 1355 | 1356 | public interface OnTimeoutListener { 1357 | void onTimeout(); 1358 | } 1359 | } 1360 | 1361 | public static class SyncValue { 1362 | 1363 | private final CountDownLatch mLatch = new CountDownLatch(1); 1364 | private final AtomicBoolean mFlag = new AtomicBoolean(); 1365 | private T mValue; 1366 | 1367 | public void setValue(T value) { 1368 | if (mFlag.compareAndSet(false, true)) { 1369 | mValue = value; 1370 | mLatch.countDown(); 1371 | } 1372 | } 1373 | 1374 | public T getValue() { 1375 | if (!mFlag.get()) { 1376 | try { 1377 | mLatch.await(); 1378 | } catch (InterruptedException e) { 1379 | e.printStackTrace(); 1380 | } 1381 | } 1382 | return mValue; 1383 | } 1384 | 1385 | public T getValue(long timeout, TimeUnit unit, T defaultValue) { 1386 | if (!mFlag.get()) { 1387 | try { 1388 | mLatch.await(timeout, unit); 1389 | } catch (InterruptedException e) { 1390 | e.printStackTrace(); 1391 | return defaultValue; 1392 | } 1393 | } 1394 | return mValue; 1395 | } 1396 | } 1397 | 1398 | private static Executor getGlobalDeliver() { 1399 | if (sDeliver == null) { 1400 | sDeliver = new Executor() { 1401 | @Override 1402 | public void execute(@NonNull Runnable command) { 1403 | runOnUiThread(command); 1404 | } 1405 | }; 1406 | } 1407 | return sDeliver; 1408 | } 1409 | } 1410 | --------------------------------------------------------------------------------