├── .gitignore ├── HttpWrapper.iml ├── HttpWrapper ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── jclick │ │ └── httpwrapper │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── jclick │ │ │ └── httpwrapper │ │ │ ├── cache │ │ │ ├── DiskLruCache.java │ │ │ ├── FileNameGenerator.java │ │ │ ├── IDiskCache.java │ │ │ ├── LruDiskCache.java │ │ │ ├── Md5FileNameGenerator.java │ │ │ ├── StrictLineReader.java │ │ │ └── Util.java │ │ │ ├── callback │ │ │ ├── ByteCallback.java │ │ │ ├── Callback.java │ │ │ ├── FileCallback.java │ │ │ ├── ObjectCallback.java │ │ │ ├── ResponseData.java │ │ │ └── StringCallback.java │ │ │ ├── interceptor │ │ │ ├── HandlerInterceptor.java │ │ │ └── LoggerInterceptor.java │ │ │ ├── request │ │ │ ├── HttpRequestAgent.java │ │ │ ├── RequestBuilder.java │ │ │ ├── RequestConfig.java │ │ │ └── RequestParams.java │ │ │ └── utils │ │ │ ├── Charsets.java │ │ │ ├── IOUtils.java │ │ │ ├── StorageUtils.java │ │ │ └── WrapperUtils.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── cn │ └── jclick │ └── httpwrapper │ └── ExampleUnitTest.java ├── README.md ├── build.gradle ├── demo ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── jclick │ │ └── demo │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── jclick │ │ │ └── demo │ │ │ ├── DemoResultBean.java │ │ │ ├── Location.java │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── content_main.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── cn │ └── jclick │ └── demo │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Eclipse ### 4 | *.pydevproject 5 | .metadata 6 | .gradle 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .settings/ 15 | .loadpath 16 | 17 | # Eclipse Core 18 | #.project 19 | 20 | # External tool builders 21 | .externalToolBuilders/ 22 | 23 | # Locally stored "Eclipse launch configurations" 24 | *.launch 25 | 26 | # CDT-specific 27 | .cproject 28 | 29 | # JDT-specific (Eclipse Java Development Tools) 30 | .classpath 31 | 32 | # PDT-specific 33 | .buildpath 34 | 35 | # sbteclipse plugin 36 | .target 37 | 38 | # TeXlipse plugin 39 | .texlipse 40 | 41 | 42 | ### Intellij ### 43 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 44 | 45 | *.iml 46 | 47 | ## Directory-based project format: 48 | .idea/ 49 | # if you remove the above rule, at least ignore the following: 50 | 51 | # User-specific stuff: 52 | # .idea/workspace.xml 53 | # .idea/tasks.xml 54 | # .idea/dictionaries 55 | 56 | # Sensitive or high-churn files: 57 | # .idea/dataSources.ids 58 | # .idea/dataSources.xml 59 | # .idea/sqlDataSources.xml 60 | # .idea/dynamic.xml 61 | # .idea/uiDesigner.xml 62 | 63 | # Gradle: 64 | # .idea/gradle.xml 65 | # .idea/libraries 66 | 67 | # Mongo Explorer plugin: 68 | # .idea/mongoSettings.xml 69 | 70 | ## File-based project format: 71 | *.ipr 72 | *.iws 73 | 74 | ## Plugin-specific files: 75 | 76 | # IntelliJ 77 | /out/ 78 | 79 | # mpeltonen/sbt-idea plugin 80 | .idea_modules/ 81 | 82 | # JIRA plugin 83 | atlassian-ide-plugin.xml 84 | 85 | # Crashlytics plugin (for Android Studio and IntelliJ) 86 | com_crashlytics_export_strings.xml 87 | crashlytics.properties 88 | crashlytics-build.properties 89 | 90 | 91 | ### Android ### 92 | # Built application files 93 | *.apk 94 | *.ap_ 95 | 96 | # Files for the Dalvik VM 97 | *.dex 98 | 99 | # Java class files 100 | *.class 101 | 102 | # Generated files 103 | bin/ 104 | gen/ 105 | 106 | # Gradle files 107 | .gradle/ 108 | build/ 109 | /*/build/ 110 | 111 | # Local configuration file (sdk path, etc) 112 | local.properties 113 | 114 | # Proguard folder generated by Eclipse 115 | proguard/ 116 | 117 | # Log Files 118 | *.log 119 | 120 | ### Android Patch ### 121 | gen-external-apklibs 122 | 123 | 124 | ### Windows ### 125 | # Windows image file caches 126 | Thumbs.db 127 | ehthumbs.db 128 | 129 | # Folder config file 130 | Desktop.ini 131 | 132 | # Recycle Bin used on file shares 133 | $RECYCLE.BIN/ 134 | 135 | ### Mac OS ### 136 | .DS_Store 137 | .DS_Store? 138 | ._* 139 | .Spotlight-V100 140 | .Trashes -------------------------------------------------------------------------------- /HttpWrapper.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /HttpWrapper/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Eclipse ### 4 | *.pydevproject 5 | .metadata 6 | .gradle 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .settings/ 15 | .loadpath 16 | 17 | # Eclipse Core 18 | #.project 19 | 20 | # External tool builders 21 | .externalToolBuilders/ 22 | 23 | # Locally stored "Eclipse launch configurations" 24 | *.launch 25 | 26 | # CDT-specific 27 | .cproject 28 | 29 | # JDT-specific (Eclipse Java Development Tools) 30 | .classpath 31 | 32 | # PDT-specific 33 | .buildpath 34 | 35 | # sbteclipse plugin 36 | .target 37 | 38 | # TeXlipse plugin 39 | .texlipse 40 | 41 | 42 | ### Intellij ### 43 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 44 | 45 | *.iml 46 | 47 | ## Directory-based project format: 48 | .idea/ 49 | # if you remove the above rule, at least ignore the following: 50 | 51 | # User-specific stuff: 52 | # .idea/workspace.xml 53 | # .idea/tasks.xml 54 | # .idea/dictionaries 55 | 56 | # Sensitive or high-churn files: 57 | # .idea/dataSources.ids 58 | # .idea/dataSources.xml 59 | # .idea/sqlDataSources.xml 60 | # .idea/dynamic.xml 61 | # .idea/uiDesigner.xml 62 | 63 | # Gradle: 64 | # .idea/gradle.xml 65 | # .idea/libraries 66 | 67 | # Mongo Explorer plugin: 68 | # .idea/mongoSettings.xml 69 | 70 | ## File-based project format: 71 | *.ipr 72 | *.iws 73 | 74 | ## Plugin-specific files: 75 | 76 | # IntelliJ 77 | /out/ 78 | 79 | # mpeltonen/sbt-idea plugin 80 | .idea_modules/ 81 | 82 | # JIRA plugin 83 | atlassian-ide-plugin.xml 84 | 85 | # Crashlytics plugin (for Android Studio and IntelliJ) 86 | com_crashlytics_export_strings.xml 87 | crashlytics.properties 88 | crashlytics-build.properties 89 | 90 | 91 | ### Android ### 92 | # Built application files 93 | *.apk 94 | *.ap_ 95 | 96 | # Files for the Dalvik VM 97 | *.dex 98 | 99 | # Java class files 100 | *.class 101 | 102 | # Generated files 103 | bin/ 104 | gen/ 105 | 106 | # Gradle files 107 | .gradle/ 108 | build/ 109 | /*/build/ 110 | 111 | # Local configuration file (sdk path, etc) 112 | local.properties 113 | 114 | # Proguard folder generated by Eclipse 115 | proguard/ 116 | 117 | # Log Files 118 | *.log 119 | 120 | ### Android Patch ### 121 | gen-external-apklibs 122 | 123 | 124 | ### Windows ### 125 | # Windows image file caches 126 | Thumbs.db 127 | ehthumbs.db 128 | 129 | # Folder config file 130 | Desktop.ini 131 | 132 | # Recycle Bin used on file shares 133 | $RECYCLE.BIN/ 134 | 135 | ### Mac OS ### 136 | .DS_Store 137 | .DS_Store? 138 | ._* 139 | .Spotlight-V100 140 | .Trashes -------------------------------------------------------------------------------- /HttpWrapper/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 9 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | compile 'com.alibaba:fastjson:1.2.6' 24 | compile 'com.squareup.okhttp3:okhttp:3.0.1' 25 | } 26 | -------------------------------------------------------------------------------- /HttpWrapper/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/XuYingjian/software/SDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /HttpWrapper/src/androidTest/java/cn/jclick/httpwrapper/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /HttpWrapper/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/cache/DiskLruCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.jclick.httpwrapper.cache; 18 | 19 | import java.io.BufferedWriter; 20 | import java.io.Closeable; 21 | import java.io.EOFException; 22 | import java.io.File; 23 | import java.io.FileInputStream; 24 | import java.io.FileNotFoundException; 25 | import java.io.FileOutputStream; 26 | import java.io.FilterOutputStream; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.io.InputStreamReader; 30 | import java.io.OutputStream; 31 | import java.io.OutputStreamWriter; 32 | import java.io.Writer; 33 | import java.util.ArrayList; 34 | import java.util.Iterator; 35 | import java.util.LinkedHashMap; 36 | import java.util.Map; 37 | import java.util.concurrent.Callable; 38 | import java.util.concurrent.LinkedBlockingQueue; 39 | import java.util.concurrent.ThreadPoolExecutor; 40 | import java.util.concurrent.TimeUnit; 41 | import java.util.regex.Matcher; 42 | import java.util.regex.Pattern; 43 | 44 | /** 45 | * A cache that uses a bounded amount of space on a filesystem. Each cache 46 | * entry has a string key and a fixed number of values. Each key must match 47 | * the regex [a-z0-9_-]{1,120}. Values are byte sequences, 48 | * accessible as streams or files. Each value must be between {@code 0} and 49 | * {@code Integer.MAX_VALUE} bytes in length. 50 | * 51 | *

The cache stores its data in a directory on the filesystem. This 52 | * directory must be exclusive to the cache; the cache may delete or overwrite 53 | * files from its directory. It is an error for multiple processes to use the 54 | * same cache directory at the same time. 55 | * 56 | *

This cache limits the number of bytes that it will store on the 57 | * filesystem. When the number of stored bytes exceeds the limit, the cache will 58 | * remove entries in the background until the limit is satisfied. The limit is 59 | * not strict: the cache may temporarily exceed it while waiting for files to be 60 | * deleted. The limit does not include filesystem overhead or the cache 61 | * journal so space-sensitive applications should set a conservative limit. 62 | * 63 | *

