├── .gitignore ├── .idea ├── gradle.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE.txt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── xiaolei │ │ └── Example │ │ ├── MainActivity.java │ │ └── Net │ │ ├── DataBean.java │ │ ├── Net.java │ │ └── RetrofitBase.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.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 ├── cacheinterceptor ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── xiaolei │ │ └── OkhttpCacheInterceptor │ │ ├── CacheInterceptor.java │ │ ├── CacheType.java │ │ ├── Catch │ │ ├── CacheCallback.java │ │ ├── CacheManager.java │ │ └── DiskLruCache.java │ │ ├── Config │ │ └── Config.java │ │ ├── Header │ │ └── CacheHeaders.java │ │ └── Log │ │ └── Log.java │ └── res │ └── values │ └── strings.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Academic Free License (“AFL”) v. 3.0 2 | 3 | This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: 4 | 5 | Licensed under the Academic Free License version 3.0 6 | 7 | 1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: 8 | 9 | a) to reproduce the Original Work in copies, either alone or as part of a collective work; 10 | b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; 11 | c) to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor’s reserved rights and remedies, in this Academic Free License; 12 | d) to perform the Original Work publicly; and 13 | e) to display the Original Work publicly. 14 | 15 | 2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. 16 | 17 | 3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. 18 | 19 | 4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor’s trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. 20 | 21 | 5) External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). 22 | 23 | 6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. 24 | 25 | 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. 26 | 27 | 8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. 28 | 29 | 9) Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including “fair use” or “fair dealing”). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). 30 | 31 | 10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. 32 | 33 | 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. 34 | 35 | 12) Attorneys’ Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. 36 | 37 | 13) Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. 38 | 39 | 14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 40 | 41 | 15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. 42 | 43 | 16) Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OkhttpCacheInterceptor 2 | The OkHttpCacheInterceptor 一个OkHttp的网络缓存拦截器,可自定义场景对请求的数据进行缓存 3 | >前言:前段时间在开发APP的时候,经常出现由于用户设备环境的原因,拿不到从网络端获取的数据,所以在APP端展现的结果总是一个空白的框,这种情况对于用户体验来讲是极其糟糕的,所以,苦思冥想决定对OKHTTP下手(因为我在项目中使用的网络请求框架就是OKHTTP),则 写了这么一个网络数据缓存拦截器。 4 | 5 | OK,那么我们决定开始写了,我先说一下思路: 6 | ##思路篇 7 | 既然要写的是网络数据缓存拦截器,主要是利用了OKHTTP强大的拦截器功能,那么我们应该对哪些数据进行缓存呢,或者在哪些情况下启用数据进行缓存机制呢? 8 | 9 | - **第一** :支持POST请求,因为官方已经提供了一个缓存拦截器,但是有一个缺点,就是只能对GET请求的数据进行缓存,对POST则不支持。 10 | - **第二** :网络正常的时候,则是去网络端取数据,如果网络异常,比如TimeOutException UnKnowHostException 诸如此类的问题,那么我们就需要去缓存取出数据返回。 11 | - **第三** :如果从缓存中取出的数据是空的,那么我们还是需要让这次请求走剩下的正常的流程。 12 | - **第四** :调用者必须对缓存机制完全掌控,可以根据自己的业务需求选择性的对数据决定是否进行缓存。 13 | - **第五** :使用必须简单,这是最最最最重要的一点。 14 | 15 | 好,我们上面罗列了五点是我们的大概思路,现在来说一下代码部分: 16 | 17 | ##代码篇 18 | 19 | - **缓存框架** :我这里使用的缓存框架是**DiskLruCache** **[https://github.com/JakeWharton/DiskLruCache](https://github.com/JakeWharton/DiskLruCache "缓存框架")** 这个缓存框架可以存储到本地,也经过谷歌认可,这也是选择这个框架的主要原因。我这里也对缓存框架进行封装了一个CacheManager类: 20 | 21 | ```java 22 | import android.content.Context; 23 | import android.content.pm.PackageInfo; 24 | import android.content.pm.PackageManager; 25 | 26 | import com.xiaolei.OkhttpCacheInterceptor.Log.Log; 27 | import java.io.ByteArrayOutputStream; 28 | import java.io.File; 29 | import java.io.FileInputStream; 30 | import java.io.IOException; 31 | import java.io.OutputStream; 32 | import java.io.UnsupportedEncodingException; 33 | import java.security.MessageDigest; 34 | import java.security.NoSuchAlgorithmException; 35 | 36 | 37 | /** 38 | * Created by xiaolei on 2017/5/17. 39 | */ 40 | 41 | public class CacheManager 42 | { 43 | public static final String TAG = "CacheManager"; 44 | 45 | //max cache size 10mb 46 | private static final long DISK_CACHE_SIZE = 1024 * 1024 * 10; 47 | 48 | private static final int DISK_CACHE_INDEX = 0; 49 | 50 | private static final String CACHE_DIR = "responses"; 51 | 52 | private DiskLruCache mDiskLruCache; 53 | 54 | private volatile static CacheManager mCacheManager; 55 | 56 | public static CacheManager getInstance(Context context) 57 | { 58 | if (mCacheManager == null) 59 | { 60 | synchronized (CacheManager.class) 61 | { 62 | if (mCacheManager == null) 63 | { 64 | mCacheManager = new CacheManager(context); 65 | } 66 | } 67 | } 68 | return mCacheManager; 69 | } 70 | 71 | private CacheManager(Context context) 72 | { 73 | File diskCacheDir = getDiskCacheDir(context, CACHE_DIR); 74 | if (!diskCacheDir.exists()) 75 | { 76 | boolean b = diskCacheDir.mkdirs(); 77 | Log.d(TAG, "!diskCacheDir.exists() --- diskCacheDir.mkdirs()=" + b); 78 | } 79 | if (diskCacheDir.getUsableSpace() > DISK_CACHE_SIZE) 80 | { 81 | try 82 | { 83 | mDiskLruCache = DiskLruCache.open(diskCacheDir, 84 | getAppVersion(context), 1/*一个key对应多少个文件*/, DISK_CACHE_SIZE); 85 | Log.d(TAG, "mDiskLruCache created"); 86 | } catch (IOException e) 87 | { 88 | e.printStackTrace(); 89 | } 90 | } 91 | } 92 | 93 | /** 94 | * 同步设置缓存 95 | */ 96 | public void putCache(String key, String value) 97 | { 98 | if (mDiskLruCache == null) return; 99 | OutputStream os = null; 100 | try 101 | { 102 | DiskLruCache.Editor editor = mDiskLruCache.edit(encryptMD5(key)); 103 | os = editor.newOutputStream(DISK_CACHE_INDEX); 104 | os.write(value.getBytes()); 105 | os.flush(); 106 | editor.commit(); 107 | mDiskLruCache.flush(); 108 | } catch (IOException e) 109 | { 110 | e.printStackTrace(); 111 | } finally 112 | { 113 | if (os != null) 114 | { 115 | try 116 | { 117 | os.close(); 118 | } catch (IOException e) 119 | { 120 | e.printStackTrace(); 121 | } 122 | } 123 | } 124 | } 125 | 126 | /** 127 | * 异步设置缓存 128 | */ 129 | public void setCache(final String key, final String value) 130 | { 131 | new Thread() 132 | { 133 | @Override 134 | public void run() 135 | { 136 | putCache(key, value); 137 | } 138 | }.start(); 139 | } 140 | 141 | /** 142 | * 同步获取缓存 143 | */ 144 | public String getCache(String key) 145 | { 146 | if (mDiskLruCache == null) 147 | { 148 | return null; 149 | } 150 | FileInputStream fis = null; 151 | ByteArrayOutputStream bos = null; 152 | try 153 | { 154 | DiskLruCache.Snapshot snapshot = mDiskLruCache.get(encryptMD5(key)); 155 | if (snapshot != null) 156 | { 157 | fis = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX); 158 | bos = new ByteArrayOutputStream(); 159 | byte[] buf = new byte[1024]; 160 | int len; 161 | while ((len = fis.read(buf)) != -1) 162 | { 163 | bos.write(buf, 0, len); 164 | } 165 | byte[] data = bos.toByteArray(); 166 | return new String(data); 167 | } 168 | } catch (IOException e) 169 | { 170 | e.printStackTrace(); 171 | } finally 172 | { 173 | if (fis != null) 174 | { 175 | try 176 | { 177 | fis.close(); 178 | } catch (IOException e) 179 | { 180 | e.printStackTrace(); 181 | } 182 | } 183 | if (bos != null) 184 | { 185 | try 186 | { 187 | bos.close(); 188 | } catch (IOException e) 189 | { 190 | e.printStackTrace(); 191 | } 192 | } 193 | } 194 | return null; 195 | } 196 | 197 | /** 198 | * 异步获取缓存 199 | */ 200 | public void getCache(final String key, final CacheCallback callback) 201 | { 202 | new Thread() 203 | { 204 | @Override 205 | public void run() 206 | { 207 | String cache = getCache(key); 208 | callback.onGetCache(cache); 209 | } 210 | }.start(); 211 | } 212 | 213 | /** 214 | * 移除缓存 215 | */ 216 | public boolean removeCache(String key) 217 | { 218 | if (mDiskLruCache != null) 219 | { 220 | try 221 | { 222 | return mDiskLruCache.remove(encryptMD5(key)); 223 | } catch (IOException e) 224 | { 225 | e.printStackTrace(); 226 | } 227 | } 228 | return false; 229 | } 230 | 231 | /** 232 | * 获取缓存目录 233 | */ 234 | private File getDiskCacheDir(Context context, String uniqueName) 235 | { 236 | String cachePath = context.getCacheDir().getPath(); 237 | return new File(cachePath + File.separator + uniqueName); 238 | } 239 | 240 | /** 241 | * 对字符串进行MD5编码 242 | */ 243 | public static String encryptMD5(String string) 244 | { 245 | try 246 | { 247 | byte[] hash = MessageDigest.getInstance("MD5").digest( 248 | string.getBytes("UTF-8")); 249 | StringBuilder hex = new StringBuilder(hash.length * 2); 250 | for (byte b : hash) 251 | { 252 | if ((b & 0xFF) < 0x10) 253 | { 254 | hex.append("0"); 255 | } 256 | hex.append(Integer.toHexString(b & 0xFF)); 257 | } 258 | return hex.toString(); 259 | } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) 260 | { 261 | e.printStackTrace(); 262 | } 263 | return string; 264 | } 265 | 266 | /** 267 | * 获取APP版本号 268 | */ 269 | private int getAppVersion(Context context) 270 | { 271 | PackageManager pm = context.getPackageManager(); 272 | try 273 | { 274 | PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); 275 | return pi == null ? 0 : pi.versionCode; 276 | } catch (PackageManager.NameNotFoundException e) 277 | { 278 | e.printStackTrace(); 279 | } 280 | return 0; 281 | } 282 | } 283 | ``` 284 | - **缓存CacheInterceptor拦截器** :利用OkHttp的Interceptor拦截器机制,智能判断缓存场景,以及网络情况,对不同的场景进行处理。 285 | 286 | ```java 287 | import android.content.Context; 288 | 289 | import com.xiaolei.OkhttpCacheInterceptor.Catch.CacheManager; 290 | import com.xiaolei.OkhttpCacheInterceptor.Log.Log; 291 | 292 | import java.io.IOException; 293 | 294 | import okhttp3.FormBody; 295 | import okhttp3.Interceptor; 296 | import okhttp3.Protocol; 297 | import okhttp3.Request; 298 | import okhttp3.Response; 299 | import okhttp3.ResponseBody; 300 | 301 | /** 302 | * 字符串的缓存类 303 | * Created by xiaolei on 2017/12/9. 304 | */ 305 | public class CacheInterceptor implements Interceptor 306 | { 307 | private Context context; 308 | 309 | public void setContext(Context context) 310 | { 311 | this.context = context; 312 | } 313 | 314 | public CacheInterceptor(Context context) 315 | { 316 | this.context = context; 317 | } 318 | 319 | @Override 320 | public Response intercept(Chain chain) throws IOException 321 | { 322 | Request request = chain.request(); 323 | String cacheHead = request.header("cache"); 324 | String cache_control = request.header("Cache-Control"); 325 | 326 | if ("true".equals(cacheHead) || // 意思是要缓存 327 | (cache_control != null && !cache_control.isEmpty())) // 这里还支持WEB端协议的缓存头 328 | { 329 | long oldnow = System.currentTimeMillis(); 330 | String url = request.url().url().toString(); 331 | String responStr = null; 332 | String reqBodyStr = getPostParams(request); 333 | try 334 | { 335 | Response response = chain.proceed(request); 336 | if (response.isSuccessful()) // 只有在网络请求返回成功之后,才进行缓存处理,否则,404存进缓存,岂不笑话 337 | { 338 | ResponseBody responseBody = response.body(); 339 | if (responseBody != null) 340 | { 341 | responStr = responseBody.string(); 342 | if (responStr == null) 343 | { 344 | responStr = ""; 345 | } 346 | CacheManager.getInstance(context).setCache(CacheManager.encryptMD5(url + reqBodyStr), responStr);//存缓存,以链接+参数进行MD5编码为KEY存 347 | Log.i("HttpRetrofit", "--> Push Cache:" + url + " :Success"); 348 | } 349 | return getOnlineResponse(response, responStr); 350 | } else 351 | { 352 | return chain.proceed(request); 353 | } 354 | } catch (Exception e) 355 | { 356 | Response response = getCacheResponse(request, oldnow); // 发生异常了,我这里就开始去缓存,但是有可能没有缓存,那么久需要丢给下一轮处理了 357 | if (response == null) 358 | { 359 | return chain.proceed(request);//丢给下一轮处理 360 | } else 361 | { 362 | return response; 363 | } 364 | } 365 | } else 366 | { 367 | return chain.proceed(request); 368 | } 369 | } 370 | 371 | private Response getCacheResponse(Request request, long oldNow) 372 | { 373 | Log.i("HttpRetrofit", "--> Try to Get Cache --------"); 374 | String url = request.url().url().toString(); 375 | String params = getPostParams(request); 376 | String cacheStr = CacheManager.getInstance(context).getCache(CacheManager.encryptMD5(url + params));//取缓存,以链接+参数进行MD5编码为KEY取 377 | if (cacheStr == null) 378 | { 379 | Log.i("HttpRetrofit", "<-- Get Cache Failure ---------"); 380 | return null; 381 | } 382 | Response response = new Response.Builder() 383 | .code(200) 384 | .body(ResponseBody.create(null, cacheStr)) 385 | .request(request) 386 | .message("OK") 387 | .protocol(Protocol.HTTP_1_0) 388 | .build(); 389 | long useTime = System.currentTimeMillis() - oldNow; 390 | Log.i("HttpRetrofit", "<-- Get Cache: " + response.code() + " " + response.message() + " " + url + " (" + useTime + "ms)"); 391 | Log.i("HttpRetrofit", cacheStr + ""); 392 | return response; 393 | } 394 | 395 | private Response getOnlineResponse(Response response, String body) 396 | { 397 | ResponseBody responseBody = response.body(); 398 | return new Response.Builder() 399 | .code(response.code()) 400 | .body(ResponseBody.create(responseBody == null ? null : responseBody.contentType(), body)) 401 | .request(response.request()) 402 | .message(response.message()) 403 | .protocol(response.protocol()) 404 | .build(); 405 | } 406 | 407 | /** 408 | * 获取在Post方式下。向服务器发送的参数 409 | * 410 | * @param request 411 | * @return 412 | */ 413 | private String getPostParams(Request request) 414 | { 415 | String reqBodyStr = ""; 416 | String method = request.method(); 417 | if ("POST".equals(method)) // 如果是Post,则尽可能解析每个参数 418 | { 419 | StringBuilder sb = new StringBuilder(); 420 | if (request.body() instanceof FormBody) 421 | { 422 | FormBody body = (FormBody) request.body(); 423 | if (body != null) 424 | { 425 | for (int i = 0; i < body.size(); i++) 426 | { 427 | sb.append(body.encodedName(i)).append("=").append(body.encodedValue(i)).append(","); 428 | } 429 | sb.delete(sb.length() - 1, sb.length()); 430 | } 431 | reqBodyStr = sb.toString(); 432 | sb.delete(0, sb.length()); 433 | } 434 | } 435 | return reqBodyStr; 436 | } 437 | 438 | } 439 | ``` 440 | 441 | 以上是主体思路,以及主要实现代码,现在来说一下使用方式 442 | ##使用方式: 443 | 444 | gradle使用: 445 | ```grandle 446 | compile 'com.xiaolei:OkhttpCacheInterceptor:1.0.0' 447 | ``` 448 | 由于是刚刚提交到Jcenter,可能会出现拉不下来的情况(暂时还未过审核),着急的读者可以再在你的**Project:build.gradle**里的**repositories**里新增我maven的链接: 449 | 450 | ```grandle 451 | allprojects { 452 | repositories { 453 | maven{url 'https://dl.bintray.com/kavipyouxiang/maven'} 454 | } 455 | } 456 | ``` 457 | 我们新建一个项目,项目截图是这样的: 458 | ![项目截图](http://upload-images.jianshu.io/upload_images/1014308-c11944bff11291d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 459 | 460 | demo很简单,一个主页面,一个Bean,一个Retrofit,一个网络请求接口 461 | 462 | 注意,因为是网络,缓存,有关,所以,毫无疑问我们要在manifest里面添加网络请求权限,文件读写权限: 463 | 464 | ```xml 465 | 466 | 467 | 468 | ``` 469 | 470 | 使用的时候,你只需要为你的OKHttpClient添加一个Interceptor: 471 | 472 | ```java 473 | client = new OkHttpClient.Builder() 474 | .addInterceptor(new CacheInterceptor(context))//添加缓存拦截器,添加缓存的支持 475 | .retryOnConnectionFailure(true)//失败重连 476 | .connectTimeout(30, TimeUnit.SECONDS)//网络请求超时时间单位为秒 477 | .build(); 478 | ``` 479 | 480 | 如果你想哪个接口的数据缓存,那么久为你的网络接口,添加一个请求头**`CacheHeaders.java`**这个类里包含了所有的情况,一般情况下只需要`CacheHeaders.NORMAL`就可以了 481 | ```java 482 | public interface Net 483 | { 484 | @Headers(CacheHeaders.NORMAL) // 这里是关键 485 | @FormUrlEncoded 486 | @POST("geocoding") 487 | public Call getIndex(@Field("a") String a); 488 | } 489 | 490 | ``` 491 | 492 | 业务代码: 493 | ```java 494 | Net net = retrofitBase.getRetrofit().create(Net.class); 495 | Call call = net.getIndex("苏州市"); 496 | call.enqueue(new Callback() 497 | { 498 | @Override 499 | public void onResponse(Call call, Response response) 500 | { 501 | DataBean data = response.body(); 502 | Date date = new Date(); 503 | textview.setText(date.getMinutes() + " " + date.getSeconds() + ":\n" + data + ""); 504 | } 505 | 506 | @Override 507 | public void onFailure(Call call, Throwable t) 508 | { 509 | textview.setText("请求失败!"); 510 | } 511 | }); 512 | ``` 513 | 514 | 我们这里对网络请求,成功了,则在界面上输出文字,加上当前时间,网络失败,则输出一个请求失败。 515 | 516 | 大概代码就是这样子的,详细代码,文章末尾将贴出demo地址 517 | 518 | ##看效果:演示图 519 | 520 | ![演示图](http://upload-images.jianshu.io/upload_images/1014308-f4a982a2d9e1ef75.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 521 | 522 | 这里演示了,从网络正常,到网络不正常,再恢复到正常的情况。 523 | 524 | ##结尾 525 | 以上篇章就是整个从思路,到代码,再到效果图的流程,这里贴一下DEMO的地址,喜欢的可以点个Start 526 | 527 | [Demo地址:https://github.com/xiaolei123/OkhttpCacheInterceptor](https://github.com/xiaolei123/OkhttpCacheInterceptor) 528 | 529 | 530 | 531 | 532 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | defaultConfig { 6 | applicationId "com.xiaolei.retrofitccheinterceptor" 7 | minSdkVersion 15 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | } 16 | } 17 | } 18 | 19 | dependencies { 20 | implementation fileTree(include: ['*.jar'], dir: 'libs') 21 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 22 | compile 'com.squareup.okhttp3:okhttp:3.9.1' 23 | implementation project(':cacheinterceptor') 24 | 25 | compile 'com.squareup.retrofit2:retrofit:2.3.0' 26 | compile 'com.squareup.retrofit2:converter-gson:2.0.2' 27 | compile 'com.google.code.gson:gson:2.8.0' 28 | compile 'com.squareup.okhttp3:logging-interceptor:3.4.1' 29 | } 30 | -------------------------------------------------------------------------------- /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/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaolei/Example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.xiaolei.Example; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | import android.widget.TextView; 8 | 9 | import com.xiaolei.Example.Net.DataBean; 10 | import com.xiaolei.Example.Net.Net; 11 | import com.xiaolei.Example.Net.RetrofitBase; 12 | 13 | import java.util.Date; 14 | 15 | import retrofit2.Call; 16 | import retrofit2.Callback; 17 | import retrofit2.Response; 18 | 19 | public class MainActivity extends Activity 20 | { 21 | TextView textview; 22 | Button button; 23 | RetrofitBase retrofitBase; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) 27 | { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_main); 30 | 31 | textview = findViewById(R.id.textview); 32 | button = findViewById(R.id.button); 33 | 34 | retrofitBase = new RetrofitBase(this); 35 | 36 | button.setOnClickListener(new View.OnClickListener() 37 | { 38 | @Override 39 | public void onClick(View v) 40 | { 41 | doSomething(); 42 | } 43 | }); 44 | } 45 | 46 | private void doSomething() 47 | { 48 | Net net = retrofitBase.getRetrofit().create(Net.class); 49 | Call call = net.getIndex("苏州市"); 50 | call.enqueue(new Callback() 51 | { 52 | @Override 53 | public void onResponse(Call call, Response response) 54 | { 55 | DataBean data = response.body(); 56 | Date date = new Date(); 57 | textview.setText(date.getMinutes() + " " + date.getSeconds() + ":\n" + data + ""); 58 | } 59 | 60 | @Override 61 | public void onFailure(Call call, Throwable t) 62 | { 63 | textview.setText("请求失败!"); 64 | } 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaolei/Example/Net/DataBean.java: -------------------------------------------------------------------------------- 1 | package com.xiaolei.Example.Net; 2 | 3 | /** 4 | * Created by xiaolei on 2017/12/8. 5 | */ 6 | 7 | public class DataBean 8 | { 9 | 10 | /** 11 | * lon : 120.58531 12 | * level : 2 13 | * address : 14 | * cityName : 15 | * alevel : 4 16 | * lat : 31.29888 17 | */ 18 | 19 | private String lon; 20 | private String level; 21 | private String address; 22 | private String cityName; 23 | private String alevel; 24 | private String lat; 25 | 26 | public String getLon() 27 | { 28 | return lon; 29 | } 30 | 31 | public void setLon(String lon) 32 | { 33 | this.lon = lon; 34 | } 35 | 36 | public String getLevel() 37 | { 38 | return level; 39 | } 40 | 41 | public void setLevel(String level) 42 | { 43 | this.level = level; 44 | } 45 | 46 | public String getAddress() 47 | { 48 | return address; 49 | } 50 | 51 | public void setAddress(String address) 52 | { 53 | this.address = address; 54 | } 55 | 56 | public String getCityName() 57 | { 58 | return cityName; 59 | } 60 | 61 | public void setCityName(String cityName) 62 | { 63 | this.cityName = cityName; 64 | } 65 | 66 | public String getAlevel() 67 | { 68 | return alevel; 69 | } 70 | 71 | public void setAlevel(String alevel) 72 | { 73 | this.alevel = alevel; 74 | } 75 | 76 | public String getLat() 77 | { 78 | return lat; 79 | } 80 | 81 | public void setLat(String lat) 82 | { 83 | this.lat = lat; 84 | } 85 | 86 | @Override 87 | public String toString() 88 | { 89 | return " lon=" + lon + 90 | " level=" + level + 91 | " address=" + address + 92 | " cityName=" + cityName + 93 | " alevel=" + alevel + 94 | " lat=" + lat; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaolei/Example/Net/Net.java: -------------------------------------------------------------------------------- 1 | package com.xiaolei.Example.Net; 2 | 3 | 4 | import com.xiaolei.OkhttpCacheInterceptor.Header.CacheHeaders; 5 | 6 | import retrofit2.Call; 7 | import retrofit2.http.Field; 8 | import retrofit2.http.FormUrlEncoded; 9 | import retrofit2.http.Headers; 10 | import retrofit2.http.POST; 11 | 12 | /** 13 | * Created by xiaolei on 2017/7/9. 14 | */ 15 | 16 | public interface Net 17 | { 18 | @Headers(CacheHeaders.NORMAL) 19 | @FormUrlEncoded 20 | @POST("geocoding") 21 | public Call getIndex(@Field("a") String a); 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/xiaolei/Example/Net/RetrofitBase.java: -------------------------------------------------------------------------------- 1 | package com.xiaolei.Example.Net; 2 | 3 | import android.content.Context; 4 | 5 | import com.xiaolei.OkhttpCacheInterceptor.CacheInterceptor; 6 | import com.xiaolei.OkhttpCacheInterceptor.Log.Log; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import okhttp3.OkHttpClient; 11 | import okhttp3.logging.HttpLoggingInterceptor; 12 | import retrofit2.Retrofit; 13 | import retrofit2.converter.gson.GsonConverterFactory; 14 | 15 | /** 16 | * Created by xiaolei on 2017/12/9. 17 | */ 18 | 19 | public class RetrofitBase 20 | { 21 | private Retrofit retrofit; 22 | private OkHttpClient client; 23 | private HttpLoggingInterceptor loggingInterceptor; 24 | 25 | public RetrofitBase(Context context) 26 | { 27 | loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() 28 | { 29 | @Override 30 | public void log(String message) 31 | { 32 | Log.d("HttpRetrofit", message + ""); 33 | } 34 | }); 35 | loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 36 | client = new OkHttpClient.Builder() 37 | .addInterceptor(new CacheInterceptor(context))//添加缓存拦截器,添加缓存的支持 38 | .addInterceptor(loggingInterceptor) 39 | .retryOnConnectionFailure(true)//失败重连 40 | .connectTimeout(30, TimeUnit.SECONDS)//网络请求超时时间单位为秒 41 | .build(); 42 | retrofit = new Retrofit.Builder() //01:获取Retrofit对象 43 | .baseUrl("http://gc.ditu.aliyun.com/") //02采用链式结构绑定Base url 44 | .addConverterFactory(GsonConverterFactory.create())//再将转换成bean 45 | .client(client) 46 | .build();//03执行操作 47 | } 48 | 49 | public Retrofit getRetrofit() 50 | { 51 | return retrofit; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /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 | 7 | 8 | 17 | 18 |