├── .gitignore ├── build.gradle ├── glint ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── ysbing │ │ └── glint │ │ └── socket │ │ ├── GlintSocketBuilderWrapper.aidl │ │ └── GlintSocketService.aidl │ └── java │ └── com │ └── ysbing │ └── glint │ ├── base │ ├── BaseHttpModule.java │ ├── Glint.java │ ├── GlintBaseBuilder.java │ └── GlintResultBean.java │ ├── download │ ├── GlintDownload.java │ ├── GlintDownloadBuilder.java │ ├── GlintDownloadCore.java │ ├── GlintDownloadDispatcher.java │ ├── GlintDownloadInfo.java │ ├── GlintDownloadListener.java │ └── GlintDownloadProgressListener.java │ ├── http │ ├── GilntHttpFragmentLifecycleCallbacks.java │ ├── GlintHttp.java │ ├── GlintHttpActivityLifecycleCallbacks.java │ ├── GlintHttpBuilder.java │ ├── GlintHttpCache.java │ ├── GlintHttpCore.java │ ├── GlintHttpDispatcher.java │ ├── GlintHttpListener.java │ └── GlintHttpRetry.java │ ├── socket │ ├── GlintSocket.java │ ├── GlintSocketBuilder.java │ ├── GlintSocketBuilderStub.java │ ├── GlintSocketCore.java │ ├── GlintSocketDispatcher.java │ ├── GlintSocketListener.java │ ├── GlintSocketServiceNative.java │ ├── GlintSocketServiceStub.java │ ├── SocketHttpModule.java │ ├── SocketInnerResultBean.java │ └── socketio │ │ ├── GlintSocketIOCallback.java │ │ ├── GlintSocketIOCore.java │ │ ├── IOMessage.java │ │ ├── IOWebSocketTransport.java │ │ └── Protocol.java │ ├── upload │ ├── GlintUpload.java │ ├── GlintUploadBuilder.java │ ├── GlintUploadCore.java │ ├── GlintUploadCountingRequestBody.java │ ├── GlintUploadDispatcher.java │ └── GlintUploadListener.java │ └── util │ ├── ContextHelper.java │ ├── GlintRequestUtil.java │ ├── Md5Util.java │ ├── UiKit.java │ ├── UiKitHandlerPoster.java │ ├── UiKitSyncPost.java │ └── UiStack.java ├── gradle.properties ├── gradle ├── publish.gradle ├── publish_local_android.gradle ├── publish_local_java.gradle ├── publish_remote.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── readme.md ├── samples ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── ysbing │ │ └── samples │ │ └── glint │ │ ├── MainActivity.java │ │ ├── SplashActivity.java │ │ ├── download │ │ └── DownloadRequestActivity.java │ │ ├── http │ │ ├── HttpModuleRequestActivity.java │ │ ├── HttpRequestActivity.java │ │ ├── LunarBean.java │ │ └── MyHttpModule.java │ │ ├── upload │ │ ├── ContentUriUtil.java │ │ └── UploadRequestActivity.java │ │ └── websocket │ │ ├── MySocketHttpModule.java │ │ └── WebSocketRequestActivity.java │ └── res │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_download_request.xml │ ├── activity_http_request.xml │ ├── activity_main.xml │ ├── activity_upload_request.xml │ ├── activity_websocket_request.xml │ └── item_list.xml │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── network_security_config.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | .idea 5 | build 6 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:7.4.2' 11 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5' 12 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | mavenCentral() 20 | maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } 21 | } 22 | } 23 | 24 | tasks.register('clean', Delete) { 25 | delete rootProject.buildDir 26 | } 27 | 28 | ext { 29 | javaVersion = JavaVersion.VERSION_1_8 30 | 31 | GROUP = 'com.ysbing.glint' 32 | VERSION_NAME = "${GLINT_VERSION}" 33 | 34 | POM_NAME = "Glint" 35 | POM_PACKAGING = "pom" 36 | POM_DESCRIPTION = "Glint is an Http standard protocol framework based on OkHttp for Android. It supports four functions: interface request, file download, file upload and WebSocket." 37 | 38 | POM_URL = "https://github.com/ysbing/Glint" 39 | POM_SCM_URL = "https://github.com/ysbing/Glint" 40 | POM_ISSUE_URL = 'https://github.com/ysbing/Glint/issues' 41 | 42 | POM_LICENCE_NAME = "Apache-2.0" 43 | POM_LICENCE_URL = " http://www.apache.org/licenses/" 44 | POM_LICENCE_DIST = "repo" 45 | 46 | POM_DEVELOPER_ID = "ysbing" 47 | POM_DEVELOPER_NAME = "Chen Zhujie" 48 | 49 | BINTRAY_LICENCE = ["Apache-2.0"] 50 | BINTRAY_ORGANIZATION = "ysbing" 51 | } -------------------------------------------------------------------------------- /glint/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | build 3 | -------------------------------------------------------------------------------- /glint/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | //apply from: rootProject.file('gradle/publish.gradle') 3 | 4 | android { 5 | compileSdk 34 6 | defaultConfig { 7 | minSdk 9 8 | compileSdk 34 9 | } 10 | } 11 | 12 | dependencies { 13 | compileOnly "androidx.fragment:fragment:1.6.2" 14 | api 'com.google.code.gson:gson:2.10.1' 15 | api 'com.squareup.okhttp3:okhttp:4.11.0' 16 | } 17 | -------------------------------------------------------------------------------- /glint/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=glint 2 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /glint/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\Administrator\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -keep public class * extends com.ysbing.glint.base.BaseHttpModule -------------------------------------------------------------------------------- /glint/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /glint/src/main/aidl/com/ysbing/glint/socket/GlintSocketBuilderWrapper.aidl: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.socket; 2 | 3 | interface GlintSocketBuilderWrapper { 4 | 5 | String getUrl(); 6 | 7 | String getCmdId(); 8 | 9 | String getParams(); 10 | 11 | int getSendId(); 12 | 13 | int getTag(); 14 | 15 | String getResponseCmdId(String response); 16 | 17 | void onResponse(String response); 18 | 19 | void onError(String error); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /glint/src/main/aidl/com/ysbing/glint/socket/GlintSocketService.aidl: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.socket; 2 | 3 | import com.ysbing.glint.socket.GlintSocketBuilderWrapper; 4 | 5 | interface GlintSocketService { 6 | 7 | void connect(String url); 8 | 9 | void connectIO(String url); 10 | 11 | void send(GlintSocketBuilderWrapper builderWrapper); 12 | 13 | void sendIO(GlintSocketBuilderWrapper builderWrapper); 14 | 15 | void on(GlintSocketBuilderWrapper builderWrapper); 16 | 17 | void onIO(GlintSocketBuilderWrapper builderWrapper); 18 | 19 | void off(String url, String cmdId, int tag); 20 | 21 | void offAll(); 22 | } 23 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/base/BaseHttpModule.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.base; 2 | 3 | import androidx.annotation.CallSuper; 4 | import androidx.annotation.NonNull; 5 | import androidx.annotation.Nullable; 6 | 7 | import com.google.gson.Gson; 8 | import com.google.gson.JsonElement; 9 | import com.google.gson.JsonObject; 10 | 11 | import java.lang.reflect.Type; 12 | import java.util.TreeMap; 13 | 14 | import okhttp3.Headers; 15 | import okhttp3.OkHttpClient; 16 | 17 | /** 18 | * 自定义网络模块 19 | * 20 | * @author ysbing 21 | */ 22 | public abstract class BaseHttpModule { 23 | /** 24 | * 创建OkHttpClient的额外配置 25 | * 26 | * @param clientType 类型,0为http普通请求,1为上传,2为下载 27 | * @param builder OkHttpClient配置 28 | * @return OkHttpClient配置 29 | */ 30 | public OkHttpClient.Builder onOkHttpBuildCreate(@NonNull Glint.GlintType clientType, 31 | @NonNull OkHttpClient.Builder builder) { 32 | return builder; 33 | } 34 | 35 | @CallSuper 36 | public void configDefaultBuilder( 37 | @NonNull GlintBaseBuilder builder) { 38 | } 39 | 40 | /** 41 | * @param builder 构造完毕的builder,该builder是克隆出来的,对它修改不影响原builder 42 | * @param BaseHttpModule子类 43 | * @throws Exception 未知异常 44 | */ 45 | public void onBuilderCreated(@NonNull GlintBaseBuilder builder) 46 | throws Exception { 47 | } 48 | 49 | public UrlResult getUrl(@NonNull String originalUrl) throws Exception { 50 | return new UrlResult(originalUrl, true); 51 | } 52 | 53 | public boolean getParams(@NonNull TreeMap originalParams) throws Exception { 54 | return true; 55 | } 56 | 57 | public boolean getParams(@NonNull TreeMap originalParams, 58 | @Nullable JsonObject originalJsonParams) throws Exception { 59 | return true; 60 | } 61 | 62 | public boolean getHeaders(@NonNull Headers.Builder originalHeader) throws Exception { 63 | return true; 64 | } 65 | 66 | public boolean customDeserialize(@NonNull GlintResultBean result, 67 | @NonNull JsonElement jsonEl, @NonNull Gson gson, 68 | @NonNull Type typeOfT) throws Exception { 69 | return true; 70 | } 71 | 72 | public class UrlResult { 73 | public final String url; 74 | public final boolean transitive; 75 | 76 | public UrlResult(@NonNull String url, boolean transitive) { 77 | this.url = url; 78 | this.transitive = transitive; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/base/Glint.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.base; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.pm.ApplicationInfo; 6 | import android.content.pm.PackageManager; 7 | import android.os.Build; 8 | import android.text.TextUtils; 9 | 10 | import androidx.annotation.NonNull; 11 | 12 | import com.google.gson.Gson; 13 | import com.google.gson.JsonElement; 14 | import com.ysbing.glint.http.GlintHttpActivityLifecycleCallbacks; 15 | import com.ysbing.glint.util.ContextHelper; 16 | 17 | import java.lang.reflect.Type; 18 | import java.util.Set; 19 | import java.util.TreeMap; 20 | 21 | import okhttp3.Headers; 22 | import okhttp3.OkHttpClient; 23 | 24 | /** 25 | * 获取Application做初始化工作的地方 26 | * 27 | * @author ysbing 28 | */ 29 | public final class Glint extends BaseHttpModule { 30 | private static volatile Glint sInstance; 31 | private BaseHttpModule mClazzBaseHttpModule; 32 | 33 | public static Glint getsInstance() { 34 | if (sInstance == null) { 35 | synchronized (Glint.class) { 36 | if (sInstance == null) { 37 | Application application = ContextHelper.getApplication(); 38 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 39 | application.registerActivityLifecycleCallbacks( 40 | new GlintHttpActivityLifecycleCallbacks( 41 | application.getApplicationContext())); 42 | } 43 | sInstance = new Glint(application.getApplicationContext()); 44 | } 45 | } 46 | } 47 | return sInstance; 48 | } 49 | 50 | private Glint(Context context) { 51 | if (context == null) { 52 | throw new RuntimeException("Context is null"); 53 | } 54 | if (!(context instanceof Application)) { 55 | throw new RuntimeException("Context is not Application"); 56 | } 57 | initGlintHttpModule(context); 58 | } 59 | 60 | private void initGlintHttpModule(@NonNull Context context) { 61 | try { 62 | ApplicationInfo appInfo = context.getPackageManager() 63 | .getApplicationInfo(context.getPackageName(), 64 | PackageManager.GET_META_DATA); 65 | Set keySet = appInfo.metaData.keySet(); 66 | for (String key : keySet) { 67 | Object value = appInfo.metaData.get(key); 68 | if (!(value instanceof String)) { 69 | continue; 70 | } 71 | String valueStr = (String) value; 72 | if (TextUtils.equals(valueStr, "GlintHttpModule")) { 73 | Class clazz = Class.forName(key); 74 | mClazzBaseHttpModule = (BaseHttpModule) clazz.newInstance(); 75 | break; 76 | } 77 | } 78 | } catch (Exception ignored) { 79 | } 80 | } 81 | 82 | @Override 83 | public OkHttpClient.Builder onOkHttpBuildCreate(@NonNull GlintType clientType, 84 | @NonNull OkHttpClient.Builder builder) { 85 | if (mClazzBaseHttpModule != null) { 86 | return mClazzBaseHttpModule.onOkHttpBuildCreate(clientType, builder); 87 | } else { 88 | return builder; 89 | } 90 | } 91 | 92 | @Override 93 | public void configDefaultBuilder( 94 | @NonNull GlintBaseBuilder builder) { 95 | super.configDefaultBuilder(builder); 96 | if (mClazzBaseHttpModule != null) { 97 | mClazzBaseHttpModule.configDefaultBuilder(builder); 98 | } 99 | } 100 | 101 | @Override 102 | public void onBuilderCreated(@NonNull GlintBaseBuilder builder) 103 | throws Exception { 104 | super.onBuilderCreated(builder); 105 | if (mClazzBaseHttpModule != null) { 106 | mClazzBaseHttpModule.onBuilderCreated(builder); 107 | } 108 | } 109 | 110 | @Override 111 | public UrlResult getUrl(@NonNull String originalUrl) throws Exception { 112 | if (mClazzBaseHttpModule != null) { 113 | return mClazzBaseHttpModule.getUrl(originalUrl); 114 | } else { 115 | return new UrlResult(originalUrl, true); 116 | } 117 | } 118 | 119 | @Override 120 | public boolean getParams(@NonNull TreeMap originalParams) throws Exception { 121 | return mClazzBaseHttpModule == null || mClazzBaseHttpModule.getParams(originalParams); 122 | } 123 | 124 | @Override 125 | public boolean getHeaders(@NonNull Headers.Builder originalHeader) throws Exception { 126 | return mClazzBaseHttpModule == null || mClazzBaseHttpModule.getHeaders(originalHeader); 127 | } 128 | 129 | @Override 130 | public boolean customDeserialize(@NonNull GlintResultBean result, 131 | @NonNull JsonElement jsonEl, @NonNull Gson gson, 132 | @NonNull Type typeOfT) throws Exception { 133 | if (mClazzBaseHttpModule != null) { 134 | return mClazzBaseHttpModule.customDeserialize(result, jsonEl, gson, typeOfT); 135 | } 136 | return super.customDeserialize(result, jsonEl, gson, typeOfT); 137 | } 138 | 139 | /** 140 | * 使用框架的类型,目前有三种 141 | */ 142 | public enum GlintType { 143 | /** 144 | * 普通接口请求 145 | */ 146 | HTTP, 147 | /** 148 | * 文件上传 149 | */ 150 | UPLOAD, 151 | /** 152 | * 文件下载 153 | */ 154 | DOWNLOAD, 155 | /** 156 | * 长连接 157 | */ 158 | SOCKET, 159 | /** 160 | * 柚子IO长连接 161 | */ 162 | SOCKET_IO 163 | } 164 | 165 | public enum ResultStatus { 166 | /** 167 | * 解析成功的状态 168 | */ 169 | STATUS_SUCCESS, 170 | /** 171 | * 解析错误或者网络错误的状态 172 | */ 173 | STATUS_ERROR 174 | } 175 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/base/GlintBaseBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.base; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.google.gson.JsonObject; 8 | 9 | import java.util.LinkedHashSet; 10 | import java.util.Set; 11 | import java.util.TreeMap; 12 | 13 | import okhttp3.Headers; 14 | 15 | /** 16 | * 请求基础参数配置 17 | * 18 | * @author ysbing 19 | * 创建于 2018/1/16 20 | */ 21 | public class GlintBaseBuilder { 22 | /** 23 | * 请求的地址 24 | */ 25 | public String url; 26 | /** 27 | * 请求参数 28 | */ 29 | public TreeMap params; 30 | /** 31 | * 使用JSON协议的请求参数 32 | */ 33 | public JsonObject jsonParams; 34 | /** 35 | * 是否使用标准json解析 36 | */ 37 | public boolean standardDeserialize = true; 38 | /** 39 | * 使用签名,默认否 40 | */ 41 | public boolean signature = false; 42 | /** 43 | * 是否跳转回UI线程 44 | */ 45 | public boolean mainThread = true; 46 | /** 47 | * 头部信息 48 | */ 49 | public Headers.Builder headers = new Headers.Builder(); 50 | /** 51 | * 额外添加的cookie 52 | */ 53 | public String cookie; 54 | /** 55 | * 上传协议类型 56 | */ 57 | public String mimeType; 58 | /** 59 | * 额外的配置数据 60 | */ 61 | public Bundle otherBuilder = new Bundle(); 62 | /** 63 | * 自定义解析Module 64 | */ 65 | public Set customGlintModule = new LinkedHashSet<>(); 66 | /** 67 | * 请求标签,用于取消请求 68 | */ 69 | public int tag; 70 | 71 | public void addCustomGlintModule(@NonNull E customGlintModule) { 72 | this.customGlintModule.add(customGlintModule); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/base/GlintResultBean.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.base; 2 | 3 | import com.ysbing.glint.http.GlintHttpCore; 4 | 5 | import okhttp3.Headers; 6 | 7 | /** 8 | * 接口请求统一结果实体类 9 | * 10 | * @author ysbing 11 | */ 12 | public final class GlintResultBean { 13 | /** 14 | * 原始数据 15 | */ 16 | private String responseStr; 17 | /** 18 | * 运行时状态码,有三种 19 | * 详情看{@link GlintHttpCore} 20 | */ 21 | private Glint.ResultStatus runStatus; 22 | /** 23 | * 接口返回的状态 24 | */ 25 | private int status; 26 | /** 27 | * 解析后数据 28 | */ 29 | private T data; 30 | /** 31 | * 非成功状态的错误码 32 | */ 33 | private String message; 34 | /** 35 | * 头部信息 36 | */ 37 | private Headers headers; 38 | 39 | public GlintResultBean() { 40 | } 41 | 42 | public void setResponseStr(String responseStr) { 43 | this.responseStr = responseStr; 44 | } 45 | 46 | public String getResponseStr() { 47 | return responseStr; 48 | } 49 | 50 | public void setRunStatus(Glint.ResultStatus runStatus) { 51 | this.runStatus = runStatus; 52 | } 53 | 54 | public Glint.ResultStatus getRunStatus() { 55 | return runStatus; 56 | } 57 | 58 | public int getStatus() { 59 | return status; 60 | } 61 | 62 | public void setStatus(int status) { 63 | this.status = status; 64 | } 65 | 66 | public T getData() { 67 | return data; 68 | } 69 | 70 | public void setData(T data) { 71 | this.data = data; 72 | } 73 | 74 | public void setMessage(String message) { 75 | this.message = message; 76 | } 77 | 78 | public String getMessage() { 79 | return message; 80 | } 81 | 82 | public void setHeaders(Headers headers) { 83 | this.headers = headers; 84 | } 85 | 86 | public Headers getHeaders() { 87 | return headers; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/download/GlintDownload.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.download; 2 | 3 | import android.text.TextUtils; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.ysbing.glint.base.BaseHttpModule; 8 | import com.ysbing.glint.util.GlintRequestUtil; 9 | 10 | import java.io.File; 11 | import java.util.TreeMap; 12 | 13 | import okhttp3.Headers; 14 | 15 | /** 16 | * 下载请求的入口 17 | * 该类不可继承,如果有自定义功能需求,就继承{@link GlintDownloadCore}进行扩展 18 | * 19 | * @author ysbing 20 | */ 21 | public final class GlintDownload extends GlintDownloadCore { 22 | 23 | public static GlintDownload download(@NonNull String url, @NonNull String savePath) { 24 | return download(url, new File(savePath)); 25 | } 26 | 27 | public static GlintDownload download(@NonNull String url, @NonNull File saveFile) { 28 | return download(url, saveFile, new TreeMap()); 29 | } 30 | 31 | public static GlintDownload download(@NonNull String url, @NonNull String savePath, @NonNull TreeMap params) { 32 | return download(url, new File(savePath), params); 33 | } 34 | 35 | public static GlintDownload download(@NonNull String url, @NonNull File saveFile, @NonNull TreeMap params) { 36 | return new GlintDownload(url, saveFile, params); 37 | } 38 | 39 | public GlintDownload(@NonNull String url, @NonNull File saveFile, @NonNull TreeMap params) { 40 | mBuilder = createBuilder(); 41 | mBuilder.url = url; 42 | mBuilder.saveFile = saveFile; 43 | mBuilder.params = params; 44 | } 45 | 46 | /** 47 | * 额外设置的header,出去登录相关的 48 | */ 49 | public GlintDownload setHeader(@NonNull Headers.Builder headers) { 50 | mBuilder.headers = headers; 51 | return this; 52 | } 53 | 54 | /** 55 | * 增加cookie到头部信息 56 | * 57 | * @param cookie 登录返回的cookie 58 | */ 59 | public GlintDownload addCookie(@NonNull String cookie) { 60 | mBuilder.cookie = cookie; 61 | return this; 62 | } 63 | 64 | /** 65 | * 设置是否使用通用签名,默认是 66 | * 67 | * @param signature 使用通用签名 68 | */ 69 | public GlintDownload signature(boolean signature) { 70 | mBuilder.signature = signature; 71 | return this; 72 | } 73 | 74 | /** 75 | * 设置文件的协议 76 | * 77 | * @param mimeType 文件协议 78 | */ 79 | public GlintDownload setMimeType(@NonNull String mimeType) { 80 | mBuilder.mimeType = mimeType; 81 | return this; 82 | } 83 | 84 | /** 85 | * 使用自定义Module,可做高级操作 86 | * 该方法将会重置builder,使用时需在第一个使用 87 | * 88 | * @param module 自定义Module 89 | */ 90 | public GlintDownload using(@NonNull BaseHttpModule module) { 91 | super.moduleUsing(module); 92 | return this; 93 | } 94 | 95 | /** 96 | * @param tag 请求标签,用于取消请求 97 | */ 98 | public GlintDownload setTag(@NonNull String tag) { 99 | mBuilder.tag = tag.hashCode(); 100 | return this; 101 | } 102 | 103 | public GlintDownload setTag(int tag) { 104 | mBuilder.tag = tag; 105 | return this; 106 | } 107 | 108 | /** 109 | * @param urgent 是否紧急,用于插队 110 | */ 111 | public GlintDownload urgent(boolean urgent) { 112 | mBuilder.urgent = urgent; 113 | return this; 114 | } 115 | 116 | /** 117 | * @param checkMd5 是否检查文件完整性 118 | */ 119 | public GlintDownload checkMd5(boolean checkMd5) { 120 | mBuilder.checkMd5 = checkMd5; 121 | return this; 122 | } 123 | 124 | /** 125 | * @param md5 传入要检查文件完整性的哈希码 126 | */ 127 | public GlintDownload md5(String md5) { 128 | mBuilder.md5 = md5; 129 | return this; 130 | } 131 | 132 | 133 | /** 134 | * 设置是否使用主线程 135 | * 136 | * @param mainThread 是否使用主线程 137 | */ 138 | public GlintDownload mainThread(boolean mainThread) { 139 | mBuilder.mainThread = mainThread; 140 | return this; 141 | } 142 | 143 | /** 144 | * 暂停 145 | */ 146 | public void pause() { 147 | GlintDownloadDispatcher.getInstance().pause(mBuilder.tag); 148 | } 149 | 150 | /** 151 | * 继续 152 | */ 153 | public void resume() { 154 | GlintDownloadDispatcher.getInstance().resume(mBuilder.tag); 155 | } 156 | 157 | /** 158 | * 用于获取下载的下载状态 159 | * 160 | * @return 是否下载结束 161 | */ 162 | public boolean isFinish() { 163 | return super.isFinish(); 164 | } 165 | 166 | 167 | public synchronized void cancel() { 168 | GlintDownloadDispatcher.getInstance().cancel(mBuilder.tag); 169 | } 170 | 171 | /** 172 | * 执行网络请求 173 | */ 174 | public void execute() { 175 | if (!TextUtils.isEmpty(mBuilder.url)) { 176 | GlintRequestUtil.addDownloadRequestTag(mBuilder); 177 | GlintDownloadDispatcher.getInstance().executed(this, mBuilder.urgent); 178 | } 179 | } 180 | 181 | /** 182 | * 执行网络请求 183 | * 184 | * @param listener 回调 185 | */ 186 | public void execute(@NonNull GlintDownloadListener listener) { 187 | if (!TextUtils.isEmpty(mBuilder.url)) { 188 | boolean newRequest = true; 189 | GlintRequestUtil.addDownloadRequestTag(mBuilder); 190 | for (GlintDownloadCore downloadCore : GlintDownloadDispatcher.getInstance().getDownloaderList()) { 191 | if (downloadCore.mBuilder.tag == mBuilder.tag) { 192 | downloadCore.mBuilder.listeners.add(listener); 193 | newRequest = false; 194 | break; 195 | } 196 | } 197 | if (newRequest) { 198 | mBuilder.listeners.add(listener); 199 | GlintDownloadDispatcher.getInstance().executed(this); 200 | } 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/download/GlintDownloadBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.download; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | import com.ysbing.glint.base.BaseHttpModule; 7 | import com.ysbing.glint.base.Glint; 8 | import com.ysbing.glint.base.GlintBaseBuilder; 9 | 10 | import java.io.File; 11 | import java.util.concurrent.CopyOnWriteArrayList; 12 | 13 | /** 14 | * 下载的参数配置 15 | * 16 | * @author ysbing 17 | */ 18 | public final class GlintDownloadBuilder extends GlintBaseBuilder implements Cloneable { 19 | 20 | /** 21 | * 上层的监听回调 22 | */ 23 | @NonNull 24 | public CopyOnWriteArrayList listeners = new CopyOnWriteArrayList<>(); 25 | /** 26 | * 要保存的文件 27 | */ 28 | public File saveFile; 29 | /** 30 | * 断点续传,用于暂停和恢复 31 | */ 32 | public long range; 33 | /** 34 | * 文件长度 35 | */ 36 | public long contentLength; 37 | /** 38 | * 是否紧急,用于插队,默认是false 39 | */ 40 | public boolean urgent = false; 41 | /** 42 | * 下载完成校验MD5,默认是true 43 | */ 44 | public boolean checkMd5 = true; 45 | /** 46 | * 用于校验文件是否完整 47 | */ 48 | public String md5; 49 | 50 | public GlintDownloadBuilder(@NonNull Glint glint) { 51 | this(glint, true); 52 | } 53 | 54 | public GlintDownloadBuilder(@NonNull Glint glint, boolean init) { 55 | super(); 56 | this.mimeType = "application/x-www-form-urlencoded; charset=utf-8"; 57 | if (init) { 58 | glint.configDefaultBuilder(this); 59 | } 60 | } 61 | 62 | @NonNull 63 | @Override 64 | protected GlintDownloadBuilder clone() throws CloneNotSupportedException { 65 | return (GlintDownloadBuilder) super.clone(); 66 | } 67 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/download/GlintDownloadDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.download; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.collection.SparseArrayCompat; 5 | 6 | import java.util.ArrayDeque; 7 | import java.util.Deque; 8 | import java.util.Iterator; 9 | import java.util.List; 10 | import java.util.concurrent.CopyOnWriteArrayList; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.SynchronousQueue; 13 | import java.util.concurrent.ThreadPoolExecutor; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | import okhttp3.internal.Util; 17 | 18 | /** 19 | * 下载的线程调度 20 | * 21 | * @author ysbing 22 | */ 23 | public final class GlintDownloadDispatcher { 24 | 25 | private int mMaxRequests = 5; 26 | 27 | /** 28 | * Executes calls. Created lazily. 29 | */ 30 | private ExecutorService mExecutorService; 31 | 32 | /** 33 | * Ready async calls in the order they'll be run. 34 | */ 35 | private final Deque mReadyAsyncCalls = new ArrayDeque<>(); 36 | 37 | /** 38 | * Running asynchronous calls. Includes canceled calls that haven't finished yet. 39 | */ 40 | private final List mRunningAsyncCalls = new CopyOnWriteArrayList<>(); 41 | private final SparseArrayCompat mCallTags = new SparseArrayCompat<>(); 42 | 43 | private static final class InstanceHolder { 44 | static final GlintDownloadDispatcher mInstance = new GlintDownloadDispatcher(); 45 | } 46 | 47 | public static GlintDownloadDispatcher getInstance() { 48 | return InstanceHolder.mInstance; 49 | } 50 | 51 | private GlintDownloadDispatcher() { 52 | } 53 | 54 | private synchronized ExecutorService executorService() { 55 | if (mExecutorService == null) { 56 | mExecutorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, 57 | new SynchronousQueue(), Util.threadFactory("GlintDownload Dispatcher", false)); 58 | } 59 | return mExecutorService; 60 | } 61 | 62 | public void setMaxRequests(int maxRequests) { 63 | if (maxRequests > 0) { 64 | this.mMaxRequests = maxRequests; 65 | } 66 | } 67 | 68 | public void executed(GlintDownloadCore call) { 69 | executed(call, false); 70 | } 71 | 72 | public synchronized void executed(GlintDownloadCore call, boolean urgent) { 73 | if (mRunningAsyncCalls.size() < mMaxRequests) { 74 | mRunningAsyncCalls.add(call); 75 | executorService().execute(call); 76 | } else { 77 | if (urgent) { 78 | mReadyAsyncCalls.addFirst(call); 79 | } else { 80 | mReadyAsyncCalls.add(call); 81 | } 82 | } 83 | mCallTags.put(call.mBuilder.tag, call); 84 | } 85 | 86 | public synchronized void pause(int tag) { 87 | GlintDownloadCore call = mCallTags.get(tag); 88 | if (call != null) { 89 | call.corePause(); 90 | } 91 | } 92 | 93 | public synchronized void resume(int tag) { 94 | GlintDownloadCore call = mCallTags.get(tag); 95 | if (call != null) { 96 | executorService().execute(call); 97 | } 98 | } 99 | 100 | public synchronized void cancel(@NonNull String tag) { 101 | cancel(tag.hashCode()); 102 | } 103 | 104 | public synchronized void cancel(int tag) { 105 | GlintDownloadCore call = mCallTags.get(tag); 106 | if (call != null) { 107 | mCallTags.remove(tag); 108 | mReadyAsyncCalls.remove(call); 109 | mRunningAsyncCalls.remove(call); 110 | call.coreCancel(); 111 | } 112 | } 113 | 114 | public GlintDownloadInfo getDownloaderInfo(@NonNull String tag) { 115 | return getDownloaderInfo(tag.hashCode()); 116 | } 117 | 118 | public GlintDownloadInfo getDownloaderInfo(int tag) { 119 | GlintDownloadCore call = mCallTags.get(tag); 120 | if (call != null) { 121 | return call.mDownloadInfo; 122 | } 123 | return null; 124 | } 125 | 126 | public List getDownloaderList() { 127 | return mRunningAsyncCalls; 128 | } 129 | 130 | public synchronized void cancelAll() { 131 | for (GlintDownloadCore call : mReadyAsyncCalls) { 132 | call.coreCancel(); 133 | } 134 | 135 | for (GlintDownloadCore call : mRunningAsyncCalls) { 136 | call.coreCancel(); 137 | } 138 | mCallTags.clear(); 139 | mReadyAsyncCalls.clear(); 140 | mRunningAsyncCalls.clear(); 141 | } 142 | 143 | private void promoteCalls() { 144 | // Already running max capacity. 145 | if (mRunningAsyncCalls.size() >= mMaxRequests) { 146 | return; 147 | } 148 | // No ready calls to promote. 149 | if (mReadyAsyncCalls.isEmpty()) { 150 | return; 151 | } 152 | 153 | for (Iterator i = mReadyAsyncCalls.iterator(); i.hasNext(); ) { 154 | GlintDownloadCore call = i.next(); 155 | i.remove(); 156 | mRunningAsyncCalls.add(call); 157 | executorService().execute(call); 158 | // Reached max capacity. 159 | if (mRunningAsyncCalls.size() >= mMaxRequests) { 160 | return; 161 | } 162 | } 163 | } 164 | 165 | /** 166 | * Used by {@code GlintDownloadCore#run} to signal completion. 167 | */ 168 | void finished(GlintDownloadCore call) { 169 | synchronized (this) { 170 | mCallTags.remove(call.mBuilder.tag); 171 | finished(mRunningAsyncCalls, call); 172 | } 173 | } 174 | 175 | private void finished(List calls, T call) { 176 | calls.remove(call); 177 | promoteCalls(); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/download/GlintDownloadInfo.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.download; 2 | 3 | /** 4 | * 下载信息结构体 5 | * 6 | * @author ysbing 7 | */ 8 | public class GlintDownloadInfo { 9 | /** 10 | * 写入的字节数 11 | */ 12 | public long bytesWritten; 13 | /** 14 | * 文件总长度 15 | */ 16 | public long contentLength; 17 | /** 18 | * 进度条,最小是0,最大是100 19 | */ 20 | public int progress; 21 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/download/GlintDownloadListener.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.download; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import com.ysbing.glint.base.BaseHttpModule; 7 | import com.ysbing.glint.base.GlintResultBean; 8 | import com.ysbing.glint.http.GlintHttpListener; 9 | 10 | import java.io.File; 11 | 12 | import okhttp3.Response; 13 | 14 | /** 15 | * 下载的回调监听 16 | * 17 | * @author ysbing 18 | * 创建于 2018/1/24 19 | */ 20 | public abstract class GlintDownloadListener extends GlintHttpListener { 21 | 22 | /** 23 | * 该方法不回调,禁止重载 24 | * 25 | * @param resultBean 封装结果类 26 | */ 27 | @Override 28 | final public void onResponse(@NonNull GlintResultBean resultBean) throws Throwable { 29 | super.onResponse(resultBean); 30 | } 31 | 32 | /** 33 | * 禁止回调 34 | * 35 | * @param error 错误 36 | */ 37 | @Override 38 | public final void onFail(@NonNull Throwable error) { 39 | super.onFail(error); 40 | } 41 | 42 | @Override 43 | public final void onError(int status, @NonNull String errMsg) throws Throwable { 44 | super.onError(status, errMsg); 45 | } 46 | 47 | @Override 48 | public final void onErrorOrFail() { 49 | super.onErrorOrFail(); 50 | } 51 | 52 | /** 53 | * 该方法在非UI线程 54 | * 55 | * @param downloadBuilder 配置参数 56 | * @return 是否终止,默认否 57 | */ 58 | public boolean onPrepared(@NonNull GlintDownloadBuilder downloadBuilder) { 59 | return false; 60 | } 61 | 62 | /** 63 | * @param bytesWritten 已读取的长度 64 | * @param contentLength 总长度 65 | * @param speed 速度,单位是每秒/字节 66 | * @param percent 百分比,最小是0,最大是100 67 | * @throws Exception 68 | */ 69 | public void onProgress(long bytesWritten, long contentLength, long speed, int percent) throws Exception { 70 | } 71 | 72 | /** 73 | * 下载失败的回调 74 | * 75 | * @param error 异常对象 76 | * @param response 网络响应对象,可能为空,使用时要判空 77 | */ 78 | public void onDownloadFail(@NonNull Throwable error, @Nullable Response response) { 79 | } 80 | 81 | /** 82 | * 暂停 83 | */ 84 | public void onPause() { 85 | } 86 | 87 | /** 88 | * 暂停后恢复 89 | */ 90 | public void onResume() { 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/download/GlintDownloadProgressListener.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.download; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.os.Message; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import java.util.concurrent.CopyOnWriteArrayList; 11 | 12 | /** 13 | * 下载的监听分发 14 | * 15 | * @author ysbing 16 | */ 17 | final class GlintDownloadProgressListener { 18 | private static final int WHAT_UPDATE = 0x01; 19 | private static final String KEY_BYTES_WRITTEN = "KEY_BYTES_WRITTEN"; 20 | private static final String KEY_CONTENT_LENGTH = "KEY_CONTENT_LENGTH"; 21 | private static final String KEY_SPEED = "KEY_SPEED"; 22 | private static final String KEY_PROGRESS = "KEY_PROGRESS"; 23 | private Handler mHandler; 24 | private final CopyOnWriteArrayList mListeners; 25 | private long mLastRefreshTime; 26 | private long mLastBytesWritten; 27 | private int mLastProgress; 28 | 29 | GlintDownloadProgressListener(CopyOnWriteArrayList listeners) { 30 | this.mListeners = listeners; 31 | } 32 | 33 | void onProgressChanged(final long bytesWritten, final long contentLength) { 34 | int progress = contentLength > 0 ? (int) (bytesWritten * 100 / contentLength) : 0; 35 | long time = System.currentTimeMillis(); 36 | if ((progress - mLastProgress > 0 || time - mLastRefreshTime > 200)) { 37 | long lastSpeed = (time - mLastRefreshTime) > 0 ? (bytesWritten - mLastBytesWritten) * 1000 / (time - mLastRefreshTime) : 0L; 38 | mLastRefreshTime = time; 39 | mLastProgress = progress; 40 | mLastBytesWritten = bytesWritten; 41 | ensureHandler(); 42 | Message message = mHandler.obtainMessage(); 43 | message.what = WHAT_UPDATE; 44 | Bundle data = new Bundle(); 45 | data.putLong(KEY_BYTES_WRITTEN, bytesWritten); 46 | data.putLong(KEY_CONTENT_LENGTH, contentLength); 47 | data.putLong(KEY_SPEED, lastSpeed); 48 | data.putInt(KEY_PROGRESS, progress); 49 | message.setData(data); 50 | mHandler.sendMessage(message); 51 | } 52 | } 53 | 54 | private void ensureHandler() { 55 | if (mHandler != null) { 56 | return; 57 | } 58 | mHandler = new Handler(Looper.getMainLooper()) { 59 | @Override 60 | public void handleMessage(@NonNull Message msg) { 61 | super.handleMessage(msg); 62 | if (msg.what == WHAT_UPDATE) { 63 | Bundle updateData = msg.getData(); 64 | if (updateData == null) { 65 | return; 66 | } 67 | final long bytesWritten = updateData.getLong(KEY_BYTES_WRITTEN); 68 | final long contentLength = updateData.getLong(KEY_CONTENT_LENGTH); 69 | final long speed = updateData.getLong(KEY_SPEED); 70 | final int progress = updateData.getInt(KEY_PROGRESS); 71 | for (GlintDownloadListener listener : mListeners) { 72 | try { 73 | listener.onProgress(bytesWritten, contentLength, speed, progress); 74 | } catch (Exception ignored) { 75 | } 76 | } 77 | } 78 | } 79 | }; 80 | } 81 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/http/GilntHttpFragmentLifecycleCallbacks.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.http; 2 | 3 | 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | import androidx.fragment.app.Fragment; 9 | import androidx.fragment.app.FragmentManager; 10 | 11 | import com.ysbing.glint.util.UiStack; 12 | 13 | /** 14 | * 将Fragment的名称收集起来,然后当该Fragment销毁的时候,及时做销毁 15 | * 16 | * @author ysbing 17 | */ 18 | class GilntHttpFragmentLifecycleCallbacks extends FragmentManager.FragmentLifecycleCallbacks { 19 | @Override 20 | public void onFragmentCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @Nullable Bundle savedInstanceState) { 21 | super.onFragmentCreated(fm, f, savedInstanceState); 22 | UiStack.getInstance().pushFragment(f); 23 | GlintHttpDispatcher.getInstance().mHostFragmentNameList.add(f.getClass().getName()); 24 | GlintHttpDispatcher.getInstance().mHashCodeList.add(f.hashCode()); 25 | } 26 | 27 | @Override 28 | public void onFragmentDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) { 29 | super.onFragmentDestroyed(fm, f); 30 | UiStack.getInstance().popFragment(f); 31 | GlintHttpDispatcher.getInstance().mHostFragmentNameList.remove(f.getClass().getName()); 32 | GlintHttpDispatcher.getInstance().mHashCodeList.remove(Integer.valueOf(f.hashCode())); 33 | GlintHttpDispatcher.getInstance().cancelAtHashCode(f.hashCode()); 34 | } 35 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/http/GlintHttp.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.http; 2 | 3 | import android.os.Bundle; 4 | import android.text.TextUtils; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import com.google.gson.JsonObject; 10 | import com.ysbing.glint.base.BaseHttpModule; 11 | import com.ysbing.glint.base.GlintResultBean; 12 | import com.ysbing.glint.util.GlintRequestUtil; 13 | 14 | import java.lang.reflect.Type; 15 | import java.util.TreeMap; 16 | 17 | import okhttp3.Headers; 18 | 19 | /** 20 | * 接口请求的入口 21 | * 该类不可继承,如果有自定义功能需求,就继承{@link GlintHttpCore}进行扩展 22 | * 23 | * @author ysbing 24 | */ 25 | public final class GlintHttp extends GlintHttpCore { 26 | 27 | public static GlintHttp get(@NonNull String url) { 28 | return get(url, new TreeMap()); 29 | } 30 | 31 | public static GlintHttp get(@NonNull String url, @NonNull TreeMap params) { 32 | return new GlintHttp(Method.GET, url, params, null); 33 | } 34 | 35 | public static GlintHttp post(@NonNull String url) { 36 | return post(url, new TreeMap()); 37 | } 38 | 39 | public static GlintHttp post(@NonNull String url, @NonNull TreeMap params) { 40 | return new GlintHttp(Method.POST, url, params, null); 41 | } 42 | 43 | public static GlintHttp post(@NonNull String url, @NonNull JsonObject jsonParams) { 44 | return new GlintHttp(Method.POST, url, new TreeMap(), jsonParams) 45 | .setMimeType("application/json; charset=utf-8"); 46 | } 47 | 48 | public GlintHttp(int method, @NonNull String url) { 49 | this(method, url, new TreeMap(), null); 50 | } 51 | 52 | public GlintHttp(int method, @NonNull String url, @NonNull TreeMap params, 53 | @Nullable JsonObject jsonParams) { 54 | mBuilder = createBuilder(); 55 | mBuilder.method = method; 56 | mBuilder.url = url; 57 | mBuilder.params = params; 58 | mBuilder.jsonParams = jsonParams; 59 | } 60 | 61 | /** 62 | * 额外设置的header,出去登录相关的 63 | */ 64 | public GlintHttp setHeader(@NonNull Headers.Builder headers) { 65 | mBuilder.headers = headers; 66 | return this; 67 | } 68 | 69 | /** 70 | * 增加cookie到头部信息 71 | * 72 | * @param cookie 登录返回的cookie 73 | */ 74 | public GlintHttp addCookie(@NonNull String cookie) { 75 | mBuilder.cookie = cookie; 76 | return this; 77 | } 78 | 79 | /** 80 | * 设置是否使用通用签名,默认是 81 | * 82 | * @param signature 使用通用签名 83 | */ 84 | public GlintHttp signature(boolean signature) { 85 | mBuilder.signature = signature; 86 | return this; 87 | } 88 | 89 | /** 90 | * 设置是否使用标准化序列化,默认否 91 | */ 92 | public GlintHttp standardDeserialize(boolean standardDeserialize) { 93 | mBuilder.standardDeserialize = standardDeserialize; 94 | return this; 95 | } 96 | 97 | /** 98 | * 默认重试20次 99 | * 100 | * @param retryOnConnectionFailure 失败后重试,默认是true 101 | */ 102 | public GlintHttp retryOnConnectionFailure(boolean retryOnConnectionFailure) { 103 | mBuilder.retryOnConnectionFailure = retryOnConnectionFailure; 104 | return this; 105 | } 106 | 107 | /** 108 | * 设置是否使用主线程 109 | * 110 | * @param mainThread 是否使用主线程 111 | */ 112 | public GlintHttp mainThread(boolean mainThread) { 113 | mBuilder.mainThread = mainThread; 114 | return this; 115 | } 116 | 117 | /** 118 | * 结果不是Json 119 | */ 120 | public GlintHttp notJson(boolean notJson) { 121 | mBuilder.notJson = notJson; 122 | return this; 123 | } 124 | 125 | /** 126 | * 设置其他配置 127 | * 128 | * @param otherBuilder 其他配置 129 | */ 130 | public GlintHttp otherBuilder(@NonNull Bundle otherBuilder) { 131 | mBuilder.otherBuilder = otherBuilder; 132 | return this; 133 | } 134 | 135 | /** 136 | * 设置文件的协议 137 | * 138 | * @param mimeType 文件协议 139 | */ 140 | public GlintHttp setMimeType(String mimeType) { 141 | mBuilder.mimeType = mimeType; 142 | return this; 143 | } 144 | 145 | /** 146 | * 使用自定义Module,可做高级操作 147 | * 148 | * @param module 自定义Module 149 | */ 150 | public GlintHttp using(@NonNull BaseHttpModule module) { 151 | super.moduleUsing(module); 152 | return this; 153 | } 154 | 155 | /** 156 | * 重试策略 157 | *

