RxEasyHttp: A Simple and Easy Network Request Framework Based on RxJava+Retrofit2

Source address: https://github.com/zhou-you/RxEasyHttp

RxEasyHttp

This library is a simple and easy-to-use network request framework based on Retrofit2+RxJava. It combines the characteristics of android platform with network encapsulation library. It uses api chain to call one point to the end, integrates cookie management, multiple caching modes, minimal https configuration, upload and download progress display, automatic retry of request errors, dynamic configuration of token, timestamp and sign ature, and request re-entry after successful automatic login. Sending function, three levels of parameter setting default global and local, default standard ApiResult can support custom data structure at the same time, has been able to meet most of the current network requests.
Note: Retrofit and Rxjava are very popular open source frameworks, both of which come from Square. This library does not introduce the use of Retrofit and Rxjava.

Why encapsulate this library?

Good open source network libraries on the Internet, such as Volley, async-http, okhttp, retrofit, etc., are very powerful. But in practice, we will not use them directly. Generally, we will encapsulate one layer according to our own business, which is more convenient and fast, and can deal with common business things such as unified data structure (code, msg, data), token processing, network anomalies and so on. When retrofit is used to request the network, more and more projects are needed, and more and more APIs are needed. a common application API is generally around 100 +. If these APIs are placed in an ApiService, they will be bloated, which is not conducive to viewing the api. If the API is classified by module, each module corresponds to several apis. In retrofit mode, it is necessary to create several ApiService, which is easy to maintain, but the number of modules increases, and the number of classes also increases. For lazy people, they want to call back the data you need through a URL. They don't want to ignore any ApiService, and they can quickly relate to their business. It's similar to encapsulating another layer on the basis of open source network library, so the library came into being.

Characteristic

  • It is simpler and easier to use than Retrofit.
  • Chain call to the end
  • Adding basic ApiService to reduce Api redundancy
  • Support dynamic configuration and custom underlying framework Okhttpclient, Retrofit.
  • Support multiple ways to access network GET, POST, PUT, DELETE and other request protocols
  • Supporting network caching, six caching strategies are optional, covering most business scenarios
  • Support for fixed and dynamic header addition
  • Support for adding global and dynamic local parameters
  • Support file download, multi-file upload and form submission data
  • Support file requests, uploads, downloads, progress callbacks, error callbacks, or custom callbacks
  • Supports three levels of configuration functions: default, global and local
  • Support automatic parsing of arbitrary data structures
  • Support for adding dynamic parameters such as timeStamp timestamp, token, signature sign
  • Support for custom extended API s
  • Supporting multiple request merges
  • Support Cookie Management
  • Support for asynchronous and synchronous requests
  • Support Https, self-signed website Https access, two-way verification
  • Failed retry mechanism is supported, and retry times and intervals can be specified
  • Support for deleting and emptying network caches according to ky
  • Provide default standard ApiResult parsing and callback, and customize ApiResult
  • Supports canceling data requests, canceling subscriptions, requests with dialog boxes do not need to cancel requests manually, and the disappearance of dialog boxes automatically cancels requests
  • Callbacks and subscriptions are used to support requests for data results
  • api design is realized by combining http protocol and android platform features, loading dialog box, real-time progress bar display
  • Return results and exception handling
  • Intelligent Thread Control with RxJava

Contact information

E-mail address: 478319399@qq.com
QQ group: 581235049 (QQ group is recommended, mailbox use is less, may not see in time)
The purpose of this group is to use me. github The people of the project provide convenience. If you encounter problems, you are welcome to ask questions in the group. A person's ability is limited, hope to learn together and progress together.
Pay attention to me github Get to know my latest project. Pay attention to me Blog Read my latest article.

Welcome to join QQ Exchange Group

Demo (star support, please)


Version description

current version

V1.0.4 fixes spelling errors of callback words

Update logs

Click View Update Log

Introduction to Usage

At present, only the mainstream development tool Andtoid Studio is supported, but Eclipse is not provided.
The server address requested by Demo in this project is removed for security, but the examples in Demo program are ok.

Click Download Demo Experience RxEasyHttp-Demo

build.gradle settings

dependencies {
 compile 'com.zhouyou:rxeasyhttp:1.0.4'
}

To view all versions, click the address below.

Latest release: https://jcenter.bintray.com/com/zhouyou/rxeasyhttp/

Permission description

If you use this library to download files to SD cards or configure cached data to SD cards, you must take into account the runtime permissions of Android 6.0 and above systems and recommend two permission libraries to you:

AndPermission
RxPermissions

To request network, read and write cache from SD card, download files to SD card and so on, you need to configure the following permissions in manifest.xml. If you have already configured these permissions, please do not configure them again:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>

Global configuration

In general, in Aplication or base class, only one call is needed to configure debugging switches, global timeout, common request headers and request parameters.
Initialization requires a Context, preferably in Application onCreate (), remember to register Application in manifest.xml.

Application:

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
    }
}

manifest.xml:

...

<application
    android:name=".MyApplication"
    ...
 />

Default initialization

If default initialization is used, everything is set by default. If you need to configure global timeout, cache, Cookie, and OkHttp at the bottom, see Advanced Initialization.

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        EasyHttp.init(this);//Default initialization
    }
}

Advanced Initialization

It can configure timeout, network cache, okhttp parameters, retrofit parameters, cookie and so on. These parameters can be configurated selectively according to business needs.

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        EasyHttp.init(this);//Default initialization, must be invoked

        //Global Setting Request Header
        HttpHeaders headers = new HttpHeaders();
        headers.put("User-Agent", SystemInfoUtils.getUserAgent(this, AppConstant.APPID));
        //Global Setting Request Parameters
        HttpParams params = new HttpParams();
        params.put("appId", AppConstant.APPID);

        //All the parameters set below are global parameters. The same parameters can be set again when the request is made. For this request, the parameters in the request will cover the global parameters.
        EasyHttp.getInstance()

                //Global URL s can be set globally and uniformly
                .setBaseUrl(Url)//Setting global URL s

                // Turn on the debug switch and set TAG. Do not join the line without requiring it.
                // The final true indicates whether or not to print the internal exception of okgo, and generally opens to facilitate debugging errors.
                .debug("EasyHttp", true)

                //If you use the default 60 seconds, the following three lines do not need to be set
                .setReadTimeOut(60 * 1000)
                .setWriteTimeOut(60 * 100)
                .setConnectTimeout(60 * 100)

                //The number of overtime reconnections can be set globally and uniformly, defaulting to 3, and the worst case will be 4 requests (one original request, three reconnection requests).
                //No need to set to 0
                .setRetryCount(3)//Network failure automatically retries 3 times
                //The time-out retry interval can be set globally and uniformly. By default, it is 500 ms. It does not need to be set to 0.
                .setRetryDelay(500)//Retry with 500 ms delay
                //Overtime retry interval overlay time can be set globally and uniformly, default is 0 ms without overlay.
                .setRetryIncreaseDelay(500)//500 ms for each delay

                //Cache mode can be set globally and uniformly. By default, no caching is used and no transmission is allowed. See Cache Mode for details.
                .setCacheMode(CacheMode.NO_CACHE)
                //The cache time can be set globally and uniformly. The default never expires.
                .setCacheTime(-1)//- 1 represents a permanent cache, in seconds, Okhttp and custom RxCache caches all work.
                //Global Setup Custom Cache Save Converter, mainly for Custom RxCache Cache
                .setCacheDiskConverter(new SerializableDiskConverter())//Default cache uses serialization transformation
                //Global setting custom cache size, default 50M
                .setCacheMaxSize(100 * 1024 * 1024)//Set the cache size to 100M
                //Set the cache version. If the cache changes, the cache will not be loaded after the modified version. Especially when caching is not available for major version upgrades
                .setCacheVersion(1)//Cached version 1
                //SetHttpCache (new Cache ()// Set Okhttp cache to work only when the cache mode is DEFAULT

                //https certificates can be set up. The following schemes can be set up by themselves as needed
                .setCertificates()                                  //Method 1: Trust all certificates, unsafe and risky
                //SetCertificates (new SafeTrust Manager ()// Method 2: Customize trust rules and verify server certificates
                //Configure https domain name matching rules, do not need to join, improper use will lead to https handshake failure
                //.setHostnameVerifier(new SafeHostnameVerifier())
                //AddConverterFactory (GsonConverterFactory. create (gson)// This framework does not use the Gson transformation of Retrofit, so no configuration is required.
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addCommonHeaders(headers)//Setting global common headers
                .addCommonParams(params)//Setting global common parameters
                //AddNetwork Interceptor (new NoCacheInterceptor ()// Setting up Network Interceptor
                //setCallFactory()// Bureau Sets Retrofit Object Factory
                //setCookieStore()// Set cookie
                //setOkproxy()// Set up global proxy
                //setOkconnectionPool()// Set Request Connection Pool
                //setCallbackExecutor()// Global Setup Retrofit callbackExecutor
                //Global interceptors can be added without adding them. Writing errors directly cause any callbacks not to execute.
                //.addInterceptor (new GzipRequestInterceptor ()// Open post data and send it to the server after gzip
                .addInterceptor(new CustomSignInterceptor());//Add parameter signature interceptor
    }
}

Request data

Network requests, using chain call, support a point to the end.

Entry method

  /**
     * get request
     */
    public static GetRequest get(String url);

    /**
     * post Request and file upload
     */
    public static PostRequest post(String url);

    /**
     * delete request
     */
    public static DeleteRequest delete(String url) ;

    /**
     * Custom Request
     */
    public static CustomRequest custom();

    /**
     * File Download
     */
    public static DownloadRequest downLoad(String url) ;

    /**
     * put request
     */
    public static PutRequest put(String url);

General Function Configuration

1. Include all the parameters that can be configured in a common request. In real use, there is no need to configure so many parameters. Selective use can be made according to your own needs.

2. The following configurations are all single-Request configurations, which do not affect the global configuration, but still use global parameters.

3. Set timeouts for individual requests, such as the need to set more read-write waiting times for files.

Complete parameter GET example:

EasyHttp.get("/v1/app/chairdressing/skinAnalyzePower/skinTestResult")
                .baseUrl("http://www.xxxx.com")//Setting url
                .writeTimeOut(30*1000)//Local write timeout of 30 seconds, unit milliseconds
                .readTimeOut(30*1000)//Local reading timeout 30 seconds, unit milliseconds
                .connectTimeout(30*1000)//Local connection timeout 30 seconds, unit milliseconds
                .headers(new HttpHeaders("header1","header1Value"))//Add request header parameters
                .headers("header2","header2Value")//Support for adding multiple request headers at the same time
                .headers("header3","header3Value")//Support for adding multiple request headers at the same time
                .params("param1","param1Value")//Support for adding multiple parameters at the same time
                .params("param2","param2Value")//Support for adding multiple parameters at the same time
                //AddCookie (new CookieManger (this). addCookies ()// Support for adding Cookies
                .cacheTime(300)//Cache 300s units s
                .cacheKey("cachekey")//Cache key
                .cacheMode(CacheMode.CACHEANDREMOTE)//Setting Request Caching Mode
                //okCache()// When using mode caching mode, go Okhttp caching
                .cacheDiskConverter(new GsonDiskConverter())//GSON-Data Converter
                //. certificates() add certificates
                .retryCount(5)//Number of retries requested
                .retryDelay(500)//The delay time for retry of this request is 500 ms
                .addInterceptor(Interceptor)//Adding interceptors
                .okproxy()//Setting up agents
                .removeHeader("header2")//Remove Header 2
                .removeAllHeaders()//Remove all request headers
                .removeParam("param1")
                .accessToken(true)//Whether to add token to this request
                .timeStamp(false)//Does this request carry a timestamp?
                .sign(false)//Do you need to sign this request?
                .syncRequest(true)//Whether it is a synchronous request or not, the default asynchronous request. true: Synchronization request
                .execute(new CallBack<SkinTestResult>() {
                    @Override
                    public void onStart() {
                        //Start the request
                    }

                    @Override
                    public void onCompleted() {
                       //Completion of request
                    }

                    @Override
                    public void onError(ApiException e) {
                      //Request error
                    }

                    @Override
                    public void onSuccess(SkinTestResult response) {
                      //Successful request
                    }
                });

url

Url can be passed into EasyHttp.getInstance().setBaseUrl("http://www.xxx.com") by initializing the configuration.
Entry method input: EasyHttp.get ("/v1/app/chairdressing/skinAnalyPower/skinTestResult"). baseUrl ("http://www.xxxx.com")
If the incoming url in the entry method contains http or https, the baseUrl of the initialization settings will not be spliced together.
For example, EasyHttp.get ("http://www.xxx.com/v1/app/chairdressing/skinAnalyPower/skinTestResult") will not be stitched together with the baseurl passed in by setBaseUrl() and baseUrl().

http request parameters

Two settings
.params(HttpParams params)
params("param1", "param1Value")// Add parameter key-value pairs

HttpParams params = new HttpParams();
params.put("appId", AppConstant.APPID);
addCommonParams(params)// Setting global common parameters

http request header

.headers(HttpHeaders headers)
headers("header2", "header2Value")// Add parameter key-value pairs

addCommonHeaders(headers)// Set global common headers

Ordinary network requests

Support get/post/delete/put, etc.
There are three ways to execute the endpoint request of chain call: execute(Class clazz), execute(Type type), and execute(CallBack callBack), which are all for standard ApiResult.

execute(CallBack callBack)

1.EasyHttp (Recommendation)
Examples:

Mode 1:
 //EasyHttp.post("/v1/app/chairdressing/skinAnalyzePower/skinTestResult")
 EasyHttp.get("/v1/app/chairdressing/skinAnalyzePower/skinTestResult")
                .readTimeOut(30 * 1000)//Locally defined read timeout
                .writeTimeOut(30 * 1000)
                .connectTimeout(30 * 1000)
                .params("name","Zhang San")
                .timeStamp(true)
                .execute(new SimpleCallBack<SkinTestResult>() {
                    @Override
                    public void onError(ApiException e) {
                        showToast(e.getMessage());
                    }

                    @Override
                    public void onSuccess(SkinTestResult response) {
                        if (response != null) showToast(response.toString());
                    }
                });

2. Manual creation of request objects

 //GetRequest ,PostRequest,DeleteRequest,PutRequest
 GetRequest request = new GetRequest("/v1/app/chairdressing/skinAnalyzePower/skinTestResult");
        request.readTimeOut(30 * 1000)//Locally defined read timeout
                .params("param1", "param1Value1")
                .execute(new SimpleCallBack<SkinTestResult>() {
                    @Override
                    public void onError(ApiException e) {

                    }

                    @Override
                    public void onSuccess(SkinTestResult response) {

                    }
                });

execute(Class clazz) and execute(Type type)

Excute (Class clazz) and execute(Type type) functions basically the same, execute(Type type) is mainly aimed at collections can not directly pass Class

EasyHttp.get(url)
                .params("param1", "paramValue1")
                .execute(SkinTestResult.class)//Very simple direct target class
                //Execute (new TypeToken < List < Section Item >() {}. getType ()//Type type)
                .subscribe(new BaseSubscriber<SkinTestResult>() {
                    @Override
                    public void onError(ApiException e) {
                        showToast(e.getMessage());
                    }

                    @Override
                    public void onNext(SkinTestResult skinTestResult) {
                        showToast(skinTestResult.toString());
                    }
                });

Request to return Subscription

The network request returns the Subscription object to facilitate the cancellation of the network request.

Subscription subscription = EasyHttp.get("/v1/app/chairdressing/skinAnalyzePower/skinTestResult")
                .params("param1", "paramValue1")
                .execute(new SimpleCallBack<SkinTestResult>() {
                    @Override
                    public void onError(ApiException e) {
                        showToast(e.getMessage());
                    }

                    @Override
                    public void onSuccess(SkinTestResult response) {
                        showToast(response.toString());
                    }
                });

        //Calls where network requests need to be cancelled, usually in onDestroy()
        //EasyHttp.cancelSubscription(subscription);

Requests with progress boxes

With the progress box request, you can set the dialog box disappear automatically cancel the network and custom dialog box function, the specific parameters of the role see the request callback explanation.

Mode 1: Progress Dialog CallBack

ProgressDialogCallBack has a request with a progress box. You can set whether the dialog box disappears automatically to cancel the functions of network and custom dialog box. See the explanation of custom CallBack for specific parameters.

 IProgressDialog mProgressDialog = new IProgressDialog() {
            @Override
            public Dialog getDialog() {
                ProgressDialog dialog = new ProgressDialog(MainActivity.this);
                dialog.setMessage("Please wait a moment...");
                return dialog;
            }
        };
        EasyHttp.get("/v1/app/chairdressing/")
                .params("param1", "paramValue1")
                .execute(new ProgressDialogCallBack<SkinTestResult>(mProgressDialog, true, true) {
                    @Override
                    public void onError(ApiException e) {
                        super.onError(e);//super.onError(e) must be written that cannot be deleted or forgotten
                        //Successful request
                    }

                    @Override
                    public void onSuccess(SkinTestResult response) {
                       //request was aborted
                    }
                });

Note: Error callback super.onError(e); must be written

Mode 2: Progress Subscriber

IProgressDialog mProgressDialog = new IProgressDialog() {
            @Override
            public Dialog getDialog() {
                ProgressDialog dialog = new ProgressDialog(MainActivity.this);
                dialog.setMessage("Please wait a moment...");
                return dialog;
            }
        };
 EasyHttp.get(URL)
                .timeStamp(true)
                .execute(SkinTestResult.class)
                .subscribe(new ProgressSubscriber<SkinTestResult>(this, mProgressDialog) {
                    @Override
                    public void onError(ApiException e) {
                        super.onError(e);
                        showToast(e.getMessage());
                    }

                    @Override
                    public void onNext(SkinTestResult skinTestResult) {
                        showToast(skinTestResult.toString());
                    }
                });

Request to return Observable

The network request can be returned to Observable, which can be well processed through Rxjava in combination with other scenarios, and even through Rxjava's connect() operator to process multiple network requests. For example, if there are multiple network requests on a page, how can the page be displayed after multiple requests have been successfully accessed? This is also the power of Rxjava.
Note: Excute (Class clazz) only supports annotated API Result structure and does not support custom API Result.
Examples:

Observable<SkinTestResult> observable = EasyHttp.get(url)
                .params("param1", "paramValue1")
                .execute(SkinTestResult.class);

        observable.subscribe(new BaseSubscriber<SkinTestResult>() {
            @Override
            public void onError(ApiException e) {
                showToast(e.getMessage());
            }

            @Override
            public void onNext(SkinTestResult skinTestResult) {
                showToast(skinTestResult.toString());
            }
        });

File Download

The file download provided by this library is very simple. There are no complicated download methods such as download manager, breakpoint continuation, multi-thread download, etc. because we don't want to overweight the library. If the download is complex, consider other download options.
If the file directory is not specified, the default download directory is / storage/emulated/0/Android/data / package name / files
If the file name is not specified, it is named according to the following rules:

1. First check whether the user has passed in the file name. If the file name is passed in, it will be named after the file name that the user has passed in.
2. If no file name is passed in, the default name is generated by a timestamp.
3. If a file name is passed in but there is no suffix, the program automatically resolves the type with an additional suffix name.

Examples:

 String url = "http://61.144.207.146:8081/b8154d3d-4166-4561-ad8d-7188a96eb195/2005/07/6c/076ce42f-3a78-4b5b-9aae-3c2959b7b1ba/kfid/2475751/qqlite_3.5.0.660_android_r108360_GuanWang_537047121_release_10000484.apk";
        EasyHttp.downLoad(url)
                .savePath("/sdcard/test/QQ")
                .saveName("release_10000484.apk")//No default name is generated by a timestamp
                .execute(new DownloadProgressCallBack<String>() {
                    @Override
                    public void update(long bytesRead, long contentLength, boolean done) {
                        int progress = (int) (bytesRead * 100 / contentLength);
                        HttpLog.e(progress + "% ");
                        dialog.setProgress(progress);
                        if (done) {//Download completed
                        }
                        ...
                    }

                    @Override
                    public void onStart() {
                       //Start downloading
                    }

                    @Override
                    public void onComplete(String path) {
                       //Download completed, path: the complete path to download and save files
                    }

                    @Override
                    public void onError(ApiException e) {
                        //Download failure
                    }
                });

POST request, upload String, json, object, body, byte []

