<!-- Network permission -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Access network status -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />-
Android 9.0 restricts plaintext traffic network requests, non-encrypted traffic requests will be prohibited by the system.
-
If the current application's request is an http request, but not https, this will lead to the system prohibiting the current application from performing this request, if the WebView's url uses the http protocol, it will also fail to load, https is unaffected
-
Create a new xml directory in res, then create a file named:
network_security_config.xml, the content of this file is as follows
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>- Then apply the above xml configuration in the application tag of AndroidManifest.xml
<application
android:networkSecurityConfig="@xml/network_security_config" />public class RequestServer implements IRequestServer {
@NonNull
@Override
public String getHost() {
return "https://www.baidu.com/";
}
@NonNull
@Override
public IHttpPostBodyStrategy getBodyType() {
// Parameters submitted in Json format (default is form)
return RequestBodyType.JSON;
}
}- Need to configure request result handling, specific encapsulation can refer to RequestHandler
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.build();
EasyConfig.with(okHttpClient)
// Whether to print logs
.setLogEnabled(BuildConfig.DEBUG)
// Set server configuration (must be set)
.setServer(server)
// Set request processing strategy (must be set)
.setHandler(new RequestHandler())
// Set request cache implementation strategy (not required)
//.setCacheStrategy(new HttpCacheStrategy())
// Set request retry count
.setRetryCount(3)
// Add global request parameters
//.addParam("token", "6666666")
// Add global request headers
//.addHeader("time", "20191030")
// Enable configuration
.into();- This is for creating configuration, updating configuration can use
EasyConfig.getInstance()
.addParam("token", data.getData().getToken());public final class LoginApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "user/login";
}
/** Username */
private String userName;
/** Login password */
private String password;
public LoginApi setUserName(String userName) {
this.userName = userName;
return this;
}
public LoginApi setPassword(String password) {
this.password = password;
return this;
}
}-
You can add some annotations to the fields of this class
-
@HttpHeader: Mark this field as a request header parameter
-
@HttpIgnore: Mark this field will not be sent to the backend
-
@HttpRename: Redefine the parameter name or header name sent to the backend
-
-
You can implement some interfaces in this class
-
implements IRequestHost: After implementing this interface, you can re-specify the main host address of this request
-
implements IRequestBodyType: After implementing this interface, you can re-specify the submission method of this request body
-
implements IRequestCacheConfig: After implementing this interface, you can re-specify the cache mode configuration of this request
-
implements IRequestHttpClient: After implementing this interface, you can re-specify the OkHttpClient object used for this request
-
-
The field is the standard for measuring request parameters
-
Assuming the attribute value of a field is empty, this field will not be sent to the backend as a request parameter
-
Assuming a field type is String, its attribute value is an empty string, then this field will be sent as a request parameter, if it is an empty object, it will not
-
Assuming a field type is int, since basic data types do not have empty values, this field will definitely be sent as a request parameter, but it can be replaced with an Integer object to avoid, because the default value of Integer is null
-
-
I'll give you an example: https://www.baidu.com/api/user/getInfo,then the standard way to write is
public final class XxxApi implements IRequestServer, IRequestApi {
@NonNull
@Override
public String getHost() {
return "https://www.baidu.com/";
}
@NonNull
@Override
public String getApi() {
return "user/getInfo";
}
}- Need to configure request status and lifecycle handling, specific encapsulation can refer to BaseActivity
EasyHttp.post(this)
.api(new LoginApi()
.setUserName("Android 轮子哥")
.setPassword("123456"))
.request(new HttpCallbackProxy<HttpData<LoginBean>>(activity) {
@Override
public void onHttpSuccess(@NonNull HttpData<LoginBean> data) {
toast("Login successful");
}
});- This shows the post method, EasyHttp also supports get, head, delete, put, patch requests, which are not demonstrated here
public final class UpdateImageApi implements IRequestApi, IRequestBodyType {
@NonNull
@Override
public String getApi() {
return "upload/";
}
@NonNull
@Override
public IHttpPostBodyStrategy getBodyType() {
// File upload needs to use the form method
return RequestBodyType.FORM;
}
/** Local image */
private File image;
public UpdateImageApi(File image) {
this.image = image;
}
public UpdateImageApi setImage(File image) {
this.image = image;
return this;
}
}EasyHttp.post(this)
.api(new UpdateImageApi(file))
.request(new OnUpdateListener<Void>() {
@Override
public void onUpdateStart(@NonNull IRequestApi api) {
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void onUpdateProgressChange(int progress) {
mProgressBar.setProgress(progress);
}
@Override
public void onUpdateSuccess(@NonNull Void result) {
toast("Upload successful");
}
@Override
public void onUpdateFail(@NonNull Throwable throwable) {
toast("Upload failed");
}
@Override
public void onUpdateEnd(@NonNull IRequestApi api) {
mProgressBar.setVisibility(View.GONE);
}
});-
Note: If the uploaded file is too large or too many, it may cause a timeout, you can re-set the timeout for this request, the timeout is recommended to be based on the file size, please refer to the documentation for specific timeout settings, you can search directly on this page.
-
Of course, in addition to using
Fileobjects for upload, you can also useFileContentResolver,InputStream,RequestBody,MultipartBody.Partobjects for upload, if you need to batch upload, please useList<File>,List<FileContentResolver>,List<InputStream>,List<RequestBody>,List<MultipartBody.Part>objects for batch upload.
- Download cache strategy: When the md5 of the specified downloaded file or the backend returns md5, the download framework enables the download cache mode by default. If the file already exists in the phone and the md5 verifies the file integrity, the framework will not download it again, but will directly call the download listener. Reduce server pressure and user waiting time.
EasyHttp.download(this)
.method(HttpMethod.GET)
.file(new File(Environment.getExternalStorageDirectory(), "微信.apk"))
//.url("https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk")
.url("http://dldir1.qq.com/weixin/android/weixin708android1540.apk")
.md5("2E8BDD7686474A7BC4A51ADC3667CABF")
// Set resumable transfer (default is not enabled)
//.resumableTransfer(true)
.listener(new OnDownloadListener() {
@Override
public void onDownloadStart(@NonNull File file) {
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void onDownloadProgressChange(@NonNull File file, int progress) {
mProgressBar.setProgress(progress);
}
@Override
public void onDownloadSuccess(@NonNull File file) {
toast("Download complete: " + file.getPath());
installApk(XxxActivity.this, file);
}
@Override
public void onDownloadFail(@NonNull File file, @NonNull Throwable throwable) {
toast("Download failed: " + throwable.getMessage());
file.delete();
}
@Override
public void onDownloadEnd(@NonNull File file) {
mProgressBar.setVisibility(View.GONE);
}
}).start();- Before Android 10, when reading and writing external storage, we could directly use File objects to upload or download files, but if your project needs the feature of Android 10 scoped storage, then when reading and writing external storage files, we cannot directly use File objects, because
ContentResolver.insertreturns aUriobject, at this time, we need to use theFileContentResolverobject provided by the framework (this object is a subclass of File), the specific usage example is as follows:
File outputFile;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentValues values = new ContentValues();
.........
// Generate a new uri path
Uri outputUri = getContentResolver().insert(MediaStore.Xxx.Media.EXTERNAL_CONTENT_URI, values);
// Adapt Android 10 scoped storage feature
outputFile = new FileContentResolver(context, outputUri);
} else {
outputFile = new File(xxxx);
}
EasyHttp.post(this)
.api(new XxxApi()
.setImage(outputFile))
.request(new HttpCallbackProxy<Xxx <Xxx>>(this) {
@Override
public void onHttpSuccess(@NonNull Xxx<Xxx> data) {
}
});- This is the upload example, the download is the same, here is no repetition.
- Note: Synchronous requests are time-consuming operations, please ensure that this operation is executed in a sub-thread, do not execute it in the main thread.
PostRequest postRequest = EasyHttp.post(MainActivity.this);
try {
HttpData<SearchBean> data = postRequest
.api(new SearchBlogsApi()
.setKeyword("搬砖不再有"))
.execute(new ResponseClass<HttpData<SearchBean>>() {});
toast("Request successful, please check the logs");
} catch (Throwable throwable) {
toast(throwable.getMessage());
}-
Need to implement interfaces for reading and writing cache, specific encapsulation can refer to HttpCacheStrategy
-
Set cache strategy when initializing the framework
public final class AppApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
EasyConfig.with(okHttpClient)
......
// Set request cache
.setCacheStrategy(new HttpCacheStrategy())
......
.into();
}
}- If you are using MMKV as the cache implementation for reading and writing, do not forget to initialize MMKV in the application startup
public final class AppApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
MMKV.initialize(this);
}
}- First, there are four ways to set the cache mode, all of which are in the
CacheModeenumeration class
public enum CacheMode {
/**
* Default (cache according to Http protocol)
*/
DEFAULT,
/**
* Do not use cache (disable Http protocol cache)
*/
NO_CACHE,
/**
* Only use cache
*
* If there is already a cache: read cache -> callback success
* If there is no cache: request network -> write cache -> callback success
*/
USE_CACHE_ONLY,
/**
* Prioritize cache
*
* If there is already a cache: read cache —> callback success —> request network —> refresh cache
* If there is no cache: request network -> write cache -> callback success
*/
USE_CACHE_FIRST,
/**
* Only read cache when network request fails
*/
USE_CACHE_AFTER_FAILURE
}- Set cache mode for a specific interface
public final class XxxApi implements IRequestApi, IRequestCacheConfig {
@NonNull
@Override
public String getApi() {
return "xxx/";
}
@NonNull
@Override
public CacheMode getCacheMode() {
// Set to prioritize cache
return CacheMode.USE_CACHE_FIRST;
}
}- Set global cache mode
public class XxxServer implements IRequestServer {
@NonNull
@Override
public String getHost() {
return "https://www.xxxxxxx.com/";
}
@NonNull
@Override
public CacheMode getCacheMode() {
// Only read cache when request fails
return CacheMode.USE_CACHE_AFTER_FAILURE;
}
}- You can use synchronous requests with coroutines for processing, using the code as follows:
lifecycleScope.launch(Dispatchers.IO) {
try {
val bean = EasyHttp.post(this@XxxActivity)
.api(XxxApi().apply {
setXxx(xxx)
setXxxx(xxxx)
})
.execute(object : ResponseClass<HttpData<XxxBean?>>() {})
withContext(Dispatchers.Main) {
// Refresh UI here
}
} catch (throwable: Throwable) {
toast(throwable.message)
}
}- If you are not familiar with coroutines, I recommend you read this article
- EasyHttp is based on OkHttp, and OkHttp itself supports setting cookies, so the usage is the same as OkHttp
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cookieJar(new XxxCookieJar())
.build();
EasyConfig.with(okHttpClient)
.setXxx()
.into();- Add global request parameters
EasyConfig.getInstance().addParam("key", "value");- Remove global request parameters
EasyConfig.getInstance().removeParam("key");- Add global request headers
EasyConfig.getInstance().addHeader("key", "value");- Remove global request headers
EasyConfig.getInstance().removeHeader("key");EasyConfig.getInstance().setInterceptor(new IRequestInterceptor() {
@Override
public void interceptArguments(@NonNull HttpRequest<?> httpRequest, @NonNull HttpParams params, @NonNull HttpHeaders headers) {
// Add request header
headers.put("key", "value");
// Add parameter
params.put("key", "value");
}
});public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
@HttpIgnore
private String token;
}IRequestServer server = EasyConfig.getInstance().getServer();
// Get the main host address of the current global server
String host = server.getHost();- First, define a server configuration
public class XxxServer implements IRequestServer {
@NonNull
@Override
public String getHost() {
return "https://www.xxxxxxx.com/";
}
}- Then apply it to the global configuration
EasyConfig.getInstance().setServer(new XxxServer());- If you only want to configure a specific interface, you can do this
public final class XxxApi extends XxxServer implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
}- If you do not want to define a separate class, you can also write it like this
public final class XxxApi implements IRequestServer, IRequestApi {
@NonNull
@Override
public String getHost() {
return "https://www.xxxxxxx.com/";
}
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
}- First, define a test server and official server configuration for a normal interface
public class TestServer implements IRequestServer {
@NonNull
@Override
public String getHost() {
return "https://www.test.xxxxxxx.com/";
}
}public class ReleaseServer implements IRequestServer {
@NonNull
@Override
public String getHost() {
return "https://www.xxxxxxx.com/";
}
}- Then apply it to the global configuration
IRequestServer server;
if (BuildConfig.DEBUG) {
server = new TestServer();
} else {
server = new ReleaseServer();
}
EasyConfig.getInstance().setServer(server);- If you want to set a specific server configuration for an H5 business module, you can do this
public class H5Server implements IRequestServer {
@NonNull
@Override
public String getHost() {
IRequestServer server = EasyConfig.getInstance().getServer();
if (server instanceof TestServer) {
return "https://www.test.h5.xxxxxxx.com/";
}
return "https://www.h5.xxxxxxx.com/";
}
}- You can inherit H5Server when configuring the interface, and other H5 module configurations are similar
public final class UserAgreementApi extends H5Server implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "user/agreement";
}
}- Submit parameters in form format (default)
public class XxxServer implements IRequestServer {
@NonNull
@Override
public String getHost() {
return "https://www.xxxxxxx.com/";
}
@NonNull
@Override
public IHttpPostBodyStrategy getBodyType() {
return RequestBodyType.FORM;
}
}- Submit parameters in Json format
public class XxxServer implements IRequestServer {
@NonNull
@Override
public String getHost() {
return "https://www.xxxxxxx.com/";
}
@NonNull
@Override
public IHttpPostBodyStrategy getBodyType() {
return RequestBodyType.JSON;
}
}- Of course, you can also configure a specific interface separately
public final class XxxApi implements IRequestApi, IRequestBodyType {
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
@NonNull
@Override
public IHttpPostBodyStrategy getBodyType() {
return RequestBodyType.JSON;
}
}- Comparison of advantages and disadvantages of form and Json submission
| Scenario | Form Method | Json Method |
|---|---|---|
| Multi-level Parameters | Not Supported | Supported |
| File Upload | Supported | Not Supported |
- Regarding this issue, it can be implemented using the IRequestInterceptor interface provided by the framework, by overriding the corresponding methods to intercept and modify the content of the object to achieve encryption.
public interface IRequestInterceptor {
/**
* Intercept parameters
*
* @param httpRequest Interface object
* @param params Request parameters
* @param headers Request header parameters
*/
default void interceptArguments(@NonNull HttpRequest<?> httpRequest, @NonNull HttpParams params, @NonNull HttpHeaders headers) {}
/**
* Intercept request header
*
* @param httpRequest Interface object
* @param request Request header object
* @return Return new request header
*/
@NonNull
default Request interceptRequest(@NonNull HttpRequest<?> httpRequest, @NonNull Request request) {
return request;
}
/**
* Interceptor response header
*
* @param httpRequest Interface object
* @param response Response header object
* @return Return new response header
*/
@NonNull
default Response interceptResponse(@NonNull HttpRequest<?> httpRequest, @NonNull Response response) {
return response;
}
}// Set interceptor when initializing the framework
EasyConfig.with(okHttpClient)
// Set request parameter interceptor
.setInterceptor(new XxxInterceptor())
.into();- If you only want to encrypt or decrypt a specific interface, you can let the Api class implement the IRequestInterceptor interface separately, so it will not follow the global configuration.
public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
@HttpIgnore
private String address;
}- Add the
@HttpHeaderannotation to the field, then it means this field is a request header, if no annotation is added, the framework will default to using the field as a request parameter
public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
@HttpHeader
private String time;
}- Pass a
HashMaptype field, and add the@HTTPHeaderannotation to it
public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
@HTTPHeader
private HashMap<String,String> headers;
}- Add the
@HttpRenameannotation to the field, then you can modify the value of the parameter name, if no annotation is added, the framework will default to using the field name as the parameter name
public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
@HttpRename("k")
private String keyword;
}- Upload using File object
public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
private File file;
}- Upload using InputStream object
public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
private InputStream inputStream;
}- Upload using RequestBody object
public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
private RequestBody requestBody;
}public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "xxx/xxxx";
}
private List<File> files;
}// Set request retry count
EasyConfig.getInstance().setRetryCount(3);
// Set request retry time
EasyConfig.getInstance().setRetryTime(1000);- Global configuration (applies to all interfaces)
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.readTimeout(5000, TimeUnit.MILLISECONDS);
builder.writeTimeout(5000, TimeUnit.MILLISECONDS);
builder.connectTimeout(5000, TimeUnit.MILLISECONDS);
EasyConfig.with(builder.build())
.into();- Local configuration (only applies to a specific interface)
public final class XxxApi implements IRequestApi, IRequestHttpClient {
@NonNull
@Override
public String getApi() {
return "xxxx/";
}
@NonNull
@Override
public OkHttpClient getOkHttpClient() {
OkHttpClient.Builder builder = EasyConfig.getInstance().getOkHttpClient().newBuilder();
builder.readTimeout(5000, TimeUnit.MILLISECONDS);
builder.writeTimeout(5000, TimeUnit.MILLISECONDS);
builder.connectTimeout(5000, TimeUnit.MILLISECONDS);
return builder.build();
}
}EasyConfig.getInstance().setLogEnabled(false);- You can first define a class that implements IHttpLogStrategy interface, then pass it in when initializing the framework
EasyConfig.with(okHttpClient)
.......
// Set custom log printing strategy
.setLogStrategy(new XxxStrategy())
.into();-
Scenarios requiring modification of log printing strategy
-
Need to write request logs to local
-
Need to modify the format of printed request logs
-
// Cancel request task based on TAG
EasyHttp.cancelByTag(Object tag);
// Cancel request with specified Tag
EasyHttp.cancelByTag(Object tag);
// Cancel all requests
EasyHttp.cancelAll();EasyHttp.post(MainActivity.this)
.api(new XxxApi())
// Request after 5 seconds
.delay(5000)
.request(new HttpCallbackProxy<HttpData<XxxBean>>(this) {
@Override
public void onHttpSuccess(@NonNull HttpData<XxxBean> result) {
}
});public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "article/query/" + pageNumber + "/json";
}
@HttpIgnore
private int pageNumber;
public XxxApi setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
return this;
}
}EasyHttp.post(this)
.api(new RequestUrl("https://xxxx.com/aaaa"))
.request(new HttpCallbackProxy<Xxx>(this) {
@Override
public void onHttpSuccess(@NonNull Xxx result) {
}
});- Set this when initializing OkHttp
HttpSslConfig sslConfig = HttpSslFactory.generateSslConfig();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.sslSocketFactory(sslConfig.getsSLSocketFactory(), sslConfig.getTrustManager())
.hostnameVerifier(HttpSslFactory.generateUnSafeHostnameVerifier())
.build();-
However, this is not recommended, as this is insecure, meaning that no Https verification will be used for each request.
-
Of course, the framework also provides some API for generating certificates, please refer to the classes under the com.hjq.http.ssl package.
- First, define a URL management class, and configure the URL in this class
public final class HttpUrls {
/** Get user info */
public static final String GET_USER_INFO = "user/getUserInfo";
}- Then introduce the interface path into EasyHttp
EasyHttp.post(this)
.api(HttpUrls.GET_USER_INFO)
.request(new HttpCallbackProxy<HttpData<XxxBean>>(this) {
@Override
public void onHttpSuccess(@NonNull HttpData<XxxBean> result) {
}
});-
However, this method can only be applied to interfaces without parameters, interfaces with parameters still need to write a class, because the framework only parses parameters in the Api class.
-
Although EasyHttp opens this writing method, as the author, I do not recommend you to write it this way, because this writing method will result in poor extensibility, for example, subsequent addition of parameters, you still need to change it back, and cannot dynamically configure the interface.
-
Among them,
androidx.appcompat.app.AppCompatActivityandandroidx.fragment.app.Fragmentare subclasses of LifecycleOwner, this is unquestionable, you can directly pass it to the framework as LifecycleOwner -
However, if you pass in an
android.app.Activityobject, which is not anandroidx.appcompat.app.AppCompatActivityobject, then you can write it like this
EasyHttp.post(new ActivityLifecycle(this))
.api(new XxxApi())
.request(new HttpCallbackProxy<HttpData<XxxBean>>(this) {
@Override
public void onHttpSuccess(@NonNull HttpData<XxxBean> result) {
}
});-
If you pass in an
android.app.Fragmentobject, which is not anandroidx.fragment.app.Fragmentobject, please directly inherit the LifecycleAppFragment class in the framework, or encapsulate a Fragment base class with Lifecycle in your project -
If you want to use EasyHttp in an
android.app.Service, please directly inherit the LifecycleService class in the framework, or encapsulate a Service base class with Lifecycle in your project -
If none of the above conditions are met, but you still want to request the network in a certain place, then you can write it like this
EasyHttp.post(ApplicationLifecycle.getInstance())
.api(new XxxApi())
.tag("abc")
.request(new OnHttpListener<HttpData<XxxBean>>() {
@Override
public void onHttpSuccess(@NonNull HttpData<XxxBean> result) {
}
@Override
public void onHttpFail(@NonNull Throwable throwable) {
}
});-
Note: Passing ApplicationLifecycle means that the framework cannot automatically control the lifecycle of the request, if you write it in Application, it is completely fine, but you cannot write it in Activity or Service, because this may lead to memory leaks.
-
In addition to Application, if you use the writing method of ApplicationLifecycle in Activity or Service, in order to avoid memory leaks or crashes, you need to set the corresponding Tag when requesting, and manually cancel the request at the appropriate time (usually when Activity or Service is destroyed or exited).
EasyHttp.cancelByTag("abc");- Step 1: Encapsulate a BaseViewModel, and incorporate the LifecycleOwner feature
public class BaseViewModel extends ViewModel implements LifecycleOwner {
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
public BaseViewModel() {
mLifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
mLifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
}
@Override
protected void onCleared() {
super.onCleared();
mLifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
@NonNull
@Override
public Lifecycle getLifecycle() {
return mLifecycle;
}
}- Step 2: Let the business ViewModel class inherit BaseViewModel, specific example as follows
public class XxxViewModel extends BaseViewModel {
public void xxxx() {
EasyHttp.post(this)
.api(new XxxApi())
.request(new OnHttpListener<HttpData<Xxx>>() {
@Override
public void onHttpSuccess(@NonNull HttpData<Xxx> result) {
}
@Override
public void onHttpFail(@NonNull Throwable throwable) {
}
});
}
}-
First, this loading dialog is not built-in by the framework, it can be modified or cancelled, there are two ways to choose from
-
First way: Override HttpCallbackProxy callback methods
EasyHttp.post(this)
.api(new XxxApi())
.request(new HttpCallbackProxy<Xxx>(this) {
@Override
public void onHttpStart(@NonNull IRequestApi api) {
// Override method and comment out parent call
//super.onHttpStart(call);
}
@Override
public void onHttpEnd(@NonNull IRequestApi api) {
// Override method and comment out parent call
//super.onHttpEnd(call);
}
});- Second way: Directly implement OnHttpListener interface
EasyHttp.post(this)
.api(new XxxApi())
.request(new OnHttpListener<Xxx>() {
@Override
public void onHttpSuccess(@NonNull Xxx result) {
}
@Override
public void onHttpFail(@NonNull Throwable throwable) {
}
});- Since the Api class will eventually be converted into a JsonObject string, if you need to upload a JsonArray string, please implement it in the following way
List<Xxx> parameter = new ArrayList<>();
list.add(xxx);
list.add(xxx);
String json = gson.toJson(parameter);
EasyHttp.post(this)
.api(new XxxApi())
.body(new JsonRequestBody(json))
.request(new HttpCallbackProxy<HttpData<Xxx>>(this) {
@Override
public void onHttpSuccess(@NonNull HttpData<Xxx> result) {
}
});- However, I personally do not recommend using JsonArray as the root type of parameters, because the extensibility of such interfaces is extremely poor.
- The framework parses the fields in the Api class to be parameters through reflection, the field name as the parameter Key, the field value as the parameter Value, since Java cannot dynamically change the field name, so it cannot be modified through normal means, if you have this requirement, please implement it through the following means
HashMap<String, Object> parameter = new HashMap<>();
// Add global parameters
HashMap<String, Object> globalParams = EasyConfig.getInstance().getParams();
Set<String> keySet = globalParams.keySet();
for (String key : keySet) {
parameter.put(key, globalParams.get(key));
}
// Add custom parameters
parameter.put("key1", value1);
parameter.put("key2", value2);
String json = gson.toJson(parameter);
JsonRequestBody jsonRequestBody = new JsonRequestBody(json)
EasyHttp.post(this)
.api(new XxxApi())
.body(jsonRequestBody)
.request(new HttpCallbackProxy<HttpData<Xxx>>(this) {
@Override
public void onHttpSuccess(@NonNull HttpData<Xxx> result) {
}
});- First, UA stands for User Agent, when we do not set a custom UA identifier, then OkHttp will add a default UA identifier in the BridgeInterceptor, then how to set a custom UA identifier in EasyHttp? It's actually very simple, UA identifier is essentially a request header, add a request header with the name
User-Agentto EasyHttp, and then how to add a request header, the previous documentation has already been introduced, here is no repetition.
EasyHttp.post(this)
.api(new XxxApi())
// Indicates that the callback is performed in a sub-thread
.schedulers(ThreadSchedulers.IO)
.request(new HttpCallbackProxy<HttpData<Xxx>>(this) {
@Override
public void onHttpSuccess(@NonNull HttpData<Xxx> result) {
}
});- In some extreme cases, the framework cannot meet the needs, at which point a custom
RequestBodyis needed, then how to use a customRequestBody? The framework actually has an open method, the specific usage example is as follows:
EasyHttp.post(this)
.api(new XxxApi())
.body(RequestBody body)
.request(new HttpCallbackProxy<HttpData<Xxx>>(this) {
@Override
public void onHttpSuccess(@NonNull HttpData<Xxx> result) {
}
});- Note: Since Post requests place parameters on the
RequestBody, and a request can only set oneRequestBody, if you set a custombody(RequestBody body), then the framework will not parse the fields in theXxxApiclass into parameters. In addition to Post requests, Put requests and Patch requests can also use this method to set, here is no repetition.
- The specific writing example is as follows:
public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "xxx/xxx";
}
@HttpHeader
@HttpRename("Content-Type")
private String contentType = "application/x-www-form-urlencoded;charset=utf-8";
}- Note: This function was added in framework 11.5 version, previous versions did not have this function.
- First, define a custom Api class, then use the
getApimethod to dynamically concatenate the parameters
public final class CustomParameterApi implements IRequestApi {
@HttpIgnore
@NonNull
private final Map<String, String> parameters;
public CustomParameterApi() {
this(new HashMap<>());
}
public CustomParameterApi(@NonNull Map<String, String> parameters) {
this.parameters = parameters;
}
@NonNull
@Override
public String getApi() {
Set<String> keys = parameters.keySet();
StringBuilder builder = new StringBuilder();
int index = 0;
for (String key : keys) {
String value = parameters.get(key);
if (index == 0) {
builder.append("?");
}
builder.append(key)
.append("=")
.append(value);
if (index < keys.size() - 1) {
builder.append("&");
}
index++;
}
return "xxx/xxx" + builder;
}
public CustomParameterApi putParameter(String key, String value) {
parameters.put(key, value);
return this;
}
public CustomParameterApi removeParameter(String key) {
parameters.remove(key);
return this;
}
}- The outer layer can call it in the following way
CustomParameterApi api = new CustomParameterApi();
api.putParameter("key1", "value1");
api.putParameter("key2", "value2");
EasyHttp.get(this)
.api(api)
.request(new HttpCallbackProxy<Xxx>(this) {
@Override
public void onHttpSuccess(@NonNull Xxx result) {
}
});- Note: This implementation method is only applicable in cases where the framework's design cannot meet the requirements, and the author does not recommend using this method, because this method is not convenient to manage the key of request parameters, it is recommended to use the method of defining fields on the class to achieve.
- Directly concatenate request parameters to the url, and ignore the value of a field (to avoid being parsed as Post parameters)
public final class XxxApi implements IRequestApi {
@NonNull
@Override
public String getApi() {
return "article/query?pageNumber=" + pageNumber;
}
@HttpIgnore
private int pageNumber;
public XxxApi setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
return this;
}
}- Ps: In general, I do not recommend writing it this way, this request looks awkward, it looks like a Get request, but in fact it is a Post request.
- Add remote dependency
dependencies {
// RxJava: https://github.com/ReactiveX/RxJava
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.12'
}- Note: RxJava needs to handle lifecycle yourself to avoid memory leaks
Observable.create(new ObservableOnSubscribe<HttpData<SearchBean>>() {
@Override
public void subscribe(ObservableEmitter<HttpData<SearchBean>> emitter) throws Exception {
HttpData<SearchBean> data1;
try {
data1 = EasyHttp.post(MainActivity.this)
.api(new SearchBlogsApi()
.setKeyword("搬砖不再有"))
.execute(new ResponseClass<HttpData<SearchBean>>() {});
} catch (Throwable throwable) {
if (throwable instanceof Exception) {
throw (Exception) throwable;
} else {
throw new RuntimeException(throwable);
}
}
HttpData<SearchBean> data2;
try {
data2 = EasyHttp.post(MainActivity.this)
.api(new SearchBlogsApi()
.setKeyword(data1.getMessage()))
.execute(new ResponseClass<HttpData<SearchBean>>() {});
} catch (Throwable throwable) {
if (throwable instanceof Exception) {
throw (Exception) throwable;
} else {
throw new RuntimeException(throwable);
}
}
emitter.onNext(data2);
emitter.onComplete();
}
})
// Let the observer execute in IO thread
.subscribeOn(Schedulers.io())
// Let the observer execute in the main thread
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<HttpData<SearchBean>>() {
@Override
public void accept(HttpData<SearchBean> data) throws Exception {
Log.i("EasyHttp", "Final result: " + data.getMessage());
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
toast(throwable.getMessage());
}
});- If the polling times are limited, you can consider using Http requests to implement it, but if the polling times are infinite, then it is not recommended to use Http requests to implement it, it should be done using WebSocket, or other long-link protocols.
// Initiate polling request, initiate three requests, the first request triggers after 5 seconds, the remaining two trigger after 1 second and 2 seconds
Observable.intervalRange(1, 3, 5000, 1000, TimeUnit.MILLISECONDS)
// Let the observer execute in IO thread
.subscribeOn(Schedulers.io())
// Let the observer execute in the main thread
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
EasyHttp.post(MainActivity.this)
.api(new SearchBlogsApi()
.setKeyword("搬砖不再有"))
.request(new HttpCallbackProxy<HttpData<SearchBean>>(MainActivity.this) {
@Override
public void onHttpSuccess(@NonNull HttpData<SearchBean> result) {
}
});
}
});Observable.create(new ObservableOnSubscribe<HttpData<SearchBean>>() {
@Override
public void subscribe(ObservableEmitter<HttpData<SearchBean>> emitter) throws Exception {
EasyHttp.post(MainActivity.this)
.api(new SearchBlogsApi()
.setKeyword("搬砖不再有"))
.request(new HttpCallbackProxy<HttpData<SearchBean>>(MainActivity.this) {
@Override
public void onHttpSuccess(@NonNull HttpData<SearchBean> result) {
emitter.onNext(result);
emitter.onComplete();
}
@Override
public void onHttpFail(@NonNull Throwable throwable) {
super.onHttpFail(throwable);
emitter.onError(throwable);
}
});
}
})
.map(new Function<HttpData<SearchBean>, String>() {
@Override
public String apply(HttpData<SearchBean> data) throws Exception {
int curPage = data.getData().getCurPage();
int pageCount = data.getData().getPageCount();
return curPage + "/" + pageCount;
}
})
// Let the observer execute in IO thread
.subscribeOn(Schedulers.io())
// Let the observer execute in the main thread
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
Log.i("EasyHttp", "Current page position: " + s);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
toast(throwable.getMessage());
}
});- Add the following configuration to the
build.gradlefile in the project root directory
buildscript {
......
dependencies {
// Auto-generate Protobuf class plugin: https://github.com/google/protobuf-gradle-plugin
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.3'
}
}- Add remote dependency in the
build.gradlefile of the app module
......
apply plugin: 'com.google.protobuf'
android {
......
sourceSets {
main {
proto {
// Specify Protobuf file path
srcDir 'src/main/proto'
}
}
}
}
protobuf {
protoc {
// You can also configure the local compiler path
artifact = 'com.google.protobuf:protoc:3.23.0'
}
generateProtoTasks {
all().each { task ->
task.builtins {
remove java
}
task.builtins {
// Produce java source code
java {}
}
}
}
}
dependencies {
......
// Protobuf: https://github.com/protocolbuffers/protobuf
implementation 'com.google.protobuf:protobuf-java:3.23.1'
implementation 'com.google.protobuf:protoc:3.23.0'
}- Create a folder named
protounderapp/src/main/, used to store Protobuf related files, then create a filePerson.proto, the specific content is as follows:
syntax = "proto3";
package tutorial;
message Person {
string name = 1;
int32 id = 2;
string email = 3;
string phone = 4;
}
- Then
Rebuild Project, you can see the plugin automatically generate thePersonOuterClassclass,PersonOuterClassclass also has a static inner class namedPerson
- Create a custom RequestBody class to parse Protocol objects into streams, it is recommended to be placed in the
com.xxx.xxx/http/modelpackage,
public class ProtocolRequestBody extends RequestBody {
/** MessageLite object */
private final MessageLite mMessageLite;
/** Byte array */
private final byte[] mBytes;
public ProtocolRequestBody(MessageLite messageLite) {
mMessageLite = messageLite;
mBytes = messageLite.toByteArray();
}
@Override
public MediaType contentType() {
return ContentType.JSON;
}
@Override
public long contentLength() {
// Note: This needs to be calculated using the length of the byte array
return mBytes.length;
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.write(mBytes, 0, mBytes.length);
}
@NonNull
@Override
public String toString() {
return mMessageLite.toString();
}
/**
* Get MessageLite object
*/
@NonNull
public MessageLite getMessageLite() {
return mMessageLite;
}
}- Example of initiating a request
// Pretend to generate a Protobuf object
Person person = Person.parseFrom("xxxxxxxxx".getBytes());
EasyHttp.post(this)
.api(new XxxApi())
.body(new ProtocolRequestBody(person))
.request(new HttpCallbackProxy<HttpData<SearchBlogsApi.Bean>>(this) {
@Override
public void onHttpSuccess(@NonNull HttpData<SearchBlogsApi.Bean> result) {
}
});- This support is very simple, just modify the implementation of the
IRequestHandlerinterface, the specific code implementation is as follows:
public final class RequestHandler implements IRequestHandler {
......
@NonNull
@Override
public Object requestSuccess(@NonNull HttpRequest<?> httpRequest, @NonNull Response response,
@NonNull Type type) throws Throwable {
......
final Object result;
try {
if (type instanceof Class<?> && AbstractParser.class.isAssignableFrom((Class<?>) type)) {
String simpleName = ((Class<?>) type).getSimpleName();
Class<?> clazz = Class.forName("tutorial." + simpleName + ".OuterClass." + simpleName);
Method parseFromMethod = clazz.getMethod("parseFrom", byte[].class);
// Call static method
result = parseFromMethod.invoke(null, (Object) text.getBytes());
} else {
result = GsonFactory.getSingletonGson().fromJson(text, type);
}
} catch (JsonSyntaxException e) {
// Return result read exception
throw new DataException(mApplication.getString(R.string.http_data_explain_error), e);
}
......
return result;
}
}