158 | * 保存当前请求 159 | * 错误回调 160 | * 失败回调 161 | *

162 | * 重试次数 163 | * 重试间隔时间 164 | * 支持时间迭代 165 | */ 166 | public GlintHttp retry(@NonNull GlintHttpRetry retry) { 167 | mBuilder.retry = retry; 168 | mBuilder.baseRetry = retry; 169 | return this; 170 | } 171 | 172 | /** 173 | * 已有智能生命周期取消网络请求,这个为额外设置的方法 174 | * 175 | * @param tag 请求标签,用于取消请求 176 | */ 177 | public GlintHttp setTag(@NonNull String tag) { 178 | mBuilder.tag = tag.hashCode(); 179 | return this; 180 | } 181 | 182 | public GlintHttp setTag(int tag) { 183 | mBuilder.tag = tag; 184 | return this; 185 | } 186 | 187 | /** 188 | * 自由的生命周期,不受该框架控制 189 | */ 190 | public GlintHttp freeLife() { 191 | mBuilder.freeLife = true; 192 | return this; 193 | } 194 | 195 | /** 196 | * 设置缓存 197 | * 198 | * @param cacheTime 缓存时间,单位是秒,默认不缓存 199 | */ 200 | public GlintHttp cache(int cacheTime) { 201 | mBuilder.cacheTime = cacheTime; 202 | return this; 203 | } 204 | 205 | /** 206 | * 取消请求 207 | */ 208 | public synchronized void cancel() { 209 | GlintHttpDispatcher.getInstance().cancel(mBuilder.tag); 210 | } 211 | 212 | /** 213 | * 执行网络请求 214 | */ 215 | public void execute() { 216 | if (!TextUtils.isEmpty(mBuilder.url)) { 217 | GlintRequestUtil.addHttpRequestTag(mBuilder); 218 | if (mBuilder.retry != null && mBuilder.baseRetry != null) { 219 | mBuilder.retry = null; 220 | mBuilder.baseRetry.onCreateRequest(this); 221 | } 222 | GlintHttpDispatcher.getInstance().executed(this); 223 | } 224 | } 225 | 226 | /** 227 | * 执行网络请求 228 | * v1.1.4 新增API,改变调用顺序,使用更加合理 229 | * 230 | * @param listener 回调 231 | */ 232 | public void execute(@NonNull GlintHttpListener listener) { 233 | if (!TextUtils.isEmpty(mBuilder.url)) { 234 | mBuilder.listener = listener; 235 | GlintRequestUtil.addHttpRequestTag(mBuilder); 236 | if (mBuilder.retry != null && mBuilder.baseRetry != null) { 237 | mBuilder.retry = null; 238 | mBuilder.baseRetry.onCreateRequest(this); 239 | } 240 | GlintHttpDispatcher.getInstance().executed(this); 241 | } 242 | } 243 | 244 | /** 245 | * 执行同步网络请求,该方法需在子线程发起,否则会异常 246 | * 247 | * @param classOfT 泛型类 248 | */ 249 | public GlintResultBean executeSync(@NonNull Class classOfT) throws Exception { 250 | return executeSync(GlintRequestUtil.getListenerType(classOfT)); 251 | } 252 | 253 | /** 254 | * 执行同步网络请求,该方法需在子线程发起,否则会异常 255 | * 256 | * @param typeOfT 通用型。您可以通过使用{@link com.google.gson.reflect.TypeToken}这个类来获取 257 | */ 258 | public GlintResultBean executeSync(@NonNull Type typeOfT) throws Exception { 259 | if (mBuilder.listener != null) { 260 | mBuilder.listener = null; 261 | } 262 | this.mTypeOfT = typeOfT; 263 | //noinspection unchecked 264 | return runSync(); 265 | } 266 | 267 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/http/GlintHttpActivityLifecycleCallbacks.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.http; 2 | 3 | 4 | import android.annotation.SuppressLint; 5 | import android.app.Activity; 6 | import android.app.ActivityManager; 7 | import android.app.Application; 8 | import android.content.Context; 9 | import android.os.Build; 10 | import android.os.Bundle; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | import androidx.annotation.RequiresApi; 15 | import androidx.fragment.app.FragmentActivity; 16 | 17 | import com.ysbing.glint.util.UiKit; 18 | import com.ysbing.glint.util.UiStack; 19 | 20 | import java.lang.reflect.Field; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | /** 25 | * 将Activity的名称收集起来,然后当该Activity销毁的时候,及时做销毁 26 | * 27 | * @author ysbing 28 | */ 29 | @RequiresApi(api = Build.VERSION_CODES.ICE_CREAM_SANDWICH) 30 | public final class GlintHttpActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks { 31 | private final GilntHttpFragmentLifecycleCallbacks mFragmentLifecycleCallbacks = new GilntHttpFragmentLifecycleCallbacks(); 32 | private ActivityManager mActivityManager; 33 | private Object mActivityThread; 34 | private Field mActivitiesField; 35 | private int mActivityCount; 36 | 37 | public GlintHttpActivityLifecycleCallbacks(Context context) { 38 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 39 | mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 40 | } else { 41 | try { 42 | @SuppressLint("PrivateApi") Class activityThreadClass = Class.forName("android.app.ActivityThread"); 43 | mActivityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null); 44 | mActivitiesField = activityThreadClass.getDeclaredField("mActivities"); 45 | mActivitiesField.setAccessible(true); 46 | } catch (Exception ignored) { 47 | } 48 | } 49 | } 50 | 51 | @Override 52 | public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { 53 | mActivityCount++; 54 | UiStack.getInstance().pushActivity(activity); 55 | GlintHttpDispatcher.getInstance().mHostActivityNameList.add(activity.getClass().getName()); 56 | GlintHttpDispatcher.getInstance().mHashCodeList.add(activity.hashCode()); 57 | if (activity instanceof FragmentActivity) { 58 | ((FragmentActivity) activity).getSupportFragmentManager().registerFragmentLifecycleCallbacks(mFragmentLifecycleCallbacks, true); 59 | } 60 | } 61 | 62 | @Override 63 | public void onActivityStarted(Activity activity) { 64 | 65 | } 66 | 67 | @Override 68 | public void onActivityResumed(Activity activity) { 69 | 70 | } 71 | 72 | @Override 73 | public void onActivityPaused(Activity activity) { 74 | 75 | } 76 | 77 | @Override 78 | public void onActivityStopped(Activity activity) { 79 | 80 | } 81 | 82 | @Override 83 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 84 | 85 | } 86 | 87 | @Override 88 | public void onActivityDestroyed(Activity activity) { 89 | UiStack.getInstance().popActivity(activity); 90 | if (activity instanceof FragmentActivity) { 91 | ((FragmentActivity) activity).getSupportFragmentManager().unregisterFragmentLifecycleCallbacks(mFragmentLifecycleCallbacks); 92 | } 93 | GlintHttpDispatcher.getInstance().mHostActivityNameList.remove(activity.getClass().getName()); 94 | GlintHttpDispatcher.getInstance().mHashCodeList.remove(Integer.valueOf(activity.hashCode())); 95 | GlintHttpDispatcher.getInstance().cancelAtHashCode(activity.hashCode()); 96 | 97 | List tasks; 98 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && (tasks = mActivityManager.getRunningTasks(1)) != null) { 99 | mActivityCount = tasks.size(); 100 | if (tasks.size() == 1 && tasks.get(0).baseActivity.toString().contains("com.google.android")) { 101 | mActivityCount--; 102 | } 103 | } else { 104 | try { 105 | if (mActivitiesField != null && mActivityThread != null) { 106 | Map activities = (Map) mActivitiesField.get(mActivityThread); 107 | mActivityCount = activities.size(); 108 | } 109 | } catch (Exception ignored) { 110 | } finally { 111 | mActivityCount--; 112 | } 113 | } 114 | if (mActivityCount == 0) { 115 | GlintHttpDispatcher.getInstance().cancelAll(); 116 | UiStack.getInstance().popAllActivity(); 117 | UiStack.getInstance().popAllFragment(); 118 | UiKit.dispose(); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/http/GlintHttpBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.http; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.ysbing.glint.base.BaseHttpModule; 6 | import com.ysbing.glint.base.Glint; 7 | import com.ysbing.glint.base.GlintBaseBuilder; 8 | 9 | /** 10 | * 网络请求的参数配置 11 | * 12 | * @author ysbing 13 | */ 14 | public final class GlintHttpBuilder extends GlintBaseBuilder implements Cloneable { 15 | /** 16 | * GET和POST请求方式 17 | */ 18 | public int method; 19 | /** 20 | * 上层的监听回调 21 | */ 22 | public GlintHttpListener listener; 23 | /** 24 | * 结果不是json 25 | */ 26 | public boolean notJson = false; 27 | /** 28 | * 是否重试 29 | */ 30 | public boolean retryOnConnectionFailure = true; 31 | /** 32 | * 自定义重试策略 33 | */ 34 | public GlintHttpRetry retry; 35 | public GlintHttpRetry baseRetry; 36 | /** 37 | * 该请求所属的宿主,有Activity和Fragment 38 | * Fragment支持support的Fragment 39 | * 用于在生命周期时销毁请求 40 | */ 41 | public int hostHashCode; 42 | /** 43 | * 自由的生命周期,不智能销毁请求 44 | */ 45 | public boolean freeLife = false; 46 | /** 47 | * 缓存时间,单位是秒,默认不缓存 48 | */ 49 | public int cacheTime; 50 | 51 | public GlintHttpBuilder(@NonNull Glint glint) { 52 | this(glint, true); 53 | } 54 | 55 | public GlintHttpBuilder(@NonNull Glint glint, boolean init) { 56 | super(); 57 | this.mimeType = "application/x-www-form-urlencoded; charset=utf-8"; 58 | if (init) { 59 | glint.configDefaultBuilder(this); 60 | } 61 | } 62 | 63 | @Override 64 | protected GlintHttpBuilder clone() throws CloneNotSupportedException { 65 | return (GlintHttpBuilder) super.clone(); 66 | } 67 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/http/GlintHttpCache.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.http; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.io.IOException; 6 | 7 | import okhttp3.Interceptor; 8 | import okhttp3.Response; 9 | 10 | /** 11 | * 缓存拦截器 12 | */ 13 | final class GlintHttpCache implements Interceptor { 14 | private final int mCacheTime; 15 | 16 | GlintHttpCache(int cacheTime) { 17 | this.mCacheTime = cacheTime; 18 | } 19 | 20 | @NonNull 21 | @Override 22 | public Response intercept(@NonNull Chain chain) throws IOException { 23 | return chain.proceed(chain.request()).newBuilder() 24 | .removeHeader("Pragma") 25 | .header("Cache-Control", "public, max-age=" + mCacheTime) 26 | .build(); 27 | } 28 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/http/GlintHttpDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.http; 2 | 3 | import androidx.collection.SparseArrayCompat; 4 | 5 | import java.util.ArrayDeque; 6 | import java.util.ArrayList; 7 | import java.util.Deque; 8 | import java.util.Iterator; 9 | import java.util.List; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.SynchronousQueue; 12 | import java.util.concurrent.ThreadPoolExecutor; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | import okhttp3.internal.Util; 16 | 17 | public final class GlintHttpDispatcher { 18 | 19 | private static final int mMaxRequests = 32; 20 | 21 | /** 22 | * Executes calls. Created lazily. 23 | */ 24 | private ExecutorService mExecutorService; 25 | 26 | /** 27 | * Ready async calls in the order they'll be run. 28 | */ 29 | private final Deque> mReadyAsyncCalls = new ArrayDeque<>(); 30 | 31 | /** 32 | * Running asynchronous calls. Includes canceled calls that haven't finished yet. 33 | */ 34 | private final Deque> mRunningAsyncCalls = new ArrayDeque<>(); 35 | private final SparseArrayCompat> mCallTags = new SparseArrayCompat<>(); 36 | private final SparseArrayCompat> mTagActivityHashCode = new SparseArrayCompat<>(); 37 | public final List mHostActivityNameList = new ArrayList<>(); 38 | public final List mHostFragmentNameList = new ArrayList<>(); 39 | final List mHashCodeList = new ArrayList<>(); 40 | 41 | private static final class InstanceHolder { 42 | static final GlintHttpDispatcher mInstance = new GlintHttpDispatcher(); 43 | } 44 | 45 | public static GlintHttpDispatcher getInstance() { 46 | return InstanceHolder.mInstance; 47 | } 48 | 49 | private GlintHttpDispatcher() { 50 | } 51 | 52 | private synchronized ExecutorService executorService() { 53 | if (mExecutorService == null) { 54 | mExecutorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, 55 | new SynchronousQueue(), Util.threadFactory("Glint Dispatcher", false)); 56 | } 57 | return mExecutorService; 58 | } 59 | 60 | public synchronized void executed(GlintHttpCore call) { 61 | if (mRunningAsyncCalls.size() < mMaxRequests) { 62 | mRunningAsyncCalls.add(call); 63 | executorService().execute(call); 64 | } else { 65 | mReadyAsyncCalls.add(call); 66 | } 67 | mCallTags.put(call.mBuilder.tag, call); 68 | 69 | List tags = mTagActivityHashCode.get(call.mBuilder.hostHashCode); 70 | if (tags == null) { 71 | tags = new ArrayList<>(); 72 | } 73 | if (call.mBuilder.tag > 0) { 74 | tags.add(call.mBuilder.tag); 75 | mTagActivityHashCode.put(call.mBuilder.hostHashCode, tags); 76 | } 77 | } 78 | 79 | public synchronized void cancel(String tag) { 80 | cancel(tag.hashCode()); 81 | } 82 | 83 | public synchronized void cancel(int tag) { 84 | GlintHttpCore call = mCallTags.get(tag); 85 | if (call != null) { 86 | mCallTags.remove(tag); 87 | List tags = mTagActivityHashCode.get(call.mBuilder.hostHashCode); 88 | if (tags != null) { 89 | tags.remove(Integer.valueOf(call.mBuilder.tag)); 90 | } 91 | mReadyAsyncCalls.remove(call); 92 | mRunningAsyncCalls.remove(call); 93 | call.coreCancel(); 94 | } 95 | } 96 | 97 | public synchronized void cancelAtHashCode(int hashCode) { 98 | List tags = mTagActivityHashCode.get(hashCode); 99 | if (tags != null) { 100 | mTagActivityHashCode.remove(hashCode); 101 | for (Integer tag : tags) { 102 | if (tag != null) { 103 | cancel(tag); 104 | } 105 | } 106 | } 107 | } 108 | 109 | public synchronized void cancelAll() { 110 | for (GlintHttpCore call : mReadyAsyncCalls) { 111 | call.coreCancel(); 112 | } 113 | 114 | for (GlintHttpCore call : mRunningAsyncCalls) { 115 | call.coreCancel(); 116 | } 117 | mReadyAsyncCalls.clear(); 118 | mRunningAsyncCalls.clear(); 119 | mCallTags.clear(); 120 | mTagActivityHashCode.clear(); 121 | mHostActivityNameList.clear(); 122 | mHostFragmentNameList.clear(); 123 | mHashCodeList.clear(); 124 | } 125 | 126 | private void promoteCalls() { 127 | // Already running max capacity. 128 | if (mRunningAsyncCalls.size() >= mMaxRequests) { 129 | return; 130 | } 131 | // No ready calls to promote. 132 | if (mReadyAsyncCalls.isEmpty()) { 133 | return; 134 | } 135 | 136 | for (Iterator> i = mReadyAsyncCalls.iterator(); i.hasNext(); ) { 137 | GlintHttpCore call = i.next(); 138 | i.remove(); 139 | mRunningAsyncCalls.add(call); 140 | executorService().execute(call); 141 | // Reached max capacity. 142 | if (mRunningAsyncCalls.size() >= mMaxRequests) { 143 | return; 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * Used by {@code GlintHttpCore#run} to signal completion. 150 | */ 151 | void finished(GlintHttpCore call) { 152 | synchronized (this) { 153 | mCallTags.remove(call.mBuilder.tag); 154 | List tags = mTagActivityHashCode.get(call.mBuilder.hostHashCode); 155 | if (tags != null) { 156 | tags.remove(Integer.valueOf(call.mBuilder.tag)); 157 | if (tags.size() == 0) { 158 | mTagActivityHashCode.remove(call.mBuilder.hostHashCode); 159 | } 160 | } 161 | finished(mRunningAsyncCalls, call); 162 | } 163 | } 164 | 165 | private void finished(Deque calls, T call) { 166 | calls.remove(call); 167 | promoteCalls(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/http/GlintHttpListener.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.http; 2 | 3 | import androidx.annotation.CallSuper; 4 | import androidx.annotation.NonNull; 5 | 6 | import com.ysbing.glint.base.GlintResultBean; 7 | 8 | /** 9 | * 请求监听类 10 | * 11 | * @author ysbing 12 | */ 13 | public abstract class GlintHttpListener { 14 | 15 | /** 16 | * 请求之前,开始的回调 17 | */ 18 | public void onStart() { 19 | } 20 | 21 | /** 22 | * @param resultBean 封装结果类 23 | */ 24 | public void onResponse(@NonNull GlintResultBean resultBean) throws Throwable { 25 | } 26 | 27 | /** 28 | * @param result 经过反序列化后的实体类 29 | */ 30 | public void onSuccess(@NonNull T result) throws Throwable { 31 | } 32 | 33 | /** 34 | * 当http状态码200,状态码非200的回调 35 | * 36 | * @param status 状态码 37 | * @param errMsg 对应的错误提示 38 | */ 39 | public void onError(int status, @NonNull String errMsg) throws Throwable { 40 | } 41 | 42 | /** 43 | * 当网络请求出现错误的时候,或者非标准json解析错误时的回调 44 | * 45 | * @param error 错误 46 | */ 47 | @CallSuper 48 | public void onFail(@NonNull Throwable error) { 49 | error.printStackTrace(); 50 | } 51 | 52 | /** 53 | * 当出现onError或者onFail的时候,会同时响应该回调 54 | * 如果有公共处理的逻辑,可以在此回调进行 55 | */ 56 | public void onErrorOrFail() { 57 | } 58 | 59 | /** 60 | * 取消该请求 61 | */ 62 | public void onCancel() { 63 | } 64 | 65 | /** 66 | * 无论成功与否,都会响应该回调 67 | */ 68 | public void onFinish() { 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/http/GlintHttpRetry.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.http; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | /** 6 | * 重试接口类 7 | * 8 | * @author ysbing 9 | */ 10 | public interface GlintHttpRetry { 11 | /** 12 | * 创建请求 13 | * 14 | * @param request 将该请求保存起来,在重试的时候使用 15 | */ 16 | void onCreateRequest(@NonNull GlintHttp request); 17 | 18 | /** 19 | * 请求失败的时候回调,一般是网络超时等原因 20 | * 21 | * @param error 错误对象 22 | * @return 是否继续传递 23 | */ 24 | boolean retryOnFail(@NonNull Throwable error); 25 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/GlintSocket.java: -------------------------------------------------------------------------------- 1 | 2 | package com.ysbing.glint.socket; 3 | 4 | import android.content.Context; 5 | import android.text.TextUtils; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | 10 | import com.ysbing.glint.socket.socketio.GlintSocketIOCore; 11 | import com.ysbing.glint.socket.socketio.Protocol; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * Socket请求类,请求的入口 18 | * 19 | * @author ysbing 20 | */ 21 | public class GlintSocket { 22 | 23 | /** 24 | * 连接成功事件 25 | */ 26 | public static final String EVENT_CONNECT = "EVENT_CONNECT"; 27 | /** 28 | * 正常断开连接事件 29 | */ 30 | public static final String EVENT_DISCONNECT = "EVENT_DISCONNECT"; 31 | /** 32 | * 异常断开事件,如网络中断 33 | */ 34 | public static final String EVENT_ERROR = "EVENT_ERROR"; 35 | public static final List ALL_EVENT = new ArrayList() {{ 36 | add(EVENT_CONNECT); 37 | add(EVENT_DISCONNECT); 38 | add(EVENT_ERROR); 39 | }}; 40 | /** 41 | * 网络错误 42 | */ 43 | public static final int ERROR_NET = 0x1; 44 | /** 45 | * 异常错误 46 | */ 47 | public static final int ERROR_EXCEPTION = 0x2; 48 | 49 | private final GlintSocketBuilder builder; 50 | 51 | /** 52 | * 如果有跨进程需求,就做初始化 53 | * 在单一进程使用的话,不需要初始化 54 | * 55 | * @param context 上下文对象 56 | */ 57 | public static void init(@NonNull Context context) { 58 | GlintSocketDispatcher.getInstance().init(context.getApplicationContext()); 59 | } 60 | 61 | /** 62 | * 删除所有,包括Socket的连接 63 | */ 64 | public static void removeAll() { 65 | GlintSocketDispatcher.getInstance().removeAll(); 66 | } 67 | 68 | /** 69 | * 发送一条socket消息 70 | */ 71 | public static GlintSocket send(@NonNull String url, @NonNull String message) { 72 | return new GlintSocket(url, null, message, -1, GlintSocketBuilder.RequestType.SEND); 73 | } 74 | 75 | /** 76 | * 发送一条socket消息 77 | */ 78 | public static GlintSocket send(@NonNull String url, @Nullable String cmdId, @NonNull String message) { 79 | return new GlintSocket(url, cmdId, message, -1, GlintSocketBuilder.RequestType.SEND); 80 | } 81 | 82 | /** 83 | * 发送一条柚子IO socket消息 84 | */ 85 | public static GlintSocket sendIO(@NonNull String url, @NonNull String cmdId, @NonNull String message) { 86 | final int sendId = GlintSocketIOCore.sSendId.incrementAndGet(); 87 | return new GlintSocket(url, cmdId, "3:::" + Protocol.encode(sendId, cmdId, message), sendId, GlintSocketBuilder.RequestType.IO_SEND); 88 | } 89 | 90 | /** 91 | * 设置推送监听 92 | */ 93 | public static GlintSocket on(@NonNull String url, @NonNull String cmdId) { 94 | return new GlintSocket(url, cmdId, "", -1, GlintSocketBuilder.RequestType.PUSH_LISTENER); 95 | } 96 | 97 | /** 98 | * 设置柚子IO推送监听 99 | */ 100 | public static GlintSocket onIO(@NonNull String url, @NonNull String cmdId) { 101 | return new GlintSocket(url, cmdId, "", -1, GlintSocketBuilder.RequestType.IO_PUSH_LISTENER); 102 | } 103 | 104 | /** 105 | * 移除推送监听 106 | */ 107 | public static void off(@NonNull String url) { 108 | off(url, ""); 109 | } 110 | 111 | public static void off(@NonNull String url, @Nullable String cmdId) { 112 | off(url, cmdId, 0); 113 | } 114 | 115 | public static void off(@NonNull String url, @Nullable String cmdId, int tag) { 116 | GlintSocketBuilder builder = new GlintSocketBuilder<>(); 117 | builder.url = url; 118 | builder.cmdId = cmdId; 119 | builder.tag = tag; 120 | GlintSocketDispatcher.getInstance().removePushListener(builder); 121 | } 122 | 123 | public void off() { 124 | GlintSocketDispatcher.getInstance().removePushListener(builder); 125 | } 126 | 127 | /** 128 | * @param tag 请求标签,用于取消请求 129 | */ 130 | public GlintSocket setTag(@NonNull String tag) { 131 | builder.tag = tag.hashCode(); 132 | return this; 133 | } 134 | 135 | public GlintSocket setTag(int tag) { 136 | builder.tag = tag; 137 | return this; 138 | } 139 | 140 | /** 141 | * 使用自定义Module,可做高级操作 142 | * 143 | * @param module 自定义Module 144 | */ 145 | public GlintSocket using(@NonNull SocketHttpModule module) { 146 | builder.customGlintModule = module; 147 | return this; 148 | } 149 | 150 | public GlintSocket(@NonNull String url, @Nullable String cmdId, @NonNull String params, int sendId, @NonNull GlintSocketBuilder.RequestType requestType) { 151 | builder = new GlintSocketBuilder<>(); 152 | builder.url = url; 153 | builder.cmdId = cmdId; 154 | builder.params = params; 155 | builder.sendId = sendId; 156 | builder.requestType = requestType; 157 | } 158 | 159 | public void execute() { 160 | execute(null); 161 | } 162 | 163 | public void execute(@Nullable GlintSocketListener listener) { 164 | if (!TextUtils.isEmpty(builder.url)) { 165 | builder.listener = listener; 166 | if (builder.tag == 0) { 167 | builder.tag = System.identityHashCode(builder); 168 | } 169 | switch (builder.requestType) { 170 | case SEND: 171 | if (builder.sendId != -1) { 172 | builder.cmdId += GlintSocket.class.getSimpleName() + builder.sendId; 173 | } 174 | GlintSocketDispatcher.getInstance().send(builder); 175 | break; 176 | case IO_SEND: 177 | if (builder.sendId != -1) { 178 | builder.cmdId += GlintSocket.class.getSimpleName() + builder.sendId; 179 | } 180 | GlintSocketDispatcher.getInstance().sendIO(builder); 181 | break; 182 | case PUSH_LISTENER: 183 | GlintSocketDispatcher.getInstance().on(builder); 184 | break; 185 | case IO_PUSH_LISTENER: 186 | GlintSocketDispatcher.getInstance().onIO(builder); 187 | break; 188 | default: 189 | break; 190 | } 191 | } 192 | } 193 | 194 | 195 | } 196 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/GlintSocketBuilder.java: -------------------------------------------------------------------------------- 1 | 2 | package com.ysbing.glint.socket; 3 | 4 | /** 5 | * 重要类,使用时定义的任务 6 | * 7 | * @author ysbing 8 | */ 9 | public final class GlintSocketBuilder { 10 | /** 11 | * 请求的地址 12 | */ 13 | public String url; 14 | /** 15 | * 消息命令 16 | */ 17 | public String cmdId; 18 | /** 19 | * 请求参数 20 | */ 21 | public String params; 22 | /** 23 | * 发送消息的id,用于回调识别 24 | */ 25 | public int sendId; 26 | /** 27 | * 上层的监听回调 28 | */ 29 | public GlintSocketListener listener; 30 | /** 31 | * 请求类型,有发送消息和监听推送两种 32 | */ 33 | public RequestType requestType; 34 | /** 35 | * 请求标签,用于取消请求 36 | */ 37 | public int tag; 38 | /** 39 | * 自定义解析Module 40 | */ 41 | public SocketHttpModule customGlintModule; 42 | 43 | public enum RequestType { 44 | SEND, 45 | IO_SEND, 46 | PUSH_LISTENER, 47 | IO_PUSH_LISTENER 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/GlintSocketBuilderStub.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.socket; 2 | 3 | import static com.ysbing.glint.util.GlintRequestUtil.sGson; 4 | 5 | import android.os.RemoteException; 6 | 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonParser; 9 | import com.ysbing.glint.util.GlintRequestUtil; 10 | import com.ysbing.glint.util.UiKit; 11 | 12 | import java.lang.reflect.Type; 13 | 14 | /** 15 | * 只做Google的Gson中的JsonObject 16 | * 该类可做参考,用于扩展更多的实现 17 | * 18 | * @author ysbing 19 | */ 20 | public class GlintSocketBuilderStub extends GlintSocketBuilderWrapper.Stub { 21 | 22 | private final GlintSocketBuilder builder; 23 | private Type typeOfT; 24 | 25 | public GlintSocketBuilderStub(GlintSocketBuilder builder) { 26 | this.builder = builder; 27 | if (builder.listener != null) { 28 | typeOfT = GlintRequestUtil.getListenerType(builder.listener.getClass()); 29 | } 30 | } 31 | 32 | @Override 33 | public String getUrl() { 34 | return builder.url; 35 | } 36 | 37 | @Override 38 | public String getCmdId() { 39 | return builder.cmdId; 40 | } 41 | 42 | @Override 43 | public String getParams() { 44 | return builder.params; 45 | } 46 | 47 | @Override 48 | public int getSendId() { 49 | return builder.sendId; 50 | } 51 | 52 | @Override 53 | public int getTag() { 54 | return builder.tag; 55 | } 56 | 57 | @Override 58 | public String getResponseCmdId(String response) throws RemoteException { 59 | if (builder.listener != null && builder.customGlintModule != null) { 60 | if (!typeOfT.equals(Void.class)) { 61 | JsonParser parser = new JsonParser(); 62 | JsonElement jsonElement = parser.parse(response); 63 | return builder.customGlintModule.getCmdId(jsonElement, sGson, typeOfT); 64 | } 65 | } 66 | return ""; 67 | } 68 | 69 | @Override 70 | public void onResponse(String response) throws RemoteException { 71 | if (builder.listener != null) { 72 | T t; 73 | if (!typeOfT.equals(Void.class)) { 74 | JsonElement jsonElement = JsonParser.parseString(response); 75 | if (builder.customGlintModule != null) { 76 | try { 77 | t = builder.customGlintModule.customDeserialize(jsonElement, sGson, typeOfT); 78 | onProcess(t); 79 | } catch (Exception e) { 80 | onError(e.getMessage()); 81 | } 82 | } else { 83 | t = GlintRequestUtil.successDeserialize(sGson, jsonElement, typeOfT); 84 | onProcess(t); 85 | } 86 | } 87 | } 88 | } 89 | 90 | private void onProcess(final T t) { 91 | UiKit.runOnMainThreadAsync(new Runnable() { 92 | @Override 93 | public void run() { 94 | try { 95 | builder.listener.onProcess(t); 96 | } catch (Throwable e) { 97 | onError(e.getMessage()); 98 | } 99 | } 100 | }); 101 | } 102 | 103 | @Override 104 | public void onError(final String error) { 105 | if (builder.listener != null) { 106 | UiKit.runOnMainThreadAsync(new Runnable() { 107 | @Override 108 | public void run() { 109 | builder.listener.onError(error); 110 | } 111 | }); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/GlintSocketCore.java: -------------------------------------------------------------------------------- 1 | 2 | package com.ysbing.glint.socket; 3 | 4 | import android.text.TextUtils; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import com.ysbing.glint.base.Glint; 10 | import com.ysbing.glint.socket.socketio.GlintSocketIOCallback; 11 | import com.ysbing.glint.socket.socketio.IOWebSocketTransport; 12 | 13 | import java.net.URI; 14 | import java.util.ArrayDeque; 15 | import java.util.Deque; 16 | import java.util.Map; 17 | import java.util.Objects; 18 | import java.util.Set; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | 21 | import okhttp3.OkHttpClient; 22 | import okhttp3.Request; 23 | import okhttp3.Response; 24 | import okhttp3.WebSocket; 25 | import okhttp3.WebSocketListener; 26 | 27 | /** 28 | * WebSocket核心类 29 | * 30 | * @author ysbing 31 | * 创建于 2020/6/25 32 | */ 33 | public class GlintSocketCore { 34 | 35 | private static final Glint GLINT = Glint.getsInstance(); 36 | private static final OkHttpClient sClient; 37 | 38 | private WebSocket mWebSocket; 39 | private final URI mUrl; 40 | private final Deque mWaitMessages = new ArrayDeque<>(); 41 | private final Map> mAsyncListeners = new ConcurrentHashMap<>(); 42 | private boolean mConnecting = false; 43 | private boolean mConnected = false; 44 | 45 | static { 46 | if (GLINT != null) { 47 | sClient = GLINT.onOkHttpBuildCreate(Glint.GlintType.SOCKET, new OkHttpClient.Builder()).build(); 48 | } else { 49 | sClient = new OkHttpClient.Builder().build(); 50 | } 51 | } 52 | 53 | GlintSocketCore(@NonNull URI url) { 54 | this.mUrl = url; 55 | } 56 | 57 | synchronized void connect() { 58 | if (mConnecting || mConnected) { 59 | return; 60 | } 61 | mConnecting = true; 62 | IOWebSocketTransport.getUrl(mUrl, new GlintSocketIOCallback() { 63 | @Override 64 | public void onSocketUrl(@NonNull String socketUrl) { 65 | socketConnect(socketUrl); 66 | } 67 | 68 | @Override 69 | public void onError(@NonNull Throwable throwable) { 70 | mConnecting = false; 71 | Set keySet = mAsyncListeners.keySet(); 72 | for (String s : keySet) { 73 | if (GlintSocket.ALL_EVENT.contains(s)) { 74 | continue; 75 | } 76 | GlintSocketListener listener = mAsyncListeners.get(s); 77 | if (listener != null) { 78 | listener.onError(throwable.toString()); 79 | } 80 | } 81 | if (mAsyncListeners.containsKey(GlintSocket.EVENT_ERROR)) { 82 | GlintSocketListener listener = mAsyncListeners.get(GlintSocket.EVENT_ERROR); 83 | if (listener != null) { 84 | try { 85 | SocketInnerResultBean bean = new SocketInnerResultBean(); 86 | bean.response = String.valueOf(GlintSocket.ERROR_NET); 87 | bean.msgType = 1; 88 | listener.onProcess(bean); 89 | } catch (Throwable ignored) { 90 | } 91 | } 92 | } 93 | } 94 | }); 95 | } 96 | 97 | private void socketConnect(@NonNull String socketUrl) { 98 | if (mWebSocket != null) { 99 | mWebSocket.cancel(); 100 | mWebSocket = null; 101 | } 102 | Request request = new Request.Builder().url(socketUrl).build(); 103 | sClient.newWebSocket(request, getSocketListener()); 104 | } 105 | 106 | private WebSocketListener getSocketListener() { 107 | return new WebSocketListener() { 108 | @Override 109 | public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) { 110 | super.onOpen(webSocket, response); 111 | mWebSocket = webSocket; 112 | mConnecting = false; 113 | mConnected = true; 114 | for (String waitMessage : mWaitMessages) { 115 | webSocket.send(waitMessage); 116 | } 117 | mWaitMessages.clear(); 118 | if (mAsyncListeners.containsKey(GlintSocket.EVENT_CONNECT)) { 119 | GlintSocketListener listener = mAsyncListeners.get(GlintSocket.EVENT_CONNECT); 120 | if (listener != null) { 121 | try { 122 | SocketInnerResultBean bean = new SocketInnerResultBean(); 123 | bean.response = ""; 124 | bean.msgType = 1; 125 | listener.onProcess(bean); 126 | } catch (Throwable ignored) { 127 | } 128 | } 129 | } 130 | } 131 | 132 | @Override 133 | public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) { 134 | super.onMessage(webSocket, text); 135 | messagePush(text); 136 | } 137 | 138 | @Override 139 | public void onClosing(@NonNull WebSocket webSocket, int code, @NonNull String reason) { 140 | super.onClosing(webSocket, code, reason); 141 | mConnected = false; 142 | } 143 | 144 | @Override 145 | public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) { 146 | super.onClosed(webSocket, code, reason); 147 | mConnected = false; 148 | if (mAsyncListeners.containsKey(GlintSocket.EVENT_DISCONNECT)) { 149 | GlintSocketListener listener = mAsyncListeners.get(GlintSocket.EVENT_DISCONNECT); 150 | if (listener != null) { 151 | try { 152 | SocketInnerResultBean bean = new SocketInnerResultBean(); 153 | bean.response = ""; 154 | bean.msgType = 1; 155 | listener.onProcess(bean); 156 | } catch (Throwable ignored) { 157 | } 158 | } 159 | } 160 | } 161 | 162 | @Override 163 | public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, @Nullable Response response) { 164 | super.onFailure(webSocket, t, response); 165 | if (mAsyncListeners.containsKey(GlintSocket.EVENT_ERROR)) { 166 | GlintSocketListener listener = mAsyncListeners.get(GlintSocket.EVENT_ERROR); 167 | if (listener != null) { 168 | try { 169 | SocketInnerResultBean bean = new SocketInnerResultBean(); 170 | bean.response = String.valueOf(GlintSocket.ERROR_EXCEPTION); 171 | bean.msgType = 1; 172 | listener.onProcess(bean); 173 | } catch (Throwable ignored) { 174 | } 175 | } 176 | } 177 | disconnect(); 178 | } 179 | }; 180 | } 181 | 182 | private void messagePush(String messageStr) { 183 | Set keySet = mAsyncListeners.keySet(); 184 | for (String s : keySet) { 185 | GlintSocketListener listener = mAsyncListeners.get(s); 186 | if (listener != null) { 187 | try { 188 | SocketInnerResultBean bean = new SocketInnerResultBean(); 189 | bean.response = messageStr; 190 | bean.msgType = 0; 191 | listener.onProcess(bean); 192 | } catch (Throwable e) { 193 | listener.onError(Objects.requireNonNull(e.getMessage())); 194 | } 195 | } 196 | } 197 | } 198 | 199 | synchronized void disconnect() { 200 | mConnected = false; 201 | mConnecting = false; 202 | mAsyncListeners.clear(); 203 | if (mWebSocket != null) { 204 | mWebSocket.cancel(); 205 | mWebSocket = null; 206 | } 207 | } 208 | 209 | boolean isConnected() { 210 | return mConnected; 211 | } 212 | 213 | void on(@NonNull String cmdId, @NonNull GlintSocketListener listener) { 214 | mAsyncListeners.put(cmdId, listener); 215 | } 216 | 217 | synchronized void send(@NonNull String message) { 218 | if (mWebSocket != null && isConnected()) { 219 | mWebSocket.send(message); 220 | } else { 221 | mWaitMessages.add(message); 222 | } 223 | } 224 | 225 | public synchronized void off(@Nullable String cmdId) { 226 | if (TextUtils.isEmpty(cmdId)) { 227 | disconnect(); 228 | } else { 229 | for (String key : mAsyncListeners.keySet()) { 230 | if (key.contains(GlintSocket.class.getSimpleName()) && key.contains(cmdId)) { 231 | mAsyncListeners.remove(key); 232 | } 233 | } 234 | mAsyncListeners.remove(cmdId); 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/GlintSocketDispatcher.java: -------------------------------------------------------------------------------- 1 | 2 | package com.ysbing.glint.socket; 3 | 4 | import android.app.Service; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.ServiceConnection; 9 | import android.os.IBinder; 10 | import android.os.RemoteException; 11 | 12 | import androidx.annotation.NonNull; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.concurrent.LinkedBlockingQueue; 17 | 18 | /** 19 | * Socket请求类,请求的入口 20 | * 21 | * @author ysbing 22 | */ 23 | public class GlintSocketDispatcher implements ServiceConnection { 24 | 25 | private static GlintSocketDispatcher mInstance = null; 26 | 27 | private final LinkedBlockingQueue mQueue = new LinkedBlockingQueue<>(); 28 | private final LinkedBlockingQueue mIOQueue = new LinkedBlockingQueue<>(); 29 | private final List mSocketTaskBuilders = new ArrayList<>(); 30 | private final GlintSocketServiceStub mSocketServiceStub; 31 | 32 | private GlintSocketService mService; 33 | private boolean mInit; 34 | 35 | private GlintSocketDispatcher() { 36 | Thread worker = new Worker(); 37 | Thread workerIO = new WorkerIO(); 38 | worker.start(); 39 | workerIO.start(); 40 | mSocketServiceStub = new GlintSocketServiceStub(); 41 | } 42 | 43 | public static GlintSocketDispatcher getInstance() { 44 | if (mInstance == null) { 45 | synchronized (GlintSocketDispatcher.class) { 46 | if (mInstance == null) { 47 | mInstance = new GlintSocketDispatcher(); 48 | } 49 | } 50 | } 51 | return mInstance; 52 | } 53 | 54 | /** 55 | * 如果有跨进程需求,就做初始化 56 | * 在单一进程使用的话,不需要初始化 57 | * 58 | * @param context 上下文对象 59 | */ 60 | void init(Context context) { 61 | mInit = true; 62 | if (mService == null) { 63 | Intent i = new Intent(context, GlintSocketServiceNative.class); 64 | context.bindService(i, mInstance, Service.BIND_AUTO_CREATE); 65 | } 66 | } 67 | 68 | /** 69 | * 发送一条socket消息 70 | */ 71 | void send(@NonNull GlintSocketBuilder builder) { 72 | mQueue.offer(new GlintSocketBuilderStub<>(builder)); 73 | } 74 | 75 | /** 76 | * 发送一条柚子IO socket消息 77 | */ 78 | void sendIO(@NonNull GlintSocketBuilder builder) { 79 | mIOQueue.offer(new GlintSocketBuilderStub<>(builder)); 80 | } 81 | 82 | /** 83 | * 设置一个推送监听 84 | */ 85 | public void on(@NonNull GlintSocketBuilder builder) { 86 | if (mService == null && mInit) { 87 | mSocketTaskBuilders.add(builder); 88 | return; 89 | } 90 | try { 91 | if (mService != null) { 92 | mService.connect(builder.url); 93 | } else { 94 | mSocketServiceStub.connect(builder.url); 95 | } 96 | } catch (RemoteException e) { 97 | e.printStackTrace(); 98 | } 99 | try { 100 | if (mService != null) { 101 | mService.on(new GlintSocketBuilderStub<>(builder)); 102 | } else { 103 | mSocketServiceStub.on(new GlintSocketBuilderStub<>(builder)); 104 | } 105 | } catch (Exception e) { 106 | if (builder.listener != null) { 107 | builder.listener.onError(e.getMessage()); 108 | } 109 | } 110 | } 111 | 112 | /** 113 | * 设置一个柚子IO推送监听 114 | */ 115 | public void onIO(@NonNull GlintSocketBuilder builder) { 116 | if (mService == null && mInit) { 117 | mSocketTaskBuilders.add(builder); 118 | return; 119 | } 120 | try { 121 | if (mService != null) { 122 | mService.connectIO(builder.url); 123 | } else { 124 | mSocketServiceStub.connectIO(builder.url); 125 | } 126 | } catch (RemoteException e) { 127 | e.printStackTrace(); 128 | } 129 | try { 130 | if (mService != null) { 131 | mService.onIO(new GlintSocketBuilderStub<>(builder)); 132 | } else { 133 | mSocketServiceStub.onIO(new GlintSocketBuilderStub<>(builder)); 134 | } 135 | } catch (Exception e) { 136 | if (builder.listener != null) { 137 | builder.listener.onError(e.getMessage()); 138 | } 139 | } 140 | } 141 | 142 | /** 143 | * 移除发送队列 144 | */ 145 | void removePushListener(GlintSocketBuilder builder) { 146 | try { 147 | if (mService != null) { 148 | mService.off(builder.url, builder.cmdId, builder.tag); 149 | } else { 150 | mSocketServiceStub.off(builder.url, builder.cmdId, builder.tag); 151 | } 152 | } catch (RemoteException e) { 153 | e.printStackTrace(); 154 | } 155 | } 156 | 157 | /** 158 | * 删除所有,包括Socket的连接 159 | */ 160 | void removeAll() { 161 | try { 162 | if (mService != null) { 163 | mService.offAll(); 164 | } else { 165 | mSocketServiceStub.offAll(); 166 | } 167 | } catch (RemoteException e) { 168 | e.printStackTrace(); 169 | } 170 | } 171 | 172 | 173 | @Override 174 | public void onServiceConnected(ComponentName name, IBinder iBinder) { 175 | try { 176 | mService = GlintSocketService.Stub.asInterface(iBinder); 177 | for (GlintSocketBuilder taskBuilder : mSocketTaskBuilders) { 178 | onIO(taskBuilder); 179 | } 180 | } catch (Exception e) { 181 | mService = null; 182 | } 183 | } 184 | 185 | @Override 186 | public void onServiceDisconnected(ComponentName name) { 187 | mService = null; 188 | } 189 | 190 | private void continueProcessTaskWrappers() { 191 | try { 192 | GlintSocketBuilderWrapper taskWrapper = mQueue.take(); 193 | if (mService != null) { 194 | if (taskWrapper == null) { 195 | return; 196 | } 197 | mService.send(taskWrapper); 198 | } else { 199 | if (taskWrapper == null) { 200 | return; 201 | } 202 | mSocketServiceStub.send(taskWrapper); 203 | } 204 | } catch (Exception ignored) { 205 | } 206 | } 207 | 208 | private void continueProcessTaskWrappersIO() { 209 | try { 210 | GlintSocketBuilderWrapper taskWrapper = mIOQueue.take(); 211 | if (mService != null) { 212 | if (taskWrapper == null) { 213 | return; 214 | } 215 | mService.sendIO(taskWrapper); 216 | } else { 217 | if (taskWrapper == null) { 218 | return; 219 | } 220 | mSocketServiceStub.sendIO(taskWrapper); 221 | } 222 | } catch (Exception ignored) { 223 | } 224 | } 225 | 226 | private class Worker extends Thread { 227 | @Override 228 | public void run() { 229 | //noinspection InfiniteLoopStatement 230 | for (; ; ) { 231 | continueProcessTaskWrappers(); 232 | try { 233 | Thread.sleep(50); 234 | } catch (InterruptedException e) { 235 | // 236 | } 237 | } 238 | } 239 | } 240 | 241 | private class WorkerIO extends Thread { 242 | @Override 243 | public void run() { 244 | //这样写可以防止代码注入 245 | //noinspection InfiniteLoopStatement 246 | for (; ; ) { 247 | continueProcessTaskWrappersIO(); 248 | try { 249 | Thread.sleep(50); 250 | } catch (InterruptedException e) { 251 | // 252 | } 253 | } 254 | } 255 | } 256 | 257 | } 258 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/GlintSocketListener.java: -------------------------------------------------------------------------------- 1 | 2 | package com.ysbing.glint.socket; 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | /** 7 | * Socket监听器 8 | * 9 | * @author ysbing 10 | */ 11 | public abstract class GlintSocketListener { 12 | 13 | public void onProcess(@NonNull T result) throws Throwable { 14 | } 15 | 16 | public void onError(@NonNull String error) { 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/GlintSocketServiceNative.java: -------------------------------------------------------------------------------- 1 | 2 | package com.ysbing.glint.socket; 3 | 4 | import android.app.Service; 5 | import android.content.Intent; 6 | import android.os.IBinder; 7 | import android.os.RemoteException; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | /** 12 | * Service接口 13 | * 14 | * @author ysbing 15 | */ 16 | public class GlintSocketServiceNative extends Service implements GlintSocketService { 17 | 18 | private GlintSocketServiceStub mStub; 19 | 20 | @Nullable 21 | @Override 22 | public IBinder onBind(Intent intent) { 23 | return mStub; 24 | } 25 | 26 | @Override 27 | public void onCreate() { 28 | super.onCreate(); 29 | mStub = new GlintSocketServiceStub(); 30 | } 31 | 32 | @Override 33 | public void connect(String url) throws RemoteException { 34 | mStub.connect(url); 35 | } 36 | 37 | @Override 38 | public void connectIO(String url) throws RemoteException { 39 | mStub.connectIO(url); 40 | } 41 | 42 | @Override 43 | public void send(GlintSocketBuilderWrapper builderWrapper) throws RemoteException { 44 | mStub.send(builderWrapper); 45 | } 46 | 47 | @Override 48 | public void sendIO(GlintSocketBuilderWrapper builderWrapper) throws RemoteException { 49 | mStub.sendIO(builderWrapper); 50 | } 51 | 52 | @Override 53 | public void on(GlintSocketBuilderWrapper builderWrapper) throws RemoteException { 54 | mStub.on(builderWrapper); 55 | } 56 | 57 | @Override 58 | public void onIO(GlintSocketBuilderWrapper builderWrapper) throws RemoteException { 59 | mStub.onIO(builderWrapper); 60 | } 61 | 62 | @Override 63 | public void off(String url, String cmdId, int tag) throws RemoteException { 64 | mStub.off(url, cmdId, tag); 65 | } 66 | 67 | @Override 68 | public void offAll() throws RemoteException { 69 | mStub.offAll(); 70 | } 71 | 72 | @Override 73 | public IBinder asBinder() { 74 | return mStub; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/SocketHttpModule.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.socket; 2 | 3 | 4 | import static com.ysbing.glint.util.GlintRequestUtil.sGson; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.google.gson.Gson; 9 | import com.google.gson.JsonElement; 10 | import com.ysbing.glint.util.GlintRequestUtil; 11 | 12 | import java.lang.reflect.Type; 13 | 14 | /** 15 | * 自定义Socket网络模块 16 | * 17 | * @author ysbing 18 | */ 19 | public abstract class SocketHttpModule { 20 | 21 | public String getCmdId(@NonNull JsonElement jsonEl, @NonNull Gson gson, @NonNull Type typeOfT) { 22 | return null; 23 | } 24 | 25 | public T customDeserialize(@NonNull JsonElement jsonEl, @NonNull Gson gson, @NonNull Type typeOfT) throws Exception { 26 | return GlintRequestUtil.successDeserialize(sGson, jsonEl, typeOfT); 27 | } 28 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/SocketInnerResultBean.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.socket; 2 | 3 | public class SocketInnerResultBean { 4 | /** 5 | * 服务端响应内容 6 | */ 7 | public String response; 8 | /** 9 | * 消息类型,0是普通消息,1是事件消息 10 | */ 11 | public int msgType; 12 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/socketio/GlintSocketIOCallback.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.socket.socketio; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | /** 7 | * 用户SocketIO的连接地址 8 | * 9 | * @author ysbing 10 | * 创建于 2018/3/25 11 | */ 12 | public interface GlintSocketIOCallback { 13 | /** 14 | * 异步获取socket io地址 15 | * 16 | * @param socketUrl 经过拼接的socket地址 17 | */ 18 | void onSocketUrl(@NonNull String socketUrl); 19 | 20 | /** 21 | * 错误回调 22 | * 23 | * @param throwable 未知错误 24 | */ 25 | void onError(@NonNull Throwable throwable); 26 | } 27 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/socketio/IOMessage.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.socket.socketio; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | public class IOMessage { 7 | public static final int TYPE_DISCONNECT = 0; 8 | public static final int TYPE_CONNECT = 1; 9 | public static final int TYPE_HEARTBEAT = 2; 10 | public static final int TYPE_MESSAGE = 3; 11 | public static final int TYPE_JSON_MESSAGE = 4; 12 | public static final int TYPE_EVENT = 5; 13 | public static final int TYPE_ACK = 6; 14 | public static final int TYPE_ERROR = 7; 15 | public static final int TYPE_NOOP = 8; 16 | public static final int FIELD_TYPE = 0; 17 | public static final int FIELD_ID = 1; 18 | public static final int FIELD_ENDPOINT = 2; 19 | public static final int FIELD_DATA = 3; 20 | public static final int NUM_FIELDS = 4; 21 | private final String[] mFields; 22 | private int type; 23 | 24 | private IOMessage(int type, String id, String namespace, String data) { 25 | this.mFields = new String[4]; 26 | this.type = type; 27 | this.mFields[1] = id; 28 | this.mFields[0] = "" + type; 29 | this.mFields[2] = namespace; 30 | this.mFields[3] = data; 31 | } 32 | 33 | public IOMessage(String message) { 34 | this.mFields = new String[4]; 35 | String[] fields = message.split(":", 4); 36 | 37 | for (int i = 0; i < fields.length; ++i) { 38 | this.mFields[i] = fields[i]; 39 | if (i == 0) { 40 | this.type = Integer.parseInt(fields[i]); 41 | } 42 | } 43 | 44 | } 45 | 46 | @NonNull 47 | @Override 48 | public String toString() { 49 | StringBuilder builder = new StringBuilder(); 50 | for (String field : this.mFields) { 51 | builder.append(':'); 52 | if (field != null) { 53 | builder.append(field); 54 | } 55 | } 56 | return builder.substring(1); 57 | } 58 | 59 | public int getType() { 60 | return this.type; 61 | } 62 | 63 | public String getId() { 64 | return this.mFields[1]; 65 | } 66 | 67 | public void setId(String id) { 68 | this.mFields[1] = id; 69 | } 70 | 71 | public String getEndpoint() { 72 | return this.mFields[2]; 73 | } 74 | 75 | public String getData() { 76 | return this.mFields[3]; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/socketio/IOWebSocketTransport.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.socket.socketio; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | import com.ysbing.glint.http.GlintHttp; 7 | import com.ysbing.glint.http.GlintHttpListener; 8 | 9 | import java.net.URI; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.regex.Pattern; 13 | 14 | /** 15 | * SocketIO的URL处理类 16 | * 17 | * @author ysbing 18 | * 创建于 2018/3/25 19 | */ 20 | public class IOWebSocketTransport { 21 | private static final Pattern PATTERN_HTTP = Pattern.compile("^http"); 22 | 23 | public static void getUrl(@NonNull final URI url, @NonNull final GlintSocketIOCallback callback) { 24 | if ("ws".equals(url.getScheme()) || "wss".equals(url.getScheme())) { 25 | callback.onSocketUrl(url.toString()); 26 | } else if ("http".equals(url.getScheme()) || "https".equals(url.getScheme())) { 27 | GlintHttp.get(url + "/socket.io/1/").signature(false).notJson(true).mainThread(false).execute(new GlintHttpListener() { 28 | @Override 29 | public void onSuccess(@NonNull String result) throws Throwable { 30 | super.onSuccess(result); 31 | String[] data = result.split(":"); 32 | String sessionId = data[0]; 33 | List protocols = Arrays.asList(data[3].split(",")); 34 | String socketUrl; 35 | if (protocols.contains("websocket")) { 36 | socketUrl = PATTERN_HTTP.matcher(url.toString()).replaceFirst("ws") + "/socket.io/1/" + "websocket" + "/" + sessionId; 37 | } else { 38 | if (!protocols.contains("xhr-polling")) { 39 | callback.onError(new RuntimeException("socket url is empty")); 40 | return; 41 | } 42 | socketUrl = url.toString() + "/socket.io/1/" + "xhr-polling" + "/" + sessionId; 43 | 44 | } 45 | callback.onSocketUrl(socketUrl); 46 | } 47 | 48 | @Override 49 | public void onFail(@NonNull Throwable error) { 50 | super.onFail(error); 51 | callback.onError(error); 52 | } 53 | }); 54 | } else { 55 | callback.onError(new RuntimeException("Unknown url")); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/socket/socketio/Protocol.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.socket.socketio; 2 | 3 | public class Protocol { 4 | private final static int HEADER = 5; 5 | 6 | public static String encode(int id, String route, String msg) { 7 | if (route.length() > 255) { 8 | throw new RuntimeException("route max length is overflow."); 9 | } 10 | byte[] arr = new byte[HEADER + route.length()]; 11 | int index = 0; 12 | arr[index++] = (byte) ((id >> 24) & 0xFF); 13 | arr[index++] = (byte) ((id >> 16) & 0xFF); 14 | arr[index++] = (byte) ((id >> 8) & 0xFF); 15 | arr[index++] = (byte) (id & 0xFF); 16 | arr[index++] = (byte) (route.length() & 0xFF); 17 | 18 | for (int i = 0; i < route.length(); i++) { 19 | arr[index++] = (byte) route.codePointAt(i); 20 | } 21 | return bt2Str(arr, arr.length) + msg; 22 | } 23 | 24 | private static String bt2Str(byte[] arr, int end) { 25 | StringBuilder buff = new StringBuilder(); 26 | for (int i = 0; i < arr.length && i < end; i++) { 27 | buff.append(String.valueOf(Character.toChars((arr[i] + 256) % 256))); 28 | } 29 | return buff.toString(); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/upload/GlintUpload.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.upload; 2 | 3 | import android.os.Bundle; 4 | import android.text.TextUtils; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import com.ysbing.glint.base.BaseHttpModule; 10 | 11 | import java.io.File; 12 | import java.util.TreeMap; 13 | 14 | import okhttp3.Headers; 15 | 16 | /** 17 | * 上传请求的入口 18 | * 该类不可继承,如果有自定义功能需求,就继承{@link GlintUploadCore}进行扩展 19 | * 20 | * @author ysbing 21 | * 创建于 2018/1/16 22 | */ 23 | public final class GlintUpload extends GlintUploadCore { 24 | 25 | public static GlintUpload upload(@NonNull String url, @NonNull File file) { 26 | return upload(url, file, new TreeMap()); 27 | } 28 | 29 | public static GlintUpload upload(@NonNull String url, @NonNull File file, @NonNull String keyName) { 30 | return upload(url, file, keyName, new TreeMap()); 31 | } 32 | 33 | public static GlintUpload upload(@NonNull String url, @NonNull File file, @NonNull TreeMap params) { 34 | return upload(url, file, file.getName(), params); 35 | } 36 | 37 | public static GlintUpload upload(@NonNull String url, @NonNull File file, @NonNull String keyName, @NonNull TreeMap params) { 38 | return new GlintUpload(url, file, null, keyName, params); 39 | } 40 | 41 | public static GlintUpload upload(@NonNull String url, @NonNull byte[] data, @NonNull String keyName) { 42 | return upload(url, data, keyName, new TreeMap()); 43 | } 44 | 45 | public static GlintUpload upload(@NonNull String url, @NonNull byte[] data, @NonNull String keyName, @NonNull TreeMap params) { 46 | return new GlintUpload(url, null, data, keyName, params); 47 | } 48 | 49 | 50 | public GlintUpload(@NonNull String url, @Nullable File file, @Nullable byte[] data, @Nullable String keyName, @NonNull TreeMap params) { 51 | mBuilder = createBuilder(); 52 | mBuilder.url = url; 53 | mBuilder.file = file; 54 | mBuilder.data = data; 55 | mBuilder.keyName = keyName; 56 | mBuilder.params = params; 57 | if (mBuilder.file != null && TextUtils.isEmpty(mBuilder.keyName)) { 58 | mBuilder.keyName = mBuilder.file.getName(); 59 | } else if (this.mBuilder == null && TextUtils.isEmpty(mBuilder.keyName)) { 60 | throw new RuntimeException(mBuilder.url + ":keyName is null"); 61 | } 62 | } 63 | 64 | /** 65 | * 额外设置的header,出去登录相关的 66 | */ 67 | public GlintUpload setHeader(@NonNull Headers.Builder headers) { 68 | mBuilder.headers = headers; 69 | return this; 70 | } 71 | 72 | /** 73 | * 增加cookie到头部信息 74 | * 75 | * @param cookie 登录返回的cookie 76 | */ 77 | public GlintUpload addCookie(@NonNull String cookie) { 78 | mBuilder.cookie = cookie; 79 | return this; 80 | } 81 | 82 | /** 83 | * 设置是否使用通用签名,默认是 84 | * 85 | * @param signature 使用通用签名 86 | */ 87 | public GlintUpload signature(boolean signature) { 88 | mBuilder.signature = signature; 89 | return this; 90 | } 91 | 92 | /** 93 | * 设置是否使用标准化序列化,默认否 94 | */ 95 | public GlintUpload standardDeserialize(boolean standardDeserialize) { 96 | mBuilder.standardDeserialize = standardDeserialize; 97 | return this; 98 | } 99 | 100 | /** 101 | * 设置是否使用主线程 102 | */ 103 | public GlintUpload mainThread() { 104 | mBuilder.mainThread = true; 105 | return this; 106 | } 107 | 108 | /** 109 | * 结果不是Json 110 | */ 111 | public GlintUpload notJson(boolean notJson) { 112 | mBuilder.notJson = notJson; 113 | return this; 114 | } 115 | 116 | /** 117 | * 设置文件的协议 118 | * 119 | * @param mimeType 文件协议 120 | */ 121 | public GlintUpload setMimeType(String mimeType) { 122 | mBuilder.mimeType = mimeType; 123 | return this; 124 | } 125 | 126 | /** 127 | * 使用自定义Module,可做高级操作 128 | * 该方法将会重置builder,使用时需在第一个使用 129 | * 130 | * @param module 自定义Module 131 | */ 132 | public GlintUpload using(@NonNull BaseHttpModule module) { 133 | super.moduleUsing(module); 134 | return this; 135 | } 136 | 137 | 138 | /** 139 | * 设置其他配置 140 | * 141 | * @param otherBuilder 其他配置 142 | */ 143 | public GlintUpload otherBuilder(@NonNull Bundle otherBuilder) { 144 | mBuilder.otherBuilder = otherBuilder; 145 | return this; 146 | } 147 | 148 | /** 149 | * 上传中途取消 150 | */ 151 | public synchronized void cancel() { 152 | GlintUploadDispatcher.getInstance().cancel(this); 153 | } 154 | 155 | /** 156 | * 执行网络请求 157 | */ 158 | public void execute() { 159 | if (!TextUtils.isEmpty(mBuilder.url)) { 160 | GlintUploadDispatcher.getInstance().executed(this); 161 | } 162 | } 163 | 164 | public void execute(@NonNull GlintUploadListener listener) { 165 | if (!TextUtils.isEmpty(mBuilder.url)) { 166 | mBuilder.listener = listener; 167 | GlintUploadDispatcher.getInstance().executed(this); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/upload/GlintUploadBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.upload; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.ysbing.glint.base.BaseHttpModule; 6 | import com.ysbing.glint.base.Glint; 7 | import com.ysbing.glint.base.GlintBaseBuilder; 8 | 9 | import java.io.File; 10 | 11 | /** 12 | * 上传的参数配置 13 | * 14 | * @author ysbing 15 | * 创建于 2018/1/16 16 | */ 17 | public final class GlintUploadBuilder extends GlintBaseBuilder implements Cloneable { 18 | 19 | /** 20 | * 上层的监听回调 21 | */ 22 | public GlintUploadListener listener; 23 | /** 24 | * 要上传的文件,与{@link #data}同时不为空时优先使用data 25 | */ 26 | public File file; 27 | /** 28 | * 要上传的数据,与{@link #file}同时不为空时优先使用data 29 | */ 30 | public byte[] data; 31 | /** 32 | * 要上传的文件名称,默认使用file.getName, 33 | * 如使用byte[]而不指定keyName,将抛出异常 34 | */ 35 | public String keyName; 36 | /** 37 | * 结果不是json 38 | */ 39 | public boolean notJson = false; 40 | 41 | public GlintUploadBuilder(@NonNull Glint glint) { 42 | this(glint, true); 43 | } 44 | 45 | public GlintUploadBuilder(@NonNull Glint glint, boolean init) { 46 | super(); 47 | this.mimeType = "multipart/form-data;charset=utf-8"; 48 | if (init) { 49 | glint.configDefaultBuilder(this); 50 | } 51 | } 52 | 53 | @Override 54 | protected GlintUploadBuilder clone() throws CloneNotSupportedException { 55 | return (GlintUploadBuilder) super.clone(); 56 | } 57 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/upload/GlintUploadCountingRequestBody.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.upload; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.ysbing.glint.util.UiKit; 6 | 7 | import java.io.IOException; 8 | 9 | import okhttp3.MediaType; 10 | import okhttp3.RequestBody; 11 | import okio.Buffer; 12 | import okio.BufferedSink; 13 | import okio.ForwardingSink; 14 | import okio.Okio; 15 | import okio.Sink; 16 | 17 | /** 18 | * 上传读文件操作 19 | * 20 | * @author ysbing 21 | * 创建于 2018/1/16 22 | */ 23 | public final class GlintUploadCountingRequestBody extends RequestBody { 24 | 25 | private final RequestBody body; 26 | private final GlintUploadListener listener; 27 | private long lastRefreshTime; 28 | private long lastBytesWritten; 29 | private long speed; 30 | 31 | GlintUploadCountingRequestBody(RequestBody body, GlintUploadListener listener) { 32 | this.body = body; 33 | this.listener = listener; 34 | } 35 | 36 | @Override 37 | public long contentLength() throws IOException { 38 | return body.contentLength(); 39 | } 40 | 41 | @Override 42 | public MediaType contentType() { 43 | return body.contentType(); 44 | } 45 | 46 | @Override 47 | public void writeTo(@NonNull BufferedSink sink) throws IOException { 48 | BufferedSink bufferedSink; 49 | CountingSink countingSink = new CountingSink(sink); 50 | bufferedSink = Okio.buffer(countingSink); 51 | body.writeTo(bufferedSink); 52 | bufferedSink.flush(); 53 | } 54 | 55 | protected final class CountingSink extends ForwardingSink { 56 | 57 | private long bytesWritten = 0; 58 | 59 | CountingSink(Sink delegate) { 60 | super(delegate); 61 | } 62 | 63 | @Override 64 | public void write(@NonNull Buffer source, long byteCount) throws IOException { 65 | super.write(source, byteCount); 66 | synchronized (GlintUploadCountingRequestBody.class) { 67 | bytesWritten += byteCount; 68 | UiKit.runOnMainThreadAsync(new Runnable() { 69 | @Override 70 | public void run() { 71 | if (listener != null) { 72 | try { 73 | long time = System.currentTimeMillis(); 74 | if (time > lastRefreshTime) { 75 | long tempSpeed = (bytesWritten - lastBytesWritten) * 1000 / (time - lastRefreshTime); 76 | if (tempSpeed > 0) { 77 | speed = tempSpeed; 78 | } 79 | } 80 | lastRefreshTime = time; 81 | lastBytesWritten = bytesWritten; 82 | listener.onProgress(bytesWritten, contentLength(), speed, (int) (bytesWritten * 100 / contentLength())); 83 | } catch (Exception e) { 84 | e.printStackTrace(); 85 | listener.onFail(e); 86 | } 87 | } 88 | } 89 | }); 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/upload/GlintUploadDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.upload; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.Deque; 5 | import java.util.Iterator; 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.SynchronousQueue; 8 | import java.util.concurrent.ThreadPoolExecutor; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import okhttp3.internal.Util; 12 | 13 | /** 14 | * 下载的线程调度 15 | * 16 | * @author ysbing 17 | * 创建于 2018/1/16 18 | */ 19 | 20 | public final class GlintUploadDispatcher { 21 | 22 | private static final int maxRequests = 5; 23 | 24 | /** 25 | * Executes calls. Created lazily. 26 | */ 27 | private ExecutorService executorService; 28 | 29 | /** 30 | * Ready async calls in the order they'll be run. 31 | */ 32 | private final Deque> readyAsyncCalls = new ArrayDeque<>(); 33 | 34 | /** 35 | * Running asynchronous calls. Includes canceled calls that haven't finished yet. 36 | */ 37 | private final Deque> runningAsyncCalls = new ArrayDeque<>(); 38 | 39 | private static final class InstanceHolder { 40 | static final GlintUploadDispatcher mInstance = new GlintUploadDispatcher(); 41 | } 42 | 43 | public static GlintUploadDispatcher getInstance() { 44 | return InstanceHolder.mInstance; 45 | } 46 | 47 | private GlintUploadDispatcher() { 48 | } 49 | 50 | private synchronized ExecutorService executorService() { 51 | if (executorService == null) { 52 | executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, 53 | new SynchronousQueue(), Util.threadFactory("GlintUpload Dispatcher", false)); 54 | } 55 | return executorService; 56 | } 57 | 58 | public synchronized void executed(GlintUploadCore call) { 59 | if (runningAsyncCalls.size() < maxRequests) { 60 | runningAsyncCalls.add(call); 61 | executorService().execute(call); 62 | } else { 63 | readyAsyncCalls.add(call); 64 | } 65 | } 66 | 67 | public synchronized void cancel(GlintUploadCore call) { 68 | if (call != null) { 69 | readyAsyncCalls.remove(call); 70 | runningAsyncCalls.remove(call); 71 | call.coreCancel(); 72 | } 73 | } 74 | 75 | public synchronized void cancelAll() { 76 | for (GlintUploadCore call : readyAsyncCalls) { 77 | call.coreCancel(); 78 | } 79 | 80 | for (GlintUploadCore call : runningAsyncCalls) { 81 | call.coreCancel(); 82 | } 83 | readyAsyncCalls.clear(); 84 | runningAsyncCalls.clear(); 85 | } 86 | 87 | private void promoteCalls() { 88 | // Already running max capacity. 89 | if (runningAsyncCalls.size() >= maxRequests) { 90 | return; 91 | } 92 | // No ready calls to promote. 93 | if (readyAsyncCalls.isEmpty()) { 94 | return; 95 | } 96 | 97 | for (Iterator> i = readyAsyncCalls.iterator(); i.hasNext(); ) { 98 | GlintUploadCore call = i.next(); 99 | i.remove(); 100 | runningAsyncCalls.add(call); 101 | executorService().execute(call); 102 | // Reached max capacity. 103 | if (runningAsyncCalls.size() >= maxRequests) { 104 | return; 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * Used by {@code GlintUploadCore#run} to signal completion. 111 | */ 112 | void finished(GlintUploadCore call) { 113 | synchronized (this) { 114 | finished(runningAsyncCalls, call); 115 | } 116 | } 117 | 118 | private void finished(Deque calls, T call) { 119 | calls.remove(call); 120 | promoteCalls(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/upload/GlintUploadListener.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.upload; 2 | 3 | import com.ysbing.glint.http.GlintHttpListener; 4 | 5 | /** 6 | * 上传监听回调 7 | * 8 | * @author ysbing 9 | * 创建于 2018/1/16 10 | */ 11 | public abstract class GlintUploadListener extends GlintHttpListener { 12 | /** 13 | * @param bytesWritten 已读取的长度 14 | * @param contentLength 总长度 15 | * @param speed 速度,单位是每秒/字节 16 | * @param percent 百分比,最小是0,最大是100 17 | * @throws Exception 18 | */ 19 | public void onProgress(long bytesWritten, long contentLength, long speed, int percent) throws Exception { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/util/ContextHelper.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.util; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Application; 5 | import android.content.Context; 6 | 7 | import java.lang.ref.WeakReference; 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * 获取Context对象的助手 12 | * 13 | * @author ysbing 14 | */ 15 | public class ContextHelper { 16 | 17 | private static WeakReference mApplicationWeak; 18 | 19 | public static Context getAppContext() { 20 | return getApplication().getApplicationContext(); 21 | } 22 | 23 | public static Application getApplication() { 24 | if (mApplicationWeak == null || mApplicationWeak.get() == null) { 25 | synchronized (ContextHelper.class) { 26 | if (mApplicationWeak == null || mApplicationWeak.get() == null) { 27 | try { 28 | @SuppressLint("PrivateApi") Class clazzActivityThread = Class.forName("android.app.ActivityThread"); 29 | Method currentActivityThread = clazzActivityThread.getDeclaredMethod("currentActivityThread"); 30 | Method getApplicationMethod = clazzActivityThread.getMethod("getApplication"); 31 | Object activityThread = currentActivityThread.invoke(null); 32 | Application application = (Application) getApplicationMethod.invoke(activityThread); 33 | mApplicationWeak = new WeakReference<>(application); 34 | } catch (Exception e) { 35 | e.printStackTrace(); 36 | } 37 | } 38 | } 39 | } 40 | return mApplicationWeak.get(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/util/Md5Util.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.util; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | import okio.BufferedSource; 10 | import okio.ByteString; 11 | import okio.Okio; 12 | 13 | /** 14 | * MD5获取工具 15 | * 16 | * @author ysbing 17 | */ 18 | public class Md5Util { 19 | 20 | /** 21 | * 获取字符串的MD5 22 | * 23 | * @param str 要计算的字符串 24 | * @return 该文件的MD5 25 | */ 26 | public static String getMD5Str(@NonNull String str) { 27 | return ByteString.encodeUtf8(str).md5().hex(); 28 | } 29 | 30 | /** 31 | * 获取字节数组的MD5 32 | * 33 | * @param bytes 要计算的字节数组 34 | * @return 该文件的MD5 35 | */ 36 | public static String getMD5Str(byte[] bytes) { 37 | if (bytes == null || bytes.length == 0) { 38 | return ""; 39 | } 40 | return ByteString.of(bytes).md5().hex(); 41 | } 42 | 43 | /** 44 | * 获取单个文件的MD5值 45 | * 46 | * @param file 要计算的文件 47 | * @return 该文件的MD5 48 | */ 49 | public static String getMD5Str(@NonNull File file) { 50 | if (!file.isFile()) { 51 | return ""; 52 | } 53 | BufferedSource source = null; 54 | try { 55 | source = Okio.buffer(Okio.source(file)); 56 | return source.readByteString().md5().hex(); 57 | } catch (IOException e) { 58 | e.printStackTrace(); 59 | } finally { 60 | try { 61 | if (source != null) { 62 | source.close(); 63 | } 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | return ""; 69 | } 70 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/util/UiKit.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.util; 2 | /* 3 | * Copyright (C) 2014 Qiujuer 4 | * WebSite http://www.qiujuer.net 5 | * Created 11/24/2014 6 | * Changed 2015/11/21 7 | * Version 3.0.0 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | import android.os.Looper; 23 | 24 | /** 25 | * This is UI operation class 26 | * You can run thread on MainThread By Async and Sync 27 | *

28 | * You don't need initialize, but when you don't need run 29 | * You should call {@link #dispose()} operation for destruction. 30 | */ 31 | final public class UiKit { 32 | private static volatile UiKitHandlerPoster mainPoster = null; 33 | 34 | private static UiKitHandlerPoster getMainPoster() { 35 | if (mainPoster == null) { 36 | synchronized (UiKit.class) { 37 | if (mainPoster == null) { 38 | // This time is 1000/60 (60fps) 39 | mainPoster = new UiKitHandlerPoster(Looper.getMainLooper()); 40 | } 41 | } 42 | } 43 | return mainPoster; 44 | } 45 | 46 | /** 47 | * Asynchronously 48 | * The child thread asynchronous run relative to the main thread, 49 | * not blocking the child thread 50 | * 51 | * @param runnable Runnable Interface 52 | */ 53 | public static void runOnMainThreadAsync(Runnable runnable) { 54 | if (Looper.myLooper() == Looper.getMainLooper()) { 55 | runnable.run(); 56 | return; 57 | } 58 | getMainPoster().async(runnable); 59 | } 60 | 61 | /** 62 | * Synchronously 63 | * The child thread relative thread synchronization operation, 64 | * blocking the child thread, 65 | * thread for the main thread to complete 66 | * 67 | * @param runnable Runnable Interface 68 | */ 69 | public static void runOnMainThreadSync(Runnable runnable) { 70 | if (Looper.myLooper() == Looper.getMainLooper()) { 71 | runnable.run(); 72 | return; 73 | } 74 | UiKitSyncPost poster = new UiKitSyncPost(runnable); 75 | getMainPoster().sync(poster); 76 | poster.waitRun(); 77 | } 78 | 79 | /** 80 | * Synchronously 81 | * The child thread relative thread synchronization operation, 82 | * blocking the child thread, 83 | * thread for the main thread to complete 84 | * But the child thread just wait for the waitTime long. 85 | * 86 | * @param runnable Runnable Interface 87 | * @param waitTime wait for the main thread run Time 88 | * @param cancel on the child thread cancel the runnable task 89 | */ 90 | public static void runOnMainThreadSync(Runnable runnable, int waitTime, boolean cancel) { 91 | if (Looper.myLooper() == Looper.getMainLooper()) { 92 | runnable.run(); 93 | return; 94 | } 95 | UiKitSyncPost poster = new UiKitSyncPost(runnable); 96 | getMainPoster().sync(poster); 97 | poster.waitRun(waitTime, cancel); 98 | } 99 | 100 | public static void dispose() { 101 | if (mainPoster != null) { 102 | mainPoster.dispose(); 103 | mainPoster = null; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/util/UiKitHandlerPoster.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.util; 2 | /* 3 | * Copyright (C) 2014 Qiujuer 4 | * WebSite http://www.qiujuer.net 5 | * Created 11/24/2014 6 | * Changed 1/07/2016 7 | * Version 3.0.0 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | import android.os.Handler; 23 | import android.os.Looper; 24 | import android.os.Message; 25 | import android.os.SystemClock; 26 | 27 | import java.util.LinkedList; 28 | import java.util.NoSuchElementException; 29 | import java.util.Queue; 30 | 31 | /** 32 | * UiKit Handler Poster extends Handler 33 | *

34 | * In class have two queue with {@link #mAsyncPool,#mSyncPool} 35 | */ 36 | final class UiKitHandlerPoster extends Handler { 37 | private static final int ASYNC = 0x1; 38 | private static final int SYNC = 0x2; 39 | private final Queue mAsyncPool; 40 | private final Queue mSyncPool; 41 | private final int mMaxMillisInsideHandleMessage; 42 | private boolean isAsyncActive; 43 | private boolean isSyncActive; 44 | 45 | /** 46 | * Init this 47 | * @param looper Handler Looper 48 | * 49 | */ 50 | UiKitHandlerPoster(Looper looper) { 51 | super(looper); 52 | this.mMaxMillisInsideHandleMessage = 16; 53 | mAsyncPool = new LinkedList<>(); 54 | mSyncPool = new LinkedList<>(); 55 | } 56 | 57 | /** 58 | * Pool clear 59 | */ 60 | void dispose() { 61 | this.removeCallbacksAndMessages(null); 62 | this.mAsyncPool.clear(); 63 | this.mSyncPool.clear(); 64 | } 65 | 66 | 67 | void async(Runnable runnable) { 68 | synchronized (mAsyncPool) { 69 | mAsyncPool.offer(runnable); 70 | if (!isAsyncActive) { 71 | isAsyncActive = true; 72 | if (!sendMessage(obtainMessage(ASYNC))) { 73 | throw new RuntimeException("Could not send handler message"); 74 | } 75 | } 76 | } 77 | } 78 | 79 | void sync(UiKitSyncPost post) { 80 | synchronized (mSyncPool) { 81 | mSyncPool.offer(post); 82 | if (!isSyncActive) { 83 | isSyncActive = true; 84 | if (!sendMessage(obtainMessage(SYNC))) { 85 | throw new RuntimeException("Could not send handler message"); 86 | } 87 | } 88 | } 89 | } 90 | 91 | /** 92 | * Run in main thread 93 | * 94 | * @param msg call messages 95 | */ 96 | @Override 97 | public void handleMessage(Message msg) { 98 | switch (msg.what) { 99 | case ASYNC: { 100 | boolean rescheduled = false; 101 | try { 102 | long started = SystemClock.uptimeMillis(); 103 | while (true) { 104 | Runnable runnable = poolAsyncPost(); 105 | if (runnable == null) { 106 | synchronized (mAsyncPool) { 107 | // Check again, this time in synchronized 108 | runnable = poolAsyncPost(); 109 | if (runnable == null) { 110 | isAsyncActive = false; 111 | return; 112 | } 113 | } 114 | } 115 | runnable.run(); 116 | long timeInMethod = SystemClock.uptimeMillis() - started; 117 | if (timeInMethod >= mMaxMillisInsideHandleMessage) { 118 | if (!sendMessage(obtainMessage(ASYNC))) { 119 | throw new RuntimeException("Could not send handler message"); 120 | } 121 | rescheduled = true; 122 | return; 123 | } 124 | } 125 | } finally { 126 | isAsyncActive = rescheduled; 127 | } 128 | } 129 | case SYNC: { 130 | boolean rescheduled = false; 131 | try { 132 | long started = SystemClock.uptimeMillis(); 133 | while (true) { 134 | UiKitSyncPost post = poolSyncPost(); 135 | if (post == null) { 136 | synchronized (mSyncPool) { 137 | // Check again, this time in synchronized 138 | post = poolSyncPost(); 139 | if (post == null) { 140 | isSyncActive = false; 141 | return; 142 | } 143 | } 144 | } 145 | post.run(); 146 | long timeInMethod = SystemClock.uptimeMillis() - started; 147 | if (timeInMethod >= mMaxMillisInsideHandleMessage) { 148 | if (!sendMessage(obtainMessage(SYNC))) { 149 | throw new RuntimeException("Could not send handler message"); 150 | } 151 | rescheduled = true; 152 | return; 153 | } 154 | } 155 | } finally { 156 | isSyncActive = rescheduled; 157 | } 158 | } 159 | default: 160 | super.handleMessage(msg); 161 | break; 162 | } 163 | } 164 | 165 | private Runnable poolAsyncPost() { 166 | synchronized (mAsyncPool) { 167 | try { 168 | return mAsyncPool.poll(); 169 | } catch (NoSuchElementException e) { 170 | e.printStackTrace(); 171 | return null; 172 | } 173 | } 174 | } 175 | 176 | private UiKitSyncPost poolSyncPost() { 177 | synchronized (mSyncPool) { 178 | try { 179 | return mSyncPool.poll(); 180 | } catch (NoSuchElementException e) { 181 | e.printStackTrace(); 182 | return null; 183 | } 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/util/UiKitSyncPost.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.util; 2 | /* 3 | * Copyright (C) 2014 Qiujuer 4 | * WebSite http://www.qiujuer.net 5 | * Created 11/24/2014 6 | * Changed 03/08/2015 7 | * Version 3.0.0 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | /** 23 | * UiKitSyncPost use to {@link UiKitHandlerPoster} 24 | *

25 | * See {@link UiKit} 26 | */ 27 | final class UiKitSyncPost { 28 | private final Runnable mRunnable; 29 | private volatile boolean isEnd = false; 30 | 31 | 32 | UiKitSyncPost(Runnable runnable) { 33 | this.mRunnable = runnable; 34 | } 35 | 36 | /** 37 | * Run to doing something 38 | */ 39 | public void run() { 40 | if (!isEnd) { 41 | synchronized (this) { 42 | if (!isEnd) { 43 | mRunnable.run(); 44 | isEnd = true; 45 | try { 46 | this.notifyAll(); 47 | } catch (Exception e) { 48 | e.printStackTrace(); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * Wait to run end 57 | */ 58 | public void waitRun() { 59 | if (!isEnd) { 60 | synchronized (this) { 61 | if (!isEnd) { 62 | try { 63 | this.wait(); 64 | } catch (InterruptedException e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * Wait for a period of time to run end 74 | * 75 | * @param time wait time 76 | * @param cancel when wait end cancel the run 77 | */ 78 | public void waitRun(int time, boolean cancel) { 79 | if (!isEnd) { 80 | synchronized (this) { 81 | if (!isEnd) { 82 | try { 83 | this.wait(time); 84 | } catch (InterruptedException e) { 85 | e.printStackTrace(); 86 | } finally { 87 | if (!isEnd && cancel) { 88 | isEnd = true; 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /glint/src/main/java/com/ysbing/glint/util/UiStack.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.glint.util; 2 | 3 | import android.app.Activity; 4 | 5 | import androidx.fragment.app.Fragment; 6 | 7 | import java.lang.ref.SoftReference; 8 | import java.util.Stack; 9 | 10 | /** 11 | * @author ysbing 12 | * 创建于 2017/11/2 13 | */ 14 | public class UiStack { 15 | /** 16 | * Activity堆栈 17 | */ 18 | private static final Stack> activityStack = new Stack<>(); 19 | private static final Stack> fragmentStack = new Stack<>(); 20 | /** 21 | * 应用管理对象 22 | */ 23 | private static final UiStack instance = new UiStack(); 24 | 25 | private UiStack() { 26 | } 27 | 28 | /** 29 | * 单一实例 30 | * 31 | * @return 应用管理对象 32 | */ 33 | public static UiStack getInstance() { 34 | return instance; 35 | } 36 | 37 | /** 38 | * 移除所有Activity 39 | */ 40 | public void popAllActivity() { 41 | activityStack.clear(); 42 | } 43 | 44 | /** 45 | * 移除所有Fragment 46 | */ 47 | public void popAllFragment() { 48 | fragmentStack.clear(); 49 | } 50 | 51 | /** 52 | * 移除指定Activity 53 | */ 54 | public void popActivity(Activity activity) { 55 | int removeIndex = -1; 56 | for (int i = 0; i < activityStack.size(); i++) { 57 | SoftReference activitySoftReference = activityStack.get(i); 58 | if (activitySoftReference != null && activitySoftReference.get() != null && activitySoftReference.get() == activity) { 59 | removeIndex = i; 60 | break; 61 | } 62 | } 63 | if (removeIndex > -1) { 64 | activityStack.remove(removeIndex); 65 | } 66 | } 67 | 68 | /** 69 | * 移除指定Fragment 70 | */ 71 | public void popFragment(Fragment fragment) { 72 | int removeIndex = -1; 73 | for (int i = 0; i < fragmentStack.size(); i++) { 74 | SoftReference fragmentSoftReference = fragmentStack.get(i); 75 | if (fragmentSoftReference != null && fragmentSoftReference.get() != null && fragmentSoftReference.get() == fragment) { 76 | removeIndex = i; 77 | break; 78 | } 79 | } 80 | if (removeIndex > -1) { 81 | fragmentStack.remove(removeIndex); 82 | } 83 | } 84 | 85 | /** 86 | * 添加Activity 87 | */ 88 | public void pushActivity(Activity activity) { 89 | SoftReference activitySoftReference = new SoftReference<>(activity); 90 | activityStack.add(activitySoftReference); 91 | } 92 | 93 | /** 94 | * 添加Activity 95 | */ 96 | public void pushFragment(Fragment fragment) { 97 | SoftReference fragmentSoftReference = new SoftReference<>(fragment); 98 | fragmentStack.add(fragmentSoftReference); 99 | } 100 | 101 | 102 | /** 103 | * 根据类名获取Activity对象 104 | * 105 | * @param className 类名 106 | * @return Activity对象 107 | */ 108 | public Activity getActivity(String className) { 109 | Class cls = null; 110 | try { 111 | cls = Class.forName(className); 112 | } catch (Exception ignored) { 113 | } 114 | if (cls != null) { 115 | for (int i = activityStack.size() - 1; i >= 0; i--) { 116 | if (activityStack.get(i).get() != null && activityStack.get(i).get().getClass().equals(cls)) { 117 | return activityStack.get(i).get(); 118 | } 119 | } 120 | } else { 121 | return null; 122 | } 123 | return null; 124 | } 125 | 126 | /** 127 | * 根据类名获取Fragment对象 128 | * 129 | * @param className 类名 130 | * @return Fragment对象 131 | */ 132 | public Fragment getFragment(String className) { 133 | Class cls = null; 134 | try { 135 | cls = Class.forName(className); 136 | } catch (Exception ignored) { 137 | } 138 | if (cls != null) { 139 | for (int i = 0; i < fragmentStack.size(); i++) { 140 | if (fragmentStack.get(i).get() != null && fragmentStack.get(i).get().getClass().equals(cls)) { 141 | return fragmentStack.get(i).get(); 142 | } 143 | } 144 | } else { 145 | return null; 146 | } 147 | return null; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /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=-Xmx1536m 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 | 15 | GLINT_VERSION=1.1.6-SNAPSHOT 16 | android.useAndroidX=true -------------------------------------------------------------------------------- /gradle/publish.gradle: -------------------------------------------------------------------------------- 1 | afterEvaluate { 2 | apply from: rootProject.file('gradle/publish_remote.gradle') 3 | if (project.plugins.hasPlugin("com.android.internal.library")) { 4 | apply from: rootProject.file('gradle/publish_local_android.gradle') 5 | } else { 6 | apply from: rootProject.file('gradle/publish_local_java.gradle') 7 | } 8 | } -------------------------------------------------------------------------------- /gradle/publish_local_android.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | 3 | sourceCompatibility = rootProject.ext.javaVersion 4 | targetCompatibility = rootProject.ext.javaVersion 5 | 6 | version rootProject.ext.VERSION_NAME 7 | group rootProject.ext.GROUP 8 | 9 | task androidJavadocs(type: Javadoc) { 10 | source = android.sourceSets.main.java.srcDirs 11 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 12 | android.libraryVariants.all { variant -> 13 | if (variant.name == 'release') { 14 | owner.classpath += variant.javaCompileProvider.get().classpath 15 | } 16 | } 17 | exclude '**/R.html', '**/R.*.html', '**/index.html' 18 | } 19 | 20 | task sourcesJar(type: Jar) { 21 | from android.sourceSets.main.java.srcDirs 22 | classifier = 'sources' 23 | } 24 | 25 | task javadocJar(type: Jar, dependsOn: androidJavadocs) { 26 | classifier = 'javadoc' 27 | from androidJavadocs.destinationDir 28 | } 29 | 30 | artifacts { 31 | archives javadocJar 32 | archives sourcesJar 33 | } 34 | 35 | publishing { 36 | publications { 37 | maven(MavenPublication) { 38 | from components.release 39 | groupId = group 40 | artifactId = POM_ARTIFACT_ID 41 | version = version 42 | } 43 | } 44 | } 45 | 46 | task buildAndPublishLocalMaven(dependsOn: ['build', 'publishResguardPublicationToMavenLocal']) {} -------------------------------------------------------------------------------- /gradle/publish_local_java.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | 3 | sourceCompatibility = rootProject.ext.javaVersion 4 | targetCompatibility = rootProject.ext.javaVersion 5 | 6 | version rootProject.ext.VERSION_NAME 7 | group rootProject.ext.GROUP 8 | 9 | task sourcesJar(type: Jar) { 10 | from sourceSets.main.java.srcDirs 11 | classifier = 'sources' 12 | } 13 | 14 | task javadocJar(type: Jar, dependsOn: javadoc) { 15 | classifier = 'javadoc' 16 | from javadoc.destinationDir 17 | } 18 | 19 | artifacts { 20 | archives javadocJar 21 | archives sourcesJar 22 | } 23 | 24 | publishing { 25 | publications { 26 | maven(MavenPublication) { 27 | from components.java 28 | groupId = group 29 | artifactId = POM_ARTIFACT_ID 30 | version = version 31 | } 32 | } 33 | } 34 | 35 | task buildAndPublishLocalMaven(dependsOn: ['build', 'publishResguardPublicationToMavenLocal']) {} 36 | 37 | -------------------------------------------------------------------------------- /gradle/publish_remote.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven' 2 | apply plugin: 'com.jfrog.bintray' 3 | 4 | def getBintrayUser() { 5 | return hasProperty('BINTRAY_USER') ? BINTRAY_USER : 6 | readPropertyFromLocalProperties('BINTRAY_USER') 7 | } 8 | 9 | def getBintrayKey() { 10 | return hasProperty('BINTRAY_APIKEY') ? BINTRAY_APIKEY : 11 | readPropertyFromLocalProperties('BINTRAY_APIKEY') 12 | } 13 | 14 | def readPropertyFromLocalProperties(String key) { 15 | Properties properties = new Properties() 16 | try { 17 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 18 | } catch (Exception ignore) { 19 | } 20 | return properties.getProperty(key) 21 | } 22 | 23 | bintray { 24 | user = getBintrayUser() 25 | key = getBintrayKey() 26 | configurations = ['archives'] 27 | publications = ['ResguardPub'] 28 | 29 | pkg { 30 | repo = POM_NAME 31 | userOrg = BINTRAY_ORGANIZATION 32 | name = "${GROUP}:${POM_ARTIFACT_ID}" 33 | desc = POM_DESCRIPTION 34 | licenses = BINTRAY_LICENCE 35 | vcsUrl = POM_SCM_URL 36 | websiteUrl = POM_URL 37 | issueTrackerUrl = POM_ISSUE_URL 38 | publicDownloadNumbers = true 39 | publish = true 40 | dryRun = false 41 | } 42 | } 43 | 44 | task buildAndPublishRepo(dependsOn: ['build', 'uploadArchives']) { 45 | doLast { 46 | println "*published to repo: ${project.group}:${project.name}:${project.version}" 47 | } 48 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysbing/Glint/d7ca0d2d1e81cfc50777b59153c64cf01094bcee/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue May 30 17:11:33 CST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## 一、简介 2 | 3 | Glint是Android实现基于OkHttp的Http标准协议框架,支持接口请求,文件下载,文件上传,WebSocket四大功能。 4 | 5 | ## 二、框架特性 6 | * 底层框架为OkHttp 7 | * 支持异步请求、支持同步请求 8 | * 支持文件上传 9 | * 支持文件下载,支持暂停和恢复,断点续传 10 | * 支持长连接,兼容柚子IO 11 | * 失败重试机制 12 | * 智能生命周期,避免离开Activity或Fragment后导致内存泄露 13 | * 自定义HttpModule,用于自定义解析请求结果和添加公共参数 14 | * 支持取消某个请求、取消所有请求 15 | * 入口好记,分别是GlintHttp,GlintDownload,GlintUpload,GlintSocket 16 | 17 | ## 三、安装 18 | 19 | 推荐使用 Maven: 20 | ``` gradle 21 | dependencies { 22 | implementation 'com.ysbing.glint:glint:1.1.4' 23 | } 24 | ``` 25 | 26 | ## 四、HTTP请求 27 | 28 | ### 请求队列 29 | 最基本的请求: 30 | 31 | ``` java 32 | String url = "https://www.sojson.com/open/api/lunar/json.shtml?date=2017-05-27"; 33 | GlintHttp.get(url).execute(new GlintHttpListener() { 34 | @Override 35 | public void onSuccess(@NonNull String result) throws Throwable { 36 | super.onSuccess(result); 37 | } 38 | }); 39 | ``` 40 | 41 | ### 高级自定义配置 42 | 43 | 新建一个类,继承BaseHttpModule,使用HttpModule有两种使用方式 44 | 45 | 第一种方式:在AndroidManifest文件中声明该Module作为该Application通用的Module,需注意的是,每一个Application只能拥有一个通用的Module。 46 | 47 | ``` xml 48 | 52 | ``` 53 | 54 | 55 | 第二种方式:在单独请求的时候,使用using方法,将该Module传入。 56 | 57 | 如果以上两种方式都不使用的话,将采取标准的json解析到对应的实体类。 58 | 59 | 使用第一种方式的HttpModule类必须是public的,以便在GlintHttp延迟初始化时,可以通过反射将它们实例化。 60 | 61 | 带有配置的请求,使用using方法 62 | 63 | ``` java 64 | String url = "https://www.sojson.com/open/api/lunar/json.shtml"; 65 | TreeMap params = new TreeMap<>(); 66 | params.put("date", "2018-10-01"); 67 | GlintHttp.get(url, params).using(MyHttpModule.get()).execute(new GlintHttpListener() { 68 | @Override 69 | public void onSuccess(@NonNull LunarBean result) throws Throwable { 70 | super.onSuccess(result); 71 | } 72 | 73 | @Override 74 | public void onFail(@NonNull Throwable error) { 75 | super.onFail(error); 76 | } 77 | 78 | @Override 79 | public void onFinish() { 80 | super.onFinish(); 81 | } 82 | }); 83 | ``` 84 | 85 | 其他使用方法请查阅GlintHttp的公开方法。 86 | 87 | ### HTTP请求添加参数 88 | 参数使用TreeMap来装载数据,使用时,请勿传入null键 89 | 90 | ### OkHttp配置 91 | 复写BaseHttpModule里的onOkHttpBuildCreate方法 92 | 93 | ``` java 94 | @Override 95 | public OkHttpClient.Builder onOkHttpBuildCreate(@NonNull Glint.GlintType clientType, @NonNull OkHttpClient.Builder builder) { 96 | return builder.readTimeout(3000L, TimeUnit.MILLISECONDS) 97 | .writeTimeout(5000L, TimeUnit.MILLISECONDS); 98 | } 99 | ``` 100 | 101 | ### GlintHttpModule配置 102 | 103 | ``` java 104 | public class MyHttpModule extends BaseHttpModule { 105 | 106 | public static MyHttpModule get() { 107 | return new MyHttpModule(); 108 | } 109 | 110 | @Override 111 | public OkHttpClient.Builder onOkHttpBuildCreate(@NonNull Glint.GlintType clientType, @NonNull OkHttpClient.Builder builder) { 112 | return builder.readTimeout(3000L, TimeUnit.MILLISECONDS) 113 | .writeTimeout(5000L, TimeUnit.MILLISECONDS); 114 | } 115 | 116 | @Override 117 | public void configDefaultBuilder(@NonNull GlintBaseBuilder builder) { 118 | super.configDefaultBuilder(builder); 119 | } 120 | 121 | @Override 122 | public boolean getHeaders(@NonNull Map originalHeader) throws Exception { 123 | return super.getHeaders(originalHeader); 124 | } 125 | 126 | @Override 127 | public boolean getParams(@NonNull TreeMap originalParams) throws Exception { 128 | return super.getParams(originalParams); 129 | } 130 | 131 | @Override 132 | public boolean getParams(@NonNull TreeMap originalParams, @Nullable JsonObject originalJsonParams) throws Exception { 133 | return super.getParams(originalParams, originalJsonParams); 134 | } 135 | 136 | @Override 137 | public UrlResult getUrl(@NonNull String originalUrl) throws Exception { 138 | return super.getUrl(originalUrl); 139 | } 140 | 141 | @Override 142 | public boolean customDeserialize(@NonNull GlintResultBean result, @NonNull JsonObject jsonObj, @NonNull Gson gson, @NonNull Type typeOfT) throws Exception { 143 | JsonElement statusElement = jsonObj.get("status"); 144 | JsonElement messageElement = jsonObj.get("message"); 145 | JsonElement dataElement = jsonObj.get("data"); 146 | // 如果为空,可能是标准的json,不用判断状态码,直接解析 147 | if (statusElement == null) { 148 | result.setRunStatus(Glint.ResultStatus.STATUS_NORMAL); 149 | result.setData(GlintRequestUtil.standardDeserialize(gson, jsonObj, typeOfT)); 150 | } else { 151 | // status节点,这里判断出是否请求成功 152 | int status = statusElement.getAsInt(); 153 | if (messageElement != null) { 154 | // message节点 155 | String message = messageElement.getAsString(); 156 | result.setMessage(message); 157 | } 158 | result.setStatus(status); 159 | if (status == 200) { 160 | result.setRunStatus(Glint.ResultStatus.STATUS_SUCCESS); 161 | if (dataElement != null) { 162 | result.setData(GlintRequestUtil.successDeserialize(gson, dataElement, typeOfT)); 163 | } 164 | } else { 165 | result.setRunStatus(Glint.ResultStatus.STATUS_ERROR); 166 | } 167 | } 168 | return false; 169 | } 170 | } 171 | ``` 172 | 这样就全部配置完毕了,如果你不需要这个高级配置的话,可无需任何配置也可以正常使用,在这份配置中,onOkHttpBuildCreate方法在本进程只执行一次,其他方法在每个请求都会执行。 173 | 174 | 175 | ## 五、文件下载请求 176 | 177 | 下面是基本的使用方法 178 | 179 | ``` java 180 | GlintDownload.download("https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk", new File(getExternalCacheDir(), "mobileqq_android.apk")).execute(new GlintDownloadListener() { 181 | @Override 182 | public void onProgress(long bytesWritten, long contentLength, long speed, int percent) throws Exception { 183 | super.onProgress(bytesWritten, contentLength, speed, percent); 184 | } 185 | 186 | @Override 187 | public void onSuccess(@NonNull File result) throws Throwable { 188 | super.onSuccess(result); 189 | } 190 | }); 191 | ``` 192 | 更多高级用法可参阅源码,如下载过程支持可取消,可暂停和恢复。 193 | 194 | ## 六、文件上传请求 195 | 196 | 下面是基本的使用方法: 197 | 198 | ``` 199 | GlintUpload.upload("https://www.qq.com/", new File(getExternalCacheDir(), "mobileqq_android.apk")).execute(new GlintUploadListener() { 200 | @Override 201 | public void onProgress(long bytesWritten, long contentLength, long speed, int percent) throws Throwable { 202 | super.onProgress(bytesWritten, contentLength, speed, percent); 203 | } 204 | 205 | @Override 206 | public void onSuccess(@NonNull String result) throws Throwable { 207 | super.onSuccess(result); 208 | } 209 | }); 210 | ``` 211 | 212 | ## 七、WebSocket请求 213 | 214 | ### 下面是发送一条消息的使用方法: 215 | 216 | ``` java 217 | GlintSocket.sendIO(url, "cmd", "我是消息").execute(new GlintSocketListener() { 218 | @Override 219 | public void onProcess(@NonNull String result) throws Throwable { 220 | super.onProcess(result); 221 | } 222 | 223 | @Override 224 | public void onError(@NonNull String error) { 225 | super.onError(error); 226 | } 227 | }); 228 | ``` 229 | 230 | 注意,GlintSocketRequest有send和sendIO和两个方法,如果连接的是WebSocket就使用send,连接的是柚子IO就使用sendIO 231 | 232 | ### 下面是设置一个事件监听的使用方法: 233 | 234 | ``` java 235 | GlintSocket.on("http://socket.test", "cmd").execute(new GlintSocketListener() { 236 | @Override 237 | public void onProcess(@NonNull String result) throws Throwable { 238 | super.onProcess(result); 239 | } 240 | 241 | @Override 242 | public void onError(@NonNull String error) { 243 | super.onError(error); 244 | } 245 | }); 246 | ``` 247 | 当不需要Socket的时候,记住要将该连接做释放处理,否则会导致内存泄露,释放的使用方法如下: 248 | 249 | ``` java 250 | GlintSocket.off("http://socket.test", "cmd"); 251 | ``` 252 | 253 | ## 八、实用工具类 254 | 255 | ### 轻松获取一个Contex对象获取Application对象: 256 | 257 | ``` java 258 | Context context = ContextHelper.getAppContext(); 259 | Application application = ContextHelper.getApplication(); 260 | ``` 261 | 262 | ### 获取字符串或文件的MD5值: 263 | 264 | ``` java 265 | String strMd5 = Md5Util.getMD5Str("hello, world"); 266 | String fileMd5 = Md5Util.getMD5Str(new File("demo.txt")); 267 | ``` 268 | 269 | ### 从子线程快速切到主线程: 270 | 271 | ``` java 272 | UiKit.runOnMainThreadAsync(new Runnable() { 273 | @Override 274 | public void run() { 275 | 276 | } 277 | }); 278 | UiKit.runOnMainThreadSync(new Runnable() { 279 | @Override 280 | public void run() { 281 | 282 | } 283 | }); 284 | UiKit.runOnMainThreadSync(new Runnable() { 285 | @Override 286 | public void run() { 287 | 288 | } 289 | }, 1000, false); 290 | ``` -------------------------------------------------------------------------------- /samples/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /samples/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | //apply plugin: 'YRouter' 3 | 4 | android { 5 | compileSdk 34 6 | defaultConfig { 7 | applicationId "com.ysbing.samples.glint" 8 | minSdk 14 9 | compileSdk 34 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'androidx.appcompat:appcompat:1.6.1' 24 | implementation 'androidx.recyclerview:recyclerview:1.3.2' 25 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 26 | implementation 'com.ysbing:ypermission:1.1.0' 27 | implementation project(':glint') 28 | } -------------------------------------------------------------------------------- /samples/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 | -------------------------------------------------------------------------------- /samples/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 31 | 35 | 39 | 43 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /samples/src/main/java/com/ysbing/samples/glint/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.samples.glint; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.appcompat.app.AppCompatActivity; 11 | import androidx.recyclerview.widget.DividerItemDecoration; 12 | import androidx.recyclerview.widget.LinearLayoutManager; 13 | import androidx.recyclerview.widget.RecyclerView; 14 | 15 | import com.ysbing.samples.glint.download.DownloadRequestActivity; 16 | import com.ysbing.samples.glint.http.HttpModuleRequestActivity; 17 | import com.ysbing.samples.glint.http.HttpRequestActivity; 18 | import com.ysbing.samples.glint.upload.UploadRequestActivity; 19 | import com.ysbing.samples.glint.websocket.WebSocketRequestActivity; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | public class MainActivity extends AppCompatActivity { 25 | 26 | private static class MyViewHolder extends RecyclerView.ViewHolder { 27 | private MyViewHolder(@NonNull View itemView) { 28 | super(itemView); 29 | } 30 | } 31 | 32 | private static class MyData { 33 | private final String title; 34 | private final String des; 35 | 36 | private MyData(String title, String des) { 37 | this.title = title; 38 | this.des = des; 39 | } 40 | } 41 | 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.activity_main); 46 | RecyclerView recyclerView = findViewById(R.id.recyclerView); 47 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 48 | recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); 49 | recyclerView.setAdapter(new RecyclerView.Adapter() { 50 | private final List data = new ArrayList() {{ 51 | add(new MyData("普通请求", "请求一个HTML页面")); 52 | add(new MyData("复杂请求", "自定义Module,可对OkHttp加拦截器和自定义解析数据等配置")); 53 | add(new MyData("上传请求", "将本地文件上传到服务器")); 54 | add(new MyData("下载请求", "下载文件到本地")); 55 | add(new MyData("WebSocket请求", "监听Socket事件")); 56 | }}; 57 | 58 | @NonNull 59 | @Override 60 | public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { 61 | View itemView = LayoutInflater.from(MainActivity.this) 62 | .inflate(R.layout.item_list, viewGroup, false); 63 | return new MyViewHolder(itemView); 64 | } 65 | 66 | @Override 67 | public void onBindViewHolder(@NonNull final MyViewHolder myViewHolder, int i) { 68 | TextView titleView = myViewHolder.itemView.findViewById(R.id.tv_title); 69 | TextView contentView = myViewHolder.itemView.findViewById(R.id.tv_content); 70 | titleView.setText(data.get(i).title); 71 | contentView.setText(data.get(i).des); 72 | myViewHolder.itemView.setOnClickListener(v -> { 73 | switch (myViewHolder.getBindingAdapterPosition()) { 74 | case 0: 75 | HttpRequestActivity.startAction(MainActivity.this); 76 | break; 77 | case 1: 78 | HttpModuleRequestActivity.startAction(MainActivity.this); 79 | break; 80 | case 2: 81 | UploadRequestActivity.startAction(MainActivity.this); 82 | break; 83 | case 3: 84 | DownloadRequestActivity.startAction(MainActivity.this); 85 | break; 86 | case 4: 87 | WebSocketRequestActivity.startAction(MainActivity.this); 88 | break; 89 | default: 90 | break; 91 | } 92 | }); 93 | } 94 | 95 | @Override 96 | public int getItemCount() { 97 | return data.size(); 98 | } 99 | }); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /samples/src/main/java/com/ysbing/samples/glint/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.samples.glint; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.os.Handler; 8 | import android.view.Gravity; 9 | import android.widget.TextView; 10 | 11 | public class SplashActivity extends Activity { 12 | private final Handler mHandler = new Handler(); 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | TextView textView = new TextView(this); 18 | textView.setText("Glint"); 19 | textView.setTextColor(getResources().getColor(android.R.color.white)); 20 | textView.setTextSize(50f); 21 | textView.setGravity(Gravity.CENTER); 22 | textView.setBackgroundColor(Color.parseColor("#70C360")); 23 | setContentView(textView); 24 | mHandler.postDelayed(() -> { 25 | startActivity(new Intent(SplashActivity.this, MainActivity.class)); 26 | finish(); 27 | }, 1000L); 28 | } 29 | 30 | @Override 31 | protected void onPause() { 32 | super.onPause(); 33 | if (isFinishing()) { 34 | mHandler.removeCallbacksAndMessages(null); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /samples/src/main/java/com/ysbing/samples/glint/download/DownloadRequestActivity.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.samples.glint.download; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.ProgressBar; 9 | import android.widget.TextView; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.annotation.Nullable; 13 | import androidx.appcompat.app.AppCompatActivity; 14 | 15 | import com.ysbing.glint.download.GlintDownload; 16 | import com.ysbing.glint.download.GlintDownloadListener; 17 | import com.ysbing.samples.glint.R; 18 | 19 | import java.io.File; 20 | 21 | import okhttp3.Response; 22 | 23 | /** 24 | * Created by chenzhujie on 2019/5/14 25 | */ 26 | public class DownloadRequestActivity extends AppCompatActivity { 27 | private Button mButtonDownload; 28 | private TextView mTextViewStatus; 29 | private ProgressBar mProgressBar; 30 | private GlintDownload mGlintDownload; 31 | private boolean isDownload; 32 | 33 | public static void startAction(Activity activity) { 34 | Intent intent = new Intent(activity, DownloadRequestActivity.class); 35 | activity.startActivity(intent); 36 | } 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_download_request); 42 | mButtonDownload = findViewById(R.id.btn_download); 43 | mTextViewStatus = findViewById(R.id.tv_status); 44 | mProgressBar = findViewById(R.id.progress_bar); 45 | mButtonDownload.setOnClickListener(new View.OnClickListener() { 46 | @Override 47 | public void onClick(View v) { 48 | if (mGlintDownload != null) { 49 | if (isDownload) { 50 | mGlintDownload.pause(); 51 | } else { 52 | mGlintDownload.resume(); 53 | } 54 | } else { 55 | request(); 56 | } 57 | } 58 | }); 59 | } 60 | 61 | @Override 62 | protected void onPause() { 63 | super.onPause(); 64 | if (mGlintDownload != null) { 65 | mGlintDownload.cancel(); 66 | } 67 | } 68 | 69 | private void request() { 70 | String url = "https://download.sj.qq.com/upload/connAssitantDownload/upload/MobileAssistant_1.apk"; 71 | File savePath = getExternalFilesDir("download"); 72 | if (savePath == null) { 73 | savePath = getFilesDir(); 74 | } 75 | mGlintDownload = GlintDownload.download(url, savePath); 76 | mGlintDownload.execute(new GlintDownloadListener() { 77 | @Override 78 | public void onStart() { 79 | super.onStart(); 80 | String str = "onStart()"; 81 | mTextViewStatus.setText(str); 82 | isDownload = true; 83 | mButtonDownload.setText("暂停"); 84 | } 85 | 86 | @Override 87 | public void onProgress(long bytesWritten, long contentLength, long speed, int percent) throws Exception { 88 | super.onProgress(bytesWritten, contentLength, speed, percent); 89 | String str = percent + "%,onProgress()"; 90 | mTextViewStatus.setText(str); 91 | mProgressBar.setProgress(percent); 92 | } 93 | 94 | @Override 95 | public void onPause() { 96 | super.onPause(); 97 | String str = "onPause()"; 98 | mTextViewStatus.setText(str); 99 | mButtonDownload.setText("继续"); 100 | isDownload = false; 101 | } 102 | 103 | @Override 104 | public void onResume() { 105 | super.onResume(); 106 | String str = "onResume()"; 107 | mTextViewStatus.setText(str); 108 | mButtonDownload.setText("暂停"); 109 | isDownload = true; 110 | } 111 | 112 | @Override 113 | public void onCancel() { 114 | super.onCancel(); 115 | String str = "onCancel()"; 116 | mTextViewStatus.setText(str); 117 | mButtonDownload.setText("开始"); 118 | isDownload = false; 119 | } 120 | 121 | @Override 122 | public void onDownloadFail(@NonNull Throwable error, @Nullable Response response) { 123 | super.onDownloadFail(error, response); 124 | String str = "onDownloadFail(),error:" + error; 125 | mTextViewStatus.setText(str); 126 | mButtonDownload.setText("继续"); 127 | isDownload = false; 128 | } 129 | 130 | @Override 131 | public void onSuccess(@NonNull File result) throws Throwable { 132 | super.onSuccess(result); 133 | String str = "onSuccess(),file:" + result; 134 | mTextViewStatus.setText(str); 135 | } 136 | 137 | @Override 138 | public void onFinish() { 139 | super.onFinish(); 140 | isDownload = false; 141 | mGlintDownload = null; 142 | mButtonDownload.setText("开始"); 143 | } 144 | }); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /samples/src/main/java/com/ysbing/samples/glint/http/HttpModuleRequestActivity.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.samples.glint.http; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.TextView; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.appcompat.app.AppCompatActivity; 12 | 13 | import com.ysbing.glint.base.GlintResultBean; 14 | import com.ysbing.glint.http.GlintHttp; 15 | import com.ysbing.glint.http.GlintHttpListener; 16 | import com.ysbing.samples.glint.R; 17 | 18 | import java.util.TreeMap; 19 | 20 | import okhttp3.Headers; 21 | 22 | /** 23 | * Created by chenzhujie on 2019/5/13 24 | */ 25 | public class HttpModuleRequestActivity extends AppCompatActivity { 26 | 27 | private TextView mTextView; 28 | 29 | public static void startAction(Activity activity) { 30 | Intent intent = new Intent(activity, HttpModuleRequestActivity.class); 31 | activity.startActivity(intent); 32 | } 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_http_request); 38 | Button button = findViewById(R.id.button); 39 | mTextView = findViewById(R.id.content); 40 | button.setOnClickListener(new View.OnClickListener() { 41 | @Override 42 | public void onClick(View v) { 43 | request(); 44 | } 45 | }); 46 | } 47 | 48 | private void request() { 49 | String url = "https://www.sojson.com/open/api/lunar/json.shtml"; 50 | TreeMap params = new TreeMap<>(); 51 | params.put("date", "2019-05-01"); 52 | Headers.Builder headers = new Headers.Builder(); 53 | headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"); 54 | GlintHttp.get(url, params).setHeader(headers) 55 | .using(MyHttpModule.get()).execute(new GlintHttpListener() { 56 | @Override 57 | public void onStart() { 58 | super.onStart(); 59 | String str = "->onStart()"; 60 | mTextView.setText(str); 61 | } 62 | 63 | @Override 64 | public void onResponse(@NonNull GlintResultBean resultBean) throws Throwable { 65 | super.onResponse(resultBean); 66 | String str = mTextView.getText() + "\n\n->onResponse()," + resultBean.getResponseStr(); 67 | mTextView.setText(str); 68 | } 69 | 70 | @Override 71 | public void onSuccess(@NonNull LunarBean result) throws Throwable { 72 | super.onSuccess(result); 73 | String str = mTextView.getText() + "\n\n->onSuccess()," + result; 74 | mTextView.setText(str); 75 | } 76 | 77 | @Override 78 | public void onFail(@NonNull Throwable error) { 79 | super.onFail(error); 80 | String str = mTextView.getText() + "\n\n->onFail()," + error.toString(); 81 | mTextView.setText(str); 82 | } 83 | 84 | @Override 85 | public void onError(int status, @NonNull String errMsg) throws Throwable { 86 | super.onError(status, errMsg); 87 | String str = mTextView.getText() + "\n\n->onError(),status:" + status + ",errMsg:" + errMsg; 88 | mTextView.setText(str); 89 | } 90 | 91 | @Override 92 | public void onFinish() { 93 | super.onFinish(); 94 | String str = mTextView.getText() + "\n\n->onFinish()"; 95 | mTextView.setText(str); 96 | } 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /samples/src/main/java/com/ysbing/samples/glint/http/HttpRequestActivity.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.samples.glint.http; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.text.method.ScrollingMovementMethod; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.TextView; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.appcompat.app.AppCompatActivity; 13 | 14 | import com.ysbing.glint.http.GlintHttp; 15 | import com.ysbing.glint.http.GlintHttpListener; 16 | import com.ysbing.samples.glint.R; 17 | 18 | /** 19 | * Created by chenzhujie on 2019/5/14 20 | */ 21 | public class HttpRequestActivity extends AppCompatActivity { 22 | 23 | private TextView mTextView; 24 | 25 | public static void startAction(Activity activity) { 26 | Intent intent = new Intent(activity, HttpRequestActivity.class); 27 | activity.startActivity(intent); 28 | } 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_http_request); 34 | Button button = findViewById(R.id.button); 35 | mTextView = findViewById(R.id.content); 36 | mTextView.setMovementMethod(ScrollingMovementMethod.getInstance()); 37 | button.setOnClickListener(new View.OnClickListener() { 38 | @Override 39 | public void onClick(View v) { 40 | request(); 41 | } 42 | }); 43 | } 44 | 45 | private void request() { 46 | String url = "https://www.baidu.com/"; 47 | GlintHttp.get(url).notJson(true).execute(new GlintHttpListener() { 48 | @Override 49 | public void onSuccess(@NonNull String result) throws Throwable { 50 | super.onSuccess(result); 51 | mTextView.setText(result); 52 | } 53 | }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /samples/src/main/java/com/ysbing/samples/glint/http/LunarBean.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.samples.glint.http; 2 | 3 | import java.util.List; 4 | 5 | public class LunarBean { 6 | public int year; 7 | public int month; 8 | public int day; 9 | public int lunarYear; 10 | public int lunarMonth; 11 | public int lunarDay; 12 | public String cnyear; 13 | public String cnmonth; 14 | public String cnday; 15 | public String hyear; 16 | public String cyclicalYear; 17 | public String cyclicalMonth; 18 | public String cyclicalDay; 19 | public String suit; 20 | public String taboo; 21 | public String animal; 22 | public String week; 23 | public List festivalList; 24 | public int maxDayInMonth; 25 | public boolean leap; 26 | public String lunarYearString; 27 | public boolean bigMonth; 28 | } 29 | -------------------------------------------------------------------------------- /samples/src/main/java/com/ysbing/samples/glint/http/MyHttpModule.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.samples.glint.http; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import com.google.gson.Gson; 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.ysbing.glint.base.BaseHttpModule; 10 | import com.ysbing.glint.base.Glint; 11 | import com.ysbing.glint.base.GlintBaseBuilder; 12 | import com.ysbing.glint.base.GlintResultBean; 13 | import com.ysbing.glint.util.GlintRequestUtil; 14 | 15 | import java.lang.reflect.Type; 16 | import java.util.TreeMap; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | import okhttp3.Headers; 20 | import okhttp3.OkHttpClient; 21 | 22 | public class MyHttpModule extends BaseHttpModule { 23 | 24 | public static MyHttpModule get() { 25 | return new MyHttpModule(); 26 | } 27 | 28 | @Override 29 | public OkHttpClient.Builder onOkHttpBuildCreate(@NonNull Glint.GlintType clientType, @NonNull OkHttpClient.Builder builder) { 30 | return builder.readTimeout(3000L, TimeUnit.MILLISECONDS) 31 | .writeTimeout(5000L, TimeUnit.MILLISECONDS); 32 | } 33 | 34 | @Override 35 | public void configDefaultBuilder( 36 | @NonNull GlintBaseBuilder builder) { 37 | super.configDefaultBuilder(builder); 38 | } 39 | 40 | @Override 41 | public boolean getHeaders(@NonNull Headers.Builder originalHeader) throws Exception { 42 | return super.getHeaders(originalHeader); 43 | } 44 | 45 | @Override 46 | public boolean getParams(@NonNull TreeMap originalParams) throws Exception { 47 | return super.getParams(originalParams); 48 | } 49 | 50 | @Override 51 | public boolean getParams(@NonNull TreeMap originalParams, @Nullable JsonObject originalJsonParams) throws Exception { 52 | return super.getParams(originalParams, originalJsonParams); 53 | } 54 | 55 | @Override 56 | public UrlResult getUrl(@NonNull String originalUrl) throws Exception { 57 | return super.getUrl(originalUrl); 58 | } 59 | 60 | @Override 61 | public boolean customDeserialize(@NonNull GlintResultBean result, 62 | @NonNull JsonElement jsonEl, @NonNull Gson gson, 63 | @NonNull Type typeOfT) throws Exception { 64 | JsonObject jsonObj = jsonEl.getAsJsonObject(); 65 | JsonElement statusElement = jsonObj.get("status"); 66 | JsonElement messageElement = jsonObj.get("message"); 67 | JsonElement dataElement = jsonObj.get("data"); 68 | // 如果为空,可能是标准的json,不用判断状态码,直接解析 69 | if (statusElement == null) { 70 | result.setRunStatus(Glint.ResultStatus.STATUS_ERROR); 71 | result.setMessage(GlintRequestUtil.errorDeserialize(jsonObj)); 72 | } else { 73 | // status节点,这里判断出是否请求成功 74 | int status = statusElement.getAsInt(); 75 | if (messageElement != null) { 76 | // message节点 77 | String message = messageElement.getAsString(); 78 | result.setMessage(message); 79 | } 80 | result.setStatus(status); 81 | if (status == 200) { 82 | result.setRunStatus(Glint.ResultStatus.STATUS_SUCCESS); 83 | if (dataElement != null) { 84 | result.setData( 85 | GlintRequestUtil.successDeserialize(gson, dataElement, typeOfT)); 86 | } 87 | } else { 88 | result.setRunStatus(Glint.ResultStatus.STATUS_ERROR); 89 | } 90 | } 91 | return false; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /samples/src/main/java/com/ysbing/samples/glint/upload/ContentUriUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007-2008 OpenIntents.org 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ysbing.samples.glint.upload; 18 | 19 | import android.content.ContentUris; 20 | import android.content.Context; 21 | import android.database.Cursor; 22 | import android.net.Uri; 23 | import android.os.Build; 24 | import android.os.Environment; 25 | import android.provider.DocumentsContract; 26 | import android.provider.MediaStore; 27 | 28 | public class ContentUriUtil { 29 | /** 30 | * Get a file path from a Uri. This will get the the path for Storage Access 31 | * Framework Documents, as well as the _data field for the MediaStore and 32 | * other file-based ContentProviders. 33 | * 34 | * @param context The context. 35 | * @param uri The Uri to query. 36 | * @author paulburke 37 | */ 38 | public static String getPath(final Context context, final Uri uri) { 39 | // DocumentProvider 40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT 41 | && DocumentsContract.isDocumentUri(context, uri)) { 42 | // ExternalStorageProvider 43 | if (isExternalStorageDocument(uri)) { 44 | final String docId = DocumentsContract.getDocumentId(uri); 45 | final String[] split = docId.split(":"); 46 | final String type = split[0]; 47 | 48 | if ("primary".equalsIgnoreCase(type)) { 49 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 50 | } 51 | 52 | // TODO handle non-primary volumes 53 | } 54 | // DownloadsProvider 55 | else if (isDownloadsDocument(uri)) { 56 | 57 | final String id = DocumentsContract.getDocumentId(uri); 58 | final Uri contentUri = ContentUris.withAppendedId( 59 | Uri.parse("content://downloads/public_downloads"), Long.parseLong(id)); 60 | 61 | return getDataColumn(context, contentUri, null, null); 62 | } 63 | // MediaProvider 64 | else if (isMediaDocument(uri)) { 65 | final String docId = DocumentsContract.getDocumentId(uri); 66 | final String[] split = docId.split(":"); 67 | final String type = split[0]; 68 | 69 | Uri contentUri = null; 70 | if ("image".equals(type)) { 71 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 72 | } else if ("video".equals(type)) { 73 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 74 | } else if ("audio".equals(type)) { 75 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 76 | } 77 | 78 | final String selection = "_id=?"; 79 | final String[] selectionArgs = new String[]{ 80 | split[1] 81 | }; 82 | 83 | return getDataColumn(context, contentUri, selection, selectionArgs); 84 | } 85 | } 86 | // MediaStore (and general) 87 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 88 | return getDataColumn(context, uri, null, null); 89 | } 90 | // File 91 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 92 | return uri.getPath(); 93 | } 94 | 95 | return null; 96 | } 97 | 98 | /** 99 | * Get the value of the data column for this Uri. This is useful for 100 | * MediaStore Uris, and other file-based ContentProviders. 101 | * 102 | * @param context The context. 103 | * @param uri The Uri to query. 104 | * @param selection (Optional) Filter used in the query. 105 | * @param selectionArgs (Optional) Selection arguments used in the query. 106 | * @return The value of the _data column, which is typically a file path. 107 | */ 108 | public static String getDataColumn(Context context, Uri uri, String selection, 109 | String[] selectionArgs) { 110 | 111 | Cursor cursor = null; 112 | final String column = "_data"; 113 | final String[] projection = { 114 | column 115 | }; 116 | 117 | try { 118 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 119 | null); 120 | if (cursor != null && cursor.moveToFirst()) { 121 | final int column_index = cursor.getColumnIndexOrThrow(column); 122 | return cursor.getString(column_index); 123 | } 124 | } finally { 125 | if (cursor != null) { 126 | cursor.close(); 127 | } 128 | } 129 | return null; 130 | } 131 | 132 | 133 | /** 134 | * @param uri The Uri to check. 135 | * @return Whether the Uri authority is ExternalStorageProvider. 136 | */ 137 | public static boolean isExternalStorageDocument(Uri uri) { 138 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 139 | } 140 | 141 | /** 142 | * @param uri The Uri to check. 143 | * @return Whether the Uri authority is DownloadsProvider. 144 | */ 145 | public static boolean isDownloadsDocument(Uri uri) { 146 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 147 | } 148 | 149 | /** 150 | * @param uri The Uri to check. 151 | * @return Whether the Uri authority is MediaProvider. 152 | */ 153 | public static boolean isMediaDocument(Uri uri) { 154 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 155 | } 156 | } -------------------------------------------------------------------------------- /samples/src/main/java/com/ysbing/samples/glint/upload/UploadRequestActivity.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.samples.glint.upload; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.text.TextUtils; 9 | import android.widget.Button; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | import android.widget.Toast; 13 | 14 | import androidx.activity.result.contract.ActivityResultContracts; 15 | import androidx.annotation.NonNull; 16 | import androidx.annotation.RequiresApi; 17 | import androidx.appcompat.app.AppCompatActivity; 18 | 19 | import com.ysbing.glint.upload.GlintUpload; 20 | import com.ysbing.glint.upload.GlintUploadListener; 21 | import com.ysbing.samples.glint.R; 22 | import com.ysbing.ypermission.PermissionManager; 23 | 24 | import java.io.File; 25 | 26 | /** 27 | * Created by chenzhujie on 2019/5/14 28 | */ 29 | public class UploadRequestActivity extends AppCompatActivity { 30 | 31 | private TextView mTextViewStatus; 32 | private ImageView mImageView; 33 | private String path; 34 | 35 | public static void startAction(Activity activity) { 36 | Intent intent = new Intent(activity, UploadRequestActivity.class); 37 | activity.startActivity(intent); 38 | } 39 | 40 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_upload_request); 45 | Button buttonSelect = findViewById(R.id.btn_select); 46 | Button buttonUpload = findViewById(R.id.btn_upload); 47 | mTextViewStatus = findViewById(R.id.tv_status); 48 | mImageView = findViewById(R.id.iv_image); 49 | buttonSelect.setOnClickListener(v -> { 50 | String[] permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}; 51 | PermissionManager.requestPermission(UploadRequestActivity.this, permissions, new PermissionManager.PermissionsListener() { 52 | @Override 53 | public void onPermissionGranted() { 54 | pickPhoto(); 55 | } 56 | }); 57 | }); 58 | buttonUpload.setOnClickListener(v -> request()); 59 | } 60 | 61 | /*** 62 | * 从相册中选取图片 63 | */ 64 | public void pickPhoto() { 65 | Intent intent = new Intent(); 66 | intent.setType("image/*"); 67 | intent.setAction(Intent.ACTION_GET_CONTENT); 68 | registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { 69 | if (result.getResultCode() == Activity.RESULT_OK) { 70 | if (result.getData() != null && result.getData().getData() != null) { 71 | path = ContentUriUtil.getPath(UploadRequestActivity.this, result.getData().getData()); 72 | mImageView.setImageURI(result.getData().getData()); 73 | } 74 | } 75 | }).launch(Intent.createChooser(intent, "Choose Image...")); 76 | } 77 | 78 | private void request() { 79 | if (TextUtils.isEmpty(path)) { 80 | Toast.makeText(this, "请选择文件", Toast.LENGTH_SHORT).show(); 81 | } else { 82 | String url = "http://google.com/upload.do"; 83 | GlintUpload.upload(url, new File(path)).execute(new GlintUploadListener() { 84 | @Override 85 | public void onStart() { 86 | super.onStart(); 87 | String str = "onStart()"; 88 | mTextViewStatus.setText(str); 89 | } 90 | 91 | @Override 92 | public void onProgress(long bytesWritten, long contentLength, long speed, int percent) throws Exception { 93 | super.onProgress(bytesWritten, contentLength, speed, percent); 94 | String str = "onProgress(),percent:" + percent + "%"; 95 | mTextViewStatus.setText(str); 96 | } 97 | 98 | @Override 99 | public void onSuccess(@NonNull String result) throws Throwable { 100 | super.onSuccess(result); 101 | String str = "onSuccess()," + result; 102 | mTextViewStatus.setText(str); 103 | } 104 | 105 | @Override 106 | public void onFail(@NonNull Throwable error) { 107 | super.onFail(error); 108 | String str = "onFail()," + error; 109 | mTextViewStatus.setText(str); 110 | } 111 | }); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /samples/src/main/java/com/ysbing/samples/glint/websocket/MySocketHttpModule.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.samples.glint.websocket; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | import com.google.gson.Gson; 7 | import com.google.gson.JsonElement; 8 | import com.ysbing.glint.socket.SocketHttpModule; 9 | 10 | import java.lang.reflect.Type; 11 | 12 | /** 13 | * 自定义网络模块 14 | * 15 | * @author ysbing 16 | */ 17 | public class MySocketHttpModule extends SocketHttpModule { 18 | public static final String SOCKET_CMD_SEND = "MY_CMD"; 19 | 20 | public static MySocketHttpModule get() { 21 | return new MySocketHttpModule(); 22 | } 23 | 24 | @Override 25 | public String getCmdId(@NonNull JsonElement jsonEl, @NonNull Gson gson, @NonNull Type typeOfT) { 26 | return SOCKET_CMD_SEND; 27 | } 28 | 29 | @Override 30 | public T customDeserialize(@NonNull JsonElement jsonEl, @NonNull Gson gson, @NonNull Type typeOfT) throws Exception { 31 | return super.customDeserialize(jsonEl, gson, typeOfT); 32 | } 33 | } -------------------------------------------------------------------------------- /samples/src/main/java/com/ysbing/samples/glint/websocket/WebSocketRequestActivity.java: -------------------------------------------------------------------------------- 1 | package com.ysbing.samples.glint.websocket; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.text.method.ScrollingMovementMethod; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.TextView; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.appcompat.app.AppCompatActivity; 13 | 14 | import com.ysbing.glint.socket.GlintSocket; 15 | import com.ysbing.glint.socket.GlintSocketListener; 16 | import com.ysbing.samples.glint.R; 17 | 18 | import java.util.concurrent.atomic.AtomicInteger; 19 | 20 | /** 21 | * Created by chenzhujie on 2019/5/14 22 | */ 23 | public class WebSocketRequestActivity extends AppCompatActivity { 24 | private static final String SOCKET_URL = "ws://echo.websocket.org"; 25 | private final AtomicInteger msgId = new AtomicInteger(); 26 | private TextView mTextView; 27 | private String text = ""; 28 | 29 | public static void startAction(Activity activity) { 30 | Intent intent = new Intent(activity, WebSocketRequestActivity.class); 31 | activity.startActivity(intent); 32 | } 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_websocket_request); 38 | Button buttonOn = findViewById(R.id.btn_on); 39 | Button buttonSend = findViewById(R.id.btn_send); 40 | mTextView = findViewById(R.id.et_content); 41 | mTextView.setMovementMethod(ScrollingMovementMethod.getInstance()); 42 | buttonOn.setOnClickListener(new View.OnClickListener() { 43 | @Override 44 | public void onClick(View v) { 45 | GlintSocket.off(SOCKET_URL, GlintSocket.EVENT_CONNECT); 46 | GlintSocket.off(SOCKET_URL, MySocketHttpModule.SOCKET_CMD_SEND); 47 | socketOn(); 48 | } 49 | }); 50 | buttonSend.setOnClickListener(new View.OnClickListener() { 51 | @Override 52 | public void onClick(View v) { 53 | socketSend(); 54 | } 55 | }); 56 | } 57 | 58 | @Override 59 | protected void onStop() { 60 | super.onStop(); 61 | if (isFinishing()) { 62 | GlintSocket.off(SOCKET_URL); 63 | } 64 | } 65 | 66 | private void socketOn() { 67 | GlintSocket.on(SOCKET_URL, GlintSocket.EVENT_CONNECT).execute(new GlintSocketListener() { 68 | @Override 69 | public void onProcess(@NonNull String result) throws Throwable { 70 | super.onProcess(result); 71 | text = "socketOn,EVENT_CONNECT\n\n" + text; 72 | updateEditText(); 73 | } 74 | 75 | @Override 76 | public void onError(@NonNull String error) { 77 | super.onError(error); 78 | text = "socketOn,EVENT_CONNECT,error:" + error + "\n\n" + text; 79 | updateEditText(); 80 | } 81 | }); 82 | GlintSocket.on(SOCKET_URL, MySocketHttpModule.SOCKET_CMD_SEND) 83 | .using(MySocketHttpModule.get()).execute(new GlintSocketListener() { 84 | @Override 85 | public void onProcess(@NonNull String result) throws Throwable { 86 | super.onProcess(result); 87 | text = "socketOn," + MySocketHttpModule.SOCKET_CMD_SEND + ",result:" + result + "\n\n" + text; 88 | updateEditText(); 89 | } 90 | 91 | @Override 92 | public void onError(@NonNull String error) { 93 | super.onError(error); 94 | text = "socketOn," + MySocketHttpModule.SOCKET_CMD_SEND + ",error:" + error + "\n\n" + text; 95 | updateEditText(); 96 | } 97 | }); 98 | } 99 | 100 | private void socketSend() { 101 | GlintSocket.send(SOCKET_URL, "我是消息" + msgId.incrementAndGet()).execute(); 102 | } 103 | 104 | private void updateEditText() { 105 | mTextView.setText(text); 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /samples/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 | -------------------------------------------------------------------------------- /samples/src/main/res/layout/activity_download_request.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |