├── .gitignore ├── LICENSE ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro ├── release │ ├── app-release.apk │ └── output.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── per │ │ └── goweii │ │ └── android │ │ └── rxhttp │ │ ├── MainActivity.java │ │ ├── TestDownloadActivity.java │ │ ├── TestRequestActivity.java │ │ ├── bean │ │ ├── RecommendPoetryBean.java │ │ ├── SinglePoetryBean.java │ │ └── WeatherBean.java │ │ └── http │ │ ├── FreeApi.java │ │ └── ResponseBean.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_main.xml │ ├── activity_test_download.xml │ └── activity_test_request.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── rxhttp ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── per │ │ └── goweii │ │ └── rxhttp │ │ ├── core │ │ ├── RxHttp.java │ │ ├── RxLife.java │ │ ├── exception │ │ │ └── RxHttpUninitializedException.java │ │ ├── manager │ │ │ └── BaseClientManager.java │ │ └── utils │ │ │ ├── BaseUrlUtils.java │ │ │ └── SDCardUtils.java │ │ ├── download │ │ ├── DownloadApi.java │ │ ├── DownloadClientManager.java │ │ ├── DownloadInfo.java │ │ ├── RxDownload.java │ │ ├── exception │ │ │ ├── RangeLengthIsZeroException.java │ │ │ ├── SaveFileBrokenPointException.java │ │ │ ├── SaveFileDirMakeException.java │ │ │ └── SaveFileWriteException.java │ │ ├── interceptor │ │ │ ├── DownloadResponseBody.java │ │ │ └── RealNameInterceptor.java │ │ ├── setting │ │ │ ├── DefaultDownloadSetting.java │ │ │ └── DownloadSetting.java │ │ └── utils │ │ │ ├── DownloadInfoChecker.java │ │ │ ├── RxNotify.java │ │ │ └── UnitFormatUtils.java │ │ └── request │ │ ├── Api.java │ │ ├── RequestClientManager.java │ │ ├── RxRequest.java │ │ ├── RxResponse.java │ │ ├── base │ │ ├── BaseBean.java │ │ └── BaseResponse.java │ │ ├── exception │ │ ├── ApiException.java │ │ ├── ExceptionHandle.java │ │ └── NullRequestSettingException.java │ │ ├── gson │ │ ├── BaseBeanDeserializer.java │ │ ├── IntDeserializer.java │ │ ├── ListDeserializer.java │ │ └── StringDeserializer.java │ │ ├── interceptor │ │ ├── BaseCacheControlInterceptor.java │ │ ├── BaseUrlRedirectInterceptor.java │ │ ├── CacheControlInterceptor.java │ │ ├── CacheControlNetworkInterceptor.java │ │ ├── PublicHeadersInterceptor.java │ │ └── PublicQueryParameterInterceptor.java │ │ ├── setting │ │ ├── DefaultRequestSetting.java │ │ ├── ParameterGetter.java │ │ └── RequestSetting.java │ │ └── utils │ │ ├── FileUtils.java │ │ ├── HttpsCompat.java │ │ ├── JsonFormatUtils.java │ │ ├── JsonObjUtils.java │ │ ├── NetUtils.java │ │ ├── NonNullUtils.java │ │ └── RequestBodyUtils.java │ └── res │ └── values │ └── strings.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /.idea 4 | .DS_Store 5 | /build 6 | /captures 7 | .externalNativeBuild 8 | ### Android template 9 | # Built application files 10 | 11 | # Files for the ART/Dalvik VM 12 | *.dex 13 | 14 | # Java class files 15 | *.class 16 | 17 | # Generated files 18 | bin/ 19 | gen/ 20 | out/ 21 | 22 | # Gradle files 23 | .gradle/ 24 | build/ 25 | 26 | # Local configuration file (sdk path, etc) 27 | local.properties 28 | 29 | # Proguard folder generated by Eclipse 30 | proguard/ 31 | 32 | # Log Files 33 | *.log 34 | 35 | # Android Studio Navigation editor temp files 36 | .navigation/ 37 | 38 | # Android Studio captures folder 39 | captures/ 40 | 41 | # IntelliJ 42 | *.iml 43 | .idea/ 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | buildToolsVersion '28.0.3' 6 | defaultConfig { 7 | applicationId "per.goweii.android.rxhttp" 8 | minSdkVersion 14 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_8 22 | targetCompatibility JavaVersion.VERSION_1_8 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(include: ['*.jar'], dir: 'libs') 28 | implementation 'com.android.support:appcompat-v7:28.0.0' 29 | api project(':rxhttp') 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/app/release/app-release.apk -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/per/goweii/android/rxhttp/MainActivity.java: -------------------------------------------------------------------------------- 1 | package per.goweii.android.rxhttp; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | import per.goweii.rxhttp.core.RxHttp; 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | 17 | RxHttp.init(this); 18 | 19 | findViewById(R.id.tv_go_test_request).setOnClickListener(new View.OnClickListener() { 20 | @Override 21 | public void onClick(View v) { 22 | Intent intent = new Intent(MainActivity.this, TestRequestActivity.class); 23 | startActivity(intent); 24 | } 25 | }); 26 | 27 | findViewById(R.id.tv_go_test_download).setOnClickListener(new View.OnClickListener() { 28 | @Override 29 | public void onClick(View v) { 30 | Intent intent = new Intent(MainActivity.this, TestDownloadActivity.class); 31 | startActivity(intent); 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/per/goweii/android/rxhttp/TestDownloadActivity.java: -------------------------------------------------------------------------------- 1 | package per.goweii.android.rxhttp; 2 | 3 | import android.content.SharedPreferences; 4 | import android.os.Bundle; 5 | import android.preference.PreferenceManager; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.text.TextUtils; 8 | import android.view.KeyEvent; 9 | import android.view.View; 10 | import android.widget.EditText; 11 | import android.widget.ProgressBar; 12 | import android.widget.TextView; 13 | 14 | import per.goweii.rxhttp.download.DownloadInfo; 15 | import per.goweii.rxhttp.download.RxDownload; 16 | import per.goweii.rxhttp.download.utils.UnitFormatUtils; 17 | 18 | public class TestDownloadActivity extends AppCompatActivity { 19 | 20 | public static final String url_1 = "https://imtt.dd.qq.com/16891/601BBD228F1F77DB1FB03FE38EF9BC93.apk?fsname=com.tencent.tmgp.sgame_1.41.2.4_41020401.apk&csr=1bbd"; 21 | public static final String url = "https://imtt.dd.qq.com/16891/513D2C5324E6EBE77F94C85D7C76EBAE.apk?fsname=com.tencent.mobileqq_7.8.2_926.apk&csr=1bbd"; 22 | private RxDownload mRxDownload; 23 | private boolean isStart = false; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_test_download); 29 | 30 | EditText et_url = findViewById(R.id.et_url); 31 | ProgressBar pb_1 = findViewById(R.id.pb_1); 32 | pb_1.setMax(10000); 33 | TextView tv_download_length = findViewById(R.id.tv_download_length); 34 | TextView tv_content_length = findViewById(R.id.tv_content_length); 35 | TextView tv_speed = findViewById(R.id.tv_speed); 36 | TextView tv_start_stop = findViewById(R.id.tv_start_stop); 37 | TextView tv_cancel = findViewById(R.id.tv_cancel); 38 | TextView tv_clean = findViewById(R.id.tv_clean); 39 | 40 | et_url.setText(url); 41 | 42 | tv_start_stop.setOnClickListener(new View.OnClickListener() { 43 | @Override 44 | public void onClick(View v) { 45 | if (isStart) { 46 | mRxDownload.stop(); 47 | isStart = false; 48 | } else { 49 | mRxDownload.start(); 50 | isStart = true; 51 | } 52 | } 53 | }); 54 | 55 | tv_cancel.setOnClickListener(new View.OnClickListener() { 56 | @Override 57 | public void onClick(View v) { 58 | mRxDownload.cancel(); 59 | isStart = false; 60 | } 61 | }); 62 | 63 | tv_clean.setOnClickListener(new View.OnClickListener() { 64 | @Override 65 | public void onClick(View v) { 66 | cleanDownloadInfo(); 67 | tv_download_length.setText(""); 68 | tv_content_length.setText(""); 69 | tv_speed.setText(""); 70 | pb_1.setProgress(0); 71 | tv_start_stop.setText("开始下载"); 72 | tv_cancel.setText("取消下载"); 73 | } 74 | }); 75 | 76 | DownloadInfo downloadInfo = getDownloadInfo(); 77 | if (downloadInfo == null) { 78 | mRxDownload = RxDownload.create(DownloadInfo.create(et_url.getText().toString())); 79 | } else { 80 | pb_1.setProgress((int) (((float)downloadInfo.downloadLength / (float)downloadInfo.contentLength) * 10000)); 81 | tv_download_length.setText(UnitFormatUtils.formatBytesLength(downloadInfo.downloadLength)); 82 | tv_content_length.setText(UnitFormatUtils.formatBytesLength(downloadInfo.contentLength)); 83 | DownloadInfo info = DownloadInfo.create(downloadInfo.url, 84 | downloadInfo.saveDirPath, downloadInfo.saveFileName, 85 | downloadInfo.downloadLength, downloadInfo.contentLength); 86 | mRxDownload = RxDownload.create(info); 87 | } 88 | mRxDownload.setDownloadListener(new RxDownload.DownloadListener() { 89 | @Override 90 | public void onStarting(DownloadInfo info) { 91 | tv_start_stop.setText("暂停下载"); 92 | tv_cancel.setText("取消下载"); 93 | } 94 | 95 | @Override 96 | public void onDownloading(DownloadInfo info) { 97 | tv_start_stop.setText("暂停下载"); 98 | } 99 | 100 | @Override 101 | public void onError(DownloadInfo info, Throwable e) { 102 | saveDownloadInfo(); 103 | tv_start_stop.setText("开始下载"); 104 | tv_speed.setText(""); 105 | } 106 | 107 | @Override 108 | public void onStopped(DownloadInfo info) { 109 | saveDownloadInfo(); 110 | tv_start_stop.setText("开始下载"); 111 | tv_speed.setText(""); 112 | } 113 | 114 | @Override 115 | public void onCanceled(DownloadInfo info) { 116 | saveDownloadInfo(); 117 | tv_start_stop.setText("开始下载"); 118 | tv_cancel.setText("已取消"); 119 | pb_1.setProgress(0); 120 | tv_speed.setText(""); 121 | tv_download_length.setText(""); 122 | tv_content_length.setText(""); 123 | } 124 | 125 | @Override 126 | public void onCompletion(DownloadInfo info) { 127 | saveDownloadInfo(); 128 | tv_start_stop.setText("下载成功"); 129 | tv_speed.setText(""); 130 | } 131 | }) 132 | .setProgressListener(new RxDownload.ProgressListener() { 133 | @Override 134 | public void onProgress(float progress, long downloadLength, long contentLength) { 135 | pb_1.setProgress((int) (progress * 10000)); 136 | tv_download_length.setText(UnitFormatUtils.formatBytesLength(downloadLength)); 137 | tv_content_length.setText(UnitFormatUtils.formatBytesLength(contentLength)); 138 | } 139 | }) 140 | .setSpeedListener(new RxDownload.SpeedListener() { 141 | @Override 142 | public void onSpeedChange(float bytePerSecond, String speedFormat) { 143 | tv_speed.setText(speedFormat); 144 | } 145 | }); 146 | } 147 | 148 | @Override 149 | public boolean onKeyDown(int keyCode, KeyEvent event) { 150 | if (event.getAction() == KeyEvent.ACTION_DOWN) { 151 | if (keyCode == KeyEvent.KEYCODE_BACK) { 152 | if (isStart) { 153 | mRxDownload.stop(); 154 | isStart = false; 155 | return false; 156 | } 157 | } 158 | } 159 | return super.onKeyDown(keyCode, event); 160 | } 161 | 162 | private DownloadInfo getDownloadInfo() { 163 | SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 164 | String url = sp.getString("url", ""); 165 | String saveDirName = sp.getString("saveDirPath", ""); 166 | String saveFileName = sp.getString("saveFileName", ""); 167 | long downloadLength = sp.getLong("downloadLength", 0); 168 | long contentLength = sp.getLong("contentLength", 0); 169 | if (TextUtils.isEmpty(url) || TextUtils.isEmpty(saveDirName) || TextUtils.isEmpty(saveFileName)){ 170 | return null; 171 | } 172 | if (downloadLength == 0){ 173 | return null; 174 | } 175 | if (contentLength < downloadLength){ 176 | return null; 177 | } 178 | return DownloadInfo.create(url, saveDirName, saveFileName, downloadLength, contentLength); 179 | } 180 | 181 | private void saveDownloadInfo() { 182 | DownloadInfo info = mRxDownload.getDownloadInfo(); 183 | SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 184 | SharedPreferences.Editor editor = sp.edit(); 185 | editor.putString("url", info.url); 186 | editor.putString("saveDirPath", info.saveDirPath); 187 | editor.putString("saveFileName", info.saveFileName); 188 | editor.putLong("downloadLength", info.downloadLength); 189 | editor.putLong("contentLength", info.contentLength); 190 | editor.apply(); 191 | } 192 | 193 | private void cleanDownloadInfo() { 194 | SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); 195 | sp.edit().clear().apply(); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /app/src/main/java/per/goweii/android/rxhttp/TestRequestActivity.java: -------------------------------------------------------------------------------- 1 | package per.goweii.android.rxhttp; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | import android.view.View; 9 | import android.widget.EditText; 10 | import android.widget.TextView; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | import javax.net.ssl.HostnameVerifier; 16 | import javax.net.ssl.SSLSession; 17 | 18 | import okhttp3.OkHttpClient; 19 | import per.goweii.android.rxhttp.bean.RecommendPoetryBean; 20 | import per.goweii.android.rxhttp.bean.SinglePoetryBean; 21 | import per.goweii.android.rxhttp.bean.WeatherBean; 22 | import per.goweii.android.rxhttp.http.FreeApi; 23 | import per.goweii.rxhttp.core.RxHttp; 24 | import per.goweii.rxhttp.core.RxLife; 25 | import per.goweii.rxhttp.request.RxRequest; 26 | import per.goweii.rxhttp.request.base.BaseBean; 27 | import per.goweii.rxhttp.request.exception.ExceptionHandle; 28 | import per.goweii.rxhttp.request.setting.DefaultRequestSetting; 29 | import per.goweii.rxhttp.request.setting.ParameterGetter; 30 | 31 | public class TestRequestActivity extends AppCompatActivity { 32 | private static final String TAG = "TestRequestActivity"; 33 | 34 | private RxLife mRxLife; 35 | private TextView tvLog; 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_test_request); 41 | RxHttp.initRequest(new DefaultRequestSetting() { 42 | @NonNull 43 | @Override 44 | public String getBaseUrl() { 45 | return FreeApi.Config.BASE_URL; 46 | } 47 | 48 | @Override 49 | public Map getRedirectBaseUrl() { 50 | Map urls = new HashMap<>(2); 51 | urls.put(FreeApi.Config.BASE_URL_OTHER_NAME, FreeApi.Config.BASE_URL_OTHER); 52 | urls.put(FreeApi.Config.BASE_URL_ERROR_NAME, FreeApi.Config.BASE_URL_ERROR); 53 | urls.put(FreeApi.Config.BASE_URL_HTTPS_NAME, FreeApi.Config.BASE_URL_HTTPS); 54 | return urls; 55 | } 56 | 57 | @Override 58 | public int getSuccessCode() { 59 | return FreeApi.Code.SUCCESS; 60 | } 61 | 62 | @Override 63 | public Map getStaticPublicQueryParameter() { 64 | Map parameters = new HashMap<>(2); 65 | parameters.put("system", "android"); 66 | parameters.put("version_code", "1"); 67 | parameters.put("device_num", "666"); 68 | return parameters; 69 | } 70 | 71 | @Override 72 | public Map getDynamicPublicQueryParameter() { 73 | Map parameters = new HashMap<>(2); 74 | parameters.put("user_id", new ParameterGetter() { 75 | @io.reactivex.annotations.NonNull 76 | @Override 77 | public String get() { 78 | return "100001"; 79 | } 80 | }); 81 | return parameters; 82 | } 83 | 84 | @Override 85 | public void setOkHttpClient(@io.reactivex.annotations.NonNull OkHttpClient.Builder builder) { 86 | builder.hostnameVerifier(new HostnameVerifier() { 87 | @Override 88 | public boolean verify(String hostname, SSLSession session) { 89 | return true; 90 | } 91 | }); 92 | } 93 | }); 94 | mRxLife = RxLife.create(); 95 | 96 | tvLog = findViewById(R.id.tv_log); 97 | findViewById(R.id.tv_get_singlePoetry).setOnClickListener(new View.OnClickListener() { 98 | @Override 99 | public void onClick(View v) { 100 | getSinglePoetry(); 101 | } 102 | }); 103 | findViewById(R.id.tv_get_recommendPoetry).setOnClickListener(new View.OnClickListener() { 104 | @Override 105 | public void onClick(View v) { 106 | getRecommendPoetry(); 107 | } 108 | }); 109 | findViewById(R.id.tv_get_weather).setOnClickListener(new View.OnClickListener() { 110 | @Override 111 | public void onClick(View v) { 112 | EditText etWeatherCity = findViewById(R.id.et_weather_city); 113 | getWeather(etWeatherCity.getText().toString()); 114 | } 115 | }); 116 | findViewById(R.id.tv_http_host_error).setOnClickListener(new View.OnClickListener() { 117 | @Override 118 | public void onClick(View v) { 119 | getErrorHost(); 120 | } 121 | }); 122 | findViewById(R.id.tv_https).setOnClickListener(new View.OnClickListener() { 123 | @Override 124 | public void onClick(View v) { 125 | getHttps(); 126 | } 127 | }); 128 | } 129 | 130 | @Override 131 | protected void onDestroy() { 132 | super.onDestroy(); 133 | mRxLife.destroy(); 134 | } 135 | 136 | private void getSinglePoetry() { 137 | mRxLife.add(RxHttp.request(FreeApi.api().singlePoetry()).listener(new RxRequest.RequestListener() { 138 | private long timeStart = 0; 139 | 140 | @Override 141 | public void onStart() { 142 | log(null); 143 | log("onStart()"); 144 | timeStart = System.currentTimeMillis(); 145 | } 146 | 147 | @Override 148 | public void onError(@NonNull ExceptionHandle handle) { 149 | log("onError(" + handle.getMsg() + ")"); 150 | } 151 | 152 | @Override 153 | public void onFinish() { 154 | long cast = System.currentTimeMillis() - timeStart; 155 | log("onFinish(cast=" + cast + ")"); 156 | } 157 | }).request(new RxRequest.ResultCallback() { 158 | @Override 159 | public void onSuccess(int code, SinglePoetryBean data) { 160 | log("onSuccess(code=" + code + ",data=" + data.toFormatJson() + ")"); 161 | } 162 | 163 | @Override 164 | public void onFailed(int code, String msg) { 165 | log("onFailed(code=" + code + ",msg=" + msg + ")"); 166 | } 167 | })); 168 | } 169 | 170 | private void getRecommendPoetry() { 171 | mRxLife.add(RxRequest.create(FreeApi.api().recommendPoetry()).listener(new RxRequest.RequestListener() { 172 | private long timeStart = 0; 173 | 174 | @Override 175 | public void onStart() { 176 | log(null); 177 | log("onStart()"); 178 | timeStart = System.currentTimeMillis(); 179 | } 180 | 181 | @Override 182 | public void onError(@NonNull ExceptionHandle handle) { 183 | log("onError(" + handle.getMsg() + ")"); 184 | } 185 | 186 | @Override 187 | public void onFinish() { 188 | long cast = System.currentTimeMillis() - timeStart; 189 | log("onFinish(cast=" + cast + ")"); 190 | } 191 | }).request(new RxRequest.ResultCallback() { 192 | @Override 193 | public void onSuccess(int code, RecommendPoetryBean data) { 194 | log("onSuccess(code=" + code + ",data=" + data.toFormatJson() + ")"); 195 | } 196 | 197 | @Override 198 | public void onFailed(int code, String msg) { 199 | log("onFailed(code=" + code + ",msg=" + msg + ")"); 200 | } 201 | })); 202 | } 203 | 204 | private void getWeather(String city) { 205 | mRxLife.add(RxRequest.create(FreeApi.api().weather(city)).listener(new RxRequest.RequestListener() { 206 | private long timeStart = 0; 207 | 208 | @Override 209 | public void onStart() { 210 | log(null); 211 | log("onStart()"); 212 | timeStart = System.currentTimeMillis(); 213 | } 214 | 215 | @Override 216 | public void onError(@NonNull ExceptionHandle handle) { 217 | log("onError(" + handle.getMsg() + ")"); 218 | } 219 | 220 | @Override 221 | public void onFinish() { 222 | long cast = System.currentTimeMillis() - timeStart; 223 | log("onFinish(cast=" + cast + ")"); 224 | } 225 | }).request(new RxRequest.ResultCallback() { 226 | @Override 227 | public void onSuccess(int code, WeatherBean data) { 228 | log("onSuccess(code=" + code + ",data=" + data.toFormatJson() + ")"); 229 | } 230 | 231 | @Override 232 | public void onFailed(int code, String msg) { 233 | log("onFailed(code=" + code + ",msg=" + msg + ")"); 234 | } 235 | })); 236 | } 237 | 238 | private void getErrorHost() { 239 | mRxLife.add(RxRequest.create(FreeApi.api().errorHost()).listener(new RxRequest.RequestListener() { 240 | private long timeStart = 0; 241 | 242 | @Override 243 | public void onStart() { 244 | log(null); 245 | log("onStart()"); 246 | timeStart = System.currentTimeMillis(); 247 | } 248 | 249 | @Override 250 | public void onError(@NonNull ExceptionHandle handle) { 251 | log("onError(" + handle.getMsg() + ")"); 252 | } 253 | 254 | @Override 255 | public void onFinish() { 256 | long cast = System.currentTimeMillis() - timeStart; 257 | log("onFinish(cast=" + cast + ")"); 258 | } 259 | }).request(new RxRequest.ResultCallback() { 260 | @Override 261 | public void onSuccess(int code, BaseBean data) { 262 | log("onSuccess(code=" + code + ",data=" + data.toFormatJson() + ")"); 263 | } 264 | 265 | @Override 266 | public void onFailed(int code, String msg) { 267 | log("onFailed(code=" + code + ",msg=" + msg + ")"); 268 | } 269 | })); 270 | } 271 | 272 | private void getHttps() { 273 | mRxLife.add(RxRequest.create(FreeApi.api().https("哈哈")).listener(new RxRequest.RequestListener() { 274 | private long timeStart = 0; 275 | 276 | @Override 277 | public void onStart() { 278 | log(null); 279 | log("onStart()"); 280 | timeStart = System.currentTimeMillis(); 281 | } 282 | 283 | @Override 284 | public void onError(@NonNull ExceptionHandle handle) { 285 | log("onError(" + handle.getMsg() + ")"); 286 | } 287 | 288 | @Override 289 | public void onFinish() { 290 | long cast = System.currentTimeMillis() - timeStart; 291 | log("onFinish(cast=" + cast + ")"); 292 | } 293 | }).request(new RxRequest.ResultCallback() { 294 | @Override 295 | public void onSuccess(int code, BaseBean data) { 296 | log("onSuccess(code=" + code + ",data=" + data.toFormatJson() + ")"); 297 | } 298 | 299 | @Override 300 | public void onFailed(int code, String msg) { 301 | log("onFailed(code=" + code + ",msg=" + msg + ")"); 302 | } 303 | })); 304 | } 305 | 306 | private void log(String text){ 307 | if (text == null) { 308 | tvLog.setText(""); 309 | } else { 310 | Log.d(TAG, text); 311 | String textOld = tvLog.getText().toString(); 312 | if (TextUtils.isEmpty(textOld)) { 313 | tvLog.setText(text); 314 | } else { 315 | tvLog.setText(tvLog.getText().toString() + "\n" + text); 316 | } 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /app/src/main/java/per/goweii/android/rxhttp/bean/RecommendPoetryBean.java: -------------------------------------------------------------------------------- 1 | package per.goweii.android.rxhttp.bean; 2 | 3 | import per.goweii.rxhttp.request.base.BaseBean; 4 | 5 | /** 6 | * 描述: 7 | * 8 | * @author Cuizhen 9 | * @date 2018/10/13 10 | */ 11 | public class RecommendPoetryBean extends BaseBean { 12 | /** 13 | * title : 饯郑安阳入蜀 14 | * content : 彭山折坂外,井络少城隈。|地是三巴俗,人非百里材。|畏途君怅望,岐路我裴徊。|心赏风烟隔,容华岁月催。|遥遥分凤野,去去转龙媒。|遗锦非前邑,鸣琴即旧台。|剑门千仞起,石路五丁开。|海客乘槎渡,仙童驭竹回。|魂将离鹤远,思逐断猿哀。|唯有双凫舄,飞去复飞来。 15 | * authors : 骆宾王 16 | */ 17 | 18 | private String title; 19 | private String content; 20 | private String authors; 21 | 22 | public String getTitle() { 23 | return title; 24 | } 25 | 26 | public void setTitle(String title) { 27 | this.title = title; 28 | } 29 | 30 | public String getContent() { 31 | return content; 32 | } 33 | 34 | public void setContent(String content) { 35 | this.content = content; 36 | } 37 | 38 | public String getAuthors() { 39 | return authors; 40 | } 41 | 42 | public void setAuthors(String authors) { 43 | this.authors = authors; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/per/goweii/android/rxhttp/bean/SinglePoetryBean.java: -------------------------------------------------------------------------------- 1 | package per.goweii.android.rxhttp.bean; 2 | 3 | import per.goweii.rxhttp.request.base.BaseBean; 4 | 5 | /** 6 | * 描述: 7 | * 8 | * @author Cuizhen 9 | * @date 2018/10/13 10 | */ 11 | public class SinglePoetryBean extends BaseBean { 12 | /** 13 | * author : 苏辙 14 | * origin : 水调歌头·徐州中秋 15 | * category : 古诗文-节日-中秋节 16 | * content : 离别一何久,七度过中秋。 17 | */ 18 | 19 | private String author; 20 | private String origin; 21 | private String category; 22 | private String content; 23 | 24 | public String getAuthor() { 25 | return author; 26 | } 27 | 28 | public void setAuthor(String author) { 29 | this.author = author; 30 | } 31 | 32 | public String getOrigin() { 33 | return origin; 34 | } 35 | 36 | public void setOrigin(String origin) { 37 | this.origin = origin; 38 | } 39 | 40 | public String getCategory() { 41 | return category; 42 | } 43 | 44 | public void setCategory(String category) { 45 | this.category = category; 46 | } 47 | 48 | public String getContent() { 49 | return content; 50 | } 51 | 52 | public void setContent(String content) { 53 | this.content = content; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/per/goweii/android/rxhttp/bean/WeatherBean.java: -------------------------------------------------------------------------------- 1 | package per.goweii.android.rxhttp.bean; 2 | 3 | import java.util.List; 4 | 5 | import per.goweii.rxhttp.request.base.BaseBean; 6 | 7 | /** 8 | * 描述: 9 | * 10 | * @author Cuizhen 11 | * @date 2018/10/13 12 | */ 13 | public class WeatherBean extends BaseBean { 14 | 15 | /** 16 | * yesterday : {"date":"12日星期五","high":"高温 22℃","fx":"东北风","low":"低温 13℃","fl":"","type":"多云"} 17 | * city : 西安 18 | * aqi : 81 19 | * forecast : [{"date":"13日星期六","high":"高温 19℃","fengli":"","low":"低温 11℃","fengxiang":"东风","type":"小雨"},{"date":"14日星期天","high":"高温 20℃","fengli":"","low":"低温 12℃","fengxiang":"东北风","type":"阴"},{"date":"15日星期一","high":"高温 21℃","fengli":"","low":"低温 10℃","fengxiang":"北风","type":"阴"},{"date":"16日星期二","high":"高温 20℃","fengli":"","low":"低温 11℃","fengxiang":"东北风","type":"阴"},{"date":"17日星期三","high":"高温 22℃","fengli":"","low":"低温 14℃","fengxiang":"东北风","type":"多云"}] 20 | * ganmao : 昼夜温差较大,较易发生感冒,请适当增减衣服。体质较弱的朋友请注意防护。 21 | * wendu : 15 22 | */ 23 | 24 | private YesterdayBean yesterday; 25 | private String city; 26 | private String aqi; 27 | private String ganmao; 28 | private String wendu; 29 | private List forecast; 30 | 31 | public YesterdayBean getYesterday() { 32 | return yesterday; 33 | } 34 | 35 | public void setYesterday(YesterdayBean yesterday) { 36 | this.yesterday = yesterday; 37 | } 38 | 39 | public String getCity() { 40 | return city; 41 | } 42 | 43 | public void setCity(String city) { 44 | this.city = city; 45 | } 46 | 47 | public String getAqi() { 48 | return aqi; 49 | } 50 | 51 | public void setAqi(String aqi) { 52 | this.aqi = aqi; 53 | } 54 | 55 | public String getGanmao() { 56 | return ganmao; 57 | } 58 | 59 | public void setGanmao(String ganmao) { 60 | this.ganmao = ganmao; 61 | } 62 | 63 | public String getWendu() { 64 | return wendu; 65 | } 66 | 67 | public void setWendu(String wendu) { 68 | this.wendu = wendu; 69 | } 70 | 71 | public List getForecast() { 72 | return forecast; 73 | } 74 | 75 | public void setForecast(List forecast) { 76 | this.forecast = forecast; 77 | } 78 | 79 | public static class YesterdayBean { 80 | /** 81 | * date : 12日星期五 82 | * high : 高温 22℃ 83 | * fx : 东北风 84 | * low : 低温 13℃ 85 | * fl : 86 | * type : 多云 87 | */ 88 | 89 | private String date; 90 | private String high; 91 | private String fx; 92 | private String low; 93 | private String fl; 94 | private String type; 95 | 96 | public String getDate() { 97 | return date; 98 | } 99 | 100 | public void setDate(String date) { 101 | this.date = date; 102 | } 103 | 104 | public String getHigh() { 105 | return high; 106 | } 107 | 108 | public void setHigh(String high) { 109 | this.high = high; 110 | } 111 | 112 | public String getFx() { 113 | return fx; 114 | } 115 | 116 | public void setFx(String fx) { 117 | this.fx = fx; 118 | } 119 | 120 | public String getLow() { 121 | return low; 122 | } 123 | 124 | public void setLow(String low) { 125 | this.low = low; 126 | } 127 | 128 | public String getFl() { 129 | return fl; 130 | } 131 | 132 | public void setFl(String fl) { 133 | this.fl = fl; 134 | } 135 | 136 | public String getType() { 137 | return type; 138 | } 139 | 140 | public void setType(String type) { 141 | this.type = type; 142 | } 143 | } 144 | 145 | public static class ForecastBean { 146 | /** 147 | * date : 13日星期六 148 | * high : 高温 19℃ 149 | * fengli : 150 | * low : 低温 11℃ 151 | * fengxiang : 东风 152 | * type : 小雨 153 | */ 154 | 155 | private String date; 156 | private String high; 157 | private String fengli; 158 | private String low; 159 | private String fengxiang; 160 | private String type; 161 | 162 | public String getDate() { 163 | return date; 164 | } 165 | 166 | public void setDate(String date) { 167 | this.date = date; 168 | } 169 | 170 | public String getHigh() { 171 | return high; 172 | } 173 | 174 | public void setHigh(String high) { 175 | this.high = high; 176 | } 177 | 178 | public String getFengli() { 179 | return fengli; 180 | } 181 | 182 | public void setFengli(String fengli) { 183 | this.fengli = fengli; 184 | } 185 | 186 | public String getLow() { 187 | return low; 188 | } 189 | 190 | public void setLow(String low) { 191 | this.low = low; 192 | } 193 | 194 | public String getFengxiang() { 195 | return fengxiang; 196 | } 197 | 198 | public void setFengxiang(String fengxiang) { 199 | this.fengxiang = fengxiang; 200 | } 201 | 202 | public String getType() { 203 | return type; 204 | } 205 | 206 | public void setType(String type) { 207 | this.type = type; 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /app/src/main/java/per/goweii/android/rxhttp/http/FreeApi.java: -------------------------------------------------------------------------------- 1 | package per.goweii.android.rxhttp.http; 2 | 3 | import io.reactivex.Observable; 4 | import per.goweii.android.rxhttp.bean.RecommendPoetryBean; 5 | import per.goweii.android.rxhttp.bean.SinglePoetryBean; 6 | import per.goweii.android.rxhttp.bean.WeatherBean; 7 | import per.goweii.rxhttp.request.Api; 8 | import per.goweii.rxhttp.request.base.BaseBean; 9 | import retrofit2.http.GET; 10 | import retrofit2.http.Headers; 11 | import retrofit2.http.Query; 12 | 13 | /** 14 | * @author Cuizhen 15 | * @date 2018/10/13 16 | */ 17 | public class FreeApi extends Api { 18 | 19 | public static Service api() { 20 | return Api.api(Service.class); 21 | } 22 | 23 | public interface Code{ 24 | int SUCCESS = 200; 25 | } 26 | 27 | public interface Config { 28 | String BASE_URL = "http://api.apiopen.top/"; 29 | 30 | String BASE_URL_OTHER_NAME = "other"; 31 | String BASE_URL_OTHER = "https://www.apiopen.top/"; 32 | 33 | String BASE_URL_ERROR_NAME = "error"; 34 | String BASE_URL_ERROR = "https://www.apiopen1.top/"; 35 | 36 | String BASE_URL_HTTPS_NAME = "https"; 37 | String BASE_URL_HTTPS = "https://www.baidu.com/"; 38 | } 39 | 40 | public interface Service { 41 | /** 42 | * 随机单句诗词推荐 43 | */ 44 | @Headers({Header.CACHE_ALIVE_SECOND + ":" + 10}) 45 | @GET("singlePoetry") 46 | Observable> singlePoetry(); 47 | 48 | /** 49 | * 随机一首诗词推荐 50 | */ 51 | @Headers({Header.CACHE_ALIVE_SECOND + ":" + 0}) 52 | @GET("recommendPoetry") 53 | Observable> recommendPoetry(); 54 | 55 | /** 56 | * 获取天气 57 | */ 58 | @Headers({Header.BASE_URL_REDIRECT + ":" + Config.BASE_URL_OTHER_NAME}) 59 | @GET("weatherApi?") 60 | Observable> weather(@Query("city") String city); 61 | 62 | /** 63 | * 错误地址 64 | */ 65 | @Headers({Header.BASE_URL_REDIRECT + ":" + Config.BASE_URL_ERROR_NAME}) 66 | @GET("weatherApi") 67 | Observable> errorHost(); 68 | 69 | /** 70 | * https 71 | */ 72 | @Headers({Header.BASE_URL_REDIRECT + ":" + Config.BASE_URL_HTTPS_NAME}) 73 | @GET("s") 74 | Observable> https(@Query("wd") String wd); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/per/goweii/android/rxhttp/http/ResponseBean.java: -------------------------------------------------------------------------------- 1 | package per.goweii.android.rxhttp.http; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import per.goweii.rxhttp.request.base.BaseResponse; 6 | 7 | /** 8 | * 描述: 9 | * 10 | * @author Cuizhen 11 | * @date 2018/10/13 12 | */ 13 | public class ResponseBean implements BaseResponse { 14 | 15 | @SerializedName(value = "code", alternate = {"status"}) 16 | private int code; 17 | @SerializedName(value = "data", alternate = {"result"}) 18 | private E data; 19 | @SerializedName(value = "msg", alternate = {"message"}) 20 | private String message; 21 | 22 | @Override 23 | public int getCode() { 24 | return code; 25 | } 26 | 27 | @Override 28 | public void setCode(int code) { 29 | this.code = code; 30 | } 31 | 32 | @Override 33 | public E getData() { 34 | return data; 35 | } 36 | 37 | @Override 38 | public void setData(E data) { 39 | this.data = data; 40 | } 41 | 42 | @Override 43 | public String getMsg() { 44 | return message; 45 | } 46 | 47 | @Override 48 | public void setMsg(String msg) { 49 | this.message = msg; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_test_download.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 26 | 27 | 33 | 34 | 39 | 40 | 46 | 47 | 54 | 55 | 56 | 57 | 62 | 63 | 69 | 70 | 77 | 78 | 79 | 80 | 85 | 86 | 92 | 93 | 100 | 101 | 102 | 103 | 104 | 105 | 113 | 114 | 122 | 123 | 131 | 132 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_test_request.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 47 | 48 | 56 | 57 | 58 | 59 | 63 | 64 | 71 | 72 | 80 | 81 | 82 | 83 | 87 | 88 | 95 | 96 | 104 | 105 | 112 | 113 | 114 | 115 | 119 | 120 | 127 | 128 | 136 | 137 | 138 | 139 | 143 | 144 | 151 | 152 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RxHttp 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | dependencies { 7 | classpath "com.android.tools.build:gradle:4.1.1" 8 | } 9 | } 10 | 11 | allprojects { 12 | repositories { 13 | google() 14 | jcenter() 15 | } 16 | } 17 | 18 | task clean(type: Delete) { 19 | delete rootProject.buildDir 20 | } 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goweii/RxHttp/e50b8110a88cc4b3b38b47d7daaabf213ceea877/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Feb 28 00:04:10 CST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 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 | -------------------------------------------------------------------------------- /rxhttp/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /rxhttp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | buildToolsVersion '28.0.3' 6 | defaultConfig { 7 | minSdkVersion 14 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_7 22 | targetCompatibility JavaVersion.VERSION_1_7 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(include: ['*.jar'], dir: 'libs') 28 | implementation 'com.android.support:appcompat-v7:28.0.0' 29 | 30 | // Retrofit2 31 | api 'com.squareup.retrofit2:retrofit:2.4.0' 32 | api 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' 33 | api 'com.squareup.retrofit2:converter-gson:2.4.0' 34 | 35 | // OkHttp 36 | api 'com.squareup.okhttp3:logging-interceptor:3.9.0' 37 | api 'com.squareup.okhttp3:okhttp:3.11.0' 38 | api 'com.squareup.okio:okio:1.14.0' 39 | 40 | // RxJava2 41 | api 'io.reactivex.rxjava2:rxjava:2.1.16' 42 | api 'io.reactivex.rxjava2:rxandroid:2.0.2' 43 | } 44 | -------------------------------------------------------------------------------- /rxhttp/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 | -------------------------------------------------------------------------------- /rxhttp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/core/RxHttp.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.core; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.support.annotation.NonNull; 6 | 7 | import io.reactivex.Observable; 8 | import per.goweii.rxhttp.core.exception.RxHttpUninitializedException; 9 | import per.goweii.rxhttp.download.DownloadInfo; 10 | import per.goweii.rxhttp.download.RxDownload; 11 | import per.goweii.rxhttp.download.setting.DefaultDownloadSetting; 12 | import per.goweii.rxhttp.download.setting.DownloadSetting; 13 | import per.goweii.rxhttp.request.RxRequest; 14 | import per.goweii.rxhttp.request.base.BaseResponse; 15 | import per.goweii.rxhttp.request.exception.NullRequestSettingException; 16 | import per.goweii.rxhttp.request.setting.RequestSetting; 17 | 18 | /** 19 | * 描述: 20 | * 21 | * @author Cuizhen 22 | * @date 2018/10/12 23 | */ 24 | @SuppressLint("StaticFieldLeak") 25 | public class RxHttp { 26 | 27 | private static RxHttp INSTANCE = null; 28 | 29 | private final Context mAppContext; 30 | private RequestSetting mRequestSetting = null; 31 | private DownloadSetting mDownloadSetting = null; 32 | 33 | private RxHttp(@NonNull Context context) { 34 | mAppContext = context; 35 | } 36 | 37 | public static void init(@NonNull Context context) { 38 | INSTANCE = new RxHttp(context.getApplicationContext()); 39 | } 40 | 41 | @NonNull 42 | public static RxHttp getInstance() { 43 | if (INSTANCE == null) { 44 | throw new RxHttpUninitializedException(); 45 | } 46 | return INSTANCE; 47 | } 48 | 49 | public static void initRequest(@NonNull RequestSetting setting) { 50 | getInstance().mRequestSetting = setting; 51 | } 52 | 53 | public static void initDownload(@NonNull DownloadSetting setting) { 54 | getInstance().mDownloadSetting = setting; 55 | } 56 | 57 | @NonNull 58 | public static Context getAppContext() { 59 | return getInstance().mAppContext; 60 | } 61 | 62 | @NonNull 63 | public static RequestSetting getRequestSetting() { 64 | RequestSetting setting = getInstance().mRequestSetting; 65 | if (setting == null) { 66 | throw new NullRequestSettingException(); 67 | } 68 | return setting; 69 | } 70 | 71 | @NonNull 72 | public static DownloadSetting getDownloadSetting() { 73 | DownloadSetting setting = getInstance().mDownloadSetting; 74 | if (setting == null) { 75 | setting = new DefaultDownloadSetting(); 76 | } 77 | return setting; 78 | } 79 | 80 | public static > RxRequest request(@NonNull Observable observable) { 81 | return RxRequest.create(observable); 82 | } 83 | 84 | public static RxDownload download(@NonNull DownloadInfo info) { 85 | return RxDownload.create(info); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/core/RxLife.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.core; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import io.reactivex.disposables.CompositeDisposable; 6 | import io.reactivex.disposables.Disposable; 7 | 8 | /** 9 | * 描述: 10 | * 11 | * @author Cuizhen 12 | * @date 2018/10/13 13 | */ 14 | public class RxLife { 15 | 16 | private CompositeDisposable mCompositeDisposable = null; 17 | 18 | private RxLife() { 19 | mCompositeDisposable = new CompositeDisposable(); 20 | } 21 | 22 | @NonNull 23 | public static RxLife create() { 24 | return new RxLife(); 25 | } 26 | 27 | public void destroy() { 28 | if (mCompositeDisposable == null || mCompositeDisposable.isDisposed()) { 29 | return; 30 | } 31 | mCompositeDisposable.dispose(); 32 | } 33 | 34 | public void add(Disposable d) { 35 | if (mCompositeDisposable == null || mCompositeDisposable.isDisposed()) { 36 | mCompositeDisposable = new CompositeDisposable(); 37 | } 38 | mCompositeDisposable.add(d); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/core/exception/RxHttpUninitializedException.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.core.exception; 2 | 3 | import per.goweii.rxhttp.core.RxHttp; 4 | 5 | /** 6 | * 描述:在调用网络请求之前应该先进行初始化,建议在Application中初始化 7 | * {@link RxHttp#init(android.content.Context)} 8 | * 9 | * @author Cuizhen 10 | * @date 2018/10/12 11 | */ 12 | public class RxHttpUninitializedException extends RuntimeException { 13 | public RxHttpUninitializedException() { 14 | super("RxHttp未初始化"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/core/manager/BaseClientManager.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.core.manager; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import retrofit2.Retrofit; 6 | 7 | /** 8 | * 用于管理Retrofit实例 9 | * 子类继承后自行判断是否采用单例模式 10 | * 11 | * @author Cuizhen 12 | * @date 2018/9/4 13 | */ 14 | public abstract class BaseClientManager { 15 | @NonNull 16 | protected abstract Retrofit create(); 17 | } 18 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/core/utils/BaseUrlUtils.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.core.utils; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | /** 6 | * 描述:检查BaseUrl是否以"/"结尾 7 | * 8 | * @author Cuizhen 9 | * @date 2018/10/13 10 | */ 11 | public class BaseUrlUtils { 12 | 13 | public static String checkBaseUrl(@NonNull String url) { 14 | if (url.endsWith("/")) { 15 | return url; 16 | } else { 17 | return url + "/"; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/core/utils/SDCardUtils.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.core.utils; 2 | 3 | import android.os.Environment; 4 | import android.support.annotation.NonNull; 5 | 6 | import java.io.File; 7 | 8 | import per.goweii.rxhttp.core.RxHttp; 9 | 10 | /** 11 | * @author Cuizhen 12 | * @date 18/4/23 13 | */ 14 | public class SDCardUtils { 15 | 16 | @NonNull 17 | public static String getCacheDir() { 18 | File cacheFile = null; 19 | if (isSDCardAlive()) { 20 | cacheFile = RxHttp.getAppContext().getExternalCacheDir(); 21 | } 22 | if (cacheFile == null) { 23 | cacheFile = RxHttp.getAppContext().getCacheDir(); 24 | } 25 | return cacheFile.getAbsolutePath(); 26 | } 27 | 28 | @NonNull 29 | public static String getDownloadCacheDir() { 30 | File dir = null; 31 | if (isSDCardAlive()) { 32 | dir = RxHttp.getAppContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); 33 | } 34 | if (dir == null) { 35 | dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); 36 | } 37 | return dir.getAbsolutePath(); 38 | } 39 | 40 | private static boolean isSDCardAlive() { 41 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/DownloadApi.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download; 2 | 3 | import io.reactivex.Observable; 4 | import okhttp3.ResponseBody; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Header; 7 | import retrofit2.http.Streaming; 8 | import retrofit2.http.Url; 9 | 10 | /** 11 | * @author CuiZhen 12 | * @date 2018/10/14 13 | * QQ: 302833254 14 | * E-mail: goweii@163.com 15 | * GitHub: https://github.com/goweii 16 | */ 17 | public interface DownloadApi{ 18 | @Streaming 19 | @GET 20 | Observable download(@Header("RANGE") String range, @Url String url); 21 | } 22 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/DownloadClientManager.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import okhttp3.OkHttpClient; 8 | import per.goweii.rxhttp.core.RxHttp; 9 | import per.goweii.rxhttp.core.manager.BaseClientManager; 10 | import per.goweii.rxhttp.core.utils.BaseUrlUtils; 11 | import per.goweii.rxhttp.download.interceptor.RealNameInterceptor; 12 | import retrofit2.Retrofit; 13 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 14 | 15 | /** 16 | * 描述: 17 | * 18 | * @author Cuizhen 19 | * @date 2018/10/15 20 | */ 21 | class DownloadClientManager extends BaseClientManager { 22 | 23 | private static DownloadClientManager INSTANCE = null; 24 | private final Retrofit mRetrofit; 25 | 26 | private DownloadClientManager() { 27 | mRetrofit = create(); 28 | } 29 | 30 | /** 31 | * 采用单例模式 32 | * 33 | * @return RequestClientManager 34 | */ 35 | @NonNull 36 | private static DownloadClientManager getInstance() { 37 | if (INSTANCE == null) { 38 | synchronized (DownloadClientManager.class) { 39 | if (INSTANCE == null) { 40 | INSTANCE = new DownloadClientManager(); 41 | } 42 | } 43 | } 44 | return INSTANCE; 45 | } 46 | 47 | static DownloadApi getService() { 48 | return getInstance().mRetrofit.create(DownloadApi.class); 49 | } 50 | 51 | @NonNull 52 | @Override 53 | protected Retrofit create() { 54 | return new Retrofit.Builder() 55 | .client(createOkHttpClient()) 56 | .baseUrl(BaseUrlUtils.checkBaseUrl(RxHttp.getDownloadSetting().getBaseUrl())) 57 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 58 | .build(); 59 | } 60 | 61 | @NonNull 62 | private OkHttpClient createOkHttpClient(){ 63 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 64 | long timeout = RxHttp.getDownloadSetting().getTimeout(); 65 | long connectTimeout = RxHttp.getDownloadSetting().getConnectTimeout(); 66 | long readTimeout = RxHttp.getDownloadSetting().getReadTimeout(); 67 | long writeTimeout = RxHttp.getDownloadSetting().getWriteTimeout(); 68 | builder.connectTimeout(connectTimeout > 0 ? connectTimeout : timeout, TimeUnit.MILLISECONDS); 69 | builder.readTimeout(readTimeout > 0 ? readTimeout : timeout, TimeUnit.MILLISECONDS); 70 | builder.writeTimeout(writeTimeout > 0 ? writeTimeout : timeout, TimeUnit.MILLISECONDS); 71 | RealNameInterceptor.addTo(builder); 72 | return builder.build(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/DownloadInfo.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download; 2 | 3 | import android.support.annotation.IntRange; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | 7 | import per.goweii.rxhttp.core.RxHttp; 8 | 9 | /** 10 | * 描述: 11 | * 12 | * @author Cuizhen 13 | * @date 2018/10/15 14 | */ 15 | public class DownloadInfo { 16 | 17 | @NonNull 18 | public String url; 19 | @Nullable 20 | public String saveDirPath; 21 | @Nullable 22 | public String saveFileName; 23 | @IntRange(from = 0) 24 | public long downloadLength; 25 | @IntRange(from = 0) 26 | public long contentLength; 27 | @NonNull 28 | public State state; 29 | @NonNull 30 | public Mode mode; 31 | 32 | private DownloadInfo(@NonNull String url, @Nullable String saveDirPath, @Nullable String saveFileName, 33 | @IntRange(from = 0) long downloadLength, @IntRange(from = 0) long contentLength) { 34 | this.url = url; 35 | this.saveDirPath = saveDirPath; 36 | this.saveFileName = saveFileName; 37 | this.downloadLength = downloadLength; 38 | this.contentLength = contentLength; 39 | this.state = State.STOPPED; 40 | this.mode = RxHttp.getDownloadSetting().getDefaultDownloadMode(); 41 | } 42 | 43 | @NonNull 44 | public static DownloadInfo create(@NonNull String url){ 45 | return create(url, null, null); 46 | } 47 | 48 | @NonNull 49 | public static DownloadInfo create(@NonNull String url, 50 | @Nullable String saveDirPath, 51 | @Nullable String saveFileName){ 52 | return create(url, saveDirPath, saveFileName, 0, 0); 53 | } 54 | 55 | @NonNull 56 | public static DownloadInfo create(@NonNull String url, 57 | @Nullable String saveDirPath, 58 | @Nullable String saveFileName, 59 | @IntRange(from = 0) long downloadLength, 60 | @IntRange(from = 0) long contentLength){ 61 | return new DownloadInfo(url, saveDirPath, saveFileName, downloadLength, contentLength); 62 | } 63 | 64 | /** 65 | * 如果路径文件存在,但是断点续传时未传入已下载长度信息,此时的写入模式 66 | */ 67 | public enum Mode{ 68 | /** 69 | * 追加 70 | */ 71 | APPEND, 72 | /** 73 | * 替换 74 | */ 75 | REPLACE, 76 | /** 77 | * 重命名 78 | */ 79 | RENAME 80 | } 81 | 82 | public enum State{ 83 | /** 84 | * 正在开始 85 | */ 86 | STARTING, 87 | /** 88 | * 正在下载 89 | */ 90 | DOWNLOADING, 91 | /** 92 | * 已停止 93 | */ 94 | STOPPED, 95 | /** 96 | * 下载出错 97 | */ 98 | ERROR, 99 | /** 100 | * 下载完成 101 | */ 102 | COMPLETION 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/RxDownload.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download; 2 | 3 | import android.net.Uri; 4 | import android.support.annotation.NonNull; 5 | import android.text.TextUtils; 6 | 7 | import java.io.File; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.lang.reflect.Field; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import io.reactivex.Observable; 15 | import io.reactivex.ObservableEmitter; 16 | import io.reactivex.ObservableOnSubscribe; 17 | import io.reactivex.ObservableSource; 18 | import io.reactivex.Observer; 19 | import io.reactivex.android.schedulers.AndroidSchedulers; 20 | import io.reactivex.disposables.Disposable; 21 | import io.reactivex.functions.Action; 22 | import io.reactivex.functions.Consumer; 23 | import io.reactivex.functions.Function; 24 | import io.reactivex.schedulers.Schedulers; 25 | import okhttp3.ResponseBody; 26 | import per.goweii.rxhttp.download.exception.RangeLengthIsZeroException; 27 | import per.goweii.rxhttp.download.exception.SaveFileBrokenPointException; 28 | import per.goweii.rxhttp.download.exception.SaveFileDirMakeException; 29 | import per.goweii.rxhttp.download.exception.SaveFileWriteException; 30 | import per.goweii.rxhttp.download.interceptor.DownloadResponseBody; 31 | import per.goweii.rxhttp.download.utils.DownloadInfoChecker; 32 | import per.goweii.rxhttp.download.utils.RxNotify; 33 | import per.goweii.rxhttp.download.utils.UnitFormatUtils; 34 | 35 | /** 36 | * 描述:网络请求 37 | * 38 | * @author Cuizhen 39 | * @date 2018/9/9 40 | */ 41 | public class RxDownload { 42 | 43 | private final DownloadInfo mInfo; 44 | private DownloadListener mDownloadListener = null; 45 | private ProgressListener mProgressListener = null; 46 | private SpeedListener mSpeedListener = null; 47 | private Disposable mDisposableDownload = null; 48 | private Disposable mDisposableSpeed = null; 49 | 50 | private RxDownload(@NonNull DownloadInfo info) { 51 | mInfo = info; 52 | } 53 | 54 | @NonNull 55 | public static RxDownload create(@NonNull DownloadInfo info) { 56 | return new RxDownload(info); 57 | } 58 | 59 | @NonNull 60 | public RxDownload setDownloadListener(@NonNull DownloadListener listener) { 61 | mDownloadListener = listener; 62 | return this; 63 | } 64 | 65 | @NonNull 66 | public RxDownload setProgressListener(@NonNull ProgressListener listener) { 67 | mProgressListener = listener; 68 | return this; 69 | } 70 | 71 | @NonNull 72 | public RxDownload setSpeedListener(@NonNull SpeedListener listener) { 73 | mSpeedListener = listener; 74 | return this; 75 | } 76 | 77 | @NonNull 78 | public DownloadInfo getDownloadInfo() { 79 | return mInfo; 80 | } 81 | 82 | public void start() { 83 | if (mDisposableDownload != null && !mDisposableDownload.isDisposed()) { 84 | return; 85 | } 86 | Observable.create(new ObservableOnSubscribe() { 87 | @Override 88 | public void subscribe(@NonNull ObservableEmitter emitter) throws Exception { 89 | DownloadInfoChecker.checkDownloadLength(mInfo); 90 | DownloadInfoChecker.checkContentLength(mInfo); 91 | emitter.onNext("bytes=" + mInfo.downloadLength + "-" + (mInfo.contentLength == 0 ? "" : mInfo.contentLength)); 92 | } 93 | }).subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).flatMap(new Function>() { 94 | @Override 95 | public ObservableSource apply(@NonNull String range) throws Exception { 96 | return DownloadClientManager.getService().download(range, mInfo.url); 97 | } 98 | }).doOnNext(new Consumer() { 99 | @Override 100 | public void accept(ResponseBody responseBody) throws Exception { 101 | if (mInfo.contentLength == 0) { 102 | mInfo.contentLength = mInfo.downloadLength + responseBody.contentLength(); 103 | } else if (mInfo.downloadLength + responseBody.contentLength() != mInfo.contentLength) { 104 | throw new SaveFileBrokenPointException(); 105 | } 106 | DownloadInfoChecker.checkDirPath(mInfo); 107 | if (TextUtils.isEmpty(mInfo.saveFileName)) { 108 | try { 109 | Class clazz = responseBody.getClass(); 110 | Field field = clazz.getDeclaredField("delegate"); 111 | field.setAccessible(true); 112 | DownloadResponseBody body = (DownloadResponseBody) field.get(responseBody); 113 | mInfo.saveFileName = body.getRealName(); 114 | } catch (Throwable ignore) { 115 | } 116 | } 117 | if (TextUtils.isEmpty(mInfo.saveFileName)) { 118 | try { 119 | String filename = null; 120 | Uri uri = Uri.parse(mInfo.url); 121 | for (String name : uri.getQueryParameterNames()) { 122 | if (name.contains("name")) { 123 | filename = uri.getQueryParameter(name); 124 | } 125 | } 126 | if (TextUtils.isEmpty(filename)) { 127 | filename = uri.getLastPathSegment(); 128 | } 129 | mInfo.saveFileName = filename; 130 | } catch (Throwable ignore) { 131 | } 132 | } 133 | DownloadInfoChecker.checkFileName(mInfo); 134 | mInfo.state = DownloadInfo.State.DOWNLOADING; 135 | notifyDownloading(); 136 | write(responseBody.byteStream(), createSaveFile(mInfo)); 137 | } 138 | }).observeOn(AndroidSchedulers.mainThread()).doOnDispose(new Action() { 139 | @Override 140 | public void run() throws Exception { 141 | cancelSpeedObserver(); 142 | } 143 | }).subscribe(new Observer() { 144 | @Override 145 | public void onSubscribe(@NonNull Disposable d) { 146 | mDisposableDownload = d; 147 | mInfo.state = DownloadInfo.State.STARTING; 148 | if (mDownloadListener != null) { 149 | mDownloadListener.onStarting(mInfo); 150 | } 151 | } 152 | 153 | @Override 154 | public void onNext(@NonNull ResponseBody responseBody) { 155 | mInfo.state = DownloadInfo.State.COMPLETION; 156 | if (mDownloadListener != null) { 157 | mDownloadListener.onCompletion(mInfo); 158 | } 159 | } 160 | 161 | @Override 162 | public void onError(@NonNull Throwable e) { 163 | if (e instanceof RangeLengthIsZeroException) { 164 | mInfo.state = DownloadInfo.State.COMPLETION; 165 | if (mDownloadListener != null) { 166 | mDownloadListener.onCompletion(mInfo); 167 | } 168 | } else { 169 | mInfo.state = DownloadInfo.State.ERROR; 170 | if (mDownloadListener != null) { 171 | mDownloadListener.onError(mInfo, e); 172 | } 173 | } 174 | } 175 | 176 | @Override 177 | public void onComplete() { 178 | } 179 | }); 180 | } 181 | 182 | public void stop() { 183 | if (mDisposableDownload != null && !mDisposableDownload.isDisposed()) { 184 | mDisposableDownload.dispose(); 185 | mDisposableDownload = null; 186 | } 187 | mInfo.state = DownloadInfo.State.STOPPED; 188 | if (mDownloadListener != null) { 189 | mDownloadListener.onStopped(mInfo); 190 | } 191 | } 192 | 193 | public void cancel() { 194 | if (mDisposableDownload != null && !mDisposableDownload.isDisposed()) { 195 | mDisposableDownload.dispose(); 196 | mDisposableDownload = null; 197 | } 198 | deleteSaveFile(mInfo); 199 | mInfo.state = DownloadInfo.State.STOPPED; 200 | if (mDownloadListener != null) { 201 | mDownloadListener.onCanceled(mInfo); 202 | } 203 | } 204 | 205 | private File createSaveFile(DownloadInfo info) throws SaveFileDirMakeException { 206 | File file = new File(info.saveDirPath, info.saveFileName); 207 | if (!file.getParentFile().exists()) { 208 | if (!file.getParentFile().mkdirs()) { 209 | throw new SaveFileDirMakeException(); 210 | } 211 | } 212 | return file; 213 | } 214 | 215 | private void deleteSaveFile(DownloadInfo info) { 216 | try { 217 | if (new File(info.saveDirPath, info.saveFileName).delete()) { 218 | mInfo.downloadLength = 0; 219 | } 220 | } catch (Exception ignore) { 221 | } 222 | } 223 | 224 | private void write(InputStream is, File file) throws SaveFileWriteException { 225 | createSpeedObserver(); 226 | FileOutputStream fos = null; 227 | try { 228 | fos = new FileOutputStream(file, true); 229 | byte[] buffer = new byte[2048]; 230 | int len; 231 | while ((len = is.read(buffer)) != -1) { 232 | fos.write(buffer, 0, len); 233 | mInfo.downloadLength += len; 234 | notifyProgress(); 235 | } 236 | fos.flush(); 237 | } catch (IOException e) { 238 | throw new SaveFileWriteException(); 239 | } finally { 240 | if (is != null) { 241 | try { 242 | is.close(); 243 | } catch (IOException e) { 244 | e.printStackTrace(); 245 | } 246 | } 247 | if (fos != null) { 248 | try { 249 | fos.close(); 250 | } catch (IOException e) { 251 | e.printStackTrace(); 252 | } 253 | } 254 | } 255 | } 256 | 257 | private void notifyDownloading() { 258 | if (mDownloadListener != null) { 259 | RxNotify.runOnUiThread(new RxNotify.Action() { 260 | @Override 261 | public void run() { 262 | mDownloadListener.onDownloading(mInfo); 263 | } 264 | }); 265 | } 266 | } 267 | 268 | private void notifyProgress() { 269 | if (mProgressListener != null) { 270 | RxNotify.runOnUiThread(new RxNotify.Action() { 271 | @Override 272 | public void run() { 273 | float progress = (float) mInfo.downloadLength / (float) mInfo.contentLength; 274 | mProgressListener.onProgress(progress, mInfo.downloadLength, mInfo.contentLength); 275 | } 276 | }); 277 | } 278 | } 279 | 280 | private void createSpeedObserver() { 281 | if (mDisposableSpeed != null && !mDisposableSpeed.isDisposed()) { 282 | return; 283 | } 284 | mDisposableSpeed = Observable.interval(1, 1, TimeUnit.SECONDS) 285 | .subscribeOn(Schedulers.computation()) 286 | .map(new Function() { 287 | private long lastDownloadLength = 0; 288 | 289 | @Override 290 | public Float apply(@NonNull Long ms) throws Exception { 291 | float bytesPerSecond = UnitFormatUtils.calculateSpeed(mInfo.downloadLength - lastDownloadLength, 1); 292 | lastDownloadLength = mInfo.downloadLength; 293 | return bytesPerSecond; 294 | } 295 | }) 296 | .observeOn(AndroidSchedulers.mainThread()) 297 | .subscribe(new Consumer() { 298 | @Override 299 | public void accept(Float speedPerSecond) throws Exception { 300 | if (mSpeedListener != null) { 301 | mSpeedListener.onSpeedChange(speedPerSecond, UnitFormatUtils.formatSpeedPerSecond(speedPerSecond)); 302 | } 303 | } 304 | }); 305 | } 306 | 307 | private void cancelSpeedObserver() { 308 | if (mDisposableSpeed != null && !mDisposableSpeed.isDisposed()) { 309 | mDisposableSpeed.dispose(); 310 | } 311 | mDisposableSpeed = null; 312 | } 313 | 314 | public interface DownloadListener { 315 | void onStarting(DownloadInfo info); 316 | 317 | void onDownloading(DownloadInfo info); 318 | 319 | void onStopped(DownloadInfo info); 320 | 321 | void onCanceled(DownloadInfo info); 322 | 323 | void onCompletion(DownloadInfo info); 324 | 325 | void onError(DownloadInfo info, Throwable e); 326 | } 327 | 328 | public interface ProgressListener { 329 | void onProgress(float progress, long downloadLength, long contentLength); 330 | } 331 | 332 | public interface SpeedListener { 333 | void onSpeedChange(float bytesPerSecond, String speedFormat); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/exception/RangeLengthIsZeroException.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download.exception; 2 | 3 | /** 4 | * @author Cuizhen 5 | * @date 2018/10/12 6 | */ 7 | public class RangeLengthIsZeroException extends RuntimeException { 8 | public RangeLengthIsZeroException() { 9 | super("断点处请求长度为0"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/exception/SaveFileBrokenPointException.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download.exception; 2 | 3 | /** 4 | * @author Cuizhen 5 | * @date 2018/10/12 6 | */ 7 | public class SaveFileBrokenPointException extends RuntimeException { 8 | public SaveFileBrokenPointException() { 9 | super("文件已下载部分与断点续传不符"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/exception/SaveFileDirMakeException.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download.exception; 2 | 3 | /** 4 | * @author Cuizhen 5 | * @date 2018/10/12 6 | */ 7 | public class SaveFileDirMakeException extends RuntimeException { 8 | public SaveFileDirMakeException() { 9 | super("下载保存的文件父文件夹创建失败"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/exception/SaveFileWriteException.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download.exception; 2 | 3 | /** 4 | * @author Cuizhen 5 | * @date 2018/10/12 6 | */ 7 | public class SaveFileWriteException extends RuntimeException { 8 | public SaveFileWriteException() { 9 | super("下载保存的文件写入失败"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/interceptor/DownloadResponseBody.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download.interceptor; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import java.io.IOException; 7 | 8 | import okhttp3.MediaType; 9 | import okhttp3.ResponseBody; 10 | import okio.Buffer; 11 | import okio.BufferedSource; 12 | import okio.ForwardingSource; 13 | import okio.Okio; 14 | import okio.Source; 15 | 16 | /** 17 | * 描述: 18 | * 19 | * @author Cuizhen 20 | * @date 2018/10/21 21 | */ 22 | public class DownloadResponseBody extends ResponseBody { 23 | 24 | private final ResponseBody responseBody; 25 | private BufferedSource source = null; 26 | private String realName = null; 27 | 28 | public DownloadResponseBody(ResponseBody responseBody) { 29 | this.responseBody = responseBody; 30 | } 31 | 32 | @Nullable 33 | public String getRealName() { 34 | return realName; 35 | } 36 | 37 | public void setRealName(@Nullable String realName) { 38 | this.realName = realName; 39 | } 40 | 41 | @Nullable 42 | @Override 43 | public MediaType contentType() { 44 | return responseBody.contentType(); 45 | } 46 | 47 | @Override 48 | public long contentLength() { 49 | return responseBody.contentLength(); 50 | } 51 | 52 | @NonNull 53 | @Override 54 | public BufferedSource source() { 55 | if (source == null) { 56 | source = Okio.buffer(source(responseBody.source())); 57 | } 58 | return source; 59 | } 60 | 61 | /** 62 | * 读取,回调进度接口 63 | */ 64 | private Source source(Source source) { 65 | return new ForwardingSource(source) { 66 | @Override 67 | public long read(Buffer sink, long byteCount) throws IOException { 68 | return super.read(sink, byteCount); 69 | } 70 | }; 71 | } 72 | } -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/interceptor/RealNameInterceptor.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download.interceptor; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.text.TextUtils; 6 | 7 | import java.io.IOException; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import okhttp3.Interceptor; 12 | import okhttp3.OkHttpClient; 13 | import okhttp3.Response; 14 | 15 | /** 16 | * @author Cuizhen 17 | * @date 2018/10/18 18 | */ 19 | public class RealNameInterceptor implements Interceptor { 20 | 21 | public static void addTo(@NonNull OkHttpClient.Builder builder) { 22 | builder.addInterceptor(new RealNameInterceptor()); 23 | } 24 | 25 | @Override 26 | public Response intercept(Chain chain) throws IOException { 27 | Response response = chain.proceed(chain.request()); 28 | String disposition = response.header("Content-Disposition"); 29 | String realName = parseRealName(disposition); 30 | DownloadResponseBody responseBody = new DownloadResponseBody(response.body()); 31 | responseBody.setRealName(realName); 32 | return response.newBuilder() 33 | .body(responseBody) 34 | .build(); 35 | } 36 | 37 | private String parseRealName(@Nullable String disposition) { 38 | if (disposition == null || disposition.isEmpty()) { 39 | return null; 40 | } 41 | String[] parts = disposition.split(";"); 42 | if (parts.length == 0) { 43 | return null; 44 | } 45 | Map pairs = new HashMap<>(parts.length); 46 | for (String part : parts) { 47 | String s = part.trim(); 48 | int i = s.indexOf("="); 49 | if (i == -1) { 50 | continue; 51 | } 52 | String k = s.substring(0, i); 53 | if (TextUtils.isEmpty(k)) { 54 | continue; 55 | } 56 | String v = s.substring(i + 1); 57 | if (TextUtils.isEmpty(v)) { 58 | continue; 59 | } 60 | pairs.put(k.trim(), v.trim()); 61 | } 62 | if (pairs.isEmpty()) { 63 | return null; 64 | } 65 | String filename = pairs.get("filename*"); 66 | if (filename == null || filename.isEmpty()) { 67 | filename = pairs.get("filename"); 68 | } 69 | if (filename == null || filename.isEmpty()) { 70 | filename = pairs.get("name"); 71 | } 72 | if (filename == null || filename.isEmpty()) { 73 | return null; 74 | } 75 | if (filename.startsWith("\"") && filename.endsWith("\"")) { 76 | filename = filename.substring(1, filename.length() - 1); 77 | } 78 | filename = filename.replace("UTF-8", ""); 79 | return filename; 80 | } 81 | } -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/setting/DefaultDownloadSetting.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download.setting; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import per.goweii.rxhttp.download.DownloadInfo; 7 | 8 | /** 9 | * 描述: 10 | * 11 | * @author Cuizhen 12 | * @date 2018/10/16 13 | */ 14 | public class DefaultDownloadSetting implements DownloadSetting { 15 | 16 | @NonNull 17 | @Override 18 | public String getBaseUrl() { 19 | return "http://api.rxhttp.download/"; 20 | } 21 | 22 | @Override 23 | public long getTimeout() { 24 | return 60000; 25 | } 26 | 27 | @Override 28 | public long getConnectTimeout() { 29 | return 0; 30 | } 31 | 32 | @Override 33 | public long getReadTimeout() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public long getWriteTimeout() { 39 | return 0; 40 | } 41 | 42 | @Nullable 43 | @Override 44 | public String getSaveDirPath() { 45 | return null; 46 | } 47 | 48 | @NonNull 49 | @Override 50 | public DownloadInfo.Mode getDefaultDownloadMode() { 51 | return DownloadInfo.Mode.APPEND; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/setting/DownloadSetting.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download.setting; 2 | 3 | import android.support.annotation.IntRange; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | 7 | import per.goweii.rxhttp.download.DownloadInfo; 8 | 9 | /** 10 | * 描述: 11 | * 12 | * @author Cuizhen 13 | * @date 2018/10/15 14 | */ 15 | public interface DownloadSetting { 16 | 17 | @NonNull 18 | String getBaseUrl(); 19 | 20 | /** 21 | * 获取默认超时时长,单位为毫秒数 22 | */ 23 | @IntRange(from = 1) 24 | long getTimeout(); 25 | 26 | /** 27 | * 获取Connect超时时长,单位为毫秒数 28 | * 返回0则去getTimeout 29 | */ 30 | @IntRange(from = 0) 31 | long getConnectTimeout(); 32 | 33 | /** 34 | * 获取Read超时时长,单位为毫秒数 35 | * 返回0则去getTimeout 36 | */ 37 | @IntRange(from = 0) 38 | long getReadTimeout(); 39 | 40 | /** 41 | * 获取Write超时时长,单位为毫秒数 42 | * 返回0则去getTimeout 43 | */ 44 | @IntRange(from = 0) 45 | long getWriteTimeout(); 46 | 47 | @Nullable 48 | String getSaveDirPath(); 49 | 50 | @NonNull 51 | DownloadInfo.Mode getDefaultDownloadMode(); 52 | } 53 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/utils/DownloadInfoChecker.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download.utils; 2 | 3 | import android.text.TextUtils; 4 | 5 | import java.io.File; 6 | 7 | import per.goweii.rxhttp.core.RxHttp; 8 | import per.goweii.rxhttp.core.utils.SDCardUtils; 9 | import per.goweii.rxhttp.download.DownloadInfo; 10 | import per.goweii.rxhttp.download.exception.RangeLengthIsZeroException; 11 | import per.goweii.rxhttp.download.exception.SaveFileBrokenPointException; 12 | 13 | /** 14 | * 描述: 15 | * 16 | * @author Cuizhen 17 | * @date 2018/10/19 18 | */ 19 | public class DownloadInfoChecker { 20 | 21 | public static void checkDownloadLength(DownloadInfo info) throws SaveFileBrokenPointException{ 22 | if (info.downloadLength == 0){ 23 | File file = createFile(info.saveDirPath, info.saveFileName); 24 | if (file != null && file.exists()) { 25 | if (info.mode == DownloadInfo.Mode.APPEND) { 26 | info.downloadLength = file.length(); 27 | } else if (info.mode == DownloadInfo.Mode.REPLACE) { 28 | //noinspection ResultOfMethodCallIgnored 29 | file.delete(); 30 | } else { 31 | assert info.saveFileName != null; 32 | info.saveFileName = renameFileName(info.saveFileName); 33 | } 34 | } 35 | } else { 36 | File file = createFile(info.saveDirPath, info.saveFileName); 37 | if (file != null && file.exists()) { 38 | if (info.downloadLength != file.length()) { 39 | throw new SaveFileBrokenPointException(); 40 | } 41 | } else { 42 | info.downloadLength = 0; 43 | } 44 | } 45 | } 46 | 47 | public static void checkContentLength(DownloadInfo info) throws RangeLengthIsZeroException{ 48 | //noinspection ConditionCoveredByFurtherCondition 49 | if (info.downloadLength > 0 && info.contentLength > 0 && info.contentLength <= info.downloadLength) { 50 | throw new RangeLengthIsZeroException(); 51 | } 52 | } 53 | 54 | private static String renameFileName(String fileName){ 55 | String nameLeft; 56 | String nameDivide; 57 | String nameRight; 58 | int index = fileName.lastIndexOf("."); 59 | if (index >= 0) { 60 | nameLeft = fileName.substring(0, index); 61 | nameDivide = "."; 62 | nameRight = fileName.substring(index + 1); 63 | } else { 64 | nameLeft = fileName; 65 | nameDivide = ""; 66 | nameRight = ""; 67 | } 68 | int k1 = nameLeft.lastIndexOf("("); 69 | int k2 = nameLeft.lastIndexOf(")"); 70 | int i = 1; 71 | if (k2 + 1 == nameLeft.length() && k1 >= 0 && k2 >= 0 && k2 > k1) { 72 | String num = nameLeft.substring(k1 + 1, k2); 73 | nameLeft = nameLeft.substring(0, k1); 74 | try { 75 | i = Integer.parseInt(num); 76 | i += 1; 77 | } catch (NumberFormatException ignore){ 78 | } 79 | } 80 | return nameLeft + "(" + i + ")" + nameDivide + nameRight; 81 | } 82 | 83 | private static File createFile(String dirPath, String fileName){ 84 | if (TextUtils.isEmpty(dirPath) || TextUtils.isEmpty(fileName)) { 85 | return null; 86 | } 87 | return new File(dirPath, fileName); 88 | } 89 | 90 | public static void checkDirPath(DownloadInfo info){ 91 | if (TextUtils.isEmpty(info.saveDirPath)) { 92 | info.saveDirPath = RxHttp.getDownloadSetting().getSaveDirPath(); 93 | } 94 | if (TextUtils.isEmpty(info.saveDirPath)) { 95 | info.saveDirPath = SDCardUtils.getDownloadCacheDir(); 96 | } 97 | } 98 | 99 | public static void checkFileName(DownloadInfo info){ 100 | if (TextUtils.isEmpty(info.saveFileName)) { 101 | info.saveFileName = System.currentTimeMillis() + ".rxdownload"; 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/utils/RxNotify.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download.utils; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import io.reactivex.Observable; 6 | import io.reactivex.Observer; 7 | import io.reactivex.android.schedulers.AndroidSchedulers; 8 | import io.reactivex.disposables.Disposable; 9 | import io.reactivex.schedulers.Schedulers; 10 | 11 | /** 12 | * 描述: 13 | * 14 | * @author Cuizhen 15 | * @date 2018/10/19 16 | */ 17 | public class RxNotify { 18 | 19 | public static void runOnUiThread(@NonNull final Action action){ 20 | Observable.empty() 21 | .subscribeOn(AndroidSchedulers.mainThread()) 22 | .observeOn(AndroidSchedulers.mainThread()) 23 | .subscribe(new Observer() { 24 | @Override 25 | public void onSubscribe(@NonNull Disposable d) { 26 | } 27 | 28 | @Override 29 | public void onNext(@NonNull Object o) { 30 | } 31 | 32 | @Override 33 | public void onError(@NonNull Throwable e) { 34 | } 35 | 36 | @Override 37 | public void onComplete() { 38 | action.run(); 39 | } 40 | }); 41 | } 42 | 43 | public interface Action{ 44 | void run(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/download/utils/UnitFormatUtils.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.download.utils; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import java.text.DecimalFormat; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * 描述:单位格式化 10 | * 11 | * @author Cuizhen 12 | * @date 2018/10/18 13 | */ 14 | public class UnitFormatUtils { 15 | 16 | private static class Format{ 17 | private static final DecimalFormat TWO = new DecimalFormat("#.##"); 18 | } 19 | 20 | public static float calculateSpeed(long increment, float duration) { 21 | return (float) increment / duration; 22 | } 23 | 24 | public static String formatSpeedPerSecond(float bytePerSecond) { 25 | return formatSpeed(bytePerSecond, TimeUnit.SECONDS); 26 | } 27 | 28 | public static String formatSpeed(float speedBytes, @NonNull TimeUnit timeUnit) { 29 | return formatBytesLength(speedBytes) + "/" + formatTimeUnit(timeUnit); 30 | } 31 | 32 | public static String formatBytesLength(float bytes){ 33 | float length; 34 | String unit; 35 | if (bytes < 1024L) { 36 | // 0B~1KB 37 | unit = "B"; 38 | length = bytes; 39 | } else if (bytes < 1024L * 1024L) { 40 | // 1KB~1MB 41 | unit = "KB"; 42 | length = bytes / (1024L); 43 | } else if (bytes < 1024L * 1024L * 1024L){ 44 | // 1MB~1GB 45 | unit = "MB"; 46 | length = bytes / (1024L * 1024L); 47 | } else if (bytes < 1024L * 1024L * 1024L * 1024L){ 48 | // 1GB~1TB 49 | unit = "GB"; 50 | length = bytes / (1024L * 1024L * 1024L); 51 | } else { 52 | // 1TB~ 53 | unit = "TB"; 54 | length = bytes / (1024L * 1024L * 1024L * 1024L); 55 | } 56 | return Format.TWO.format(length) + unit; 57 | } 58 | 59 | public static String formatTimeUnit(TimeUnit timeUnit){ 60 | if (timeUnit == null) { 61 | return "-"; 62 | } 63 | if (timeUnit == TimeUnit.NANOSECONDS) { 64 | return "ns"; 65 | } else if (timeUnit == TimeUnit.MICROSECONDS){ 66 | return "us"; 67 | } else if (timeUnit == TimeUnit.MILLISECONDS){ 68 | return "ms"; 69 | } else if (timeUnit == TimeUnit.SECONDS){ 70 | return "s"; 71 | } else if (timeUnit == TimeUnit.MINUTES){ 72 | return "m"; 73 | } else if (timeUnit == TimeUnit.HOURS){ 74 | return "h"; 75 | } else if (timeUnit == TimeUnit.DAYS){ 76 | return "d"; 77 | } else { 78 | return "-"; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/Api.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import per.goweii.rxhttp.request.setting.RequestSetting; 6 | 7 | /** 8 | * 子类继承,用于创建一个API接口实例 9 | * 新写一个无参静态方法调用{@link #api(Class)}去创建一个接口实例 10 | * 方法{@link #api(Class)}的参数为ServiceInterface,建议为内部类 11 | * 12 | * @author Cuizhen 13 | * @date 2018/10/16 14 | */ 15 | public class Api { 16 | 17 | public interface Header { 18 | /** 19 | * 添加以这个为名的Header可以让这个Request使用另一个BaseUrl 20 | * {@link RequestSetting#getRedirectBaseUrl()} 21 | */ 22 | String BASE_URL_REDIRECT = "RxHttp-BaseUrl-Redirect"; 23 | /** 24 | * 添加以这个为名的Header可以让这个Request支持缓存(有网联网获取,无网读取缓存) 25 | * 如//@Headers({Header.CACHE_ALIVE_SECOND + ":" + 10}) 26 | */ 27 | String CACHE_ALIVE_SECOND = "RxHttp-Cache-Alive-Second"; 28 | } 29 | 30 | /** 31 | * 创建一个接口实例 32 | * 33 | * @param clazz Retrofit的ServiceInterface,建议定义为子类的内部接口 34 | * @param ServiceInterface的名字 35 | * @return 接口实例 36 | */ 37 | @NonNull 38 | protected static T api(Class clazz) { 39 | return RequestClientManager.getService(clazz); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/RequestClientManager.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.text.TextUtils; 6 | 7 | import com.google.gson.Gson; 8 | 9 | import java.io.File; 10 | import java.util.HashMap; 11 | import java.util.Iterator; 12 | import java.util.Map; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | import okhttp3.Cache; 16 | import okhttp3.Interceptor; 17 | import okhttp3.OkHttpClient; 18 | import okhttp3.logging.HttpLoggingInterceptor; 19 | import per.goweii.rxhttp.core.RxHttp; 20 | import per.goweii.rxhttp.core.manager.BaseClientManager; 21 | import per.goweii.rxhttp.core.utils.BaseUrlUtils; 22 | import per.goweii.rxhttp.core.utils.SDCardUtils; 23 | import per.goweii.rxhttp.request.interceptor.BaseUrlRedirectInterceptor; 24 | import per.goweii.rxhttp.request.interceptor.CacheControlInterceptor; 25 | import per.goweii.rxhttp.request.interceptor.CacheControlNetworkInterceptor; 26 | import per.goweii.rxhttp.request.interceptor.PublicHeadersInterceptor; 27 | import per.goweii.rxhttp.request.interceptor.PublicQueryParameterInterceptor; 28 | import retrofit2.Retrofit; 29 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 30 | import retrofit2.converter.gson.GsonConverterFactory; 31 | 32 | /** 33 | * 描述:构建Retrofit实例,采用单例模式,全局共享同一个Retrofit 34 | * 35 | * @author Cuizhen 36 | * @date 2018/10/15 37 | */ 38 | class RequestClientManager extends BaseClientManager { 39 | 40 | private static RequestClientManager INSTANCE = null; 41 | private final Retrofit mRetrofit; 42 | private Map, Retrofit> mRetrofitMap = null; 43 | 44 | private RequestClientManager() { 45 | mRetrofit = create(); 46 | } 47 | 48 | /** 49 | * 采用单例模式 50 | * 51 | * @return RequestClientManager 52 | */ 53 | @NonNull 54 | private static RequestClientManager getInstance() { 55 | if (INSTANCE == null) { 56 | synchronized (RequestClientManager.class) { 57 | if (INSTANCE == null) { 58 | INSTANCE = new RequestClientManager(); 59 | } 60 | } 61 | } 62 | return INSTANCE; 63 | } 64 | 65 | /** 66 | * 创建Api接口实例 67 | * 68 | * @param clazz Api接口类 69 | * @param Api接口 70 | * @return Api接口实例 71 | */ 72 | @NonNull 73 | static T getService(@NonNull Class clazz) { 74 | return getInstance().getRetrofit(clazz).create(clazz); 75 | } 76 | 77 | @NonNull 78 | private Retrofit getRetrofit(@Nullable Class clazz) { 79 | if (clazz == null) { 80 | return mRetrofit; 81 | } 82 | Retrofit retrofit = null; 83 | if (mRetrofitMap != null && mRetrofitMap.size() > 0) { 84 | Iterator, Retrofit>> iterator = mRetrofitMap.entrySet().iterator(); 85 | while (iterator.hasNext()) { 86 | Map.Entry, Retrofit> entry = iterator.next(); 87 | if (TextUtils.equals(entry.getKey().getName(), clazz.getName())) { 88 | retrofit = entry.getValue(); 89 | if (retrofit == null) { 90 | iterator.remove(); 91 | } 92 | break; 93 | } 94 | } 95 | } 96 | if (retrofit != null) { 97 | return retrofit; 98 | } 99 | Map, String> baseUrlMap = RxHttp.getRequestSetting().getServiceBaseUrl(); 100 | if (baseUrlMap == null || baseUrlMap.size() == 0) { 101 | return mRetrofit; 102 | } 103 | String baseUrl = null; 104 | for (Map.Entry, String> entry : baseUrlMap.entrySet()) { 105 | if (TextUtils.equals(entry.getKey().getName(), clazz.getName())) { 106 | baseUrl = entry.getValue(); 107 | break; 108 | } 109 | } 110 | if (baseUrl == null) { 111 | return mRetrofit; 112 | } 113 | retrofit = create(baseUrl); 114 | if (mRetrofitMap == null) { 115 | mRetrofitMap = new HashMap<>(1); 116 | } 117 | mRetrofitMap.put(clazz, retrofit); 118 | return retrofit; 119 | } 120 | 121 | /** 122 | * 创建Retrofit实例 123 | */ 124 | @Override 125 | @NonNull 126 | protected Retrofit create() { 127 | return create(RxHttp.getRequestSetting().getBaseUrl()); 128 | } 129 | 130 | /** 131 | * 创建Retrofit实例 132 | */ 133 | @NonNull 134 | private Retrofit create(@NonNull String baseUrl) { 135 | Retrofit.Builder builder = new Retrofit.Builder() 136 | .client(createOkHttpClient()) 137 | .baseUrl(BaseUrlUtils.checkBaseUrl(baseUrl)); 138 | builder.addCallAdapterFactory(RxJava2CallAdapterFactory.create()); 139 | Gson gson = RxHttp.getRequestSetting().getGson(); 140 | if (gson == null) { 141 | gson = new Gson(); 142 | } 143 | builder.addConverterFactory(GsonConverterFactory.create(gson)); 144 | return builder.build(); 145 | } 146 | 147 | /** 148 | * 创建OkHttpClient实例 149 | * 150 | * @return OkHttpClient 151 | */ 152 | @NonNull 153 | private OkHttpClient createOkHttpClient() { 154 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 155 | // 设置调试模式打印日志 156 | if (RxHttp.getRequestSetting().isDebug()) { 157 | HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); 158 | logging.setLevel(HttpLoggingInterceptor.Level.BODY); 159 | builder.addInterceptor(logging); 160 | } 161 | // 设置缓存 162 | builder.cache(createCache()); 163 | // 设置3个超时时长 164 | long timeout = RxHttp.getRequestSetting().getTimeout(); 165 | long connectTimeout = RxHttp.getRequestSetting().getConnectTimeout(); 166 | long readTimeout = RxHttp.getRequestSetting().getReadTimeout(); 167 | long writeTimeout = RxHttp.getRequestSetting().getWriteTimeout(); 168 | builder.connectTimeout(connectTimeout > 0 ? connectTimeout : timeout, TimeUnit.MILLISECONDS); 169 | builder.readTimeout(readTimeout > 0 ? readTimeout : timeout, TimeUnit.MILLISECONDS); 170 | builder.writeTimeout(writeTimeout > 0 ? writeTimeout : timeout, TimeUnit.MILLISECONDS); 171 | // 设置应用层拦截器 172 | BaseUrlRedirectInterceptor.addTo(builder); 173 | PublicHeadersInterceptor.addTo(builder); 174 | PublicQueryParameterInterceptor.addTo(builder); 175 | CacheControlInterceptor.addTo(builder); 176 | Interceptor[] interceptors = RxHttp.getRequestSetting().getInterceptors(); 177 | if (interceptors != null && interceptors.length > 0) { 178 | for (Interceptor interceptor : interceptors) { 179 | builder.addInterceptor(interceptor); 180 | } 181 | } 182 | // 设置网络层拦截器 183 | CacheControlNetworkInterceptor.addTo(builder); 184 | Interceptor[] networkInterceptors = RxHttp.getRequestSetting().getNetworkInterceptors(); 185 | if (networkInterceptors != null && networkInterceptors.length > 0) { 186 | for (Interceptor interceptor : networkInterceptors) { 187 | builder.addNetworkInterceptor(interceptor); 188 | } 189 | } 190 | RxHttp.getRequestSetting().setOkHttpClient(builder); 191 | return builder.build(); 192 | } 193 | 194 | /** 195 | * 创建缓存 196 | * 197 | * @return Cache 198 | */ 199 | @NonNull 200 | private Cache createCache() { 201 | File cacheFile = new File(SDCardUtils.getCacheDir(), RxHttp.getRequestSetting().getCacheDirName()); 202 | if (!cacheFile.exists()) { 203 | //noinspection ResultOfMethodCallIgnored 204 | cacheFile.mkdirs(); 205 | } 206 | return new Cache(cacheFile, RxHttp.getRequestSetting().getCacheSize()); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/RxRequest.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import io.reactivex.Observable; 7 | import io.reactivex.android.schedulers.AndroidSchedulers; 8 | import io.reactivex.disposables.Disposable; 9 | import io.reactivex.functions.Action; 10 | import io.reactivex.functions.Consumer; 11 | import io.reactivex.schedulers.Schedulers; 12 | import per.goweii.rxhttp.core.RxHttp; 13 | import per.goweii.rxhttp.core.RxLife; 14 | import per.goweii.rxhttp.request.base.BaseResponse; 15 | import per.goweii.rxhttp.request.exception.ApiException; 16 | import per.goweii.rxhttp.request.exception.ExceptionHandle; 17 | 18 | /** 19 | * 描述:网络请求 20 | * 21 | * @author Cuizhen 22 | * @date 2018/9/9 23 | */ 24 | public class RxRequest> { 25 | private final Observable mObservable; 26 | private ResultCallback mCallback = null; 27 | private RequestListener mListener = null; 28 | private RxLife mRxLife = null; 29 | 30 | private RxRequest(Observable observable) { 31 | mObservable = observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); 32 | } 33 | 34 | @NonNull 35 | public static > RxRequest create(@NonNull Observable observable) { 36 | return new RxRequest<>(observable); 37 | } 38 | 39 | /** 40 | * 添加请求生命周期的监听 41 | */ 42 | @NonNull 43 | public RxRequest listener(@Nullable RequestListener listener) { 44 | mListener = listener; 45 | return this; 46 | } 47 | 48 | /** 49 | * 用于中断请求,管理请求生命周期 50 | * 51 | * @param rxLife 详见{@link RxLife} 52 | */ 53 | @NonNull 54 | public RxRequest autoLife(@Nullable RxLife rxLife) { 55 | mRxLife = rxLife; 56 | return this; 57 | } 58 | 59 | /** 60 | * 发起请求并设置成功回调 61 | * 62 | * @return Disposable 用于中断请求,管理请求生命周期 63 | * 详见{@link RxLife} 64 | */ 65 | @NonNull 66 | public Disposable request(@NonNull ResultCallback callback) { 67 | mCallback = callback; 68 | Disposable disposable = mObservable.subscribe(new Consumer>() { 69 | @Override 70 | public void accept(BaseResponse bean) throws Exception { 71 | if (!isSuccess(bean.getCode())) { 72 | throw new ApiException(bean.getCode(), bean.getMsg()); 73 | } 74 | mCallback.onSuccess(bean.getCode(), bean.getData()); 75 | } 76 | }, new Consumer() { 77 | @Override 78 | public void accept(Throwable e) throws Exception { 79 | if (e instanceof ApiException) { 80 | ApiException apiException = (ApiException) e; 81 | mCallback.onFailed(apiException.getCode(), apiException.getMsg()); 82 | } else { 83 | if (mListener != null) { 84 | ExceptionHandle handle = RxHttp.getRequestSetting().getExceptionHandle(); 85 | if (handle == null) { 86 | handle = new ExceptionHandle(); 87 | } 88 | handle.handle(e); 89 | mListener.onError(handle); 90 | } 91 | } 92 | if (mListener != null) { 93 | mListener.onFinish(); 94 | } 95 | } 96 | }, new Action() { 97 | @Override 98 | public void run() throws Exception { 99 | if (mListener != null) { 100 | mListener.onFinish(); 101 | } 102 | } 103 | }, new Consumer() { 104 | @Override 105 | public void accept(Disposable d) throws Exception { 106 | if (mListener != null) { 107 | mListener.onStart(); 108 | } 109 | } 110 | }); 111 | if (mRxLife != null) { 112 | mRxLife.add(disposable); 113 | } 114 | return disposable; 115 | } 116 | 117 | private boolean isSuccess(int code) { 118 | if (code == RxHttp.getRequestSetting().getSuccessCode()) { 119 | return true; 120 | } 121 | int[] codes = RxHttp.getRequestSetting().getMultiSuccessCode(); 122 | if (codes == null || codes.length == 0) { 123 | return false; 124 | } 125 | for (int i : codes) { 126 | if (code == i) { 127 | return true; 128 | } 129 | } 130 | return false; 131 | } 132 | 133 | public interface ResultCallback { 134 | void onSuccess(int code, E data); 135 | 136 | void onFailed(int code, @Nullable String msg); 137 | } 138 | 139 | public interface RequestListener { 140 | void onStart(); 141 | 142 | void onError(@NonNull ExceptionHandle handle); 143 | 144 | void onFinish(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/RxResponse.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import io.reactivex.Observable; 7 | import io.reactivex.android.schedulers.AndroidSchedulers; 8 | import io.reactivex.disposables.Disposable; 9 | import io.reactivex.functions.Action; 10 | import io.reactivex.functions.Consumer; 11 | import io.reactivex.schedulers.Schedulers; 12 | import per.goweii.rxhttp.core.RxHttp; 13 | import per.goweii.rxhttp.core.RxLife; 14 | import per.goweii.rxhttp.request.exception.ExceptionHandle; 15 | 16 | /** 17 | * 描述:网络请求 18 | * 19 | * @author Cuizhen 20 | * @date 2018/9/9 21 | */ 22 | public class RxResponse { 23 | 24 | private final Observable mObservable; 25 | private RequestListener mListener = null; 26 | private ResultCallback mCallback = null; 27 | private RxLife mRxLife = null; 28 | 29 | private RxResponse(@NonNull Observable observable) { 30 | mObservable = observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); 31 | } 32 | 33 | @NonNull 34 | public static RxResponse create(@NonNull Observable observable) { 35 | return new RxResponse<>(observable); 36 | } 37 | 38 | /** 39 | * 添加请求生命周期的监听 40 | */ 41 | @NonNull 42 | public RxResponse listener(@Nullable RequestListener listener) { 43 | mListener = listener; 44 | return this; 45 | } 46 | 47 | /** 48 | * 用于中断请求,管理请求生命周期 49 | * 50 | * @param rxLife 详见{@link RxLife} 51 | */ 52 | @NonNull 53 | public RxResponse autoLife(@Nullable RxLife rxLife) { 54 | mRxLife = rxLife; 55 | return this; 56 | } 57 | 58 | /** 59 | * 发起请求并设置成功回调 60 | * 61 | * @return Disposable 用于中断请求,管理请求生命周期 62 | * 详见{@link RxLife} 63 | */ 64 | @NonNull 65 | public Disposable request(@NonNull ResultCallback callback) { 66 | mCallback = callback; 67 | Disposable disposable = mObservable.subscribe(new Consumer() { 68 | @Override 69 | public void accept(Resp resp) throws Exception { 70 | mCallback.onResponse(resp); 71 | } 72 | }, new Consumer() { 73 | @Override 74 | public void accept(Throwable e) throws Exception { 75 | ExceptionHandle handle = RxHttp.getRequestSetting().getExceptionHandle(); 76 | if (handle == null) { 77 | handle = new ExceptionHandle(); 78 | } 79 | handle.handle(e); 80 | mCallback.onError(handle); 81 | if (mListener != null) { 82 | mListener.onFinish(); 83 | } 84 | } 85 | }, new Action() { 86 | @Override 87 | public void run() throws Exception { 88 | if (mListener != null) { 89 | mListener.onFinish(); 90 | } 91 | } 92 | }, new Consumer() { 93 | @Override 94 | public void accept(Disposable d) throws Exception { 95 | if (mListener != null) { 96 | mListener.onStart(); 97 | } 98 | } 99 | }); 100 | if (mRxLife != null) { 101 | mRxLife.add(disposable); 102 | } 103 | return disposable; 104 | } 105 | 106 | public interface RequestListener { 107 | void onStart(); 108 | 109 | void onFinish(); 110 | } 111 | 112 | public interface ResultCallback { 113 | void onError(@NonNull ExceptionHandle handle); 114 | 115 | void onResponse(Resp resp); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/base/BaseBean.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.base; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.google.gson.Gson; 6 | 7 | import java.io.Serializable; 8 | 9 | import per.goweii.rxhttp.request.utils.JsonFormatUtils; 10 | 11 | /** 12 | * 描述:网络请求的实体类基类 13 | * 14 | * @author Cuizhen 15 | * @date 2018/9/9 16 | */ 17 | public class BaseBean implements Serializable { 18 | 19 | @NonNull 20 | public String toJson() { 21 | return new Gson().toJson(this); 22 | } 23 | 24 | @NonNull 25 | public String toFormatJson() { 26 | return JsonFormatUtils.format(toJson()); 27 | } 28 | } -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/base/BaseResponse.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.base; 2 | 3 | /** 4 | * 描述:网络接口返回json格式对应的实体类 5 | * 6 | * @author Cuizhen 7 | * @date 2018/6/19 8 | */ 9 | public interface BaseResponse { 10 | 11 | int getCode(); 12 | 13 | void setCode(int code); 14 | 15 | E getData(); 16 | 17 | void setData(E data); 18 | 19 | String getMsg(); 20 | 21 | void setMsg(String msg); 22 | } 23 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/exception/ApiException.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.exception; 2 | 3 | /** 4 | * 描述:服务器请求成功,返回失败码时抛出,方便统一处理 5 | * 6 | * @author Cuizhen 7 | * @date 2018/10/12 8 | */ 9 | public class ApiException extends Exception { 10 | 11 | private final int code; 12 | private final String msg; 13 | 14 | public ApiException(int code, String msg) { 15 | super(msg + "(code=" + code + ")"); 16 | this.code = code; 17 | this.msg = msg; 18 | } 19 | 20 | public int getCode() { 21 | return code; 22 | } 23 | 24 | public String getMsg() { 25 | return msg; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/exception/ExceptionHandle.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.exception; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.google.gson.JsonParseException; 6 | 7 | import org.json.JSONException; 8 | 9 | import java.net.ConnectException; 10 | import java.net.SocketTimeoutException; 11 | import java.net.UnknownHostException; 12 | import java.text.ParseException; 13 | 14 | import javax.net.ssl.SSLException; 15 | 16 | import per.goweii.rxhttp.request.setting.RequestSetting; 17 | import per.goweii.rxhttp.request.utils.NetUtils; 18 | import retrofit2.HttpException; 19 | 20 | /** 21 | * 集中处理请求中异常,可通过继承自定义,在 22 | * {@link RequestSetting#getExceptionHandle()}中返回 23 | * 24 | * @author CuiZhen 25 | * @date 2018/10/14 26 | * QQ: 302833254 27 | * E-mail: goweii@163.com 28 | * GitHub: https://github.com/goweii 29 | */ 30 | public class ExceptionHandle { 31 | 32 | private Throwable e; 33 | private int code; 34 | private String msg; 35 | 36 | public final void handle(@NonNull Throwable e) { 37 | this.e = e; 38 | this.code = onGetCode(e); 39 | this.msg = onGetMsg(code); 40 | } 41 | 42 | /** 43 | * 重写该方法去返回异常对应的错误码 44 | * 45 | * @param e Throwable 46 | * @return 错误码 47 | */ 48 | protected int onGetCode(@NonNull Throwable e) { 49 | if (!NetUtils.isConnected()) { 50 | return Code.NET; 51 | } else { 52 | if (e instanceof SocketTimeoutException) { 53 | return Code.TIMEOUT; 54 | } else if (e instanceof HttpException) { 55 | return Code.HTTP; 56 | } else if (e instanceof UnknownHostException || e instanceof ConnectException) { 57 | return Code.HOST; 58 | } else if (e instanceof JsonParseException || e instanceof ParseException || e instanceof JSONException) { 59 | return Code.JSON; 60 | } else if (e instanceof SSLException) { 61 | return Code.SSL; 62 | } else { 63 | return Code.UNKNOWN; 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * 重写该方法去返回错误码对应的错误信息 70 | * 71 | * @param code 错误码 72 | * @return 错误信息 73 | */ 74 | @NonNull 75 | protected String onGetMsg(int code) { 76 | String msg; 77 | switch (code) { 78 | default: 79 | msg = "未知错误,请稍后重试"; 80 | break; 81 | case Code.NET: 82 | msg = "网络连接失败,请检查网络设置"; 83 | break; 84 | case Code.TIMEOUT: 85 | msg = "网络状况不稳定,请稍后重试"; 86 | break; 87 | case Code.JSON: 88 | msg = "JSON解析异常"; 89 | break; 90 | case Code.HTTP: 91 | msg = "请求错误,请稍后重试"; 92 | break; 93 | case Code.HOST: 94 | msg = "服务器连接失败,请检查网络设置"; 95 | break; 96 | case Code.SSL: 97 | msg = "证书验证失败"; 98 | break; 99 | } 100 | return msg; 101 | } 102 | 103 | public final int getCode() { 104 | return code; 105 | } 106 | 107 | public final String getMsg() { 108 | return msg; 109 | } 110 | 111 | public final Throwable getException() { 112 | return e; 113 | } 114 | 115 | public interface Code { 116 | int UNKNOWN = -1; 117 | int NET = 0; 118 | int TIMEOUT = 1; 119 | int JSON = 2; 120 | int HTTP = 3; 121 | int HOST = 4; 122 | int SSL = 5; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/exception/NullRequestSettingException.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.exception; 2 | 3 | /** 4 | * @author Cuizhen 5 | * @date 2018/10/15 6 | */ 7 | public class NullRequestSettingException extends RuntimeException { 8 | public NullRequestSettingException() { 9 | super("RequestSetting未设置"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/gson/BaseBeanDeserializer.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.gson; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import com.google.gson.JsonDeserializationContext; 7 | import com.google.gson.JsonDeserializer; 8 | import com.google.gson.JsonElement; 9 | import com.google.gson.JsonObject; 10 | import com.google.gson.JsonParseException; 11 | 12 | import java.lang.reflect.Field; 13 | import java.lang.reflect.Type; 14 | import java.util.Map; 15 | 16 | import per.goweii.rxhttp.request.base.BaseBean; 17 | 18 | /** 19 | * 描述: 20 | * 21 | * @author Cuizhen 22 | * @date 2019/5/10 23 | */ 24 | public class BaseBeanDeserializer implements JsonDeserializer { 25 | @Nullable 26 | @Override 27 | public BaseBean deserialize(@NonNull JsonElement json, @NonNull Type typeOfT, @NonNull JsonDeserializationContext context) throws JsonParseException { 28 | if (!json.isJsonObject()) { 29 | return null; 30 | } 31 | JsonObject jsonObj = json.getAsJsonObject(); 32 | try { 33 | Class clazz = (Class) typeOfT; 34 | BaseBean baseBean = (BaseBean) clazz.newInstance(); 35 | for (Map.Entry entry : jsonObj.entrySet()) { 36 | String itemKey = entry.getKey(); 37 | JsonElement itemElement = entry.getValue(); 38 | try { 39 | Field field = clazz.getDeclaredField(itemKey); 40 | field.setAccessible(true); 41 | Object item = context.deserialize(itemElement, field.getType()); 42 | field.set(baseBean, item); 43 | } catch (NoSuchFieldException e) { 44 | e.printStackTrace(); 45 | } catch (IllegalAccessException e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | return baseBean; 50 | } catch (IllegalAccessException e) { 51 | e.printStackTrace(); 52 | } catch (InstantiationException e) { 53 | e.printStackTrace(); 54 | } 55 | return null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/gson/IntDeserializer.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.gson; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import com.google.gson.JsonDeserializationContext; 7 | import com.google.gson.JsonDeserializer; 8 | import com.google.gson.JsonElement; 9 | import com.google.gson.JsonParseException; 10 | 11 | import java.lang.reflect.Type; 12 | 13 | /** 14 | * 描述: 15 | * 16 | * @author Cuizhen 17 | * @date 2019/5/10 18 | */ 19 | public class IntDeserializer implements JsonDeserializer { 20 | @Nullable 21 | @Override 22 | public Integer deserialize(@NonNull JsonElement json, @NonNull Type typeOfT, @NonNull JsonDeserializationContext context) throws JsonParseException { 23 | if (!json.isJsonObject()) { 24 | return 0; 25 | } 26 | try { 27 | return json.getAsInt(); 28 | } catch (Exception ignore){ 29 | } 30 | try { 31 | double d = json.getAsDouble(); 32 | return (int) d; 33 | } catch (Exception ignore){ 34 | } 35 | try { 36 | String s = json.getAsString(); 37 | return Integer.valueOf(s); 38 | } catch (Exception ignore){ 39 | } 40 | return 0; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/gson/ListDeserializer.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.gson; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import com.google.gson.JsonArray; 7 | import com.google.gson.JsonDeserializationContext; 8 | import com.google.gson.JsonDeserializer; 9 | import com.google.gson.JsonElement; 10 | import com.google.gson.JsonParseException; 11 | 12 | import java.lang.reflect.ParameterizedType; 13 | import java.lang.reflect.Type; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * 描述: 19 | * 20 | * @author Cuizhen 21 | * @date 2019/5/10 22 | */ 23 | public class ListDeserializer implements JsonDeserializer> { 24 | 25 | @Nullable 26 | @Override 27 | public List deserialize(@NonNull JsonElement json, @NonNull Type typeOfT, @NonNull JsonDeserializationContext context) throws JsonParseException { 28 | if (!json.isJsonArray()) { 29 | return null; 30 | } 31 | List list = new ArrayList<>(); 32 | JsonArray array = json.getAsJsonArray(); 33 | Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0]; 34 | for (int i = 0; i < array.size(); i++) { 35 | JsonElement element = array.get(i); 36 | Object item = context.deserialize(element, itemType); 37 | list.add(item); 38 | } 39 | return list; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/gson/StringDeserializer.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.gson; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import com.google.gson.JsonDeserializationContext; 7 | import com.google.gson.JsonDeserializer; 8 | import com.google.gson.JsonElement; 9 | import com.google.gson.JsonParseException; 10 | 11 | import java.lang.reflect.Type; 12 | 13 | /** 14 | * 描述: 15 | * 16 | * @author Cuizhen 17 | * @date 2019/5/10 18 | */ 19 | public class StringDeserializer implements JsonDeserializer { 20 | @Nullable 21 | @Override 22 | public String deserialize(@NonNull JsonElement json, @NonNull Type typeOfT, @NonNull JsonDeserializationContext context) throws JsonParseException { 23 | if (!json.isJsonObject()) { 24 | return ""; 25 | } 26 | try { 27 | return json.getAsString(); 28 | } catch (Exception ignore){ 29 | } 30 | try { 31 | long l = json.getAsLong(); 32 | return String.valueOf(l); 33 | } catch (Exception ignore){ 34 | } 35 | try { 36 | double d = json.getAsDouble(); 37 | return String.valueOf(d); 38 | } catch (Exception ignore){ 39 | } 40 | return ""; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/interceptor/BaseCacheControlInterceptor.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.interceptor; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.text.TextUtils; 5 | 6 | import java.io.IOException; 7 | import java.util.List; 8 | import okhttp3.Interceptor; 9 | import okhttp3.Request; 10 | import okhttp3.Response; 11 | import per.goweii.rxhttp.request.Api; 12 | import per.goweii.rxhttp.request.utils.NonNullUtils; 13 | 14 | /** 15 | * 描述:缓存过滤器 16 | * 在基类过滤掉非GET请求和未配置{@link Api.Header#CACHE_ALIVE_SECOND}的请求 17 | * 18 | * @author Cuizhen 19 | * @date 2018/10/18 20 | */ 21 | public class BaseCacheControlInterceptor implements Interceptor { 22 | 23 | @Override 24 | public Response intercept(@NonNull Chain chain) throws IOException { 25 | Request request = chain.request(); 26 | if (!TextUtils.equals(request.method(), "GET")) { 27 | return chain.proceed(request); 28 | } 29 | List headers = request.headers(Api.Header.CACHE_ALIVE_SECOND); 30 | if (!NonNullUtils.check(headers)) { 31 | return chain.proceed(request); 32 | } 33 | int age = getCacheControlAge(headers.get(0)); 34 | Request requestCached = getCacheRequest(request, age); 35 | Response response = chain.proceed(requestCached); 36 | return getCacheResponse(response, age); 37 | } 38 | 39 | @NonNull 40 | protected Request getCacheRequest(@NonNull Request request, int age){ 41 | return request; 42 | } 43 | 44 | @NonNull 45 | protected Response getCacheResponse(@NonNull Response response, int age){ 46 | return response; 47 | } 48 | 49 | private int getCacheControlAge(String age) { 50 | if (!TextUtils.isEmpty(age)) { 51 | return 0; 52 | } 53 | try { 54 | return Integer.parseInt(age); 55 | } catch (NumberFormatException ignore) { 56 | return 0; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/interceptor/BaseUrlRedirectInterceptor.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.interceptor; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.text.TextUtils; 5 | 6 | import java.io.IOException; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | import okhttp3.HttpUrl; 12 | import okhttp3.Interceptor; 13 | import okhttp3.OkHttpClient; 14 | import okhttp3.Request; 15 | import okhttp3.Response; 16 | import per.goweii.rxhttp.core.RxHttp; 17 | import per.goweii.rxhttp.core.utils.BaseUrlUtils; 18 | import per.goweii.rxhttp.request.Api; 19 | import per.goweii.rxhttp.request.utils.NonNullUtils; 20 | 21 | /** 22 | * BaseUrl重定向 23 | * 24 | * @author Cuizhen 25 | * @date 2018/10/13 26 | */ 27 | public class BaseUrlRedirectInterceptor implements Interceptor { 28 | 29 | public static void addTo(@NonNull OkHttpClient.Builder builder) { 30 | Map urls = RxHttp.getRequestSetting().getRedirectBaseUrl(); 31 | if (NonNullUtils.check(urls)) { 32 | builder.addInterceptor(new BaseUrlRedirectInterceptor()); 33 | } 34 | } 35 | 36 | private BaseUrlRedirectInterceptor() { 37 | } 38 | 39 | @Override 40 | public Response intercept(@NonNull Chain chain) throws IOException { 41 | Request original = chain.request(); 42 | Map urls = RxHttp.getRequestSetting().getRedirectBaseUrl(); 43 | if (!NonNullUtils.check(urls)) { 44 | return chain.proceed(original); 45 | } 46 | List urlNames = original.headers(Api.Header.BASE_URL_REDIRECT); 47 | if (!NonNullUtils.check(urlNames)) { 48 | return chain.proceed(original); 49 | } 50 | Request.Builder builder = original.newBuilder(); 51 | builder.removeHeader(Api.Header.BASE_URL_REDIRECT); 52 | String urlName = urlNames.get(0); 53 | String newUrl = urls.get(urlName); 54 | if (newUrl == null) { 55 | return chain.proceed(original); 56 | } 57 | HttpUrl newHttpUrl = HttpUrl.parse(BaseUrlUtils.checkBaseUrl(newUrl)); 58 | if (newHttpUrl == null) { 59 | return chain.proceed(original); 60 | } 61 | HttpUrl oldHttpUrl = original.url(); 62 | List pathSegments = new ArrayList<>(oldHttpUrl.pathSegments()); 63 | int oldCount = defaultBaseUrlPathSegmentCount(); 64 | for (int i = 0; i < oldCount; i++) { 65 | pathSegments.remove(0); 66 | } 67 | HttpUrl.Builder newHttpUrlBuilder = oldHttpUrl.newBuilder() 68 | .scheme(newHttpUrl.scheme()) 69 | .host(newHttpUrl.host()) 70 | .port(newHttpUrl.port()); 71 | int size1 = newHttpUrl.pathSegments().size(); 72 | for (int i = size1 - 1; i >= 0; i--) { 73 | String segment = newHttpUrl.pathSegments().get(i); 74 | if (TextUtils.isEmpty(segment)){ 75 | continue; 76 | } 77 | pathSegments.add(0, segment); 78 | } 79 | int size2 = oldHttpUrl.pathSegments().size(); 80 | for (int i = 0; i < size2; i++) { 81 | newHttpUrlBuilder.removePathSegment(0); 82 | } 83 | for (int i = 0; i < pathSegments.size(); i++) { 84 | newHttpUrlBuilder.addPathSegment(pathSegments.get(i)); 85 | } 86 | Request newRequest = builder.url(newHttpUrlBuilder.build()).build(); 87 | return chain.proceed(newRequest); 88 | } 89 | 90 | private int defaultBaseUrlPathSegmentCount(){ 91 | HttpUrl oldHttpUrl = HttpUrl.parse(BaseUrlUtils.checkBaseUrl(RxHttp.getRequestSetting().getBaseUrl())); 92 | if (oldHttpUrl == null) { 93 | return 0; 94 | } 95 | List oldSegments = oldHttpUrl.pathSegments(); 96 | if (oldSegments == null || oldSegments.size() == 0){ 97 | return 0; 98 | } 99 | int count = oldSegments.size(); 100 | if (TextUtils.isEmpty(oldSegments.get(count - 1))) { 101 | count--; 102 | } 103 | return count; 104 | } 105 | } -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/interceptor/CacheControlInterceptor.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.interceptor; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import okhttp3.CacheControl; 8 | import okhttp3.OkHttpClient; 9 | import okhttp3.Request; 10 | import per.goweii.rxhttp.request.utils.NetUtils; 11 | 12 | /** 13 | * 描述:缓存过滤器 14 | * 用于为Request配置缓存策略 15 | * 16 | * @author Cuizhen 17 | * @date 2018/10/18 18 | */ 19 | public class CacheControlInterceptor extends BaseCacheControlInterceptor { 20 | 21 | public static void addTo(@NonNull OkHttpClient.Builder builder) { 22 | builder.addInterceptor(new CacheControlInterceptor()); 23 | } 24 | 25 | private CacheControlInterceptor() { 26 | } 27 | 28 | @NonNull 29 | @Override 30 | protected Request getCacheRequest(@io.reactivex.annotations.NonNull Request request, int age) { 31 | if (NetUtils.isConnected()) { 32 | if (age <= 0) { 33 | return request.newBuilder() 34 | .cacheControl(CacheControl.FORCE_NETWORK) 35 | .build(); 36 | } else { 37 | return request.newBuilder() 38 | .cacheControl(new CacheControl.Builder().maxAge(age, TimeUnit.SECONDS).build()) 39 | .build(); 40 | } 41 | } else { 42 | return request.newBuilder() 43 | .cacheControl(CacheControl.FORCE_CACHE) 44 | .build(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/interceptor/CacheControlNetworkInterceptor.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.interceptor; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import okhttp3.OkHttpClient; 6 | import okhttp3.Request; 7 | import okhttp3.Response; 8 | import per.goweii.rxhttp.request.Api; 9 | import per.goweii.rxhttp.request.utils.NetUtils; 10 | 11 | /** 12 | * 描述:缓存过滤器 13 | * 用于为Response配置缓存策略 14 | * 15 | * @author Cuizhen 16 | * @date 2018/10/18 17 | */ 18 | public class CacheControlNetworkInterceptor extends BaseCacheControlInterceptor { 19 | 20 | public static void addTo(@NonNull OkHttpClient.Builder builder) { 21 | builder.addNetworkInterceptor(new CacheControlNetworkInterceptor()); 22 | } 23 | 24 | private CacheControlNetworkInterceptor() { 25 | } 26 | 27 | @NonNull 28 | @Override 29 | protected Request getCacheRequest(@io.reactivex.annotations.NonNull Request request, int age) { 30 | return request.newBuilder() 31 | .removeHeader(Api.Header.CACHE_ALIVE_SECOND) 32 | .build(); 33 | } 34 | 35 | @NonNull 36 | @Override 37 | protected Response getCacheResponse(@io.reactivex.annotations.NonNull Response response, int age) { 38 | if (NetUtils.isConnected()) { 39 | if (age <= 0) { 40 | return response.newBuilder() 41 | .removeHeader("Cache-Control") 42 | .build(); 43 | } else { 44 | return response.newBuilder() 45 | .removeHeader("Cache-Control") 46 | .header("Cache-Control", "public, max-age=" + age) 47 | .build(); 48 | } 49 | } else { 50 | return response.newBuilder() 51 | .removeHeader("Cache-Control") 52 | .header("Cache-Control", "public, only-if-cached, max-stale=" + Integer.MAX_VALUE) 53 | .build(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/interceptor/PublicHeadersInterceptor.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.interceptor; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import java.io.IOException; 6 | import java.util.Map; 7 | 8 | import okhttp3.Interceptor; 9 | import okhttp3.OkHttpClient; 10 | import okhttp3.Request; 11 | import okhttp3.Response; 12 | import per.goweii.rxhttp.core.RxHttp; 13 | import per.goweii.rxhttp.request.setting.ParameterGetter; 14 | import per.goweii.rxhttp.request.utils.NonNullUtils; 15 | 16 | /** 17 | * 描述:添加公共请求头 18 | * 19 | * @author Cuizhen 20 | * @date 2018/11/30 21 | */ 22 | public class PublicHeadersInterceptor implements Interceptor { 23 | 24 | public static void addTo(@NonNull OkHttpClient.Builder builder) { 25 | Map staticParameters = RxHttp.getRequestSetting().getStaticHeaderParameter(); 26 | Map dynamicParameters = RxHttp.getRequestSetting().getDynamicHeaderParameter(); 27 | if (NonNullUtils.check(staticParameters, dynamicParameters)) { 28 | builder.addInterceptor(new PublicHeadersInterceptor()); 29 | } 30 | } 31 | 32 | @Override 33 | public Response intercept(@NonNull Chain chain) throws IOException { 34 | Request request = chain.request(); 35 | Request.Builder builder = request.newBuilder(); 36 | Map staticParameters = RxHttp.getRequestSetting().getStaticHeaderParameter(); 37 | if (NonNullUtils.check(staticParameters)) { 38 | for (Map.Entry entry : staticParameters.entrySet()) { 39 | builder.header(entry.getKey(), entry.getValue()); 40 | } 41 | } 42 | Map dynamicParameters = RxHttp.getRequestSetting().getDynamicHeaderParameter(); 43 | if (NonNullUtils.check(dynamicParameters)) { 44 | for (Map.Entry entry : dynamicParameters.entrySet()) { 45 | builder.header(entry.getKey(), entry.getValue().get()); 46 | } 47 | } 48 | return chain.proceed(builder.build()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/interceptor/PublicQueryParameterInterceptor.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.interceptor; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import java.io.IOException; 6 | import java.util.Map; 7 | 8 | import okhttp3.HttpUrl; 9 | import okhttp3.Interceptor; 10 | import okhttp3.OkHttpClient; 11 | import okhttp3.Request; 12 | import okhttp3.Response; 13 | import per.goweii.rxhttp.core.RxHttp; 14 | import per.goweii.rxhttp.request.setting.ParameterGetter; 15 | import per.goweii.rxhttp.request.utils.NonNullUtils; 16 | 17 | /** 18 | * 描述:添加公共请求参数 19 | * 20 | * @author Cuizhen 21 | * @date 2018/9/28 22 | */ 23 | public class PublicQueryParameterInterceptor implements Interceptor { 24 | 25 | public static void addTo(@NonNull OkHttpClient.Builder builder) { 26 | Map staticParameters = RxHttp.getRequestSetting().getStaticPublicQueryParameter(); 27 | Map dynamicParameters = RxHttp.getRequestSetting().getDynamicPublicQueryParameter(); 28 | if (NonNullUtils.check(staticParameters, dynamicParameters)) { 29 | builder.addInterceptor(new PublicQueryParameterInterceptor()); 30 | } 31 | } 32 | 33 | private PublicQueryParameterInterceptor() { 34 | } 35 | 36 | @Override 37 | public Response intercept(@NonNull Chain chain) throws IOException { 38 | Request original = chain.request(); 39 | 40 | HttpUrl.Builder builder = original.url().newBuilder(); 41 | 42 | Map staticParameters = RxHttp.getRequestSetting().getStaticPublicQueryParameter(); 43 | if (NonNullUtils.check(staticParameters)) { 44 | for (Map.Entry entry : staticParameters.entrySet()) { 45 | builder.addQueryParameter(entry.getKey(), entry.getValue()); 46 | } 47 | } 48 | 49 | Map dynamicParameters = RxHttp.getRequestSetting().getDynamicPublicQueryParameter(); 50 | if (NonNullUtils.check(dynamicParameters)) { 51 | for (Map.Entry entry : dynamicParameters.entrySet()) { 52 | builder.addQueryParameter(entry.getKey(), entry.getValue().get()); 53 | } 54 | } 55 | 56 | Request request = original.newBuilder() 57 | .method(original.method(), original.body()) 58 | .url(builder.build()) 59 | .build(); 60 | 61 | return chain.proceed(request); 62 | } 63 | } -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/setting/DefaultRequestSetting.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.setting; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | 6 | import com.google.gson.Gson; 7 | 8 | import java.util.Map; 9 | 10 | import okhttp3.Interceptor; 11 | import okhttp3.OkHttpClient; 12 | import per.goweii.rxhttp.request.exception.ExceptionHandle; 13 | 14 | /** 15 | * 描述:网络请求设置(默认) 16 | * 17 | * @author Cuizhen 18 | * @date 2018/7/20 19 | */ 20 | public abstract class DefaultRequestSetting implements RequestSetting { 21 | 22 | @Nullable 23 | @Override 24 | public Map getRedirectBaseUrl() { 25 | return null; 26 | } 27 | 28 | @Nullable 29 | @Override 30 | public Map, String> getServiceBaseUrl() { 31 | return null; 32 | } 33 | 34 | @Override 35 | public int[] getMultiSuccessCode() { 36 | return null; 37 | } 38 | 39 | @Override 40 | public long getTimeout() { 41 | return 5000; 42 | } 43 | 44 | @Override 45 | public long getConnectTimeout() { 46 | return 0; 47 | } 48 | 49 | @Override 50 | public long getReadTimeout() { 51 | return 0; 52 | } 53 | 54 | @Override 55 | public long getWriteTimeout() { 56 | return 0; 57 | } 58 | 59 | @NonNull 60 | @Override 61 | public String getCacheDirName() { 62 | return "rxhttp_cache"; 63 | } 64 | 65 | @Override 66 | public long getCacheSize() { 67 | return 10 * 1024 * 1024; 68 | } 69 | 70 | @Nullable 71 | @Override 72 | public Map getStaticPublicQueryParameter() { 73 | return null; 74 | } 75 | 76 | @Nullable 77 | @Override 78 | public Map getDynamicPublicQueryParameter() { 79 | return null; 80 | } 81 | 82 | @Nullable 83 | @Override 84 | public Map getStaticHeaderParameter() { 85 | return null; 86 | } 87 | 88 | @Nullable 89 | @Override 90 | public Map getDynamicHeaderParameter() { 91 | return null; 92 | } 93 | 94 | @Nullable 95 | @Override 96 | public E getExceptionHandle() { 97 | return null; 98 | } 99 | 100 | @Nullable 101 | @Override 102 | public Interceptor[] getInterceptors() { 103 | return null; 104 | } 105 | 106 | @Nullable 107 | @Override 108 | public Interceptor[] getNetworkInterceptors() { 109 | return null; 110 | } 111 | 112 | @Override 113 | public boolean ignoreSslForHttps() { 114 | return false; 115 | } 116 | 117 | @Override 118 | public boolean enableTls12BelowAndroidKitkat() { 119 | return true; 120 | } 121 | 122 | @Override 123 | public void setOkHttpClient(@NonNull OkHttpClient.Builder builder) { 124 | } 125 | 126 | @Nullable 127 | @Override 128 | public Gson getGson() { 129 | return null; 130 | } 131 | 132 | @Override 133 | public boolean isDebug() { 134 | return false; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/setting/ParameterGetter.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.setting; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | /** 6 | * 描述: 7 | * 8 | * @author Cuizhen 9 | * @date 2018/10/15 10 | */ 11 | public interface ParameterGetter { 12 | @NonNull 13 | String get(); 14 | } 15 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/setting/RequestSetting.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.setting; 2 | 3 | import android.support.annotation.IntRange; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | 7 | import com.google.gson.Gson; 8 | 9 | import java.util.Map; 10 | 11 | import okhttp3.Interceptor; 12 | import okhttp3.OkHttpClient; 13 | import per.goweii.rxhttp.request.exception.ExceptionHandle; 14 | 15 | /** 16 | * 描述:网络请求设置 17 | * 18 | * @author Cuizhen 19 | * @date 2018/10/12 20 | */ 21 | public interface RequestSetting { 22 | 23 | /** 24 | * 设置默认BaseUrl 25 | */ 26 | @NonNull 27 | String getBaseUrl(); 28 | 29 | /** 30 | * 用于对不同的请求设置不同的BaseUrl 31 | * 需要配合Retrofit的@Headers注解使用 32 | * 如:@Headers({RxHttp.BASE_URL_REDIRECT + ":" + 别名}) 33 | * 34 | * @return Map 别名,BaseUrl 35 | */ 36 | @Nullable 37 | Map getRedirectBaseUrl(); 38 | 39 | /** 40 | * 用于对一组接口设置BaseUrl 41 | * 这种设置方法对资源占用较大,实现方式为每组的请求创建不同的Retrofit和OkHttpClient实例,设置均相同,及下面的设置 42 | * 建议在少数请求需要单独设置BaseUrl时使用{@link #getRedirectBaseUrl()} 43 | * 44 | * @return Map 接口类,BaseUrl 45 | */ 46 | @Nullable 47 | Map, String> getServiceBaseUrl(); 48 | 49 | int getSuccessCode(); 50 | 51 | @Nullable 52 | int[] getMultiSuccessCode(); 53 | 54 | /** 55 | * 获取默认超时时长,单位为毫秒数 56 | */ 57 | @IntRange(from = 1) 58 | long getTimeout(); 59 | 60 | /** 61 | * 获取Connect超时时长,单位为毫秒数 62 | * 返回0则取getTimeout 63 | */ 64 | @IntRange(from = 0) 65 | long getConnectTimeout(); 66 | 67 | /** 68 | * 获取Read超时时长,单位为毫秒数 69 | * 返回0则取getTimeout 70 | */ 71 | @IntRange(from = 0) 72 | long getReadTimeout(); 73 | 74 | /** 75 | * 获取Write超时时长,单位为毫秒数 76 | * 返回0则取getTimeout 77 | */ 78 | @IntRange(from = 0) 79 | long getWriteTimeout(); 80 | 81 | /** 82 | * 获取网络缓存的文件夹名 83 | */ 84 | @NonNull 85 | String getCacheDirName(); 86 | 87 | /** 88 | * 获取网络缓存的最大值 89 | */ 90 | @IntRange(from = 1) 91 | long getCacheSize(); 92 | 93 | @Nullable 94 | Map getStaticPublicQueryParameter(); 95 | 96 | @Nullable 97 | Map getDynamicPublicQueryParameter(); 98 | 99 | @Nullable 100 | Map getStaticHeaderParameter(); 101 | 102 | @Nullable 103 | Map getDynamicHeaderParameter(); 104 | 105 | @Nullable 106 | E getExceptionHandle(); 107 | 108 | @Nullable 109 | Interceptor[] getInterceptors(); 110 | 111 | @Nullable 112 | Interceptor[] getNetworkInterceptors(); 113 | 114 | /** 115 | * 忽略HTTPS的证书验证 116 | * 仅在后台未正确配置且着急调试时可临时置为true 117 | * 118 | * @return 建议为false 119 | */ 120 | boolean ignoreSslForHttps(); 121 | 122 | /** 123 | * android4.4及以下版本默认未开启Tls1.2 124 | * 返回true则强制开启 125 | */ 126 | boolean enableTls12BelowAndroidKitkat(); 127 | 128 | /** 129 | * 在创建OkHttpClient之前调用,及框架完成所有配置后 130 | */ 131 | void setOkHttpClient(@NonNull OkHttpClient.Builder builder); 132 | 133 | /** 134 | * 在创建OkHttpClient之前调用,及框架完成所有配置后 135 | */ 136 | @Nullable 137 | Gson getGson(); 138 | 139 | /** 140 | * 是否打开调试模式 141 | */ 142 | boolean isDebug(); 143 | 144 | } 145 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.utils; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.text.TextUtils; 6 | import android.webkit.MimeTypeMap; 7 | 8 | import java.io.File; 9 | import java.util.Locale; 10 | 11 | /** 12 | * 描述: 13 | * 14 | * @author Cuizhen 15 | * @date 2018/10/12 16 | */ 17 | class FileUtils { 18 | 19 | @NonNull 20 | private static String getSuffix(@Nullable File file) { 21 | if (file == null || !file.exists() || file.isDirectory()) { 22 | return ""; 23 | } 24 | String fileName = file.getName(); 25 | if (fileName.endsWith(".")) { 26 | return ""; 27 | } 28 | int index = fileName.lastIndexOf("."); 29 | if (index < 0) { 30 | return ""; 31 | } 32 | return fileName.substring(index + 1).toLowerCase(Locale.US); 33 | } 34 | 35 | @Nullable 36 | static String getMimeType(@Nullable File file){ 37 | String suffix = getSuffix(file); 38 | String mimeType = null; 39 | if (!TextUtils.isEmpty(suffix)) { 40 | mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(suffix); 41 | } 42 | if (TextUtils.isEmpty(mimeType)) { 43 | mimeType = "file/*"; 44 | } 45 | return mimeType; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/utils/HttpsCompat.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.utils; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Build; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | 8 | import java.io.IOException; 9 | import java.net.InetAddress; 10 | import java.net.Socket; 11 | import java.net.UnknownHostException; 12 | import java.security.SecureRandom; 13 | import java.security.cert.X509Certificate; 14 | 15 | import javax.net.ssl.HostnameVerifier; 16 | import javax.net.ssl.HttpsURLConnection; 17 | import javax.net.ssl.SSLContext; 18 | import javax.net.ssl.SSLSession; 19 | import javax.net.ssl.SSLSocket; 20 | import javax.net.ssl.SSLSocketFactory; 21 | import javax.net.ssl.TrustManager; 22 | import javax.net.ssl.X509TrustManager; 23 | 24 | import okhttp3.OkHttpClient; 25 | 26 | /** 27 | * 描述:HTTPS兼容类 28 | * ------------------------- 29 | * --- 如果服务器为HTTP请求 --- 30 | * ------------------------- 31 | * android9.0以上不支持HTTP请求,默认情况下启用网络传输层安全协议 (TLS),需在AndroidManifest中添加一个XML文件: 32 | * 如果您的应用以 Android 9 或更高版本为目标平台,则默认情况下 isCleartextTrafficPermitted() 函数返回 false。 33 | * 如果您的应用需要为特定域名启用明文,您必须在应用的网络安全性配置中针对这些域名将 cleartextTrafficPermitted 显式设置为 true。 34 | * 具体解决方案共二步 35 | * 1、在清单文件AndroidManifest.xml的application标签里面设置networkSecurityConfig属性如下: 36 | * 37 | * 38 | * 40 | * 41 | * 42 | * 2、在资源文件夹res/xml下面创建network_security_config.xml如下: 43 | * 44 | * 45 | * 46 | * 47 | * 48 | * 49 | * 50 | * 51 | * 52 | * -------------------------- 53 | * --- 如果服务器为HTTPS请求 --- 54 | * -------------------------- 55 | * 第一种情况:服务器未配置SSL证书 56 | * 可以选择忽略证书的验证,这样请求就和HTTP一样,失去了安全保障,不建议使用 57 | * 第二种情况:服务器正确配置SSL证书 58 | * 1、服务器打开TLS1.1和TLS1.2 59 | * 2、在android4.4及以下版本默认不支持TLS1.2,需要开启对TLS1.2的支持 60 | * 61 | * @author Cuizhen 62 | * @date 2019/1/4 63 | */ 64 | public class HttpsCompat { 65 | 66 | public static void ignoreSSLForOkHttp(@NonNull OkHttpClient.Builder builder) { 67 | builder.hostnameVerifier(getIgnoreHostnameVerifier()) 68 | .sslSocketFactory(getIgnoreSSLSocketFactory()); 69 | } 70 | 71 | public static void enableTls12ForOkHttp(@NonNull OkHttpClient.Builder builder) { 72 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { 73 | SSLSocketFactory ssl = getEnableTls12SSLSocketFactory(); 74 | if (ssl != null) { 75 | builder.sslSocketFactory(ssl); 76 | } 77 | } 78 | } 79 | 80 | public static void ignoreSSLForHttpsURLConnection() { 81 | HttpsURLConnection.setDefaultHostnameVerifier(getIgnoreHostnameVerifier()); 82 | HttpsURLConnection.setDefaultSSLSocketFactory(getIgnoreSSLSocketFactory()); 83 | } 84 | 85 | public static void enableTls12ForHttpsURLConnection() { 86 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { 87 | SSLSocketFactory ssl = getEnableTls12SSLSocketFactory(); 88 | if (ssl != null) { 89 | HttpsURLConnection.setDefaultSSLSocketFactory(ssl); 90 | } 91 | } 92 | } 93 | 94 | /** 95 | * 获取开启TLS1.2的SSLSocketFactory 96 | * 建议在android4.4及以下版本调用 97 | */ 98 | @Nullable 99 | public static SSLSocketFactory getEnableTls12SSLSocketFactory() { 100 | try { 101 | SSLContext sslContext = SSLContext.getInstance("TLS"); 102 | sslContext.init(null, null, null); 103 | return new Tls12SocketFactory(sslContext.getSocketFactory()); 104 | } catch (Exception e) { 105 | e.printStackTrace(); 106 | } 107 | return null; 108 | } 109 | 110 | /** 111 | * 获取忽略证书的HostnameVerifier 112 | * 与{@link #getIgnoreSSLSocketFactory()}同时配置使用 113 | */ 114 | @NonNull 115 | public static HostnameVerifier getIgnoreHostnameVerifier() { 116 | return new HostnameVerifier() { 117 | @SuppressLint("BadHostnameVerifier") 118 | @Override 119 | public boolean verify(String s, SSLSession sslSession) { 120 | return true; 121 | } 122 | }; 123 | } 124 | 125 | /** 126 | * 获取忽略证书的SSLSocketFactory 127 | * 与{@link #getIgnoreHostnameVerifier()}同时配置使用 128 | */ 129 | @NonNull 130 | public static SSLSocketFactory getIgnoreSSLSocketFactory() { 131 | try { 132 | SSLContext sslContext = SSLContext.getInstance("SSL"); 133 | sslContext.init(null, getTrustManager(), new SecureRandom()); 134 | return sslContext.getSocketFactory(); 135 | } catch (Exception e) { 136 | throw new RuntimeException(e); 137 | } 138 | } 139 | 140 | private static TrustManager[] getTrustManager() { 141 | return new TrustManager[]{ 142 | new X509TrustManager() { 143 | @SuppressLint("TrustAllX509TrustManager") 144 | @Override 145 | public void checkClientTrusted(X509Certificate[] chain, String authType) { 146 | } 147 | 148 | @SuppressLint("TrustAllX509TrustManager") 149 | @Override 150 | public void checkServerTrusted(X509Certificate[] chain, String authType) { 151 | } 152 | 153 | @Override 154 | public X509Certificate[] getAcceptedIssuers() { 155 | return new X509Certificate[]{}; 156 | } 157 | } 158 | }; 159 | } 160 | 161 | private static class Tls12SocketFactory extends SSLSocketFactory { 162 | private static final String[] TLS_SUPPORT_VERSION = {"TLSv1.1", "TLSv1.2"}; 163 | 164 | private final SSLSocketFactory delegate; 165 | 166 | private Tls12SocketFactory(SSLSocketFactory base) { 167 | this.delegate = base; 168 | } 169 | 170 | @Override 171 | public String[] getDefaultCipherSuites() { 172 | return delegate.getDefaultCipherSuites(); 173 | } 174 | 175 | @Override 176 | public String[] getSupportedCipherSuites() { 177 | return delegate.getSupportedCipherSuites(); 178 | } 179 | 180 | @Override 181 | public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { 182 | return patch(delegate.createSocket(s, host, port, autoClose)); 183 | } 184 | 185 | @Override 186 | public Socket createSocket(String host, int port) throws IOException, UnknownHostException { 187 | return patch(delegate.createSocket(host, port)); 188 | } 189 | 190 | @Override 191 | public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { 192 | return patch(delegate.createSocket(host, port, localHost, localPort)); 193 | } 194 | 195 | @Override 196 | public Socket createSocket(InetAddress host, int port) throws IOException { 197 | return patch(delegate.createSocket(host, port)); 198 | } 199 | 200 | @Override 201 | public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { 202 | return patch(delegate.createSocket(address, port, localAddress, localPort)); 203 | } 204 | 205 | private Socket patch(Socket s) { 206 | if (s instanceof SSLSocket) { 207 | ((SSLSocket) s).setEnabledProtocols(TLS_SUPPORT_VERSION); 208 | } 209 | return s; 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/utils/JsonFormatUtils.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.utils; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | /** 6 | * 描述: 7 | * 8 | * @author Cuizhen 9 | * @date 2018/10/13 10 | */ 11 | public class JsonFormatUtils { 12 | /** 13 | * 对json字符串格式化输出 14 | */ 15 | @NonNull 16 | public static String format(String jsonStr) { 17 | if (null == jsonStr || "".equals(jsonStr)) { 18 | return ""; 19 | } 20 | StringBuilder sb = new StringBuilder(); 21 | char last = '\0'; 22 | char current = '\0'; 23 | int indent = 0; 24 | for (int i = 0; i < jsonStr.length(); i++) { 25 | last = current; 26 | current = jsonStr.charAt(i); 27 | switch (current) { 28 | case '{': 29 | case '[': 30 | sb.append(current); 31 | sb.append('\n'); 32 | indent++; 33 | addIndentBlank(sb, indent); 34 | break; 35 | case '}': 36 | case ']': 37 | sb.append('\n'); 38 | indent--; 39 | addIndentBlank(sb, indent); 40 | sb.append(current); 41 | break; 42 | case ',': 43 | sb.append(current); 44 | if (last != '\\') { 45 | sb.append('\n'); 46 | addIndentBlank(sb, indent); 47 | } 48 | break; 49 | default: 50 | sb.append(current); 51 | } 52 | } 53 | 54 | return sb.toString(); 55 | } 56 | 57 | /** 58 | * 添加space 59 | */ 60 | private static void addIndentBlank(@NonNull StringBuilder sb, int indent) { 61 | for (int i = 0; i < indent; i++) { 62 | sb.append('\t'); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/utils/JsonObjUtils.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.utils; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | 8 | /** 9 | * 描述: 10 | * 11 | * @author Cuizhen 12 | * @date 2018/10/8 13 | */ 14 | public class JsonObjUtils { 15 | 16 | private final JSONObject mJsonObject; 17 | 18 | private JsonObjUtils() { 19 | mJsonObject = new JSONObject(); 20 | } 21 | 22 | @NonNull 23 | public static JsonObjUtils create() { 24 | return new JsonObjUtils(); 25 | } 26 | 27 | @NonNull 28 | public JsonObjUtils add(@NonNull String key, int value) { 29 | try { 30 | mJsonObject.put(key, value); 31 | } catch (JSONException e) { 32 | e.printStackTrace(); 33 | } 34 | return this; 35 | } 36 | 37 | @NonNull 38 | public JsonObjUtils add(@NonNull String key, float value) { 39 | try { 40 | mJsonObject.put(key, value); 41 | } catch (JSONException e) { 42 | e.printStackTrace(); 43 | } 44 | return this; 45 | } 46 | 47 | @NonNull 48 | public JsonObjUtils add(@NonNull String key, double value) { 49 | try { 50 | mJsonObject.put(key, value); 51 | } catch (JSONException e) { 52 | e.printStackTrace(); 53 | } 54 | return this; 55 | } 56 | 57 | @NonNull 58 | public JsonObjUtils add(@NonNull String key, boolean value) { 59 | try { 60 | mJsonObject.put(key, value ? 1 : 0); 61 | } catch (JSONException e) { 62 | e.printStackTrace(); 63 | } 64 | return this; 65 | } 66 | 67 | @NonNull 68 | public JsonObjUtils add(@NonNull String key, String value) { 69 | try { 70 | mJsonObject.put(key, value); 71 | } catch (JSONException e) { 72 | e.printStackTrace(); 73 | } 74 | return this; 75 | } 76 | 77 | @NonNull 78 | public JSONObject get() { 79 | return mJsonObject; 80 | } 81 | 82 | @NonNull 83 | public String toJson() { 84 | return mJsonObject.toString(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/utils/NetUtils.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.utils; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | import per.goweii.rxhttp.core.RxHttp; 8 | 9 | /** 10 | * 描述:判断网络的辅助类 11 | * 12 | * @author Cuizhen 13 | * @date 2018/7/20-下午2:21 14 | */ 15 | public class NetUtils { 16 | 17 | /** 18 | * 判断是否有网络 19 | */ 20 | public static boolean isConnected() { 21 | ConnectivityManager connectivityManager = (ConnectivityManager) RxHttp.getAppContext().getSystemService(Context.CONNECTIVITY_SERVICE); 22 | if (connectivityManager != null) { 23 | NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); 24 | if (networkInfo != null) { 25 | return networkInfo.isAvailable(); 26 | } 27 | } 28 | return false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/utils/NonNullUtils.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.utils; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | 6 | /** 7 | * 判断一个对象是否非空 8 | * 9 | * @author Cuizhen 10 | * @date 2018/10/15 11 | */ 12 | public class NonNullUtils { 13 | 14 | public static boolean check(Map... maps) { 15 | if (maps == null || maps.length == 0) { 16 | return false; 17 | } 18 | for (Map map : maps) { 19 | if (check(map)) { 20 | return true; 21 | } 22 | } 23 | return false; 24 | } 25 | 26 | public static boolean check(Collection... collections) { 27 | if (collections == null || collections.length == 0) { 28 | return false; 29 | } 30 | for (Collection collection : collections) { 31 | if (check(collection)) { 32 | return true; 33 | } 34 | } 35 | return false; 36 | } 37 | 38 | public static boolean check(Object... objects) { 39 | if (objects != null && objects.length > 0) { 40 | for (Object o : objects) { 41 | if (check(o)) { 42 | return true; 43 | } 44 | } 45 | } 46 | return false; 47 | } 48 | 49 | public static boolean check(Map map) { 50 | return map != null && !map.isEmpty(); 51 | } 52 | 53 | public static boolean check(Collection collection) { 54 | return collection != null && !collection.isEmpty(); 55 | } 56 | 57 | public static boolean check(Object o) { 58 | if (o == null) { 59 | return false; 60 | } 61 | if (o instanceof Object[]) { 62 | Object[] objects = (Object[]) o; 63 | return check(objects); 64 | } 65 | if (o instanceof Collection) { 66 | Collection collection = (Collection) o; 67 | return check(collection); 68 | } else if (o instanceof Map) { 69 | Map map = (Map) o; 70 | return check(map); 71 | } else { 72 | return true; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rxhttp/src/main/java/per/goweii/rxhttp/request/utils/RequestBodyUtils.java: -------------------------------------------------------------------------------- 1 | package per.goweii.rxhttp.request.utils; 2 | 3 | import android.net.Uri; 4 | import android.support.annotation.NonNull; 5 | 6 | import java.io.File; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import okhttp3.MediaType; 11 | import okhttp3.RequestBody; 12 | 13 | /** 14 | * 描述: 15 | * 16 | * @author Cuizhen 17 | * @date 2018/9/4 18 | */ 19 | public class RequestBodyUtils { 20 | 21 | private RequestBodyUtils() { 22 | } 23 | 24 | @NonNull 25 | public static RequestBodyUtils.Builder builder() { 26 | return new RequestBodyUtils.Builder(); 27 | } 28 | 29 | @NonNull 30 | public static Map create(@NonNull String key, @NonNull T value) { 31 | return builder().add(key, value).build(); 32 | } 33 | 34 | public static class Builder { 35 | private final Map mParams; 36 | 37 | private Builder() { 38 | mParams = new HashMap<>(1); 39 | } 40 | 41 | /** 42 | * 添加参数 43 | * 根据传进来的对象来判断是String还是File类型的参数 44 | */ 45 | @NonNull 46 | public Builder add(@NonNull String key, @NonNull T value) { 47 | if (value instanceof String) { 48 | addString(key, (String) value); 49 | } else if (value instanceof File) { 50 | addFile(key, (File) value); 51 | } 52 | return this; 53 | } 54 | 55 | /** 56 | * 添加参数String 57 | */ 58 | @NonNull 59 | public Builder addString(@NonNull String key, @NonNull String value) { 60 | RequestBody body = RequestBody.create(MediaType.parse("text/plain"), value); 61 | mParams.put(key, body); 62 | return this; 63 | } 64 | 65 | /** 66 | * 添加参数File 67 | */ 68 | @NonNull 69 | public Builder addFile(@NonNull String key, @NonNull File value) { 70 | if (!value.exists()) { 71 | return this; 72 | } 73 | if (value.isDirectory()) { 74 | return this; 75 | } 76 | mParams.put(getParamsKey(key, value), getParamsValue(value)); 77 | return this; 78 | } 79 | 80 | /** 81 | * 添加参数File 82 | */ 83 | @NonNull 84 | public Builder addFile(@NonNull String key, @NonNull String filePath) { 85 | return addFile(key, new File(filePath)); 86 | } 87 | 88 | @NonNull 89 | public Builder addFile(@NonNull String key, @NonNull Uri uri) { 90 | String path = uri.getPath(); 91 | if (path == null || path.isEmpty()) { 92 | return this; 93 | } 94 | return addFile(key, path); 95 | } 96 | 97 | /** 98 | * 构建RequestBody 99 | */ 100 | @NonNull 101 | public Map build() { 102 | return mParams; 103 | } 104 | 105 | private String getParamsKey(@NonNull String key, @NonNull File file) { 106 | return key + "\"; filename=\"" + file.getName(); 107 | } 108 | 109 | private RequestBody getParamsValue(@NonNull File file) { 110 | return RequestBody.create(MediaType.parse(FileUtils.getMimeType(file)), file); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /rxhttp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RxHttp 3 | 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':rxhttp' 2 | --------------------------------------------------------------------------------