Generally, this usage is used for data format agreed with the server. When using this method, the parameter settings in params are invalid. All parameters need to be specified in the text that needs to be uploaded. In addition, the additional header parameters specified are still valid.
- upString("This is long text data to upload! ")// The default type is: MediaType.parse("text/plain")
- If you have your own requirements for the request header, you can use this overloaded form to pass in custom content-type text
upString("This is long text data to upload! "," "application/xml"// for example, upload XML data, where you can specify the request header yourself
- There is no essential difference between upJson method and upString method, but the data format is json. Usually, you need to create an entity bean or a map by yourself, set the required parameters in it, then convert it into JSON string through three-party Gson or fastjson, and then submit it it directly to the server using this method.
UpJson (jsonObject. toString ()// Upload json
- upBytes(new byte []{})// Upload byte []
- requestBody(body)// Upload custom RequestBody
-.upObject(object)//Upload object

Note: The five methods of upString, upJson, requestBody, upBytes and upObject can not be used at the same time. Currently, only one method can be used.

Examples:

HashMap<String, String> params = new HashMap<>();
params.put("key1", "value1");
params.put("key2", "Here's what you need to submit json Format data");
params.put("key3", "You can also use a tripartite tool to convert objects to json Character string");
JSONObject jsonObject = new JSONObject(params);

RequestBody body=RequestBody.create(MediaType.parse("xxx/xx"),"content");
EasyHttp.post("v1/app/chairdressing/news/favorite")
                //params("param1", "paramValue1")// can not use params, upString and params are mutually exclusive, only upString data will be uploaded
                .upString("Here is the text to upload!")//The default type is: MediaType.parse("text/plain")
                //upString("This is long text data to upload! "," "application/xml"// for example, upload XML data, where you can specify the request header yourself

                 //.upJson(jsonObject.toString())
                 //.requestBody(body)
                 //.upBytes(new byte[]{})
                 //.upObject(object)
                .execute(new SimpleCallBack<String>() {
                    @Override
                    public void onError(ApiException e) {
                        showToast(e.getMessage());
                    }

                    @Override
                    public void onSuccess(String response) {
                        showToast(response);
                    }
                });

Upload pictures or files

Supporting single file upload, multi-file upload, mixed upload, and supporting progress callback.
High-level functions such as multi-threaded upload/fragmented upload/breakpoint continuous upload are not implemented for the time being.

Uploading files supports simultaneous uploading of files with parameters and multiple files with one key. The following methods are optional
Upload files support two kinds of progress callbacks: ProgressResponseCallBack (callback in thread) and UIProgressResponseCallBack (which can refresh UI)

final UIProgressResponseCallBack listener = new UIProgressResponseCallBack() {
            @Override
            public void onUIResponseProgress(long bytesRead, long contentLength, boolean done) {
                int progress = (int) (bytesRead * 100 / contentLength);
                if (done) {//complete
                }
                ...
            }
        };
        EasyHttp.post("/v1/user/uploadAvatar")
                //New parameters to support uploading
                //.params(String key, File file, ProgressResponseCallBack responseCallBack)
                //.params(String key, InputStream stream, String fileName, ProgressResponseCallBack responseCallBack)
                //.params(String key, byte[] bytes, String fileName, ProgressResponseCallBack responseCallBack) 
                //.addFileParams(String key, List<File> files, ProgressResponseCallBack responseCallBack)
                //.addFileWrapperParams(String key, List<HttpParams.FileWrapper> fileWrappers)
                //.params(String key, File file, String fileName, ProgressResponseCallBack responseCallBack)
                //.params(String key, T file, String fileName, MediaType contentType, ProgressResponseCallBack responseCallBack)

                //Mode 1: File upload
                File file = new File("/sdcard/1.jpg");
                //If there is a file name that can be passed away, it will automatically resolve to image./*
                .params("avatar", file, file.getName(), listener)
                //.params("avatar", file, file.getName(),MediaType.parse("image/*"), listener)

                //Mode 2: InputStream upload
               final InputStream inputStream = getResources().getAssets().open("1.jpg");
                .params("avatar", inputStream, "test.png", listener)

                //Mode 3: byte [] upload
                Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
                final byte[] bytes = baos.toByteArray();
                //.params("avatar",bytes,"streamfile.png",MediaType.parse("image/*"),listener)
                //If there is a file name that can be passed away, it will automatically resolve to image./*
                .params("avatar", bytes, "streamfile.png", listener)

                .params("file1", new File("filepath1"))   // File upload can be added
                .params("file2", new File("filepath2"))     // Supporting multiple files and adding uploads at the same time
                .addFileParams("key", List<File> files) // There is support for one key to pass multiple files.
                .params("param1", "paramValue1")        // Here you can upload parameters
                .accessToken(true)
                .timeStamp(true)
                .execute(new ProgressDialogCallBack<String>(mProgressDialog, true, true) {
                    @Override
                    public void onError(ApiException e) {
                        super.onError(e);
                        showToast(e.getMessage());
                    }

                    @Override
                    public void onSuccess(String response) {
                        showToast(response);
                    }
                });

Cancel the request

Cancel via Subscription

A Subscription is returned before each request, and the network request can be cancelled by canceling the subscription. If a network request with a schedule box is made, the network request need not be cancelled manually, and it will be cancelled automatically.

 Subscription mSubscription = EasyHttp.get(url).execute(callback);
  ...
  @Override
    protected void onDestroy() {
        super.onDestroy();
        EasyHttp.cancelSubscription(mSubscription);
    }

Cancel via dialog

Automatically cancel the use of ProgressDialogCallBack callback or ProgressSubscriber instead of calling cancelSubscription() manually.
ProgressDialogCallBack:

EasyHttp.get(url).execute(new ProgressDialogCallBack());

ProgressSubscriber

EasyHttp.get(url).execute(SkinTestResult.class).subscribe(new ProgressSubscriber<SkinTestResult>(this, mProgressDialog) {
            @Override
            public void onError(ApiException e) {
                super.onError(e);
                showToast(e.getMessage());
            }

            @Override
            public void onNext(SkinTestResult skinTestResult) {
                showToast(skinTestResult.toString());
            }
        })

Synchronization request

Synchronization requests only need to set the syncRequest() method

 EasyHttp.get("/v1/app/chairdressing/skinAnalyzePower/skinTestResult")
                ...
                .syncRequest(true)//Setting up synchronization requests
                .execute(new CallBack<SkinTestResult>() {});

Request callback CallBack support type

//The types that support callbacks can be Bean, String, CacheResult < Bean >, CacheResult < String >, List < Bean >.
new SimpleCallBack<CacheResult<Bean>>()//Supporting callbacks to caches, see Cache Instructions
new SimpleCallBack<CacheResult<String>>()//Supporting callbacks to caches, see Cache Instructions
new SimpleCallBack<Bean>()//Return to Bean
new SimpleCallBack<String>()//Return string
new SimpleCallBack<List<Bean>()//Return collection

Note: Other callbacks are the same.

cookie usage

The contents of cookies mainly include: name, value, expiration time, path and domain. Paths and domains together constitute the scope of cookies, and the role of cookies is no longer popular science here, you can understand it yourself.
cookie settings:

EasyHttp.getInstance()
                 ...
                  //If you don't want the library to manage cookie s, you don't need the following
                .setCookieStore(new CookieManger(this)) //Cookie persistent storage, which is always valid if the cookie does not expire
                 ...
  • View the cookie s corresponding to the url
HttpUrl httpUrl = HttpUrl.parse("http://www.xxx.com/test");
CookieManger cookieManger = getCookieJar();
List<Cookie> cookies =  cookieManger.loadForRequest(httpUrl);
  • View all cookie s in CookieManger
PersistentCookieStore cookieStore= getCookieJar().getCookieStore();
List<Cookie> cookies1= cookieStore.getCookies();
  • Add cookie s
Cookie.Builder builder = new Cookie.Builder();
Cookie cookie = builder.name("mCookieKey1").value("mCookieValue1").domain(httpUrl.host()).build();
CookieManger cookieManger = getCookieJar();
cookieManger.saveFromResponse(httpUrl, cookie);
//cookieStore.saveFromResponse(httpUrl, cookieList);//Add cookie collections
  • Remove cookie s
HttpUrl httpUrl = HttpUrl.parse("http://www.xxx.com/test");
CookieManger cookieManger = EasyHttp.getCookieJar();
Cookie cookie = builder.name("mCookieKey1").value("mCookieValue1").domain(httpUrl.host()).build();
cookieManger.remove(httpUrl,cookie);
  • Clean up cookie s
CookieManger cookieManger = EasyHttp.getCookieJar();
cookieManger.removeAll();

Custom call() request

A user-defined interface for ApiService is provided. You only need to call the call method.
Examples:

public interface LoginService {
    @POST("{path}")
    @FormUrlEncoded
    Observable<ApiResult<AuthModel>> login(@Path("path") String path, @FieldMap Map<String, String> map);
}

final CustomRequest request = EasyHttp.custom()
                .addConverterFactory(GsonConverterFactory.create(new Gson()))//Customized GsonConverterFactory can be set
                .params("param1", "paramValue1")
                .build();

        LoginService mLoginService = request.create(LoginService.class);
        LoginService mLoginService = request.create(LoginService.class);
        Observable<ApiResult<AuthModel>> observable = request.call(mLoginService.login("v1/account/login", request.getParams().urlParamsMap));
        Subscription subscription = observable.subscribe(new Action1<ApiResult<AuthModel>>() {
            @Override
            public void call(ApiResult<AuthModel> result) {
                //Successful request
            }
        }, new Action1<Throwable>() {
            @Override
            public void call(Throwable throwable) {
                //request was aborted
            }
        });

Custom apiCall() request

Provide default support for ApiResult structure, data return does not need to take ApiResult, return directly to the target.
Examples:

Observable<AuthModel> observable = request.apiCall(mLoginService.login("v1/account/login", request.getParams().urlParamsMap));

Request callbacks and subscriptions

Callback and Subscriber are two ways to request callbacks from this library

Callback mode

This method is mainly aimed at execute(CallBack callBack). Currently, callbacks provided internally include CallBack, SimpleCallBack, Progress Dialog CallBack, Download Progress CallBack can customize Callback according to their own needs.

  • CallBack Base Class, Abstract Class for All Callbacks
  • SimpleCallBack simple callback, only success and failure
  • Progress Dialog CallBack with a progress box callback allows you to customize the progress box, support whether you can cancel the dialog box, disappear the dialog box automatically cancel network requests and other parameter settings.
  • Download ProgressCallBack If you want to download a file, you must use this callback, which internally encapsulates a method for file download progress callbacks. If you use other callbacks, you can, but there is no progress notification.

The core use of the network framework is the inheritance of Callback, because different project needs, there will be personalized callbacks for customization.

CallBack callback

new CallBack<T>() {
                    @Override
                    public void onStart() {
                       //Request Start
                    }

                    @Override
                    public void onCompleted() {
                       //Completion of request
                    }

                    @Override
                    public void onError(ApiException e) {
                       //request was aborted
                    }

                    @Override
                    public void onSuccess(T t) {
                       //Successful request
                    }
                }

SimpleCallBack callback

new SimpleCallBack<T>() {
                    @Override
                    public void onError(ApiException e) {
                         //request was aborted
                    }

                    @Override
                    public void onSuccess(T t) {
                        //Successful request
                    }
                }

ProgressDialogCallBack callback

Callbacks with loading progress boxes can be customized. Canceling dialogs automatically cancel network requests

Provide two constructions

public ProgressDialogCallBack(IProgressDialog progressDialog);//The dialog box cannot be cancelled by default
public ProgressDialogCallBack(IProgressDialog progressDialog, boolean isShowProgress, boolean isCancel);//Custom loading progress box can set whether to display pop-up box, cancel progressDialog: dialog object interface isShowProgress: Whether the dialog box disappears cancel the network request isCancel: Whether the dialog box can cancel the setCancelable(isCancel) method corresponding to Dialog;

Custom Progress Dialog Dialog

 private IProgressDialog mProgressDialog = new IProgressDialog() {
        @Override
        public Dialog getDialog() {
            ProgressDialog dialog = new ProgressDialog(MainActivity.this);
            dialog.setMessage("Please wait a moment...");
            return dialog;
        }
    };

Download ProgressCallBack callback

This callback is only for file download. See the file download instructions for details.

Custom CallBack callback

If there are special requirements for callbacks, support can inherit CallBack's own extensions

Subscription

This method is mainly for execute(Class clazz) and execute(Type type). Subscriber currently provided in-house includes Base Subscriber, Download Subscriber, Progress Subscriber. Subscriber can be customized according to its own needs.
- BaseSubscriber base class, abstract class for all subscribers
- Download Subscriber Download Subscriber Subscriber Subscriber Download Subscriber Subscriber Subscriber Download Subscriber Subscriber Subscriber Subscriber Subscriber Subscriber Download Subscriber
- Subscriber Subscriber with a progress box subscription, you can customize the progress box, support whether you can cancel the dialog box, the disappearance of the dialog box automatically cancel network requests and other parameter settings

new BaseSubscriber<T>() {
            @Override
            public void onError(ApiException e) {
               //request was aborted
            }

            @Override
            public void onNext(T t) {
                //Successful request
            }
        }
new ProgressSubscriber<T>(this, mProgressDialog) {
                    @Override
                    public void onError(ApiException e) {
                        super.onError(e);
                        //request was aborted
                    }

                    @Override
                    public void onNext(T t) {
                         //Successful request
                    }
                }

Customize Subscriber

If there are special requirements for Subscriber, support can inherit BaseSubscriber's own extended subscribers

Cache usage

Caching Introduction

The Cache of this library is mainly divided into okhttp Cache and custom RxCache Cache. There are doubts that okhttp has Cache. retrofit also supports setting Cache through header. Why do we need to customize a Cache mechanism? Customized RxCache caching is simpler to use and more suitable for our common business needs (common caching strategies are not too complicated). retrofit caching is implemented by okhttp through interceptor or through interceptor. @Headers("Cache-Control: public, max-age=3600) Specific usage is not described in detail here, interested people can understand it by themselves. Dynamic modification of cache time is inconvenient, for example: the same interface, different periods of request content cache time is different, need dynamic modification.

For DEFAULT mode, it is okhttp's Cache cache. Because this mode is fully compliant with the standard http protocol, the cache time is controlled by the response head of the service end, and can also be handled by the interceptor itself.

For RxCache's caching support a variety of storage methods, providing IDiskConverter converter interface currently supports Serializable DiskConverter and GsonDiskConverter, and can also customize Parcelable, fastjson, xml, kryo and other converters.
SerializableDiskConverter
Before using caching, you must have all the cached data java bean objects implement the Serializable interface, otherwise NotSerializable Exception will be reported. Because the principle of caching is to save objects after serialization, if the Serializable interface is not implemented, the object will not be serialized and consequently can not be saved, and the effect of caching will not be achieved.
Advantages: Storage and reading do not need to be converted directly to the need for fast object speed
Disadvantage: If there are many levels of JavaBeans in javabean s, it is more troublesome to implement Serializable interface for each one.
GsonDiskConverter
This way is stored as a json string
Advantages: Compared with Serializable DiskConverter converters, stored objects do not need to be serialized
Disadvantage: Gson is used to convert storage and reading. Object - > String - > Object gives a process a relatively low conversion performance each time, but the performance is neglected.

At present, seven Cache Mode caching modes are provided. Each caching mode can specify the corresponding CacheTime, encapsulating the complex and commonly used business scenarios, so that you can focus on data processing instead of paying attention to the specific implementation of caching.

  • NO_CACHE: No caching is used. In this mode, parameters such as cacheKey and cacheTime are invalid.
  • DEFAULT: According to the default caching rules of HTTP protocol, go OKhttp Cache caching
  • FIRSTREMOTE: Request the network first, request the network to fail and then load the cache
  • FIRSTCACHE: Load the cache first, and the cache does not request the network again.
  • ONLYREMOTE: Load only the network, but the data will still be cached
  • ONLYCACHE: Read only the cache, and the cache does not return null
  • CACHEANDREMOTE: First use the cache, whether it exists or not, and still request the network. CallBack calls back twice.

Note: Regardless of the cache mode, you can specify a cacheKey. It is recommended that different cacheKey be set for different pages that need to be cached. If the same cacheKey is used, it will lead to data coverage.

Cache settings

There are two ways to set up a cache
Mode 1: Global settings, which are used by default for all requests

 EasyHttp.getInstance()
                ...
                .setCacheMode(CacheMode.CACHEANDREMOTE)//No default is NO_CACHE mode
                ...

Mode 2: Single request setting cache mode

 EasyHttp.get(URL)
                ...
                .cacheMode(CacheMode.FIRSTREMOTE)
                ...

Setting up Converter

Mode 1: Global settings, all requests will default to this storage converter

EasyHttp.getInstance().setCacheDiskConverter(new SerializableDiskConverter())//Default cache uses serialization transformation

Mode 2: Single request setting storage converter

EasyHttp.get(URL).cacheDiskConverter(new GsonDiskConverter());

Note: Select a converter for a request, remember not to use Serializable DiskConverter to cache, and GsonDiskConverter to read and report errors.

Custom Converter

If you want to have your own converter, implement the IDiskConverter interface.
Examples:

public class CustomDiskConverter implements IDiskConverter {
    @Override
    public <T> T load(InputStream source, Type type) {
        //Implementing Reading Function
        return null;
    }

    @Override
    public boolean writer(OutputStream sink, Object data) {
        //Implementing Writing Function
        return false;
    }
}

Cache callback

For callback CallBack with cache, if you want to know whether the current cache is from the local or network, you just need to add CacheResult to the callback, which is the same as the normal network request. IsFromCache in CacheResult can know whether it comes from caching, true: from caching, false: from the network. Use new SimpleCallBack < CacheResult < T () to include a layer of CacheResult on your original T. If you don't want to use isFromCache, you don't need to use CacheResult, just use new SimpleCallBack < T >()
With the CacheResult callback example:

 EasyHttp.get(url)
                .readTimeOut(30 * 1000)//Testing local reading timeout of 30s
                .cacheMode(cacheMode)
                .cacheKey(this.getClass().getSimpleName())//Cache key
                .retryCount(5)//retry count
                .cacheTime(5 * 60)//The cache time is 300s, and the default-1 permanent cache okhttp and custom cache work.
                //.okCache (new Cache ();//okhttp cache, mode is the default mode (Cache Mode. DEFAULT) only takes effect.
                //CacheDiskConverter (new GsonDiskConverter ()// New Serializable DiskConverter () is used by default;
                .cacheDiskConverter(new SerializableDiskConverter())//The default is new SerializableDiskConverter();
                .timeStamp(true)
                .execute(new SimpleCallBack<CacheResult<SkinTestResult>>() {

                    @Override
                    public void onError(ApiException e) {
                       //request was aborted
                    }

                    @Override
                    public void onSuccess(CacheResult<SkinTestResult> cacheResult) {
                        HttpLog.i(cacheResult.toString());
                        String from = "";
                        if (cacheResult.isFromCache) {
                            from = "I come from caching";
                        } else {
                            from = "I'm from a remote network.";
                        }
                       ....
                    }
                });

Remove Cache

Support for removing caches based on cache key s, mainly for RxCache

EasyHttp.removeCache("cachekey");

wipe cache

EasyHttp.clearCache();

RxCache

RxCache is a local caching library encapsulated by itself. It is implemented by Rxjava+DiskLruCache. ReadWriteLock mechanism is used in thread security to prevent abnormalities caused by frequent read and write caches. It can be used independently and can store data by RxCache alone. The combination of transformer and network request can realize network caching function. Local hard caching has cache read-write function (asynchronism), cache existence, delete cache according to key, empty cache (asynchronism), cache key can automatically encrypt MD5, can set the size of cache disk, cache key, cache time, cache storage converter, cache directory, cache Vers. The functions of ion and other libraries are not highlighted. Later, this code will be independently open source a library, as a minute to let your own network library also have caching function, please look forward to!!!

dynamic parameter

Dynamic parameters are like our token, timeStamp, signature, etc. These parameters can not be global parameters because they are changing. It is too difficult to set local parameters, and they must be obtained every time. Token is retrieved by changing the valid time or login in different places. The time stamp is usually based on the time of the system. sign is encrypted by the url and parameters of the request. Generally, it has its own signature rules. Some interfaces need these parameters and some interfaces don't need them. This library solves this problem very well.

1. The following three parameters can be set when requesting

.accessToken(true)//Whether to add token to this request
.timeStamp(false)//Does this request carry a timestamp?
.sign(false)//Do you need to sign this request?

2. Need to inherit the dynamic interceptor BaseDynamicInterceptor provided in the library

After inheriting the BaseDynamicInterceptor, you can get the settings of the parameters. See the CustomSign Interceptor's commentary for more details, or see the Demo example.
Examples:

/**
 * <p>Description: Interceptor for signing parameters, adding token, and timestamping</p>
 * Main function description: < br >
 * Because parameter signature can not be unified, the rules of signature are different, and the ways of signature encryption are different, such as MD5, BASE64 and so on. It only provides the ability to expand itself. <br>
 * Author: zhouyou < br >
 * Date: 2017/5/4 15:21 < br >
 * Version: v1.0 < br >
 */
public class CustomSignInterceptor extends BaseDynamicInterceptor<CustomSignInterceptor> {
    @Override
    public TreeMap<String, String> dynamic(TreeMap<String, String> dynamicMap) {
        //dynamicMap: The original global parameter + local parameter
        //You don't have to worry about getting / post / uploading files / mixed uploading, etc. The library will automatically handle it for you.
        //If you only use token, you don't have to deal with isTimeStamp(), isSign() as needed.
        if (isTimeStamp()) {//Whether to add a timestamp, because your field key may not be timestamp, this dynamic self-processing
            dynamicMap.put(ComParamContact.Common.TIMESTAMP, String.valueOf(System.currentTimeMillis()));
        }
        if (isSign()) {Signature or not
            //1. Because your field key may not be sign, this needs to be handled dynamically by yourself.
            //2. Because the rules of your signature are different, and the way of signature encryption is different, it only provides the ability to expand itself.
            dynamicMap.put(ComParamContact.Common.SIGN, sign(dynamicMap));
        }
        if (isAccessToken()) {//Whether to add token or not
            String acccess = TokenManager.getInstance().getAuthModel().getAccessToken();
            dynamicMap.put(ComParamContact.Common.ACCESSTOKEN, acccess);
        }
        //Logc.i("dynamicMap:" + dynamicMap.toString());
        return dynamicMap;//dynamicMap: The original global parameter + local parameter + new dynamic parameter
    }

    //Example - > Signature Rule: POST+url+parameter assembly+secret
    private String sign(TreeMap<String, String> dynamicMap) {
        String url = getHttpUrl().url().toString();
        url = url.replaceAll("%2F", "/");
        StringBuilder sb = new StringBuilder("POST");
        sb.append(url);
        for (Map.Entry<String, String> entry : dynamicMap.entrySet()) {
            sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }

        sb.append(AppConstant.APP_SECRET);
        HttpLog.i(sb.toString());
        return MD5.encode(sb.toString());
    }
}

3. Setting up custom dynamic interceptors

It's best to set it globally, because many interfaces are used in general.

 EasyHttp.getInstance()
                 ...
                .addInterceptor(new CustomSignInterceptor())//Add dynamic parameter (signature, token, timestamp) interceptors
                 ...

Customize ApiResult

By default, standard ApiResult is provided in this library. Internally, it is parsed by ApiResult. If your data structure is different from that of ApiResult, you can inherit ApiResult in your project, and then rewrite getCode(), getData(), getMsg() and isOk() to meet your needs.
ApiResult in this library is as follows:

public class ApiResult<T> {
    private int code;
    private String msg;
    private T data;
    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public boolean isOk() {//Judgment of Success of Request
        return code == 0 ? true : false;
    }
}

The json format is similar:

{
"code": 100010101,
"data": content,
"msg": "Successful request"
}

If your data structure is like this:

{
"error_code": 0,
"result": content,
"reason": "successful request"
}

So your basebean can be written like this

public class CustomApiResult<T> extends ApiResult<T> {
    String reason;
    int error_code;
    //int resultcode;
    T result;
    @Override
    public int getCode() {
        return error_code;
    }
    @Override
    public void setCode(int code) {
        error_code = code;
    }
    @Override
    public String getMsg() {
        return reason;
    }
    @Override
    public void setMsg(String msg) {
        reason = msg;
    }
    @Override
    public T getData() {
        return result;
    }
    @Override
    public void setData(T data) {
        result = data;
    }
   /* @Override
    public boolean isOk() {
        return error_code==200;//If 0 is not a success, rewrite the isOk() method.
    }*/
}

Then your network request can be written like this.

EasyHttp.get(url)
                .readTimeOut(30 * 1000)//Locally defined read timeout
                .writeTimeOut(30 * 1000)
                .connectTimeout(30 * 1000)
                //.cacheKey(this.getClass().getSimpleName()+"11")
                //.cacheMode(CacheMode.CACHEANDREMOTE)
                //.cacheMode(CacheMode.ONLYREMOTE)
                //.headers("","")//Setting header parameters
                //.params("name","Zhang San")//Setting parameters
                //.addInterceptor()
                //.addConverterFactory()
                //.addCookie()
                //.timeStamp(true)
                .baseUrl("http://apis.juhe.cn")
                .params("phone", "Cell-phone number")
                .params("dtype", "json")
                .params("key", "5682c1f44a7f486e40f9720d6c97ffe4")
                .execute(new CallBackProxy<CustomApiResult<ResultBean>, ResultBean>(new SimpleCallBack<ResultBean>() {
                    @Override
                    public void onError(ApiException e) {
                        //Request error
                    }

                    @Override
                    public void onSuccess(ResultBean response) {
                        //Successful request
                    }
                }) {
                });

This is a long way to write. The generic parameters of CallBackProxy need to be filled in every time. Custom ApiResult is inherited from ApiResult. Custom ApiResult is equivalent to the project's basebean. For a real project, basebean is fixed, so we can continue to encapsulate this method. Generally, we just need to encapsulate get and post requests as needed.

 public static <T> Subscription customExecute(CallBack<T> callBack) {
        return execute(new CallBackProxy<CustomApiResult<T>, T>(callBack) {
        });
    }

Through the above modification, CallBack is used directly when calling again without paying attention to CallBackProxy. Is it obviously much simpler, see the code Demo!!!?

Debugging mode

A good library, there must be a more humane debugging mode, in order to facilitate developers to view the request process and request logs, the library provides detailed Log printing, preferably in the development stage, please open debugging mode to output elegant logs.
The control of debugging mode can be set directly when the configuration is initialized.

public class MyApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            ...
            EasyHttp.getInstance()
                    ...
                    // Turn on the debug switch and set TAG. Do not join the line without requiring it.
                    // The final true indicates whether or not to print the internal exception of okgo, and generally opens to facilitate debugging errors.
                    .debug("EasyHttp", true);
        }
    }

Log preview description

Here is an example of a successful request:

The top logo prints a complete declaration cycle of Request, and a request logo has the following characteristics:
1. Start and finish with -> HTTP is start and -> HTTP is Complete partition requests, and the full life cycle content is printed in both the beginning and the end.
2. The request request request and response response are partitioned, respectively.

------request------

------response------

3. The URL of the request, the type of the current request GET/POST will be printed after - request. - The beginning of GET/POST - the end of END GET/POST. If the parameters are added by GET and HEAD requests, they will be printed in the form of url? Key = value & key = value.
4. Print after --response -, including response code, response status, response header, cookie,body, etc., beginning with <-200 (response code), ending with <-END HTTP.
5. Load Cache key = If the cache is set, you will see the key of the cache, and only when the network caching function is turned on will it be output.
6. Load Cache result = the result read from the cache, only when the network caching function is turned on can it be output.
7. save status => true saves the state of the cache

confusion

-dontwarn okio.**
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault
-dontwarn javax.annotation.**
-keep com.zhouyou.http.model
-keep com.zhouyou.http.cache.model

Support for open source

Thank you for your support and encouragement. Let's do something good together.
You can use the "WeChat" and "Alipay" client to appreciate:

Keywords: network Android Retrofit OkHttp

Added by Archangel915 on Sun, 23 Jun 2019 22:13:35 +0300