├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── HttpWrapper
├── src
│ ├── main
│ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── java
│ │ │ └── cn
│ │ │ │ └── jclick
│ │ │ │ └── httpwrapper
│ │ │ │ ├── cache
│ │ │ │ ├── FileNameGenerator.java
│ │ │ │ ├── IDiskCache.java
│ │ │ │ ├── Md5FileNameGenerator.java
│ │ │ │ ├── Util.java
│ │ │ │ ├── LruDiskCache.java
│ │ │ │ ├── StrictLineReader.java
│ │ │ │ └── DiskLruCache.java
│ │ │ │ ├── interceptor
│ │ │ │ ├── HandlerInterceptor.java
│ │ │ │ └── LoggerInterceptor.java
│ │ │ │ ├── callback
│ │ │ │ ├── StringCallback.java
│ │ │ │ ├── ByteCallback.java
│ │ │ │ ├── ObjectCallback.java
│ │ │ │ ├── ResponseData.java
│ │ │ │ ├── FileCallback.java
│ │ │ │ └── Callback.java
│ │ │ │ ├── utils
│ │ │ │ ├── WrapperUtils.java
│ │ │ │ ├── StorageUtils.java
│ │ │ │ └── Charsets.java
│ │ │ │ └── request
│ │ │ │ ├── RequestBuilder.java
│ │ │ │ ├── RequestConfig.java
│ │ │ │ ├── RequestParams.java
│ │ │ │ └── HttpRequestAgent.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── cn
│ │ │ └── jclick
│ │ │ └── httpwrapper
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── cn
│ │ └── jclick
│ │ └── httpwrapper
│ │ └── ApplicationTest.java
├── build.gradle
├── proguard-rules.pro
└── .gitignore
├── demo
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── 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
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-v21
│ │ │ │ └── styles.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ ├── menu
│ │ │ │ └── menu_main.xml
│ │ │ └── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ └── content_main.xml
│ │ ├── java
│ │ │ └── cn
│ │ │ │ └── jclick
│ │ │ │ └── demo
│ │ │ │ ├── DemoResultBean.java
│ │ │ │ ├── Location.java
│ │ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── cn
│ │ │ └── jclick
│ │ │ └── demo
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── cn
│ │ └── jclick
│ │ └── demo
│ │ └── ApplicationTest.java
├── proguard-rules.pro
├── build.gradle
└── .gitignore
├── local.properties
├── gradle.properties
├── HttpWrapper.iml
├── .gitignore
├── gradlew.bat
├── README.md
└── gradlew
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':HttpWrapper', ':Demo', ':demo'
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jclick/JWHttpWrapper/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/HttpWrapper/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | HttpWrapper
3 |
4 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jclick/JWHttpWrapper/HEAD/demo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jclick/JWHttpWrapper/HEAD/demo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jclick/JWHttpWrapper/HEAD/demo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jclick/JWHttpWrapper/HEAD/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jclick/JWHttpWrapper/HEAD/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Demo
3 | Settings
4 |
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Oct 21 11:34:03 PDT 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
7 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/HttpWrapper/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/demo/src/test/java/cn/jclick/demo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package cn.jclick.demo;
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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/demo/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/local.properties:
--------------------------------------------------------------------------------
1 | ## This file is automatically generated by Android Studio.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file should *NOT* be checked into Version Control Systems,
5 | # as it contains information specific to your local configuration.
6 | #
7 | # Location of the SDK. This is only used by Gradle.
8 | # For customization when using a Version Control System, please read the
9 | # header note.
10 | sdk.dir=/Users/XuYingjian/software/SDK
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/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.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/.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/.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/.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
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
20 |
21 |
26 |
27 |
32 |
33 |
38 |
39 |
40 |
44 |
45 |
50 |
51 |
56 |
57 |
58 |
62 |
63 |
70 |
71 |
77 |
83 |
84 |
85 |
93 |
94 |
100 |
101 |
108 |
109 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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/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/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 | *
34 | * US-ASCII
35 | * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.
36 | * ISO-8859-1
37 | * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
38 | * UTF-8
39 | * Eight-bit Unicode Transformation Format.
40 | * UTF-16BE
41 | * Sixteen-bit Unicode Transformation Format, big-endian byte order.
42 | * UTF-16LE
43 | * Sixteen-bit Unicode Transformation Format, little-endian byte order.
44 | * UTF-16
45 | * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order
46 | * accepted on input, big-endian used on output.)
47 | *
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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 | *
67 | * When an entry is being created it is necessary to
68 | * supply a full set of values; the empty value should be used as a
69 | * placeholder if necessary.
70 | * When an entry is being edited , it is not necessary
71 | * to supply data for every value; values default to their previous
72 | * value.
73 | *
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 |
--------------------------------------------------------------------------------