Clients call {@link #edit} to create or update the values of an entry. An 64 | * entry may have only one editor at one time; if a value is not available to be 65 | * edited then {@link #edit} will return null. 66 | *

74 | * Every {@link #edit} call must be matched by a call to {@link Editor#commit} 75 | * or {@link Editor#abort}. Committing is atomic: a read observes the full set 76 | * of values as they were before or after the commit, but never a mix of values. 77 | * 78 | *

Clients call {@link #get} to read a snapshot of an entry. The read will 79 | * observe the value at the time that {@link #get} was called. Updates and 80 | * removals after the call do not impact ongoing reads. 81 | * 82 | *

This class is tolerant of some I/O errors. If files are missing from the 83 | * filesystem, the corresponding entries will be dropped from the cache. If 84 | * an error occurs while writing a cache value, the edit will fail silently. 85 | * Callers should handle other problems by catching {@code IOException} and 86 | * responding appropriately. 87 | */ 88 | public final class DiskLruCache implements Closeable { 89 | static final String JOURNAL_FILE = "journal"; 90 | static final String JOURNAL_FILE_TEMP = "journal.tmp"; 91 | static final String JOURNAL_FILE_BACKUP = "journal.bkp"; 92 | static final String MAGIC = "libcore.io.DiskLruCache"; 93 | static final String VERSION_1 = "1"; 94 | static final long ANY_SEQUENCE_NUMBER = -1; 95 | static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}"; 96 | static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN); 97 | private static final String CLEAN = "CLEAN"; 98 | private static final String DIRTY = "DIRTY"; 99 | private static final String REMOVE = "REMOVE"; 100 | private static final String READ = "READ"; 101 | 102 | /* 103 | * This cache uses a journal file named "journal". A typical journal file 104 | * looks like this: 105 | * libcore.io.DiskLruCache 106 | * 1 107 | * 100 108 | * 2 109 | * 110 | * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 111 | * DIRTY 335c4c6028171cfddfbaae1a9c313c52 112 | * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 113 | * REMOVE 335c4c6028171cfddfbaae1a9c313c52 114 | * DIRTY 1ab96a171faeeee38496d8b330771a7a 115 | * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 116 | * READ 335c4c6028171cfddfbaae1a9c313c52 117 | * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 118 | * 119 | * The first five lines of the journal form its header. They are the 120 | * constant string "libcore.io.DiskLruCache", the disk cache's version, 121 | * the application's version, the value count, and a blank line. 122 | * 123 | * Each of the subsequent lines in the file is a record of the state of a 124 | * cache entry. Each line contains space-separated values: a state, a key, 125 | * and optional state-specific values. 126 | * o DIRTY lines track that an entry is actively being created or updated. 127 | * Every successful DIRTY action should be followed by a CLEAN or REMOVE 128 | * action. DIRTY lines without a matching CLEAN or REMOVE indicate that 129 | * temporary files may need to be deleted. 130 | * o CLEAN lines track a cache entry that has been successfully published 131 | * and may be read. A publish line is followed by the lengths of each of 132 | * its values. 133 | * o READ lines track accesses for LRU. 134 | * o REMOVE lines track entries that have been deleted. 135 | * 136 | * The journal file is appended to as cache operations occur. The journal may 137 | * occasionally be compacted by dropping redundant lines. A temporary file named 138 | * "journal.tmp" will be used during compaction; that file should be deleted if 139 | * it exists when the cache is opened. 140 | */ 141 | 142 | private final File directory; 143 | private final File journalFile; 144 | private final File journalFileTmp; 145 | private final File journalFileBackup; 146 | private final int appVersion; 147 | private long maxSize; 148 | private final int valueCount; 149 | private long size = 0; 150 | private Writer journalWriter; 151 | private final LinkedHashMap lruEntries = 152 | new LinkedHashMap(0, 0.75f, true); 153 | private int redundantOpCount; 154 | 155 | /** 156 | * To differentiate between old and current snapshots, each entry is given 157 | * a sequence number each time an edit is committed. A snapshot is stale if 158 | * its sequence number is not equal to its entry's sequence number. 159 | */ 160 | private long nextSequenceNumber = 0; 161 | 162 | /** This cache uses a single background thread to evict entries. */ 163 | final ThreadPoolExecutor executorService = 164 | new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); 165 | private final Callable cleanupCallable = new Callable() { 166 | public Void call() throws Exception { 167 | synchronized (DiskLruCache.this) { 168 | if (journalWriter == null) { 169 | return null; // Closed. 170 | } 171 | trimToSize(); 172 | if (journalRebuildRequired()) { 173 | rebuildJournal(); 174 | redundantOpCount = 0; 175 | } 176 | } 177 | return null; 178 | } 179 | }; 180 | 181 | private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { 182 | this.directory = directory; 183 | this.appVersion = appVersion; 184 | this.journalFile = new File(directory, JOURNAL_FILE); 185 | this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP); 186 | this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP); 187 | this.valueCount = valueCount; 188 | this.maxSize = maxSize; 189 | } 190 | 191 | /** 192 | * Opens the cache in {@code directory}, creating a cache if none exists 193 | * there. 194 | * 195 | * @param directory a writable directory 196 | * @param valueCount the number of values per cache entry. Must be positive. 197 | * @param maxSize the maximum number of bytes this cache should use to store 198 | * @throws IOException if reading or writing the cache directory fails 199 | */ 200 | public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) 201 | throws IOException { 202 | if (maxSize <= 0) { 203 | throw new IllegalArgumentException("maxSize <= 0"); 204 | } 205 | if (valueCount <= 0) { 206 | throw new IllegalArgumentException("valueCount <= 0"); 207 | } 208 | 209 | // If a bkp file exists, use it instead. 210 | File backupFile = new File(directory, JOURNAL_FILE_BACKUP); 211 | if (backupFile.exists()) { 212 | File journalFile = new File(directory, JOURNAL_FILE); 213 | // If journal file also exists just delete backup file. 214 | if (journalFile.exists()) { 215 | backupFile.delete(); 216 | } else { 217 | renameTo(backupFile, journalFile, false); 218 | } 219 | } 220 | 221 | // Prefer to pick up where we left off. 222 | DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); 223 | if (cache.journalFile.exists()) { 224 | try { 225 | cache.readJournal(); 226 | cache.processJournal(); 227 | return cache; 228 | } catch (IOException journalIsCorrupt) { 229 | System.out 230 | .println("DiskLruCache " 231 | + directory 232 | + " is corrupt: " 233 | + journalIsCorrupt.getMessage() 234 | + ", removing"); 235 | cache.delete(); 236 | } 237 | } 238 | 239 | // Create a new empty cache. 240 | directory.mkdirs(); 241 | cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); 242 | cache.rebuildJournal(); 243 | return cache; 244 | } 245 | 246 | private void readJournal() throws IOException { 247 | StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII); 248 | try { 249 | String magic = reader.readLine(); 250 | String version = reader.readLine(); 251 | String appVersionString = reader.readLine(); 252 | String valueCountString = reader.readLine(); 253 | String blank = reader.readLine(); 254 | if (!MAGIC.equals(magic) 255 | || !VERSION_1.equals(version) 256 | || !Integer.toString(appVersion).equals(appVersionString) 257 | || !Integer.toString(valueCount).equals(valueCountString) 258 | || !"".equals(blank)) { 259 | throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " 260 | + valueCountString + ", " + blank + "]"); 261 | } 262 | 263 | int lineCount = 0; 264 | while (true) { 265 | try { 266 | readJournalLine(reader.readLine()); 267 | lineCount++; 268 | } catch (EOFException endOfJournal) { 269 | break; 270 | } 271 | } 272 | redundantOpCount = lineCount - lruEntries.size(); 273 | 274 | // If we ended on a truncated line, rebuild the journal before appending to it. 275 | if (reader.hasUnterminatedLine()) { 276 | rebuildJournal(); 277 | } else { 278 | journalWriter = new BufferedWriter(new OutputStreamWriter( 279 | new FileOutputStream(journalFile, true), Util.US_ASCII)); 280 | } 281 | } finally { 282 | Util.closeQuietly(reader); 283 | } 284 | } 285 | 286 | private void readJournalLine(String line) throws IOException { 287 | int firstSpace = line.indexOf(' '); 288 | if (firstSpace == -1) { 289 | throw new IOException("unexpected journal line: " + line); 290 | } 291 | 292 | int keyBegin = firstSpace + 1; 293 | int secondSpace = line.indexOf(' ', keyBegin); 294 | final String key; 295 | if (secondSpace == -1) { 296 | key = line.substring(keyBegin); 297 | if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) { 298 | lruEntries.remove(key); 299 | return; 300 | } 301 | } else { 302 | key = line.substring(keyBegin, secondSpace); 303 | } 304 | 305 | Entry entry = lruEntries.get(key); 306 | if (entry == null) { 307 | entry = new Entry(key); 308 | lruEntries.put(key, entry); 309 | } 310 | 311 | if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) { 312 | String[] parts = line.substring(secondSpace + 1).split(" "); 313 | entry.readable = true; 314 | entry.currentEditor = null; 315 | entry.setLengths(parts); 316 | } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) { 317 | entry.currentEditor = new Editor(entry); 318 | } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) { 319 | // This work was already done by calling lruEntries.get(). 320 | } else { 321 | throw new IOException("unexpected journal line: " + line); 322 | } 323 | } 324 | 325 | /** 326 | * Computes the initial size and collects garbage as a part of opening the 327 | * cache. Dirty entries are assumed to be inconsistent and will be deleted. 328 | */ 329 | private void processJournal() throws IOException { 330 | deleteIfExists(journalFileTmp); 331 | for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) { 332 | Entry entry = i.next(); 333 | if (entry.currentEditor == null) { 334 | for (int t = 0; t < valueCount; t++) { 335 | size += entry.lengths[t]; 336 | } 337 | } else { 338 | entry.currentEditor = null; 339 | for (int t = 0; t < valueCount; t++) { 340 | deleteIfExists(entry.getCleanFile(t)); 341 | deleteIfExists(entry.getDirtyFile(t)); 342 | } 343 | i.remove(); 344 | } 345 | } 346 | } 347 | 348 | /** 349 | * Creates a new journal that omits redundant information. This replaces the 350 | * current journal if it exists. 351 | */ 352 | private synchronized void rebuildJournal() throws IOException { 353 | if (journalWriter != null) { 354 | journalWriter.close(); 355 | } 356 | 357 | Writer writer = new BufferedWriter( 358 | new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII)); 359 | try { 360 | writer.write(MAGIC); 361 | writer.write("\n"); 362 | writer.write(VERSION_1); 363 | writer.write("\n"); 364 | writer.write(Integer.toString(appVersion)); 365 | writer.write("\n"); 366 | writer.write(Integer.toString(valueCount)); 367 | writer.write("\n"); 368 | writer.write("\n"); 369 | 370 | for (Entry entry : lruEntries.values()) { 371 | if (entry.currentEditor != null) { 372 | writer.write(DIRTY + ' ' + entry.key + '\n'); 373 | } else { 374 | writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); 375 | } 376 | } 377 | } finally { 378 | writer.close(); 379 | } 380 | 381 | if (journalFile.exists()) { 382 | renameTo(journalFile, journalFileBackup, true); 383 | } 384 | renameTo(journalFileTmp, journalFile, false); 385 | journalFileBackup.delete(); 386 | 387 | journalWriter = new BufferedWriter( 388 | new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII)); 389 | } 390 | 391 | private static void deleteIfExists(File file) throws IOException { 392 | if (file.exists() && !file.delete()) { 393 | throw new IOException(); 394 | } 395 | } 396 | 397 | private static void renameTo(File from, File to, boolean deleteDestination) throws IOException { 398 | if (deleteDestination) { 399 | deleteIfExists(to); 400 | } 401 | if (!from.renameTo(to)) { 402 | throw new IOException(); 403 | } 404 | } 405 | 406 | /** 407 | * Returns a snapshot of the entry named {@code key}, or null if it doesn't 408 | * exist is not currently readable. If a value is returned, it is moved to 409 | * the head of the LRU queue. 410 | */ 411 | public synchronized Snapshot get(String key) throws IOException { 412 | checkNotClosed(); 413 | validateKey(key); 414 | Entry entry = lruEntries.get(key); 415 | if (entry == null) { 416 | return null; 417 | } 418 | 419 | if (!entry.readable) { 420 | return null; 421 | } 422 | 423 | // Open all streams eagerly to guarantee that we see a single published 424 | // snapshot. If we opened streams lazily then the streams could come 425 | // from different edits. 426 | InputStream[] ins = new InputStream[valueCount]; 427 | try { 428 | for (int i = 0; i < valueCount; i++) { 429 | ins[i] = new FileInputStream(entry.getCleanFile(i)); 430 | } 431 | } catch (FileNotFoundException e) { 432 | // A file must have been deleted manually! 433 | for (int i = 0; i < valueCount; i++) { 434 | if (ins[i] != null) { 435 | Util.closeQuietly(ins[i]); 436 | } else { 437 | break; 438 | } 439 | } 440 | return null; 441 | } 442 | 443 | redundantOpCount++; 444 | journalWriter.append(READ + ' ' + key + '\n'); 445 | if (journalRebuildRequired()) { 446 | executorService.submit(cleanupCallable); 447 | } 448 | 449 | return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths); 450 | } 451 | 452 | /** 453 | * Returns an editor for the entry named {@code key}, or null if another 454 | * edit is in progress. 455 | */ 456 | public Editor edit(String key) throws IOException { 457 | return edit(key, ANY_SEQUENCE_NUMBER); 458 | } 459 | 460 | private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { 461 | checkNotClosed(); 462 | validateKey(key); 463 | Entry entry = lruEntries.get(key); 464 | if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null 465 | || entry.sequenceNumber != expectedSequenceNumber)) { 466 | return null; // Snapshot is stale. 467 | } 468 | if (entry == null) { 469 | entry = new Entry(key); 470 | lruEntries.put(key, entry); 471 | } else if (entry.currentEditor != null) { 472 | return null; // Another edit is in progress. 473 | } 474 | 475 | Editor editor = new Editor(entry); 476 | entry.currentEditor = editor; 477 | 478 | // Flush the journal before creating files to prevent file leaks. 479 | journalWriter.write(DIRTY + ' ' + key + '\n'); 480 | journalWriter.flush(); 481 | return editor; 482 | } 483 | 484 | /** Returns the directory where this cache stores its data. */ 485 | public File getDirectory() { 486 | return directory; 487 | } 488 | 489 | /** 490 | * Returns the maximum number of bytes that this cache should use to store 491 | * its data. 492 | */ 493 | public synchronized long getMaxSize() { 494 | return maxSize; 495 | } 496 | 497 | /** 498 | * Changes the maximum number of bytes the cache can store and queues a job 499 | * to trim the existing store, if necessary. 500 | */ 501 | public synchronized void setMaxSize(long maxSize) { 502 | this.maxSize = maxSize; 503 | executorService.submit(cleanupCallable); 504 | } 505 | 506 | /** 507 | * Returns the number of bytes currently being used to store the values in 508 | * this cache. This may be greater than the max size if a background 509 | * deletion is pending. 510 | */ 511 | public synchronized long size() { 512 | return size; 513 | } 514 | 515 | private synchronized void completeEdit(Editor editor, boolean success) throws IOException { 516 | Entry entry = editor.entry; 517 | if (entry.currentEditor != editor) { 518 | throw new IllegalStateException(); 519 | } 520 | 521 | // If this edit is creating the entry for the first time, every index must have a value. 522 | if (success && !entry.readable) { 523 | for (int i = 0; i < valueCount; i++) { 524 | if (!editor.written[i]) { 525 | editor.abort(); 526 | throw new IllegalStateException("Newly created entry didn't create value for index " + i); 527 | } 528 | if (!entry.getDirtyFile(i).exists()) { 529 | editor.abort(); 530 | return; 531 | } 532 | } 533 | } 534 | 535 | for (int i = 0; i < valueCount; i++) { 536 | File dirty = entry.getDirtyFile(i); 537 | if (success) { 538 | if (dirty.exists()) { 539 | File clean = entry.getCleanFile(i); 540 | dirty.renameTo(clean); 541 | long oldLength = entry.lengths[i]; 542 | long newLength = clean.length(); 543 | entry.lengths[i] = newLength; 544 | size = size - oldLength + newLength; 545 | } 546 | } else { 547 | deleteIfExists(dirty); 548 | } 549 | } 550 | 551 | redundantOpCount++; 552 | entry.currentEditor = null; 553 | if (entry.readable | success) { 554 | entry.readable = true; 555 | journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); 556 | if (success) { 557 | entry.sequenceNumber = nextSequenceNumber++; 558 | } 559 | } else { 560 | lruEntries.remove(entry.key); 561 | journalWriter.write(REMOVE + ' ' + entry.key + '\n'); 562 | } 563 | journalWriter.flush(); 564 | 565 | if (size > maxSize || journalRebuildRequired()) { 566 | executorService.submit(cleanupCallable); 567 | } 568 | } 569 | 570 | /** 571 | * We only rebuild the journal when it will halve the size of the journal 572 | * and eliminate at least 2000 ops. 573 | */ 574 | private boolean journalRebuildRequired() { 575 | final int redundantOpCompactThreshold = 2000; 576 | return redundantOpCount >= redundantOpCompactThreshold // 577 | && redundantOpCount >= lruEntries.size(); 578 | } 579 | 580 | /** 581 | * Drops the entry for {@code key} if it exists and can be removed. Entries 582 | * actively being edited cannot be removed. 583 | * 584 | * @return true if an entry was removed. 585 | */ 586 | public synchronized boolean remove(String key) throws IOException { 587 | checkNotClosed(); 588 | validateKey(key); 589 | Entry entry = lruEntries.get(key); 590 | if (entry == null || entry.currentEditor != null) { 591 | return false; 592 | } 593 | 594 | for (int i = 0; i < valueCount; i++) { 595 | File file = entry.getCleanFile(i); 596 | if (file.exists() && !file.delete()) { 597 | throw new IOException("failed to delete " + file); 598 | } 599 | size -= entry.lengths[i]; 600 | entry.lengths[i] = 0; 601 | } 602 | 603 | redundantOpCount++; 604 | journalWriter.append(REMOVE + ' ' + key + '\n'); 605 | lruEntries.remove(key); 606 | 607 | if (journalRebuildRequired()) { 608 | executorService.submit(cleanupCallable); 609 | } 610 | 611 | return true; 612 | } 613 | 614 | /** Returns true if this cache has been closed. */ 615 | public synchronized boolean isClosed() { 616 | return journalWriter == null; 617 | } 618 | 619 | private void checkNotClosed() { 620 | if (journalWriter == null) { 621 | throw new IllegalStateException("cache is closed"); 622 | } 623 | } 624 | 625 | /** Force buffered operations to the filesystem. */ 626 | public synchronized void flush() throws IOException { 627 | checkNotClosed(); 628 | trimToSize(); 629 | journalWriter.flush(); 630 | } 631 | 632 | /** Closes this cache. Stored values will remain on the filesystem. */ 633 | public synchronized void close() throws IOException { 634 | if (journalWriter == null) { 635 | return; // Already closed. 636 | } 637 | for (Entry entry : new ArrayList(lruEntries.values())) { 638 | if (entry.currentEditor != null) { 639 | entry.currentEditor.abort(); 640 | } 641 | } 642 | trimToSize(); 643 | journalWriter.close(); 644 | journalWriter = null; 645 | } 646 | 647 | private void trimToSize() throws IOException { 648 | while (size > maxSize) { 649 | Map.Entry toEvict = lruEntries.entrySet().iterator().next(); 650 | remove(toEvict.getKey()); 651 | } 652 | } 653 | 654 | /** 655 | * Closes the cache and deletes all of its stored values. This will delete 656 | * all files in the cache directory including files that weren't created by 657 | * the cache. 658 | */ 659 | public void delete() throws IOException { 660 | close(); 661 | Util.deleteContents(directory); 662 | } 663 | 664 | private void validateKey(String key) { 665 | Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); 666 | if (!matcher.matches()) { 667 | throw new IllegalArgumentException("keys must match regex " 668 | + STRING_KEY_PATTERN + ": \"" + key + "\""); 669 | } 670 | } 671 | 672 | private static String inputStreamToString(InputStream in) throws IOException { 673 | return Util.readFully(new InputStreamReader(in, Util.UTF_8)); 674 | } 675 | 676 | /** A snapshot of the values for an entry. */ 677 | public final class Snapshot implements Closeable { 678 | private final String key; 679 | private final long sequenceNumber; 680 | private final InputStream[] ins; 681 | private final long[] lengths; 682 | 683 | private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) { 684 | this.key = key; 685 | this.sequenceNumber = sequenceNumber; 686 | this.ins = ins; 687 | this.lengths = lengths; 688 | } 689 | 690 | /** 691 | * Returns an editor for this snapshot's entry, or null if either the 692 | * entry has changed since this snapshot was created or if another edit 693 | * is in progress. 694 | */ 695 | public Editor edit() throws IOException { 696 | return DiskLruCache.this.edit(key, sequenceNumber); 697 | } 698 | 699 | /** Returns the unbuffered stream with the value for {@code index}. */ 700 | public InputStream getInputStream(int index) { 701 | return ins[index]; 702 | } 703 | 704 | /** Returns the string value for {@code index}. */ 705 | public String getString(int index) throws IOException { 706 | return inputStreamToString(getInputStream(index)); 707 | } 708 | 709 | /** Returns the byte length of the value for {@code index}. */ 710 | public long getLength(int index) { 711 | return lengths[index]; 712 | } 713 | 714 | public void close() { 715 | for (InputStream in : ins) { 716 | Util.closeQuietly(in); 717 | } 718 | } 719 | } 720 | 721 | private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() { 722 | @Override 723 | public void write(int b) throws IOException { 724 | // Eat all writes silently. Nom nom. 725 | } 726 | }; 727 | 728 | /** Edits the values for an entry. */ 729 | public final class Editor { 730 | private final Entry entry; 731 | private final boolean[] written; 732 | private boolean hasErrors; 733 | private boolean committed; 734 | 735 | private Editor(Entry entry) { 736 | this.entry = entry; 737 | this.written = (entry.readable) ? null : new boolean[valueCount]; 738 | } 739 | 740 | /** 741 | * Returns an unbuffered input stream to read the last committed value, 742 | * or null if no value has been committed. 743 | */ 744 | public InputStream newInputStream(int index) throws IOException { 745 | synchronized (DiskLruCache.this) { 746 | if (entry.currentEditor != this) { 747 | throw new IllegalStateException(); 748 | } 749 | if (!entry.readable) { 750 | return null; 751 | } 752 | try { 753 | return new FileInputStream(entry.getCleanFile(index)); 754 | } catch (FileNotFoundException e) { 755 | return null; 756 | } 757 | } 758 | } 759 | 760 | /** 761 | * Returns the last committed value as a string, or null if no value 762 | * has been committed. 763 | */ 764 | public String getString(int index) throws IOException { 765 | InputStream in = newInputStream(index); 766 | return in != null ? inputStreamToString(in) : null; 767 | } 768 | 769 | /** 770 | * Returns a new unbuffered output stream to write the value at 771 | * {@code index}. If the underlying output stream encounters errors 772 | * when writing to the filesystem, this edit will be aborted when 773 | * {@link #commit} is called. The returned output stream does not throw 774 | * IOExceptions. 775 | */ 776 | public OutputStream newOutputStream(int index) throws IOException { 777 | if (index < 0 || index >= valueCount) { 778 | throw new IllegalArgumentException("Expected index " + index + " to " 779 | + "be greater than 0 and less than the maximum value count " 780 | + "of " + valueCount); 781 | } 782 | synchronized (DiskLruCache.this) { 783 | if (entry.currentEditor != this) { 784 | throw new IllegalStateException(); 785 | } 786 | if (!entry.readable) { 787 | written[index] = true; 788 | } 789 | File dirtyFile = entry.getDirtyFile(index); 790 | FileOutputStream outputStream; 791 | try { 792 | outputStream = new FileOutputStream(dirtyFile); 793 | } catch (FileNotFoundException e) { 794 | // Attempt to recreate the cache directory. 795 | directory.mkdirs(); 796 | try { 797 | outputStream = new FileOutputStream(dirtyFile); 798 | } catch (FileNotFoundException e2) { 799 | // We are unable to recover. Silently eat the writes. 800 | return NULL_OUTPUT_STREAM; 801 | } 802 | } 803 | return new FaultHidingOutputStream(outputStream); 804 | } 805 | } 806 | 807 | /** Sets the value at {@code index} to {@code value}. */ 808 | public void set(int index, String value) throws IOException { 809 | Writer writer = null; 810 | try { 811 | writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8); 812 | writer.write(value); 813 | } finally { 814 | Util.closeQuietly(writer); 815 | } 816 | } 817 | 818 | /** 819 | * Commits this edit so it is visible to readers. This releases the 820 | * edit lock so another edit may be started on the same key. 821 | */ 822 | public void commit() throws IOException { 823 | if (hasErrors) { 824 | completeEdit(this, false); 825 | remove(entry.key); // The previous entry is stale. 826 | } else { 827 | completeEdit(this, true); 828 | } 829 | committed = true; 830 | } 831 | 832 | /** 833 | * Aborts this edit. This releases the edit lock so another edit may be 834 | * started on the same key. 835 | */ 836 | public void abort() throws IOException { 837 | completeEdit(this, false); 838 | } 839 | 840 | public void abortUnlessCommitted() { 841 | if (!committed) { 842 | try { 843 | abort(); 844 | } catch (IOException ignored) { 845 | } 846 | } 847 | } 848 | 849 | private class FaultHidingOutputStream extends FilterOutputStream { 850 | private FaultHidingOutputStream(OutputStream out) { 851 | super(out); 852 | } 853 | 854 | @Override public void write(int oneByte) { 855 | try { 856 | out.write(oneByte); 857 | } catch (IOException e) { 858 | hasErrors = true; 859 | } 860 | } 861 | 862 | @Override public void write(byte[] buffer, int offset, int length) { 863 | try { 864 | out.write(buffer, offset, length); 865 | } catch (IOException e) { 866 | hasErrors = true; 867 | } 868 | } 869 | 870 | @Override public void close() { 871 | try { 872 | out.close(); 873 | } catch (IOException e) { 874 | hasErrors = true; 875 | } 876 | } 877 | 878 | @Override public void flush() { 879 | try { 880 | out.flush(); 881 | } catch (IOException e) { 882 | hasErrors = true; 883 | } 884 | } 885 | } 886 | } 887 | 888 | private final class Entry { 889 | private final String key; 890 | 891 | /** Lengths of this entry's files. */ 892 | private final long[] lengths; 893 | 894 | /** True if this entry has ever been published. */ 895 | private boolean readable; 896 | 897 | /** The ongoing edit or null if this entry is not being edited. */ 898 | private Editor currentEditor; 899 | 900 | /** The sequence number of the most recently committed edit to this entry. */ 901 | private long sequenceNumber; 902 | 903 | private Entry(String key) { 904 | this.key = key; 905 | this.lengths = new long[valueCount]; 906 | } 907 | 908 | public String getLengths() throws IOException { 909 | StringBuilder result = new StringBuilder(); 910 | for (long size : lengths) { 911 | result.append(' ').append(size); 912 | } 913 | return result.toString(); 914 | } 915 | 916 | /** Set lengths using decimal numbers like "10123". */ 917 | private void setLengths(String[] strings) throws IOException { 918 | if (strings.length != valueCount) { 919 | throw invalidLengths(strings); 920 | } 921 | 922 | try { 923 | for (int i = 0; i < strings.length; i++) { 924 | lengths[i] = Long.parseLong(strings[i]); 925 | } 926 | } catch (NumberFormatException e) { 927 | throw invalidLengths(strings); 928 | } 929 | } 930 | 931 | private IOException invalidLengths(String[] strings) throws IOException { 932 | throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings)); 933 | } 934 | 935 | public File getCleanFile(int i) { 936 | return new File(directory, key + "." + i); 937 | } 938 | 939 | public File getDirtyFile(int i) { 940 | return new File(directory, key + "." + i + ".tmp"); 941 | } 942 | } 943 | } 944 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/cache/FileNameGenerator.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.cache; 2 | 3 | /** 4 | * Created by XuYingjian on 16/1/7. 5 | */ 6 | public interface FileNameGenerator { 7 | String generate(String url); 8 | } 9 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/cache/IDiskCache.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.cache; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import cn.jclick.httpwrapper.callback.ResponseData; 7 | 8 | /** 9 | * Created by jclick on 16/1/6. 10 | */ 11 | public interface IDiskCache { 12 | 13 | 14 | File getDirectory(); 15 | 16 | String getString(String url); 17 | 18 | byte[] getBytes(String url); 19 | 20 | ResponseData getData(String url); 21 | 22 | boolean putData(String url, ResponseData responseData) throws IOException; 23 | 24 | boolean putString(String url, String value) throws IOException; 25 | 26 | boolean putBytes(String url, byte[] value) throws IOException; 27 | 28 | boolean remove(String url); 29 | 30 | void clearAllCache(); 31 | } 32 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/cache/LruDiskCache.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.cache; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | import com.alibaba.fastjson.JSON; 7 | import com.alibaba.fastjson.TypeReference; 8 | 9 | import java.io.BufferedOutputStream; 10 | import java.io.File; 11 | import java.io.IOException; 12 | 13 | import cn.jclick.httpwrapper.callback.ResponseData; 14 | 15 | public class LruDiskCache implements IDiskCache { 16 | 17 | private static final String TAG = "LruDiskCache"; 18 | 19 | public static final int DEFAULT_BUFFER_SIZE = 32 * 1024; // 32 Kb 20 | 21 | private static final String ERROR_ARG_NULL = " argument must be not null"; 22 | private static final String ERROR_ARG_NEGATIVE = " argument must be positive number"; 23 | 24 | protected DiskLruCache cache; 25 | 26 | protected final FileNameGenerator fileNameGenerator; 27 | 28 | protected int bufferSize = DEFAULT_BUFFER_SIZE; 29 | 30 | /** 31 | * @param cacheDir Directory for file caching 32 | * @param fileNameGenerator Name generator for cached files. Generated names must match the regex 33 | * [a-z0-9_-]{1,64} 34 | * @param cacheMaxSize Max cache size in bytes. 0 means cache size is unlimited. 35 | * @throws IOException if cache can't be initialized (e.g. "No space left on device") 36 | */ 37 | public LruDiskCache(File cacheDir, FileNameGenerator fileNameGenerator, long cacheMaxSize) throws IOException { 38 | if (cacheDir == null) { 39 | throw new IllegalArgumentException("cacheDir" + ERROR_ARG_NULL); 40 | } 41 | if (cacheMaxSize < 0) { 42 | throw new IllegalArgumentException("cacheMaxSize" + ERROR_ARG_NEGATIVE); 43 | } 44 | if (fileNameGenerator == null) { 45 | throw new IllegalArgumentException("fileNameGenerator" + ERROR_ARG_NULL); 46 | } 47 | 48 | if (cacheMaxSize == 0) { 49 | cacheMaxSize = Long.MAX_VALUE; 50 | } 51 | 52 | this.fileNameGenerator = fileNameGenerator; 53 | initCache(cacheDir, cacheMaxSize); 54 | } 55 | 56 | private void initCache(File cacheDir, long cacheMaxSize) 57 | throws IOException { 58 | try { 59 | cache = DiskLruCache.open(cacheDir, 1, 1, cacheMaxSize); 60 | } catch (IOException e) { 61 | throw e; //new RuntimeException("Can't initialize disk cache", e); 62 | } 63 | } 64 | 65 | @Override 66 | public File getDirectory() { 67 | return cache.getDirectory(); 68 | } 69 | 70 | @Override 71 | public String getString(String url) { 72 | DiskLruCache.Snapshot snapshot = null; 73 | try { 74 | snapshot = cache.get(getKey(url)); 75 | if (snapshot == null){ 76 | return null; 77 | } 78 | return snapshot.getString(0); 79 | } catch (IOException e) { 80 | Log.e(TAG, e.getMessage()); 81 | return null; 82 | } finally { 83 | if (snapshot != null) { 84 | snapshot.close(); 85 | } 86 | } 87 | } 88 | 89 | @Override 90 | public byte[] getBytes(String url) { 91 | DiskLruCache.Snapshot snapshot = null; 92 | try { 93 | snapshot = cache.get(getKey(url)); 94 | if (snapshot == null){ 95 | return null; 96 | } 97 | String result = snapshot.getString(0); 98 | if (result == null){ 99 | return null; 100 | } 101 | return result.getBytes(); 102 | } catch (IOException e) { 103 | Log.e(TAG, e.getMessage()); 104 | return null; 105 | } finally { 106 | if (snapshot != null) { 107 | snapshot.close(); 108 | } 109 | } 110 | } 111 | 112 | @Override 113 | public boolean putString(String url, String value) throws IOException{ 114 | DiskLruCache.Editor editor = cache.edit(getKey(url)); 115 | if (editor == null) { 116 | return false; 117 | } 118 | 119 | BufferedOutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize); 120 | boolean savedSuccessfully = true; 121 | try { 122 | os.write(value.getBytes()); 123 | } catch (IOException e){ 124 | savedSuccessfully = false; 125 | e.printStackTrace(); 126 | } finally { 127 | Util.closeQuietly(os); 128 | } 129 | if (savedSuccessfully) { 130 | editor.commit(); 131 | } else { 132 | editor.abort(); 133 | } 134 | return savedSuccessfully; 135 | } 136 | 137 | @Override 138 | public boolean putBytes(String url, byte[] value) throws IOException{ 139 | DiskLruCache.Editor editor = cache.edit(getKey(url)); 140 | if (editor == null) { 141 | return false; 142 | } 143 | 144 | BufferedOutputStream os = new BufferedOutputStream(editor.newOutputStream(0), bufferSize); 145 | boolean savedSuccessfully = true; 146 | try { 147 | os.write(value); 148 | } catch (IOException e){ 149 | savedSuccessfully = false; 150 | e.printStackTrace(); 151 | } finally { 152 | Util.closeQuietly(os); 153 | } 154 | if (savedSuccessfully) { 155 | editor.commit(); 156 | } else { 157 | editor.abort(); 158 | } 159 | return savedSuccessfully; 160 | } 161 | 162 | @Override 163 | public boolean remove(String url) { 164 | try { 165 | return cache.remove(getKey(url)); 166 | } catch (IOException e) { 167 | Log.e(TAG, e.getMessage()); 168 | return false; 169 | } 170 | } 171 | 172 | @Override 173 | public void clearAllCache() { 174 | try { 175 | cache.delete(); 176 | } catch (IOException e) { 177 | Log.e(TAG, e.getMessage()); 178 | } 179 | try { 180 | initCache(cache.getDirectory(), cache.getMaxSize()); 181 | } catch (IOException e) { 182 | Log.e(TAG, e.getMessage()); 183 | } 184 | } 185 | 186 | @Override 187 | public boolean putData(String url, ResponseData responseData) throws IOException{ 188 | String data = JSON.toJSONString(responseData); 189 | if (TextUtils.isEmpty(data)){ 190 | return false; 191 | } 192 | return putString(url, data); 193 | } 194 | 195 | @Override 196 | public ResponseData getData(String url) { 197 | String data = getString(url); 198 | if (TextUtils.isEmpty(data)){ 199 | return null; 200 | } 201 | ResponseData responseData = JSON.parseObject(data, new TypeReference>(){}); 202 | if (responseData == null){ 203 | return null; 204 | } 205 | return responseData; 206 | } 207 | 208 | public void close() { 209 | try { 210 | cache.close(); 211 | } catch (IOException e) { 212 | Log.e(TAG, e.getMessage()); 213 | } 214 | cache = null; 215 | } 216 | 217 | private String getKey(String imageUri) { 218 | return fileNameGenerator.generate(imageUri); 219 | } 220 | } -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/cache/Md5FileNameGenerator.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.cache; 2 | 3 | import java.math.BigInteger; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | 7 | public class Md5FileNameGenerator implements FileNameGenerator { 8 | 9 | private static final String HASH_ALGORITHM = "MD5"; 10 | private static final int RADIX = 10 + 26; // 10 digits + 26 letters 11 | 12 | @Override 13 | public String generate(String imageUri) { 14 | byte[] md5 = getMD5(imageUri.getBytes()); 15 | BigInteger bi = new BigInteger(md5).abs(); 16 | return bi.toString(RADIX); 17 | } 18 | 19 | private byte[] getMD5(byte[] data) { 20 | byte[] hash = null; 21 | try { 22 | MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); 23 | digest.update(data); 24 | hash = digest.digest(); 25 | } catch (NoSuchAlgorithmException e) { 26 | e.printStackTrace(); 27 | } 28 | return hash; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/cache/StrictLineReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.jclick.httpwrapper.cache; 18 | 19 | import java.io.ByteArrayOutputStream; 20 | import java.io.Closeable; 21 | import java.io.EOFException; 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.io.UnsupportedEncodingException; 25 | import java.nio.charset.Charset; 26 | 27 | /** 28 | * Buffers input from an {@link InputStream} for reading lines. 29 | * 30 | *

This class is used for buffered reading of lines. For purposes of this class, a line ends 31 | * with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated 32 | * line at end of input is invalid and will be ignored, the caller may use {@code 33 | * hasUnterminatedLine()} to detect it after catching the {@code EOFException}. 34 | * 35 | *

This class is intended for reading input that strictly consists of lines, such as line-based 36 | * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction 37 | * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different 38 | * end-of-input reporting and a more restrictive definition of a line. 39 | * 40 | *

This class supports only charsets that encode '\r' and '\n' as a single byte with value 13 41 | * and 10, respectively, and the representation of no other character contains these values. 42 | * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1. 43 | * The default charset is US_ASCII. 44 | */ 45 | class StrictLineReader implements Closeable { 46 | private static final byte CR = (byte) '\r'; 47 | private static final byte LF = (byte) '\n'; 48 | 49 | private final InputStream in; 50 | private final Charset charset; 51 | 52 | /* 53 | * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end 54 | * and the data in the range [pos, end) is buffered for reading. At end of input, if there is 55 | * an unterminated line, we set end == -1, otherwise end == pos. If the underlying 56 | * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1. 57 | */ 58 | private byte[] buf; 59 | private int pos; 60 | private int end; 61 | 62 | /** 63 | * Constructs a new {@code LineReader} with the specified charset and the default capacity. 64 | * 65 | * @param in the {@code InputStream} to read data from. 66 | * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are 67 | * supported. 68 | * @throws NullPointerException if {@code in} or {@code charset} is null. 69 | * @throws IllegalArgumentException if the specified charset is not supported. 70 | */ 71 | public StrictLineReader(InputStream in, Charset charset) { 72 | this(in, 8192, charset); 73 | } 74 | 75 | /** 76 | * Constructs a new {@code LineReader} with the specified capacity and charset. 77 | * 78 | * @param in the {@code InputStream} to read data from. 79 | * @param capacity the capacity of the buffer. 80 | * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are 81 | * supported. 82 | * @throws NullPointerException if {@code in} or {@code charset} is null. 83 | * @throws IllegalArgumentException if {@code capacity} is negative or zero 84 | * or the specified charset is not supported. 85 | */ 86 | public StrictLineReader(InputStream in, int capacity, Charset charset) { 87 | if (in == null || charset == null) { 88 | throw new NullPointerException(); 89 | } 90 | if (capacity < 0) { 91 | throw new IllegalArgumentException("capacity <= 0"); 92 | } 93 | if (!(charset.equals(Util.US_ASCII))) { 94 | throw new IllegalArgumentException("Unsupported encoding"); 95 | } 96 | 97 | this.in = in; 98 | this.charset = charset; 99 | buf = new byte[capacity]; 100 | } 101 | 102 | /** 103 | * Closes the reader by closing the underlying {@code InputStream} and 104 | * marking this reader as closed. 105 | * 106 | * @throws IOException for errors when closing the underlying {@code InputStream}. 107 | */ 108 | public void close() throws IOException { 109 | synchronized (in) { 110 | if (buf != null) { 111 | buf = null; 112 | in.close(); 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"}, 119 | * this end of line marker is not included in the result. 120 | * 121 | * @return the next line from the input. 122 | * @throws IOException for underlying {@code InputStream} errors. 123 | * @throws EOFException for the end of source stream. 124 | */ 125 | public String readLine() throws IOException { 126 | synchronized (in) { 127 | if (buf == null) { 128 | throw new IOException("LineReader is closed"); 129 | } 130 | 131 | // Read more data if we are at the end of the buffered data. 132 | // Though it's an error to read after an exception, we will let {@code fillBuf()} 133 | // throw again if that happens; thus we need to handle end == -1 as well as end == pos. 134 | if (pos >= end) { 135 | fillBuf(); 136 | } 137 | // Try to find LF in the buffered data and return the line if successful. 138 | for (int i = pos; i != end; ++i) { 139 | if (buf[i] == LF) { 140 | int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i; 141 | String res = new String(buf, pos, lineEnd - pos, charset.name()); 142 | pos = i + 1; 143 | return res; 144 | } 145 | } 146 | 147 | // Let's anticipate up to 80 characters on top of those already read. 148 | ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) { 149 | @Override 150 | public String toString() { 151 | int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count; 152 | try { 153 | return new String(buf, 0, length, charset.name()); 154 | } catch (UnsupportedEncodingException e) { 155 | throw new AssertionError(e); // Since we control the charset this will never happen. 156 | } 157 | } 158 | }; 159 | 160 | while (true) { 161 | out.write(buf, pos, end - pos); 162 | // Mark unterminated line in case fillBuf throws EOFException or IOException. 163 | end = -1; 164 | fillBuf(); 165 | // Try to find LF in the buffered data and return the line if successful. 166 | for (int i = pos; i != end; ++i) { 167 | if (buf[i] == LF) { 168 | if (i != pos) { 169 | out.write(buf, pos, i - pos); 170 | } 171 | pos = i + 1; 172 | return out.toString(); 173 | } 174 | } 175 | } 176 | } 177 | } 178 | 179 | public boolean hasUnterminatedLine() { 180 | return end == -1; 181 | } 182 | 183 | /** 184 | * Reads new input data into the buffer. Call only with pos == end or end == -1, 185 | * depending on the desired outcome if the function throws. 186 | */ 187 | private void fillBuf() throws IOException { 188 | int result = in.read(buf, 0, buf.length); 189 | if (result == -1) { 190 | throw new EOFException(); 191 | } 192 | pos = 0; 193 | end = result; 194 | } 195 | } 196 | 197 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/cache/Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cn.jclick.httpwrapper.cache; 18 | 19 | import java.io.Closeable; 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.io.Reader; 23 | import java.io.StringWriter; 24 | import java.nio.charset.Charset; 25 | 26 | /** Junk drawer of utility methods. */ 27 | final class Util { 28 | static final Charset US_ASCII = Charset.forName("US-ASCII"); 29 | static final Charset UTF_8 = Charset.forName("UTF-8"); 30 | 31 | private Util() { 32 | } 33 | 34 | static String readFully(Reader reader) throws IOException { 35 | try { 36 | StringWriter writer = new StringWriter(); 37 | char[] buffer = new char[1024]; 38 | int count; 39 | while ((count = reader.read(buffer)) != -1) { 40 | writer.write(buffer, 0, count); 41 | } 42 | return writer.toString(); 43 | } finally { 44 | reader.close(); 45 | } 46 | } 47 | 48 | /** 49 | * Deletes the contents of {@code dir}. Throws an IOException if any file 50 | * could not be deleted, or if {@code dir} is not a readable directory. 51 | */ 52 | static void deleteContents(File dir) throws IOException { 53 | File[] files = dir.listFiles(); 54 | if (files == null) { 55 | throw new IOException("not a readable directory: " + dir); 56 | } 57 | for (File file : files) { 58 | if (file.isDirectory()) { 59 | deleteContents(file); 60 | } 61 | if (!file.delete()) { 62 | throw new IOException("failed to delete file: " + file); 63 | } 64 | } 65 | } 66 | 67 | static void closeQuietly(/*Auto*/Closeable closeable) { 68 | if (closeable != null) { 69 | try { 70 | closeable.close(); 71 | } catch (RuntimeException rethrown) { 72 | throw rethrown; 73 | } catch (Exception ignored) { 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/callback/ByteCallback.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.callback; 2 | 3 | import android.util.Log; 4 | 5 | import com.alibaba.fastjson.JSON; 6 | import com.alibaba.fastjson.TypeReference; 7 | 8 | import cn.jclick.httpwrapper.request.HttpRequestAgent; 9 | 10 | /** 11 | * Created by XuYingjian on 16/1/11. 12 | */ 13 | public abstract class ByteCallback extends Callback{ 14 | 15 | @Override 16 | protected void onSuccess(byte[] bytes) { 17 | ResponseData responseData = wrapResponseData(); 18 | responseData.setData(bytes); 19 | response(responseData); 20 | } 21 | 22 | private void response(final ResponseData responseData){ 23 | HttpRequestAgent.getInstance().getConfig().mainHandler.post(new Runnable() { 24 | @Override 25 | public void run() { 26 | onResponse(responseData); 27 | } 28 | }); 29 | } 30 | 31 | @Override 32 | protected boolean isCacheProcessSuccess(ResponseData data) { 33 | try{ 34 | if (!super.isCacheProcessSuccess(data)){ 35 | return false; 36 | } 37 | ResponseData responseData = convertCache(data); 38 | responseData.setData(data.getData().getBytes(charset)); 39 | response(responseData); 40 | return true; 41 | }catch (Exception e){ 42 | return false; 43 | } 44 | } 45 | 46 | @Override 47 | protected void onFailed(Exception exception) { 48 | response(wrapFailedData(exception)); 49 | } 50 | 51 | protected abstract void onResponse(ResponseData responseData); 52 | } 53 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/callback/Callback.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.callback; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.util.Log; 6 | 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.nio.charset.Charset; 11 | import java.util.Date; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import cn.jclick.httpwrapper.interceptor.LoggerInterceptor; 16 | import cn.jclick.httpwrapper.request.HttpRequestAgent; 17 | import cn.jclick.httpwrapper.request.RequestConfig; 18 | import cn.jclick.httpwrapper.request.RequestParams; 19 | import cn.jclick.httpwrapper.utils.IOUtils; 20 | import cn.jclick.httpwrapper.utils.WrapperUtils; 21 | 22 | /** 23 | * Created by jclick on 16/1/6. 24 | */ 25 | public abstract class Callback { 26 | protected Date startRequestDate; 27 | protected Date responseDate; 28 | protected long contentLength; 29 | protected RequestParams params; 30 | protected int statusCode; 31 | protected Map> headers; 32 | protected Charset charset; 33 | 34 | protected String cacheURL; 35 | 36 | public boolean beforeStart(RequestParams params) { 37 | this.startRequestDate = new Date(); 38 | this.params = params; 39 | this.cacheURL = WrapperUtils.getUrlWithQueryString(params); 40 | boolean isContinue = true; 41 | switch (params.cacheMode) { 42 | case ALWAYS_CACHE: 43 | isContinue = !isCacheProcessSuccess(cache()); 44 | break; 45 | case CACHE_WHEN_NO_NETWORK: 46 | if (!WrapperUtils.isOnline(HttpRequestAgent.getInstance().getConfig().context)) { 47 | isContinue = !isCacheProcessSuccess(cache()); 48 | } 49 | break; 50 | case CACHE_FIRST: 51 | isCacheProcessSuccess(cache()); 52 | break; 53 | } 54 | if (!isContinue && HttpRequestAgent.getInstance().getConfig().logEnable){ 55 | Log.i(LoggerInterceptor.class.getName(), "Response from cache and stop the request!\n" + cache()); 56 | } 57 | return isContinue; 58 | } 59 | 60 | public final byte[] bytes(InputStream stream) throws IOException { 61 | byte[] bytes; 62 | try{ 63 | ByteArrayOutputStream output = new ByteArrayOutputStream(); 64 | long count = 0; 65 | int n = 0; 66 | byte[] arr = new byte[4196]; 67 | while (-1 != (n = stream.read(arr))) { 68 | output.write(arr, 0, n); 69 | count += n; 70 | 71 | sendProgress(count, contentLength); 72 | } 73 | if (count > Integer.MAX_VALUE) { 74 | throw new IOException("input stream length error"); 75 | } 76 | bytes = output.toByteArray(); 77 | 78 | }finally { 79 | IOUtils.closeQuietly(stream); 80 | } 81 | return bytes; 82 | } 83 | 84 | public final void sendProgress(final long writeLength, final long contentLength){ 85 | HttpRequestAgent.getInstance().getConfig().mainHandler.post(new Runnable() { 86 | @Override 87 | public void run() { 88 | onProgress(writeLength, contentLength); 89 | } 90 | }); 91 | } 92 | 93 | public final String string(InputStream stream, Charset charset) throws IOException { 94 | return new String(bytes(stream), charset); 95 | } 96 | 97 | public final String string(byte[] bytes){ 98 | return new String(bytes, charset); 99 | } 100 | 101 | public ResponseData cache() { 102 | if (HttpRequestAgent.getInstance().getConfig().diskCache != null){ 103 | return HttpRequestAgent.getInstance().getConfig().diskCache.getData(cacheURL); 104 | } 105 | return null; 106 | } 107 | 108 | protected boolean isCacheProcessSuccess(ResponseData data) { 109 | if (data == null){ 110 | return false; 111 | } 112 | if (new Date().getTime() - data.getRequestTime().getTime() > params.cacheTimeInSeconds * 1000){ 113 | return false; 114 | } 115 | return true; 116 | } 117 | 118 | /** 119 | * 120 | * @param cacheData cache数据 121 | * @return 转换为要返回的数据 122 | */ 123 | protected final ResponseData convertCache(ResponseData cacheData){ 124 | ResponseData responseData = new ResponseData<>(); 125 | responseData.setFromCache(true); 126 | responseData.setDescription(cacheData.getDescription()); 127 | responseData.setRequestTime(cacheData.getRequestTime()); 128 | responseData.setRequestSuccess(cacheData.isRequestSuccess()); 129 | responseData.setParseSuccess(cacheData.isParseSuccess()); 130 | responseData.setStatusCode(cacheData.getStatusCode()); 131 | responseData.setHeaders(cacheData.getHeaders()); 132 | responseData.setResponseTime(cacheData.getResponseTime()); 133 | return responseData; 134 | } 135 | 136 | public final ResponseData onResponse(int statusCode, Map> headers, Charset charset, InputStream response, long contentLength) { 137 | this.statusCode = statusCode; 138 | this.headers = headers; 139 | this.charset = charset; 140 | this.contentLength = contentLength; 141 | this.responseDate = new Date(); 142 | return onSuccess(response); 143 | } 144 | 145 | protected ResponseData onSuccess(InputStream inputStream){ 146 | ResponseData responseData = wrapResponseData(); 147 | try { 148 | byte[] bytes = bytes(inputStream); 149 | if (params.cacheMode != RequestConfig.HttpCacheMode.NO_CACHE) { 150 | if (HttpRequestAgent.getInstance().getConfig().diskCache != null) { 151 | responseData.setData(string(bytes)); 152 | responseData.setFromCache(true); 153 | boolean flag = HttpRequestAgent.getInstance().getConfig().diskCache.putData(cacheURL, responseData); 154 | if (!flag) { 155 | Log.d(getClass().getName(), "response success, but save cache failed !"); 156 | } 157 | } 158 | } 159 | onSuccess(bytes); 160 | } catch (IOException e) { 161 | responseData.setParseSuccess(false); 162 | onError(e); 163 | } 164 | return responseData; 165 | } 166 | 167 | public final void onError(Exception exception) { 168 | responseDate = new Date(); 169 | if (params.cacheMode == RequestConfig.HttpCacheMode.FAILED_SHOW_CACHE) { 170 | if (isCacheProcessSuccess(cache())) { 171 | return; 172 | } 173 | } else { 174 | onFailed(exception); 175 | } 176 | } 177 | 178 | protected ResponseData wrapResponseData(){ 179 | ResponseData responseData = new ResponseData(); 180 | responseData.setParseSuccess(true); 181 | responseData.setRequestSuccess(true); 182 | responseData.setFromCache(false); 183 | responseData.setContentLength(contentLength); 184 | responseData.setRequestTime(startRequestDate); 185 | responseData.setResponseTime(responseDate); 186 | responseData.setStatusCode(statusCode); 187 | responseData.setHeaders(headers); 188 | return responseData; 189 | } 190 | 191 | public ResponseData wrapFailedData(Exception exception){ 192 | Log.e(ByteCallback.class.getName(), "request failed..", exception); 193 | ResponseData responseData = wrapResponseData(); 194 | responseData.setFromCache(false); 195 | responseData.setRequestSuccess(false); 196 | responseData.setDescription(exception.getMessage()); 197 | return responseData; 198 | } 199 | 200 | public void onProgress(long bytesWritten, long totalSize) { 201 | if (HttpRequestAgent.getInstance().getConfig().logEnable){ 202 | Log.i("onProgress", String.format("Progress %d from %d (%2.0f%%)", bytesWritten, totalSize, (totalSize > 0) ? (bytesWritten * 1.0 / totalSize) * 100 : -1)); 203 | } 204 | } 205 | 206 | protected void onFailed(Exception exception) { 207 | 208 | } 209 | 210 | protected abstract void onSuccess(byte[] bytes); 211 | } 212 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/callback/FileCallback.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.callback; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.File; 8 | import java.io.FileInputStream; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | 13 | import cn.jclick.httpwrapper.request.HttpRequestAgent; 14 | import cn.jclick.httpwrapper.request.RequestConfig; 15 | import cn.jclick.httpwrapper.utils.IOUtils; 16 | 17 | /** 18 | * Created by XuYingjian on 16/1/19. 19 | */ 20 | public abstract class FileCallback extends Callback{ 21 | 22 | private String absoluteFilePath; 23 | 24 | public FileCallback(String absoluteFilePath){ 25 | if (TextUtils.isEmpty(absoluteFilePath)){ 26 | throw new RuntimeException("请先设置文件的目标路径!!"); 27 | } 28 | this.absoluteFilePath = absoluteFilePath; 29 | } 30 | 31 | @Override 32 | protected ResponseData onSuccess(InputStream inputStream) { 33 | ResponseData responseData = wrapResponseData(); 34 | File file = new File(absoluteFilePath); 35 | try { 36 | if (!file.getParentFile().exists()){ 37 | file.getParentFile().mkdir(); 38 | } 39 | writeInputStreamToFile(inputStream, file); 40 | if (params.cacheMode != RequestConfig.HttpCacheMode.NO_CACHE) { 41 | if (HttpRequestAgent.getInstance().getConfig().diskCache != null) { 42 | responseData.setData(file.getAbsolutePath()); 43 | responseData.setFromCache(true); 44 | boolean flag = HttpRequestAgent.getInstance().getConfig().diskCache.putData(cacheURL, responseData); 45 | if (!flag) { 46 | Log.d(getClass().getName(), "response success, but save cache failed !"); 47 | } 48 | } 49 | } 50 | ResponseData response = wrapResponseData(); 51 | response.setData(file); 52 | response(response); 53 | } catch (IOException e) { 54 | responseData.setParseSuccess(false); 55 | onError(e); 56 | } 57 | return responseData; 58 | } 59 | 60 | 61 | private void response(final ResponseData responseData){ 62 | HttpRequestAgent.getInstance().getConfig().mainHandler.post(new Runnable() { 63 | @Override 64 | public void run() { 65 | onResponse(responseData); 66 | } 67 | }); 68 | } 69 | 70 | /** 71 | * 72 | * @param responseData 73 | */ 74 | protected abstract void onResponse(ResponseData responseData); 75 | 76 | 77 | @Override 78 | protected boolean isCacheProcessSuccess(ResponseData data) { 79 | try{ 80 | if (!super.isCacheProcessSuccess(data)){ 81 | return false; 82 | } 83 | ResponseData responseData = convertCache(data); 84 | //判断文件的路径并且文件是否存在 85 | if (!TextUtils.isEmpty(data.getData())){ 86 | File file = new File(data.getData()); 87 | if (file.exists()){ 88 | if (data.getData().equals(absoluteFilePath)){ 89 | responseData.setData(file); 90 | }else{ 91 | File newFile = new File(absoluteFilePath); 92 | if (!newFile.getParentFile().exists()){ 93 | newFile.getParentFile().mkdir(); 94 | } 95 | FileOutputStream fos = new FileOutputStream(newFile); 96 | FileInputStream fis = new FileInputStream(file); 97 | try{ 98 | IOUtils.copy(fis, fos); 99 | responseData.setData(newFile); 100 | }finally { 101 | IOUtils.closeQuietly(fos); 102 | IOUtils.closeQuietly(fis); 103 | } 104 | } 105 | response(responseData); 106 | return true; 107 | } 108 | } 109 | return false; 110 | }catch (Exception e){ 111 | return false; 112 | } 113 | } 114 | 115 | @Override 116 | protected void onFailed(Exception exception) { 117 | response(wrapFailedData(exception)); 118 | } 119 | 120 | /** 121 | * 为防止文件过大,这个方法不提供使用 122 | * @param bytes 123 | */ 124 | @Deprecated 125 | @Override 126 | protected void onSuccess(byte[] bytes) { 127 | 128 | } 129 | 130 | private void writeInputStreamToFile(InputStream inputStream, File file) throws IOException{ 131 | FileOutputStream output = new FileOutputStream(file); 132 | try{ 133 | long count = 0; 134 | int n = 0; 135 | byte[] arr = new byte[4196]; 136 | while (-1 != (n = inputStream.read(arr))) { 137 | output.write(arr, 0, n); 138 | count += n; 139 | 140 | sendProgress(count, contentLength); 141 | } 142 | if (count > Integer.MAX_VALUE) { 143 | throw new IOException("input stream length error"); 144 | } 145 | 146 | }finally { 147 | IOUtils.closeQuietly(inputStream); 148 | IOUtils.closeQuietly(output); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/callback/ObjectCallback.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.callback; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.TypeReference; 5 | 6 | import cn.jclick.httpwrapper.request.HttpRequestAgent; 7 | 8 | /** 9 | * Created by XuYingjian on 16/1/15. 10 | */ 11 | public abstract class ObjectCallback extends Callback{ 12 | private TypeReference typeReference; 13 | 14 | public ObjectCallback(TypeReference typeReference){ 15 | this.typeReference = typeReference; 16 | } 17 | 18 | @Override 19 | protected boolean isCacheProcessSuccess(ResponseData data) { 20 | try{ 21 | if (!super.isCacheProcessSuccess(data)){ 22 | return false; 23 | } 24 | ResponseData responseData = convertCache(data); 25 | T result = processData(data.getData()); 26 | if (result == null){ 27 | return false; 28 | } 29 | responseData.setData(result); 30 | response(responseData); 31 | return true; 32 | }catch (Exception e){ 33 | return false; 34 | } 35 | } 36 | 37 | @Override 38 | protected void onSuccess(byte[] bytes) { 39 | ResponseData responseData = wrapResponseData(); 40 | T result = null; 41 | try { 42 | result = processData(string(bytes)); 43 | } catch (Exception e) { 44 | responseData.setParseSuccess(false); 45 | responseData.setDescription(e.getMessage()); 46 | } 47 | responseData.setData(result); 48 | response(responseData); 49 | } 50 | 51 | private T processData(String data) throws Exception{ 52 | T result; 53 | if (this.typeReference != null){ 54 | result = JSON.parseObject(data, this.typeReference); 55 | }else { 56 | result = com.alibaba.fastjson.JSONObject.parseObject(data, new TypeReference() { 57 | }); 58 | } 59 | return result; 60 | } 61 | 62 | private void response(final ResponseData responseData){ 63 | HttpRequestAgent.getInstance().getConfig().mainHandler.post(new Runnable() { 64 | @Override 65 | public void run() { 66 | onResponse(responseData); 67 | } 68 | }); 69 | } 70 | 71 | @Override 72 | protected void onFailed(Exception exception) { 73 | response(wrapFailedData(exception)); 74 | } 75 | 76 | protected abstract void onResponse(ResponseData responseData); 77 | } 78 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/callback/ResponseData.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.callback; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by XuYingjian on 16/1/12. 10 | */ 11 | public class ResponseData implements Serializable{ 12 | 13 | private T data; 14 | /** 15 | * 如果失败的话,描述失败原因 16 | */ 17 | private String description; 18 | /** 19 | * 是否为缓存数据 20 | */ 21 | private boolean fromCache; 22 | /** 23 | * 是否从server请求数据成功并且解析数据成功 24 | */ 25 | private boolean success; 26 | /** 27 | * 是否请求成功 28 | */ 29 | private boolean requestSuccess; 30 | /** 31 | * 是否解析成功。 32 | */ 33 | private boolean parseSuccess; 34 | /** 35 | * 请求的时间,若为缓存数据,则为缓存的请求时间 36 | */ 37 | private Date requestTime; 38 | /** 39 | * 请求结束的时间,若为缓存数据,则为缓存的请求结束时间 40 | */ 41 | private Date responseTime; 42 | 43 | /** 44 | * 请求返回的状态码 45 | */ 46 | private int statusCode; 47 | 48 | /** 49 | * 返回数据字节流长度 50 | */ 51 | private long contentLength; 52 | 53 | /** 54 | * 请求返回的headers 55 | */ 56 | private Map> headers; 57 | 58 | public T getData() { 59 | return data; 60 | } 61 | 62 | public void setData(T data) { 63 | this.data = data; 64 | } 65 | 66 | public String getDescription() { 67 | return description; 68 | } 69 | 70 | public void setDescription(String description) { 71 | this.description = description; 72 | } 73 | 74 | public boolean isFromCache() { 75 | return fromCache; 76 | } 77 | 78 | public void setFromCache(boolean fromCache) { 79 | this.fromCache = fromCache; 80 | } 81 | 82 | public boolean isSuccess() { 83 | return requestSuccess && parseSuccess; 84 | } 85 | 86 | public boolean isRequestSuccess() { 87 | return requestSuccess; 88 | } 89 | 90 | public void setRequestSuccess(boolean requestSuccess) { 91 | this.requestSuccess = requestSuccess; 92 | } 93 | 94 | public boolean isParseSuccess() { 95 | return isRequestSuccess() && parseSuccess; 96 | } 97 | 98 | public void setParseSuccess(boolean parseSuccess) { 99 | this.parseSuccess = parseSuccess; 100 | } 101 | 102 | public Date getRequestTime() { 103 | return requestTime; 104 | } 105 | 106 | public void setRequestTime(Date requestTime) { 107 | this.requestTime = requestTime; 108 | } 109 | 110 | public Date getResponseTime() { 111 | return responseTime; 112 | } 113 | 114 | public void setResponseTime(Date responseTime) { 115 | this.responseTime = responseTime; 116 | } 117 | 118 | public int getStatusCode() { 119 | return statusCode; 120 | } 121 | 122 | public void setStatusCode(int statusCode) { 123 | this.statusCode = statusCode; 124 | } 125 | 126 | public Map> getHeaders() { 127 | return headers; 128 | } 129 | 130 | public long getContentLength() { 131 | return contentLength; 132 | } 133 | 134 | public void setContentLength(long contentLength) { 135 | this.contentLength = contentLength; 136 | } 137 | 138 | public void setHeaders(Map> headers) { 139 | this.headers = headers; 140 | } 141 | 142 | @Override 143 | public String toString() { 144 | return "ResponseData{" + 145 | "description='" + description + '\'' + 146 | ", fromCache=" + fromCache + 147 | ", success=" + isSuccess() + 148 | ", requestSuccess=" + requestSuccess + 149 | ", parseSuccess=" + parseSuccess + 150 | ", requestTime=" + requestTime + 151 | ", responseTime=" + responseTime + 152 | ", statusCode=" + statusCode + 153 | ", headers=" + headers + 154 | ", data=" + data + 155 | '}'; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/callback/StringCallback.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.callback; 2 | 3 | import android.util.Log; 4 | 5 | import cn.jclick.httpwrapper.request.HttpRequestAgent; 6 | 7 | /** 8 | * Created by XuYingjian on 16/1/11. 9 | */ 10 | public abstract class StringCallback extends Callback{ 11 | 12 | 13 | @Override 14 | protected void onSuccess(byte[] bytes) { 15 | ResponseData responseData = wrapResponseData(); 16 | responseData.setData(string(bytes)); 17 | response(responseData); 18 | } 19 | 20 | private void response(final ResponseData responseData){ 21 | HttpRequestAgent.getInstance().getConfig().mainHandler.post(new Runnable() { 22 | @Override 23 | public void run() { 24 | onResponse(responseData); 25 | } 26 | }); 27 | } 28 | 29 | @Override 30 | protected boolean isCacheProcessSuccess(ResponseData data) { 31 | try{ 32 | if (!super.isCacheProcessSuccess(data)){ 33 | return false; 34 | } 35 | ResponseData responseData = convertCache(data); 36 | responseData.setData(data.getData()); 37 | response(responseData); 38 | return true; 39 | }catch (Exception e){ 40 | return false; 41 | } 42 | } 43 | 44 | @Override 45 | protected void onFailed(Exception exception) { 46 | response(wrapFailedData(exception)); 47 | } 48 | 49 | protected abstract void onResponse(ResponseData responseData); 50 | } 51 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/interceptor/HandlerInterceptor.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.interceptor; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import cn.jclick.httpwrapper.callback.ResponseData; 8 | import cn.jclick.httpwrapper.request.RequestParams; 9 | 10 | /** 11 | * Created by jclick on 16/1/6. 12 | */ 13 | public interface HandlerInterceptor { 14 | 15 | /** 16 | * 发起请求之前的回调 17 | * @param params 18 | * @return 是否继续执行请求 19 | */ 20 | boolean preHandler(RequestParams params); 21 | 22 | /** 23 | *请求成功后的回调 24 | * @param params 请求参数 25 | * @param statusCode 请求结束的状态码 26 | * @param headers 请求的headers 27 | */ 28 | void postSuccessHandler(RequestParams params, int statusCode, Map> headers); 29 | 30 | /** 31 | * 请求失败 32 | * @param exception 33 | */ 34 | void postFailedHandler(IOException exception); 35 | 36 | /** 37 | * 执行完毕Callback后的回调 38 | */ 39 | void afterCompletion(RequestParams params, ResponseData responseData); 40 | } 41 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/interceptor/LoggerInterceptor.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.interceptor; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.IOException; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import cn.jclick.httpwrapper.callback.ResponseData; 10 | import cn.jclick.httpwrapper.request.RequestParams; 11 | import cn.jclick.httpwrapper.utils.WrapperUtils; 12 | 13 | /** 14 | * Created by XuYingjian on 16/1/11. 15 | */ 16 | public class LoggerInterceptor implements HandlerInterceptor{ 17 | 18 | private String TAG = getClass().getName(); 19 | 20 | @Override 21 | public boolean preHandler(RequestParams params) { 22 | Log.i(TAG, "Request start ! the bare url is " + WrapperUtils.getBareUrl(params)); 23 | return true; 24 | } 25 | 26 | @Override 27 | public void postFailedHandler(IOException exception) { 28 | Log.e(TAG, "Request failed !", exception); 29 | } 30 | 31 | @Override 32 | public void postSuccessHandler(RequestParams params, int statusCode, Map> headers) { 33 | Log.i(TAG, "Request success !"); 34 | } 35 | 36 | @Override 37 | public void afterCompletion(RequestParams params, ResponseData responseData) { 38 | Log.i(TAG, "Request process completion ! the url is " + WrapperUtils.getUrlWithQueryString(params) 39 | + "\n response data is :" + responseData); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/request/HttpRequestAgent.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.request; 2 | 3 | import android.text.TextUtils; 4 | 5 | import java.io.IOException; 6 | import java.lang.ref.WeakReference; 7 | import java.nio.charset.Charset; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.Iterator; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.concurrent.ExecutorService; 15 | import java.util.concurrent.Executors; 16 | import java.util.concurrent.ThreadFactory; 17 | import java.util.concurrent.TimeUnit; 18 | import java.util.concurrent.atomic.AtomicInteger; 19 | 20 | import cn.jclick.httpwrapper.callback.ResponseData; 21 | import cn.jclick.httpwrapper.interceptor.HandlerInterceptor; 22 | import cn.jclick.httpwrapper.utils.WrapperUtils; 23 | import okhttp3.Call; 24 | import okhttp3.Callback; 25 | import okhttp3.ConnectionPool; 26 | import okhttp3.Interceptor; 27 | import okhttp3.MediaType; 28 | import okhttp3.OkHttpClient; 29 | import okhttp3.Request; 30 | import okhttp3.RequestBody; 31 | import okhttp3.Response; 32 | 33 | import static okhttp3.internal.Util.UTF_8; 34 | 35 | /** 36 | * Created by XuYingjian on 16/1/6. 37 | */ 38 | public class HttpRequestAgent { 39 | 40 | private static HttpRequestAgent INSTANCE; 41 | 42 | private OkHttpClient okHttpClient; 43 | 44 | private RequestConfig requestConfig; 45 | private final Map> allRequestMap = Collections 46 | .synchronizedMap(new HashMap>()); 47 | 48 | private ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() { 49 | private final AtomicInteger threadNumber = new AtomicInteger(1); 50 | 51 | @Override 52 | public Thread newThread(Runnable r) { 53 | Thread t = new Thread(Thread.currentThread().getThreadGroup(), r, "HttpRequestAgent" + threadNumber.getAndIncrement(), 0); 54 | if (t.isDaemon()) 55 | t.setDaemon(false); 56 | t.setPriority(Thread.NORM_PRIORITY); 57 | return t; 58 | } 59 | }); 60 | 61 | private HttpRequestAgent(){ 62 | } 63 | 64 | public static HttpRequestAgent getInstance(){ 65 | if (INSTANCE == null) { 66 | synchronized (HttpRequestAgent.class) { 67 | if (INSTANCE == null) { 68 | INSTANCE = new HttpRequestAgent(); 69 | } 70 | } 71 | } 72 | return INSTANCE; 73 | } 74 | 75 | public void init(RequestConfig config){ 76 | if (config == null){ 77 | throw new NullPointerException("RequestConfig Can not Null"); 78 | } 79 | this.requestConfig = config; 80 | okHttpClient = new OkHttpClient().newBuilder().connectTimeout(config.connectionTimeOut, TimeUnit.MILLISECONDS) 81 | .addInterceptor(new RetryInterceptor(config.maxRetries)) 82 | .connectionPool(new ConnectionPool(config.maxConnections, 5, TimeUnit.SECONDS)) 83 | .build(); 84 | } 85 | 86 | public void executeRequest(RequestParams params, final cn.jclick.httpwrapper.callback.Callback callback){ 87 | executorService.execute(new RequestThread(params, callback)); 88 | } 89 | 90 | private Request buildRequest(RequestParams params, Request.Builder builder, String url){ 91 | Request request = null; 92 | if (params.requestMethod == RequestParams.RequestMethod.RequestMethodGet){ 93 | url = WrapperUtils.getUrlWithQueryString(params.urlEncodeEnable == null ? getConfig().urlEncodeEnable : params.urlEncodeEnable, url, params); 94 | request = builder.url(url).get().build(); 95 | }else{ 96 | builder = builder.url(url); 97 | RequestBody requestBody = RequestBuilder.buildRequestBody(params); 98 | switch (params.requestMethod){ 99 | case RequestMethodDelete: 100 | builder = builder.delete(requestBody); 101 | break; 102 | case RequestMethodHead: 103 | builder = builder.head(); 104 | break; 105 | case RequestMethodPatch: 106 | builder = builder.patch(requestBody); 107 | break; 108 | case RequestMethodPost: 109 | builder = builder.post(requestBody); 110 | break; 111 | case RequestMethodPut: 112 | builder = builder.put(requestBody); 113 | break; 114 | } 115 | request = builder.build(); 116 | } 117 | return request; 118 | } 119 | 120 | public synchronized void interruptRequestByTag(Object ...tags){ 121 | 122 | for (Object obj : tags){ 123 | if (allRequestMap.containsKey(obj)){ 124 | removeByTag(obj); 125 | } 126 | } 127 | } 128 | 129 | public void interruptAllRequest(){ 130 | if (okHttpClient != null){ 131 | okHttpClient.dispatcher().cancelAll(); 132 | allRequestMap.clear(); 133 | } 134 | } 135 | 136 | public RequestConfig getConfig() { 137 | return requestConfig; 138 | } 139 | 140 | private synchronized void addCall(Call call, Object tag){ 141 | 142 | if (tag == null){ 143 | return; 144 | } 145 | if (call == null || call.isCanceled()){ 146 | return; 147 | } 148 | List requestList; 149 | // Add request to request map 150 | synchronized (allRequestMap) { 151 | requestList = allRequestMap.get(tag); 152 | if (requestList == null) { 153 | requestList = Collections.synchronizedList(new LinkedList()); 154 | allRequestMap.put(tag, requestList); 155 | } 156 | } 157 | 158 | requestList.add(call); 159 | 160 | Iterator iterator = requestList.iterator(); 161 | while (iterator.hasNext()) { 162 | Call targetCall = iterator.next(); 163 | if (targetCall.isCanceled()) { 164 | iterator.remove(); 165 | } 166 | } 167 | } 168 | 169 | private synchronized void removeByTag(Object tag){ 170 | if (tag == null){ 171 | return; 172 | } 173 | synchronized (allRequestMap){ 174 | List list = allRequestMap.get(tag); 175 | if (list == null || list.isEmpty()){ 176 | return; 177 | } 178 | Iterator iterator = list.iterator(); 179 | while (iterator.hasNext()) { 180 | Call targetCall = iterator.next(); 181 | targetCall.cancel(); 182 | iterator.remove(); 183 | } 184 | } 185 | } 186 | 187 | private synchronized void removeCallByTag(Object tag, Call call){ 188 | if (tag == null){ 189 | if (call != null){ 190 | removeCall(call); 191 | } 192 | return; 193 | } 194 | if (call == null){ 195 | removeByTag(tag); 196 | return; 197 | } 198 | call.cancel(); 199 | synchronized (allRequestMap){ 200 | List list = allRequestMap.get(tag); 201 | if (list == null || list.isEmpty()){ 202 | return; 203 | } 204 | list.remove(call); 205 | } 206 | } 207 | 208 | private synchronized void removeCall(Call call){ 209 | if (call == null){ 210 | return; 211 | } 212 | call.cancel(); 213 | synchronized (allRequestMap){ 214 | for (Object tag : allRequestMap.keySet()){ 215 | List requestList = allRequestMap.get(tag); 216 | for (Call c : requestList){ 217 | if (c.isCanceled()){ 218 | requestList.remove(c); 219 | }else if (c == call){ 220 | requestList.remove(c); 221 | } 222 | } 223 | } 224 | } 225 | } 226 | 227 | private final class RetryInterceptor implements Interceptor { 228 | 229 | private int maxRetry; 230 | 231 | public RetryInterceptor(int maxRetry){ 232 | this.maxRetry = maxRetry; 233 | } 234 | 235 | @Override 236 | public Response intercept(Interceptor.Chain chain) throws IOException { 237 | Request request = chain.request(); 238 | 239 | // try the request 240 | Response response = chain.proceed(request); 241 | 242 | int tryCount = 0; 243 | while (!response.isSuccessful() && tryCount < maxRetry) { 244 | tryCount++; 245 | response = chain.proceed(request); 246 | } 247 | return response; 248 | } 249 | } 250 | 251 | private class RequestThread implements Runnable{ 252 | private RequestParams params; 253 | private WeakReference weakCallback; 254 | 255 | public RequestThread(RequestParams params, cn.jclick.httpwrapper.callback.Callback callback){ 256 | this.params = params; 257 | weakCallback = new WeakReference(callback); 258 | } 259 | 260 | @Override 261 | public void run() { 262 | Request.Builder builder = new Request.Builder(); 263 | if (params.tag != null){ 264 | builder = builder.tag(params.tag); 265 | } 266 | if (params.requestHeaders != null){ 267 | for (String key : params.requestHeaders.keySet()){ 268 | builder = builder.addHeader(key, params.requestHeaders.get(key)); 269 | } 270 | } 271 | final Object tag = params.tag; 272 | String baseUrl = null, url; 273 | if (!TextUtils.isEmpty(params.baseUrl)){ 274 | baseUrl = params.baseUrl; 275 | }else{ 276 | if (requestConfig != null){ 277 | baseUrl = requestConfig.baseUrl; 278 | } 279 | } 280 | url = params.url; 281 | if (!TextUtils.isEmpty(baseUrl)){ 282 | url = baseUrl.concat(url); 283 | } 284 | 285 | List interceptorList = requestConfig.interceptorList; 286 | if (!preHandler(interceptorList)){ 287 | return; 288 | } 289 | 290 | if (weakCallback.get() != null){ 291 | boolean isNeedRequest = weakCallback.get().beforeStart(params); 292 | if (!isNeedRequest){ 293 | return; 294 | } 295 | } 296 | final Request request = buildRequest(params, builder, url); 297 | final Call call = okHttpClient.newCall(request); 298 | addCall(call, tag); 299 | call.enqueue(new Callback() { 300 | @Override 301 | public void onFailure(Call call, IOException e) { 302 | removeCallByTag(tag, call); 303 | if (weakCallback.get() != null){ 304 | weakCallback.get().onError(e); 305 | } 306 | } 307 | 308 | @Override 309 | public void onResponse(Call call, Response response) throws IOException { 310 | for (HandlerInterceptor interceptor : requestConfig.interceptorList){ 311 | interceptor.postSuccessHandler(params, response.code(), response.headers().toMultimap()); 312 | } 313 | if(weakCallback.get() != null){ 314 | MediaType mediaType = response.body().contentType(); 315 | Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8; 316 | ResponseData data = weakCallback.get().onResponse(response.code(), response.headers().toMultimap(), charset, response.body().byteStream(), response.body().contentLength()); 317 | for (HandlerInterceptor interceptor : requestConfig.interceptorList){ 318 | interceptor.afterCompletion(params, data); 319 | } 320 | } 321 | removeCallByTag(tag, call); 322 | } 323 | }); 324 | } 325 | 326 | private boolean preHandler(List list){ 327 | 328 | boolean isSuccess = true; 329 | for (HandlerInterceptor interceptor : list){ 330 | if (!isSuccess){ 331 | isSuccess = interceptor.preHandler(params); 332 | }else{ 333 | interceptor.preHandler(params); 334 | } 335 | } 336 | return isSuccess; 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/request/RequestBuilder.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.request; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | import java.io.File; 7 | import java.net.FileNameMap; 8 | import java.net.URLConnection; 9 | 10 | import okhttp3.Headers; 11 | import okhttp3.MediaType; 12 | import okhttp3.MultipartBody; 13 | import okhttp3.Request; 14 | import okhttp3.RequestBody; 15 | 16 | /** 17 | * Created by apple on 16/1/10. 18 | */ 19 | public class RequestBuilder { 20 | 21 | public static RequestBody buildRequestBody(RequestParams params){ 22 | if (params == null){ 23 | return null; 24 | } 25 | if (params.requestParams == null || params.requestParams.isEmpty()){ 26 | return RequestBody.create(MediaType.parse("text/x-markdown; charset=utf-8"), ""); 27 | } 28 | if (params.uploadFiles != null && params.uploadFiles.length > 0){ 29 | return buildFileRequestBody(params); 30 | }else{ 31 | if (params.mediaType.equals(MultipartBody.FORM.toString())){ 32 | MultipartBody.Builder builder = new MultipartBody.Builder() 33 | .setType(MultipartBody.FORM); 34 | if (params.requestParams != null){ 35 | for (String key : params.requestParams.keySet()){ 36 | builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""), 37 | RequestBody.create(null, params.requestParams.get(key))); 38 | } 39 | } 40 | return builder.build(); 41 | }else{ 42 | JSONObject json = new JSONObject(); 43 | for (String key : params.requestParams.keySet()){ 44 | try { 45 | json.put(key, params.requestParams.get(key)); 46 | RequestBody requestBody = RequestBody.create(MediaType.parse(params.mediaType), json.toString()); 47 | return requestBody; 48 | } catch (JSONException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | return null; 53 | } 54 | } 55 | } 56 | 57 | private static RequestBody buildFileRequestBody(RequestParams params){ 58 | MultipartBody.Builder builder = new MultipartBody.Builder() 59 | .setType(MultipartBody.FORM); 60 | for (int i = 0; i < params.uploadFiles.length; i ++){ 61 | File file = params.uploadFiles[i]; 62 | FileNameMap map = URLConnection.getFileNameMap(); 63 | String contentType = map.getContentTypeFor(file.getAbsolutePath()); 64 | if (contentType == null){ 65 | contentType = "application/octet-stream"; 66 | } 67 | RequestBody fileRequestBody = RequestBody.create(MediaType.parse(contentType), file); 68 | builder.addFormDataPart("file" + i, file.getName(), fileRequestBody); 69 | } 70 | if (params.requestParams != null){ 71 | for (String key : params.requestParams.keySet()){ 72 | builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + key + "\""), 73 | RequestBody.create(null, params.requestParams.get(key))); 74 | } 75 | } 76 | return builder.build(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/request/RequestConfig.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.request; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import cn.jclick.httpwrapper.cache.IDiskCache; 12 | import cn.jclick.httpwrapper.cache.LruDiskCache; 13 | import cn.jclick.httpwrapper.cache.Md5FileNameGenerator; 14 | import cn.jclick.httpwrapper.interceptor.HandlerInterceptor; 15 | import cn.jclick.httpwrapper.interceptor.LoggerInterceptor; 16 | import cn.jclick.httpwrapper.utils.StorageUtils; 17 | 18 | /** 19 | * Created by XuYingjian on 16/1/6. 20 | */ 21 | public final class RequestConfig { 22 | 23 | private static final LoggerInterceptor loggerInterceptor = new LoggerInterceptor(); 24 | private static final long DEFAULT_CACHE_SECONDS = 10 * 60; 25 | private static final HttpCacheMode DEFAULT_CACHE_MODE = HttpCacheMode.NO_CACHE; 26 | 27 | public final String baseUrl; 28 | public final long connectionTimeOut; 29 | public final int maxConnections; 30 | public final int maxRetries; 31 | 32 | public IDiskCache diskCache; 33 | public final List interceptorList; 34 | public final boolean urlEncodeEnable; 35 | public final long cacheTimeInSeconds; 36 | public final HttpCacheMode cacheMode; 37 | public final Context context; 38 | public final Handler mainHandler; 39 | public final boolean logEnable; 40 | 41 | private RequestConfig(final Builder builder) { 42 | 43 | this.baseUrl = builder.baseUrl; 44 | this.connectionTimeOut = builder.connectionTimeOut; 45 | this.interceptorList = builder.interceptorList; 46 | this.urlEncodeEnable = builder.urlEncodeEnable; 47 | this.maxConnections = builder.maxConnections; 48 | this.maxRetries = builder.maxRetries; 49 | if (builder.cacheTimeInSeconds < 0){ 50 | this.cacheTimeInSeconds = DEFAULT_CACHE_SECONDS; 51 | }else{ 52 | this.cacheTimeInSeconds = builder.cacheTimeInSeconds; 53 | } 54 | if(builder.cacheMode == null){ 55 | this.cacheMode = DEFAULT_CACHE_MODE; 56 | }else{ 57 | this.cacheMode = builder.cacheMode; 58 | } 59 | this.context = builder.context; 60 | this.mainHandler = new Handler(context.getMainLooper()); 61 | if(builder.diskCache == null){ 62 | File cacheFileDir = StorageUtils.getCacheDirectory(this.context); 63 | try { 64 | this.diskCache = new LruDiskCache(cacheFileDir, new Md5FileNameGenerator(), 100 * 1024 * 1024); 65 | } catch (IOException e) { 66 | e.printStackTrace(); 67 | } 68 | }else{ 69 | this.diskCache = builder.diskCache; 70 | } 71 | this.logEnable = builder.logEnable; 72 | } 73 | 74 | public static class Builder{ 75 | 76 | private String baseUrl; 77 | private long connectionTimeOut; 78 | private int maxConnections; 79 | private int maxRetries; 80 | private long cacheTimeInSeconds; 81 | private HttpCacheMode cacheMode; 82 | private List interceptorList = new ArrayList<>(); 83 | private IDiskCache diskCache; 84 | private boolean urlEncodeEnable = true; 85 | private Context context; 86 | 87 | private boolean logEnable; 88 | 89 | public Builder(Context context){ 90 | this.context = context.getApplicationContext(); 91 | } 92 | 93 | public Builder baseUrl(String baseUrl){ 94 | this.baseUrl = baseUrl; 95 | return this; 96 | } 97 | 98 | public Builder maxConnections(int maxConnections){ 99 | this.maxConnections = maxConnections; 100 | return this; 101 | } 102 | 103 | public Builder maxRetries(int maxRetries){ 104 | this.maxRetries = maxRetries; 105 | return this; 106 | } 107 | 108 | public Builder connectionTimeOut(long millSeconds){ 109 | this.connectionTimeOut = millSeconds; 110 | return this; 111 | } 112 | 113 | public Builder addInterceptor(HandlerInterceptor interceptor){ 114 | if (!interceptorList.contains(interceptor)){ 115 | interceptorList.add(interceptor); 116 | } 117 | return this; 118 | } 119 | 120 | public Builder diskCache(IDiskCache diskCache){ 121 | this.diskCache = diskCache; 122 | return this; 123 | } 124 | 125 | public Builder cacheTimeInSeconds(long cacheTimeInSeconds){ 126 | this.cacheTimeInSeconds = cacheTimeInSeconds; 127 | return this; 128 | } 129 | 130 | public Builder logEnable(boolean logEnable){ 131 | this.logEnable = logEnable; 132 | if (this.logEnable){ 133 | interceptorList.add(loggerInterceptor); 134 | }else{ 135 | interceptorList.remove(loggerInterceptor); 136 | } 137 | return this; 138 | } 139 | 140 | public Builder cacheMode(HttpCacheMode cacheMode){ 141 | this.cacheMode = cacheMode; 142 | return this; 143 | } 144 | 145 | public RequestConfig build(){ 146 | return new RequestConfig(this); 147 | } 148 | 149 | } 150 | 151 | public enum HttpCacheMode{ 152 | NO_CACHE,//不使用缓存 153 | ALWAYS_CACHE,//缓存时间内,不发请求,直接返回缓存结果 154 | CACHE_FIRST,//优先返回缓存结果,然后发送请求.(总共返回二次数据) 155 | FAILED_SHOW_CACHE,//请求失败后展示缓存 156 | CACHE_WHEN_NO_NETWORK//没有网络的时候展示缓存 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/request/RequestParams.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.request; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import cn.jclick.httpwrapper.callback.Callback; 9 | import cn.jclick.httpwrapper.interceptor.HandlerInterceptor; 10 | import okhttp3.MediaType; 11 | 12 | /** 13 | * Created by XuYingjian on 16/1/6. 14 | */ 15 | public final class RequestParams { 16 | 17 | private static final String DEFAULT_MEDIA_TYPE = "multipart/form-data"; 18 | 19 | public final String url; 20 | public final String baseUrl; 21 | public final Object tag; 22 | public final long connectionTimeOut; 23 | public final RequestMethod requestMethod; 24 | public final Map requestParams; 25 | public final Map requestHeaders; 26 | public final File[] uploadFiles; 27 | public final List interceptorList; 28 | public final Callback callBack; 29 | public final RequestConfig.HttpCacheMode cacheMode; 30 | public final long cacheTimeInSeconds; 31 | 32 | public final String mediaType; 33 | 34 | public final Boolean urlEncodeEnable; 35 | 36 | private RequestParams(final Builder builder) { 37 | 38 | this.url = builder.url; 39 | this.baseUrl = builder.baseUrl; 40 | this.tag = builder.tag; 41 | this.uploadFiles = builder.uploadFiles; 42 | this.connectionTimeOut = builder.connectionTimeOut; 43 | this.requestMethod = builder.requestMethod; 44 | this.requestParams = builder.requestParams; 45 | this.requestHeaders = builder.requestHeaders; 46 | this.interceptorList = builder.interceptorList; 47 | this.callBack = builder.callBack; 48 | this.urlEncodeEnable = builder.urlEncodeEnable; 49 | if (builder.mediaType == null){ 50 | this.mediaType = DEFAULT_MEDIA_TYPE; 51 | }else{ 52 | this.mediaType = builder.mediaType; 53 | } 54 | if (builder.cacheMode == null){ 55 | cacheMode = HttpRequestAgent.getInstance().getConfig().cacheMode; 56 | }else{ 57 | cacheMode = builder.cacheMode; 58 | } 59 | 60 | if (builder.cacheTimeInSeconds <= 0){ 61 | this.cacheTimeInSeconds = HttpRequestAgent.getInstance().getConfig().cacheTimeInSeconds; 62 | }else{ 63 | this.cacheTimeInSeconds = builder.cacheTimeInSeconds; 64 | } 65 | } 66 | 67 | public static class Builder{ 68 | 69 | private String url; 70 | private String baseUrl; 71 | private Object tag; 72 | private long connectionTimeOut; 73 | private RequestMethod requestMethod = RequestMethod.RequestMethodPost; 74 | private Map requestParams; 75 | private Map requestHeaders; 76 | private File[] uploadFiles; 77 | private List interceptorList = new ArrayList<>(); 78 | private RequestConfig.HttpCacheMode cacheMode; 79 | 80 | private String mediaType; 81 | private Boolean urlEncodeEnable; 82 | 83 | private Callback callBack; 84 | private long cacheTimeInSeconds; 85 | 86 | public Builder url(String url){ 87 | this.url = url; 88 | return this; 89 | } 90 | 91 | public Builder baseUrl(String baseUrl){ 92 | this.baseUrl = baseUrl; 93 | return this; 94 | } 95 | 96 | public Builder tag(Object tag){ 97 | this.tag = tag; 98 | return this; 99 | } 100 | 101 | public Builder mediaType(String mediaType){ 102 | this.mediaType = mediaType; 103 | return this; 104 | } 105 | 106 | public Builder uploadFiles(File ...files){ 107 | this.uploadFiles = files; 108 | return this; 109 | } 110 | 111 | public Builder urlEncodeEnable(boolean enable){ 112 | this.urlEncodeEnable = enable; 113 | return this; 114 | } 115 | 116 | public Builder connectionTimeOut(long millSeconds){ 117 | this.connectionTimeOut = millSeconds; 118 | return this; 119 | } 120 | 121 | public Builder callback(Callback callBack){ 122 | this.callBack = callBack; 123 | return this; 124 | } 125 | 126 | public Builder addInterceptor(HandlerInterceptor interceptor){ 127 | if (!interceptorList.contains(interceptor)){ 128 | interceptorList.add(interceptor); 129 | } 130 | return this; 131 | } 132 | 133 | public Builder requestParams(Map requestParams){ 134 | this.requestParams = requestParams; 135 | return this; 136 | } 137 | 138 | public Builder requestHeaders(Map requestHeaders){ 139 | this.requestHeaders = requestHeaders; 140 | return this; 141 | } 142 | 143 | public Builder get(){ 144 | this.requestMethod = RequestMethod.RequestMethodGet; 145 | return this; 146 | } 147 | 148 | public Builder post(){ 149 | this.requestMethod = RequestMethod.RequestMethodPost; 150 | return this; 151 | } 152 | 153 | public Builder put(){ 154 | this.requestMethod = RequestMethod.RequestMethodPut; 155 | return this; 156 | } 157 | 158 | public Builder delete(){ 159 | this.requestMethod = RequestMethod.RequestMethodDelete; 160 | return this; 161 | } 162 | 163 | public Builder cacheMode(RequestConfig.HttpCacheMode cacheMode){ 164 | this.cacheMode = cacheMode; 165 | return this; 166 | } 167 | 168 | public Builder cacheTimeInSeconds(long cacheTimeInSeconds){ 169 | this.cacheTimeInSeconds = cacheTimeInSeconds; 170 | return this; 171 | } 172 | 173 | public Builder head(){ 174 | this.requestMethod = RequestMethod.RequestMethodHead; 175 | return this; 176 | } 177 | 178 | public Builder patch(){ 179 | this.requestMethod = RequestMethod.RequestMethodPatch; 180 | return this; 181 | } 182 | 183 | public RequestParams build(){ 184 | return new RequestParams(this); 185 | } 186 | } 187 | 188 | public enum RequestMethod{ 189 | RequestMethodGet, RequestMethodPost, RequestMethodPut, RequestMethodDelete, RequestMethodHead, RequestMethodPatch 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/utils/Charsets.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cn.jclick.httpwrapper.utils; 18 | 19 | import java.nio.charset.Charset; 20 | import java.nio.charset.UnsupportedCharsetException; 21 | 22 | /** 23 | * Charsets required of every implementation of the Java platform. 24 | * 25 | * From the Java documentation 26 | * Standard charsets: 27 | *

28 | * Every implementation of the Java platform is required to support the following character encodings. Consult the 29 | * release documentation for your implementation to see if any other encodings are supported. Consult the release 30 | * documentation for your implementation to see if any other encodings are supported. 31 | *

32 | * 33 | * 48 | * 49 | * @see Standard charsets 50 | * @since 2.3 51 | * @version $Id: Charsets.java 1311751 2012-04-10 14:26:21Z ggregory $ 52 | */ 53 | public class Charsets { 54 | // 55 | // This class should only contain Charset instances for required encodings. This guarantees that it will load 56 | // correctly and without delay on all Java platforms. 57 | // 58 | 59 | /** 60 | * Returns the given Charset or the default Charset if the given Charset is null. 61 | * 62 | * @param charset 63 | * A charset or null. 64 | * @return the given Charset or the default Charset if the given Charset is null 65 | */ 66 | public static Charset toCharset(Charset charset) { 67 | return charset == null ? Charset.defaultCharset() : charset; 68 | } 69 | 70 | /** 71 | * Returns a Charset for the named charset. If the name is null, return the default Charset. 72 | * 73 | * @param charset 74 | * The name of the requested charset, may be null. 75 | * @return a Charset for the named charset 76 | * @throws UnsupportedCharsetException 77 | * If the named charset is unavailable 78 | */ 79 | public static Charset toCharset(String charset) { 80 | return charset == null ? Charset.defaultCharset() : Charset.forName(charset); 81 | } 82 | 83 | /** 84 | * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.

85 | *

86 | * Every implementation of the Java platform is required to support this character encoding. 87 | *

88 | * 89 | * @see Standard charsets 90 | */ 91 | public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); 92 | 93 | /** 94 | *

95 | * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. 96 | *

97 | *

98 | * Every implementation of the Java platform is required to support this character encoding. 99 | *

100 | * 101 | * @see Standard charsets 102 | */ 103 | public static final Charset US_ASCII = Charset.forName("US-ASCII"); 104 | 105 | /** 106 | *

107 | * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark 108 | * (either order accepted on input, big-endian used on output) 109 | *

110 | *

111 | * Every implementation of the Java platform is required to support this character encoding. 112 | *

113 | * 114 | * @see Standard charsets 115 | */ 116 | public static final Charset UTF_16 = Charset.forName("UTF-16"); 117 | 118 | /** 119 | *

120 | * Sixteen-bit Unicode Transformation Format, big-endian byte order. 121 | *

122 | *

123 | * Every implementation of the Java platform is required to support this character encoding. 124 | *

125 | * 126 | * @see Standard charsets 127 | */ 128 | public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); 129 | 130 | /** 131 | *

132 | * Sixteen-bit Unicode Transformation Format, little-endian byte order. 133 | *

134 | *

135 | * Every implementation of the Java platform is required to support this character encoding. 136 | *

137 | * 138 | * @see Standard charsets 139 | */ 140 | public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); 141 | 142 | /** 143 | *

144 | * Eight-bit Unicode Transformation Format. 145 | *

146 | *

147 | * Every implementation of the Java platform is required to support this character encoding. 148 | *

149 | * 150 | * @see Standard charsets 151 | */ 152 | public static final Charset UTF_8 = Charset.forName("UTF-8"); 153 | } 154 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/utils/StorageUtils.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2011-2013 Sergey Tarasevich 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | *******************************************************************************/ 16 | package cn.jclick.httpwrapper.utils; 17 | 18 | import android.content.Context; 19 | import android.content.pm.PackageManager; 20 | import android.os.Environment; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | 25 | import static android.os.Environment.MEDIA_MOUNTED; 26 | 27 | /** 28 | * Provides application storage paths 29 | * 30 | * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) 31 | * @since 1.0.0 32 | */ 33 | public final class StorageUtils { 34 | 35 | public static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE"; 36 | private static final String INDIVIDUAL_DIR_NAME = "uil-images"; 37 | 38 | private StorageUtils() { 39 | } 40 | 41 | /** 42 | * Returns application cache directory. Cache directory will be created on SD card 43 | * ("/Android/data/[app_package_name]/cache") if card is mounted and app has appropriate permission. Else - 44 | * Android defines cache directory on device's file system. 45 | * 46 | * @param context Application context 47 | * @return Cache {@link File directory} 48 | */ 49 | public static File getCacheDirectory(Context context) { 50 | File appCacheDir = null; 51 | if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) { 52 | appCacheDir = getExternalCacheDir(context); 53 | } 54 | if (appCacheDir == null) { 55 | appCacheDir = context.getCacheDir(); 56 | } 57 | if (appCacheDir == null) { 58 | } 59 | return appCacheDir; 60 | } 61 | 62 | /** 63 | * Returns individual application cache directory (for only image caching from ImageLoader). Cache directory will be 64 | * created on SD card ("/Android/data/[app_package_name]/cache/uil-images") if card is mounted and app has 65 | * appropriate permission. Else - Android defines cache directory on device's file system. 66 | * 67 | * @param context Application context 68 | * @return Cache {@link File directory} 69 | */ 70 | public static File getIndividualCacheDirectory(Context context) { 71 | File cacheDir = getCacheDirectory(context); 72 | File individualCacheDir = new File(cacheDir, INDIVIDUAL_DIR_NAME); 73 | if (!individualCacheDir.exists()) { 74 | if (!individualCacheDir.mkdir()) { 75 | individualCacheDir = cacheDir; 76 | } 77 | } 78 | return individualCacheDir; 79 | } 80 | 81 | /** 82 | * Returns specified application cache directory. Cache directory will be created on SD card by defined path if card 83 | * is mounted and app has appropriate permission. Else - Android defines cache directory on device's file system. 84 | * 85 | * @param context Application context 86 | * @param cacheDir Cache directory path (e.g.: "AppCacheDir", "AppDir/cache/images") 87 | * @return Cache {@link File directory} 88 | */ 89 | public static File getOwnCacheDirectory(Context context, String cacheDir) { 90 | File appCacheDir = null; 91 | if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) { 92 | appCacheDir = new File(Environment.getExternalStorageDirectory(), cacheDir); 93 | } 94 | if (appCacheDir == null || (!appCacheDir.exists() && !appCacheDir.mkdirs())) { 95 | appCacheDir = context.getCacheDir(); 96 | } 97 | return appCacheDir; 98 | } 99 | 100 | private static File getExternalCacheDir(Context context) { 101 | File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data"); 102 | File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache"); 103 | if (!appCacheDir.exists()) { 104 | if (!appCacheDir.mkdirs()) { 105 | return null; 106 | } 107 | try { 108 | new File(appCacheDir, ".http").createNewFile(); 109 | } catch (IOException e) { 110 | } 111 | } 112 | return appCacheDir; 113 | } 114 | 115 | public static boolean hasExternalStoragePermission(Context context) { 116 | int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION); 117 | return perm == PackageManager.PERMISSION_GRANTED; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/java/cn/jclick/httpwrapper/utils/WrapperUtils.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper.utils; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | 9 | import java.net.URI; 10 | import java.net.URL; 11 | import java.net.URLDecoder; 12 | 13 | import cn.jclick.httpwrapper.request.HttpRequestAgent; 14 | import cn.jclick.httpwrapper.request.RequestConfig; 15 | import cn.jclick.httpwrapper.request.RequestParams; 16 | 17 | /** 18 | * Created by apple on 16/1/9. 19 | */ 20 | public class WrapperUtils { 21 | 22 | public static final String TAG = "WrapperUtils"; 23 | 24 | public static boolean isOnline(Context context) { 25 | ConnectivityManager cm = 26 | (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 27 | NetworkInfo netInfo = cm.getActiveNetworkInfo(); 28 | return netInfo != null && netInfo.isConnectedOrConnecting(); 29 | } 30 | 31 | public static String getUrlWithQueryString(RequestParams params){ 32 | RequestConfig config = HttpRequestAgent.getInstance().getConfig(); 33 | boolean shouldEncodeUrl = (params.urlEncodeEnable == null ? config.urlEncodeEnable : params.urlEncodeEnable); 34 | 35 | return getUrlWithQueryString(shouldEncodeUrl, getBareUrl(params), params); 36 | } 37 | 38 | public static String getBareUrl(RequestParams params){ 39 | if (params == null){ 40 | return null; 41 | } 42 | RequestConfig config = HttpRequestAgent.getInstance().getConfig(); 43 | String baseUrl = null, url; 44 | if (!TextUtils.isEmpty(params.baseUrl)){ 45 | baseUrl = params.baseUrl; 46 | }else{ 47 | if (config != null){ 48 | baseUrl = config.baseUrl; 49 | } 50 | } 51 | url = params.url; 52 | if (!TextUtils.isEmpty(baseUrl)){ 53 | url = baseUrl.concat(url); 54 | } 55 | return url; 56 | } 57 | 58 | public static String getUrlWithQueryString(boolean shouldEncodeUrl, String url, RequestParams params) { 59 | if (url == null) 60 | return null; 61 | if (shouldEncodeUrl) { 62 | try { 63 | String decodedURL = URLDecoder.decode(url, "UTF-8"); 64 | URL _url = new URL(decodedURL); 65 | URI _uri = new URI(_url.getProtocol(), _url.getUserInfo(), _url.getHost(), _url.getPort(), _url.getPath(), _url.getQuery(), _url.getRef()); 66 | url = _uri.toASCIIString(); 67 | } catch (Exception ex) { 68 | Log.e(TAG, "getUrlWithQueryString encoding URL", ex); 69 | } 70 | } 71 | 72 | if (params.requestParams != null && !params.requestParams.isEmpty()) { 73 | String paramString = ""; 74 | for (String key : params.requestParams.keySet()){ 75 | paramString += key + "=" + params.requestParams.get(key).toString(); 76 | } 77 | if (!paramString.equals("") && !paramString.equals("?")) { 78 | url += url.contains("?") ? "&" : "?"; 79 | url += paramString; 80 | } 81 | } 82 | return url; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /HttpWrapper/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | HttpWrapper 3 | 4 | -------------------------------------------------------------------------------- /HttpWrapper/src/test/java/cn/jclick/httpwrapper/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.httpwrapper; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ### JWHttpWrapper 主要功能 3 | 1. 基于OKHttp的网络库,底层依赖很小,只有一个文件依赖于OKhttp, 可以快速切换为其它网络库。 4 | 2. 支持本地文件缓存以及多样化的缓存方式。 5 | 3. 自定义Interceptor。可以拦截数据进行处理 6 | 4. 请求返回数据处理在单独现成进行,提供的回调方法在UI现成进行,可以直接用返回的数据更新UI 7 | 5. 支持发送请求的时候设置Tag, 然后单个或者批量取消请求 8 | 6. 支持进度提示 9 | 10 | ### 使用说明 11 | - 初始化 12 | 13 | ```java 14 | RequestConfig config = new RequestConfig.Builder(this).logEnable(true).cacheMode(RequestConfig.HttpCacheMode.NO_CACHE) 15 | .baseUrl("http://ip.taobao.com/").cacheTimeInSeconds(3 * 60).connectionTimeOut(30 *1000).build(); 16 | HttpRequestAgent.getInstance().init(config); 17 | ``` 18 | 19 | - 发送请求 20 | ```java 21 | RequestParams params = new RequestParams.Builder().requestParams(requestParams).url("service/getIpInfo.php").cacheMode(RequestConfig.HttpCacheMode.ALWAYS_CACHE).post().build(); 22 | HttpRequestAgent.getInstance().executeRequest(params, callback); 23 | ``` 24 | - 缓存设置 25 | ```java 26 | ALWAYS_CACHE,//缓存时间内,不发请求,直接返回缓存结果 27 | CACHE_FIRST,//优先返回缓存结果,然后发送请求.(总共返回二次数据) 28 | FAILED_SHOW_CACHE,//请求失败后展示缓存 29 | CACHE_WHEN_NO_NETWORK//没有网络的时候展示缓存 30 | ``` 31 | - Interceptor使用 32 | ```java 33 | RequestConfig config = new RequestConfig.Builder(this).logEnable(true).cacheMode(RequestConfig.HttpCacheMode.NO_CACHE) 34 | .baseUrl("http://ip.taobao.com/").addInterceptor(new HandlerInterceptor() { 35 | @Override 36 | public boolean preHandler(RequestParams params) { 37 | //TODO 请求之前的拦截 返回值决定是否继续请求 38 | return true; 39 | } 40 | 41 | @Override 42 | public void postSuccessHandler(RequestParams params, int statusCode, Map> headers) { 43 | //TODO 请求成功的拦截 44 | } 45 | 46 | @Override 47 | public void postFailedHandler(IOException exception) { 48 | //TODO 请求失败的拦截器 49 | } 50 | 51 | @Override 52 | public void afterCompletion(RequestParams params, ResponseData responseData) { 53 | //TODO 请求逻辑处理完毕的回调 54 | } 55 | }).cacheTimeInSeconds(3 * 60).connectionTimeOut(30 *1000).build()); 56 | ``` 57 | - 返回数据 58 | > 返回数据结果为ResponseData类型, 可以对象序列化直接返回JavaBean,只需要设置Callback为ObjectCallback,例如: 59 | 60 | ```java 61 | ObjectCallback> objCallback = new ObjectCallback>(new TypeReference>(){}) { 62 | @Override 63 | protected void onResponse(ResponseData> responseData) { 64 | if (responseData.isSuccess()){ 65 | if (responseData.isFromCache()){ 66 | tvCacheResult.setText(responseData.toString()); 67 | }else{ 68 | tvRequestResult.setText(responseData.toString()); 69 | } 70 | }else{ 71 | Toast.makeText(MainActivity.this, responseData.getDescription(), Toast.LENGTH_LONG).show(); 72 | } 73 | } 74 | 75 | @Override 76 | public void onProgress(long bytesWritten, long totalSize) { 77 | super.onProgress(bytesWritten, totalSize); 78 | //TODO you can update ui here 79 | } 80 | } 81 | ``` 82 | >支持设置文件路径,直接下载生成为文件 83 | 84 | ```java 85 | fileCallback = new FileCallback(downloadFile.getAbsolutePath()) { 86 | @Override 87 | protected void onResponse(ResponseData responseData) { 88 | File file = responseData.getData(); 89 | if (responseData.isSuccess()){ 90 | if (responseData.isFromCache()){ 91 | tvCacheResult.setText(responseData.toString() + "\n文件最后修改时间" + new Date(file.lastModified()) + "\n文件路径:" + file.getAbsolutePath()); 92 | }else{ 93 | tvRequestResult.setText(responseData.toString() + "\n文件最后修改时间" + new Date(file.lastModified()) + "\n文件路径:" + file.getAbsolutePath()); 94 | } 95 | }else{ 96 | Toast.makeText(MainActivity.this, responseData.getDescription(), Toast.LENGTH_LONG).show(); 97 | } 98 | } 99 | 100 | @Override 101 | public void onProgress(long bytesWritten, long totalSize) { 102 | super.onProgress(bytesWritten, totalSize); 103 | } 104 | }; 105 | 106 | ``` 107 | 108 | - 取消请求 109 | 110 | ```java 111 | HttpRequestAgent.getInstance().interruptRequestByTag(tag1, tag2, tag3);//取消设置这三个tag的请求 112 | HttpRequestAgent.getInstance().interruptAllRequest();//取消所有请求 113 | ``` 114 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.5.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Eclipse ### 4 | *.pydevproject 5 | .metadata 6 | .gradle 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .settings/ 15 | .loadpath 16 | 17 | # Eclipse Core 18 | #.project 19 | 20 | # External tool builders 21 | .externalToolBuilders/ 22 | 23 | # Locally stored "Eclipse launch configurations" 24 | *.launch 25 | 26 | # CDT-specific 27 | .cproject 28 | 29 | # JDT-specific (Eclipse Java Development Tools) 30 | .classpath 31 | 32 | # PDT-specific 33 | .buildpath 34 | 35 | # sbteclipse plugin 36 | .target 37 | 38 | # TeXlipse plugin 39 | .texlipse 40 | 41 | 42 | ### Intellij ### 43 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 44 | 45 | *.iml 46 | 47 | ## Directory-based project format: 48 | .idea/ 49 | # if you remove the above rule, at least ignore the following: 50 | 51 | # User-specific stuff: 52 | # .idea/workspace.xml 53 | # .idea/tasks.xml 54 | # .idea/dictionaries 55 | 56 | # Sensitive or high-churn files: 57 | # .idea/dataSources.ids 58 | # .idea/dataSources.xml 59 | # .idea/sqlDataSources.xml 60 | # .idea/dynamic.xml 61 | # .idea/uiDesigner.xml 62 | 63 | # Gradle: 64 | # .idea/gradle.xml 65 | # .idea/libraries 66 | 67 | # Mongo Explorer plugin: 68 | # .idea/mongoSettings.xml 69 | 70 | ## File-based project format: 71 | *.ipr 72 | *.iws 73 | 74 | ## Plugin-specific files: 75 | 76 | # IntelliJ 77 | /out/ 78 | 79 | # mpeltonen/sbt-idea plugin 80 | .idea_modules/ 81 | 82 | # JIRA plugin 83 | atlassian-ide-plugin.xml 84 | 85 | # Crashlytics plugin (for Android Studio and IntelliJ) 86 | com_crashlytics_export_strings.xml 87 | crashlytics.properties 88 | crashlytics-build.properties 89 | 90 | 91 | ### Android ### 92 | # Built application files 93 | *.apk 94 | *.ap_ 95 | 96 | # Files for the Dalvik VM 97 | *.dex 98 | 99 | # Java class files 100 | *.class 101 | 102 | # Generated files 103 | bin/ 104 | gen/ 105 | 106 | # Gradle files 107 | .gradle/ 108 | build/ 109 | /*/build/ 110 | 111 | # Local configuration file (sdk path, etc) 112 | local.properties 113 | 114 | # Proguard folder generated by Eclipse 115 | proguard/ 116 | 117 | # Log Files 118 | *.log 119 | 120 | ### Android Patch ### 121 | gen-external-apklibs 122 | 123 | 124 | ### Windows ### 125 | # Windows image file caches 126 | Thumbs.db 127 | ehthumbs.db 128 | 129 | # Folder config file 130 | Desktop.ini 131 | 132 | # Recycle Bin used on file shares 133 | $RECYCLE.BIN/ 134 | 135 | ### Mac OS ### 136 | .DS_Store 137 | .DS_Store? 138 | ._* 139 | .Spotlight-V100 140 | .Trashes -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "cn.jclick.demo" 9 | minSdkVersion 9 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.1.1' 26 | compile 'com.android.support:design:23.1.1' 27 | compile project(':HttpWrapper') 28 | } 29 | -------------------------------------------------------------------------------- /demo/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/XuYingjian/software/SDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /demo/src/androidTest/java/cn/jclick/demo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.demo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/src/main/java/cn/jclick/demo/DemoResultBean.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.demo; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by XuYingjian on 16/1/15. 7 | */ 8 | public class DemoResultBean implements Serializable{ 9 | 10 | private int code; 11 | private T data; 12 | 13 | public int getCode() { 14 | return code; 15 | } 16 | 17 | public void setCode(int code) { 18 | this.code = code; 19 | } 20 | 21 | public T getData() { 22 | return data; 23 | } 24 | 25 | public void setData(T data) { 26 | this.data = data; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "DemoResultBean{" + 32 | "code=" + code + 33 | ", data=" + data + 34 | '}'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /demo/src/main/java/cn/jclick/demo/Location.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.demo; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by XuYingjian on 16/1/15. 7 | */ 8 | public class Location implements Serializable{ 9 | private String area; 10 | private String areaId; 11 | private String city; 12 | private String cityId; 13 | private String country; 14 | private String countryId; 15 | private String county; 16 | private String countyId; 17 | private String ip; 18 | private String isp; 19 | private String ispId; 20 | private String region; 21 | private String regionId; 22 | 23 | public String getArea() { 24 | return area; 25 | } 26 | 27 | public void setArea(String area) { 28 | this.area = area; 29 | } 30 | 31 | public String getAreaId() { 32 | return areaId; 33 | } 34 | 35 | public void setAreaId(String areaId) { 36 | this.areaId = areaId; 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 getCityId() { 48 | return cityId; 49 | } 50 | 51 | public void setCityId(String cityId) { 52 | this.cityId = cityId; 53 | } 54 | 55 | public String getCountry() { 56 | return country; 57 | } 58 | 59 | public void setCountry(String country) { 60 | this.country = country; 61 | } 62 | 63 | public String getCountryId() { 64 | return countryId; 65 | } 66 | 67 | public void setCountryId(String countryId) { 68 | this.countryId = countryId; 69 | } 70 | 71 | public String getCounty() { 72 | return county; 73 | } 74 | 75 | public void setCounty(String county) { 76 | this.county = county; 77 | } 78 | 79 | public String getCountyId() { 80 | return countyId; 81 | } 82 | 83 | public void setCountyId(String countyId) { 84 | this.countyId = countyId; 85 | } 86 | 87 | public String getIp() { 88 | return ip; 89 | } 90 | 91 | public void setIp(String ip) { 92 | this.ip = ip; 93 | } 94 | 95 | public String getIsp() { 96 | return isp; 97 | } 98 | 99 | public void setIsp(String isp) { 100 | this.isp = isp; 101 | } 102 | 103 | public String getIspId() { 104 | return ispId; 105 | } 106 | 107 | public void setIspId(String ispId) { 108 | this.ispId = ispId; 109 | } 110 | 111 | public String getRegion() { 112 | return region; 113 | } 114 | 115 | public void setRegion(String region) { 116 | this.region = region; 117 | } 118 | 119 | public String getRegionId() { 120 | return regionId; 121 | } 122 | 123 | public void setRegionId(String regionId) { 124 | this.regionId = regionId; 125 | } 126 | 127 | @Override 128 | public String toString() { 129 | return "Location{" + 130 | "area='" + area + '\'' + 131 | ", areaId='" + areaId + '\'' + 132 | ", city='" + city + '\'' + 133 | ", cityId='" + cityId + '\'' + 134 | ", country='" + country + '\'' + 135 | ", countryId='" + countryId + '\'' + 136 | ", county='" + county + '\'' + 137 | ", countyId='" + countyId + '\'' + 138 | ", ip='" + ip + '\'' + 139 | ", isp='" + isp + '\'' + 140 | ", ispId='" + ispId + '\'' + 141 | ", region='" + region + '\'' + 142 | ", regionId='" + regionId + '\'' + 143 | '}'; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /demo/src/main/java/cn/jclick/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.jclick.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.Toolbar; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | import android.view.View; 9 | import android.widget.CheckBox; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | 13 | import com.alibaba.fastjson.JSON; 14 | import com.alibaba.fastjson.TypeReference; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.util.Date; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | import cn.jclick.httpwrapper.callback.Callback; 24 | import cn.jclick.httpwrapper.callback.FileCallback; 25 | import cn.jclick.httpwrapper.callback.ObjectCallback; 26 | import cn.jclick.httpwrapper.callback.ResponseData; 27 | import cn.jclick.httpwrapper.callback.StringCallback; 28 | import cn.jclick.httpwrapper.interceptor.HandlerInterceptor; 29 | import cn.jclick.httpwrapper.request.HttpRequestAgent; 30 | import cn.jclick.httpwrapper.request.RequestConfig; 31 | import cn.jclick.httpwrapper.request.RequestParams; 32 | import cn.jclick.httpwrapper.utils.StorageUtils; 33 | 34 | public class MainActivity extends AppCompatActivity { 35 | 36 | private static final String JSON_URL = "http://ip.taobao.com/service/getIpInfo2.php"; 37 | private static final String FILE_URL = "https://avatars0.githubusercontent.com/u/3241585?v=3&s=460"; 38 | 39 | private File downloadFile; 40 | 41 | private String targetURL; 42 | private TextView tvCacheResult; 43 | private TextView tvRequestResult; 44 | 45 | private CheckBox objCb; 46 | private CheckBox stringCb; 47 | private CheckBox fileCb; 48 | 49 | private Map requestParams = new HashMap<>(); 50 | private Callback callback; 51 | private StringCallback stringCallback = new StringCallback() { 52 | @Override 53 | protected void onResponse(ResponseData responseData) { 54 | if (responseData.isSuccess()){ 55 | if (responseData.isFromCache()){ 56 | tvCacheResult.setText(responseData.toString()); 57 | }else{ 58 | tvRequestResult.setText(responseData.toString()); 59 | } 60 | }else{ 61 | Toast.makeText(MainActivity.this, responseData.getDescription(), Toast.LENGTH_LONG).show(); 62 | } 63 | } 64 | 65 | @Override 66 | public void onProgress(long bytesWritten, long totalSize) { 67 | super.onProgress(bytesWritten, totalSize); 68 | //TODO you can update ui here 69 | } 70 | } ; 71 | 72 | private ObjectCallback> objCallback = new ObjectCallback>(new TypeReference>(){}) { 73 | @Override 74 | protected void onResponse(ResponseData> responseData) { 75 | if (responseData.isSuccess()){ 76 | if (responseData.isFromCache()){ 77 | tvCacheResult.setText(responseData.toString()); 78 | }else{ 79 | tvRequestResult.setText(responseData.toString()); 80 | } 81 | }else{ 82 | Toast.makeText(MainActivity.this, responseData.getDescription(), Toast.LENGTH_LONG).show(); 83 | } 84 | } 85 | 86 | @Override 87 | public void onProgress(long bytesWritten, long totalSize) { 88 | super.onProgress(bytesWritten, totalSize); 89 | //TODO you can update ui here 90 | } 91 | }; 92 | 93 | private FileCallback fileCallback; 94 | 95 | @Override 96 | protected void onCreate(Bundle savedInstanceState) { 97 | super.onCreate(savedInstanceState); 98 | setContentView(R.layout.activity_main); 99 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 100 | setSupportActionBar(toolbar); 101 | downloadFile = new File(StorageUtils.getCacheDirectory(this), "test.jpeg"); 102 | targetURL = JSON_URL; 103 | objCb = (CheckBox) findViewById(R.id.btn_object_callback); 104 | stringCb = (CheckBox) findViewById(R.id.btn_string_callback); 105 | fileCb = (CheckBox) findViewById(R.id.btn_file_callback); 106 | 107 | callback = stringCallback; 108 | tvCacheResult = (TextView) findViewById(R.id.tv_cache_result); 109 | tvRequestResult = (TextView) findViewById(R.id.tv_request_result); 110 | 111 | HttpRequestAgent.getInstance().init(new RequestConfig.Builder(this).logEnable(true).cacheMode(RequestConfig.HttpCacheMode.NO_CACHE) 112 | .addInterceptor(new HandlerInterceptor() { 113 | @Override 114 | public boolean preHandler(RequestParams params) { 115 | //TODO 请求之前的拦截 返回值决定是否继续请求 116 | return true; 117 | } 118 | 119 | @Override 120 | public void postSuccessHandler(RequestParams params, int statusCode, Map> headers) { 121 | //TODO 请求成功的拦截 122 | } 123 | 124 | @Override 125 | public void postFailedHandler(IOException exception) { 126 | //TODO 请求失败的拦截器 127 | } 128 | 129 | @Override 130 | public void afterCompletion(RequestParams params, ResponseData responseData) { 131 | //TODO 请求逻辑处理完毕的回调 132 | } 133 | }).cacheTimeInSeconds(3 * 60).connectionTimeOut(30 *1000).build()); 134 | 135 | 136 | findViewById(R.id.btn_no_cache).setOnClickListener(new View.OnClickListener() { 137 | @Override 138 | public void onClick(View v) { 139 | tvCacheResult.setText(""); 140 | tvRequestResult.setText(""); 141 | RequestParams params = new RequestParams.Builder().requestParams(requestParams).url(targetURL).cacheMode(RequestConfig.HttpCacheMode.NO_CACHE).get().build(); 142 | HttpRequestAgent.getInstance().executeRequest(params, callback); 143 | } 144 | }); 145 | findViewById(R.id.btn_always_cache).setOnClickListener(new View.OnClickListener() { 146 | @Override 147 | public void onClick(View v) { 148 | tvCacheResult.setText(""); 149 | tvRequestResult.setText(""); 150 | RequestParams params = new RequestParams.Builder().requestParams(requestParams).url(targetURL).cacheMode(RequestConfig.HttpCacheMode.ALWAYS_CACHE).get().build(); 151 | HttpRequestAgent.getInstance().executeRequest(params, callback); 152 | } 153 | }); 154 | findViewById(R.id.btn_cache_first).setOnClickListener(new View.OnClickListener() { 155 | @Override 156 | public void onClick(View v) { 157 | tvCacheResult.setText(""); 158 | tvRequestResult.setText(""); 159 | RequestParams params = new RequestParams.Builder().requestParams(requestParams).url(targetURL).cacheMode(RequestConfig.HttpCacheMode.CACHE_FIRST).get().build(); 160 | HttpRequestAgent.getInstance().executeRequest(params, callback); 161 | } 162 | }); 163 | findViewById(R.id.btn_cache_no_network).setOnClickListener(new View.OnClickListener() { 164 | @Override 165 | public void onClick(View v) { 166 | tvCacheResult.setText(""); 167 | tvRequestResult.setText(""); 168 | RequestParams params = new RequestParams.Builder().requestParams(requestParams).url(targetURL).cacheMode(RequestConfig.HttpCacheMode.CACHE_WHEN_NO_NETWORK).get().build(); 169 | HttpRequestAgent.getInstance().executeRequest(params, callback); 170 | } 171 | }); 172 | findViewById(R.id.btn_failed_show_cache).setOnClickListener(new View.OnClickListener() { 173 | @Override 174 | public void onClick(View v) { 175 | tvCacheResult.setText(""); 176 | tvRequestResult.setText(""); 177 | RequestParams params = new RequestParams.Builder().requestParams(requestParams).url(targetURL).cacheMode(RequestConfig.HttpCacheMode.FAILED_SHOW_CACHE).get().build(); 178 | HttpRequestAgent.getInstance().executeRequest(params, callback); 179 | } 180 | }); 181 | 182 | objCb.setOnClickListener(new View.OnClickListener() { 183 | @Override 184 | public void onClick(View v) { 185 | requestParams.clear(); 186 | requestParams.put("ip", "221.217.176.144"); 187 | targetURL = JSON_URL; 188 | objCb.setChecked(true); 189 | stringCb.setChecked(false); 190 | fileCb.setChecked(false); 191 | callback = objCallback; 192 | } 193 | }); 194 | 195 | stringCb.setOnClickListener(new View.OnClickListener() { 196 | @Override 197 | public void onClick(View v) { 198 | requestParams.clear(); 199 | requestParams.put("ip", "221.217.176.144"); 200 | targetURL = JSON_URL; 201 | objCb.setChecked(false); 202 | stringCb.setChecked(true); 203 | fileCb.setChecked(false); 204 | callback = stringCallback; 205 | } 206 | }); 207 | 208 | fileCb.setOnClickListener(new View.OnClickListener() { 209 | @Override 210 | public void onClick(View v) { 211 | requestParams.clear(); 212 | targetURL = FILE_URL; 213 | objCb.setChecked(false); 214 | stringCb.setChecked(false); 215 | fileCb.setChecked(true); 216 | callback = fileCallback; 217 | } 218 | }); 219 | 220 | fileCallback = new FileCallback(downloadFile.getAbsolutePath()) { 221 | @Override 222 | protected void onResponse(ResponseData responseData) { 223 | File file = responseData.getData(); 224 | if (responseData.isSuccess()){ 225 | if (responseData.isFromCache()){ 226 | tvCacheResult.setText(responseData.toString() + "\n文件最后修改时间" + new Date(file.lastModified()) + "\n文件路径:" + file.getAbsolutePath()); 227 | }else{ 228 | tvRequestResult.setText(responseData.toString() + "\n文件最后修改时间" + new Date(file.lastModified()) + "\n文件路径:" + file.getAbsolutePath()); 229 | } 230 | }else{ 231 | Toast.makeText(MainActivity.this, responseData.getDescription(), Toast.LENGTH_LONG).show(); 232 | } 233 | } 234 | 235 | @Override 236 | public void onProgress(long bytesWritten, long totalSize) { 237 | super.onProgress(bytesWritten, totalSize); 238 | } 239 | }; 240 | } 241 | 242 | @Override 243 | public boolean onCreateOptionsMenu(Menu menu) { 244 | // Inflate the menu; this adds items to the action bar if it is present. 245 | getMenuInflater().inflate(R.menu.menu_main, menu); 246 | return true; 247 | } 248 | 249 | @Override 250 | public boolean onOptionsItemSelected(MenuItem item) { 251 | // Handle action bar item clicks here. The action bar will 252 | // automatically handle clicks on the Home/Up button, so long 253 | // as you specify a parent activity in AndroidManifest.xml. 254 | int id = item.getItemId(); 255 | 256 | //noinspection SimplifiableIfStatement 257 | if (id == R.id.action_settings) { 258 | return true; 259 | } 260 | 261 | return super.onOptionsItemSelected(item); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 20 | 21 |