Detailed explanation of Android project multi server interface adaptation of ultra simple
- 2021-12-04 19:59:16
- OfStack
Status quo
If the Android project is a multi-server interface, what should I do?
Method 1: The server address is placed in Header
The server address is placed in the interface Header, and then the interceptor dynamically modifies the request address. In addition to the interface of the default server, it is a bit troublesome to add an Header. It doesn't look cool or concise.
interface ApiHeaderCase {
/************************** server A ****************************/
@Headers("host:$SERVER_HOST_A")
@GET("user/loginWithScanCode")
fun aMethod1(@Query("id") id: Int): Observable<ResponseBody>
/************************** server B ****************************/
@Headers("host:$SERVER_HOST_B")
@GET("user/loginWithScanCode")
fun bMethod1(@Query("id") id: Int): Observable<ResponseBody>
}
Method 2: Multiple sets of service classes are instantiated into multiple objects, and the interface attribution service is accurately found
Define multiple classes, and each class defines a set of service interfaces. Then instantiate them into multiple objects, and then use the exact objects to call the interface. This method is the most efficient, but during development, it may not be possible to quickly know which service the interface belongs to, and it is necessary to check the code to know accurately, which can be said to be less code prompt ability.
interface ApiA {
@GET("user/loginWithScanCode")
fun methodA(@Query("id") id: Int): Observable<ResponseBody>
}
interface ApiB {
@GET("user/loginWithScanCode")
fun methodB(@Query("id") id: Int): Observable<ResponseBody>
}
Method 3: Write it all from 1, instantiate it into multiple objects, and call the method accurately
Write all interfaces in one class, and then instantiate them into multiple objects according to the service address. Then call the method accurately. In order to ensure the accurate call of the method, each interface can be prefixed with a service name to reduce the problem of wrong method adjustment.
interface ApiAllInOne {
/************************** server A ****************************/
@GET("user/loginWithScanCode")
fun aMethod1(@Query("id") id: Int): Observable<ResponseBody>
/************************** server B ****************************/
@GET("user/loginWithScanCode")
fun bMethod1(@Query("id") id: Int): Observable<ResponseBody>
}
const val SERVER_HOST_A = "https://www.a.com/"
const val SERVER_HOST_B = "https://www.b.com/"
fun getApi(retrofit: Retrofit, host: String): ApiAllInOne {
return retrofit.newBuilder()
.baseUrl(host).build()
.create(ApiAllInOne::class.java)
}
fun showNomalUseCase(retrofit: Retrofit) {
val apiA = getApi(retrofit, SERVER_HOST_A)//save as single instance for repeated usage
apiA.aMethod1(1).subscribe()
apiA.bMethod1(1).subscribe()//invalid usage, but no compile error
val apiB = getApi(retrofit, SERVER_HOST_B)
apiB.bMethod1(1).subscribe()
apiB.aMethod1(1).subscribe()//invalid usage, but no compile error
}
Is there an easier way?
Of course there is, and it is extremely convenient!
Defining an interface
(Recommended) Define all interfaces in one KT file for easy lookup and maintenance.
interface ApiHolder : ApiA, ApiB
@BaseUrl("https://www.a.com/")
interface ApiA {
@GET("user/loginWithScanCode")
fun methodA(@Query("id") id: Int): Observable<ResponseBody>
}
@BaseUrl("https://www.b.com/")
interface ApiB {
@GET("user/loginWithScanCode")
fun methodB(@Query("id") id: Int): Observable<ResponseBody>
}
Building tool class
1 generally need a tool class, which is convenient to configure interceptors and so on. If there is no custom requirement, it can also be instantiated directly.
You can override the invokeApi method and set threads globally for each Observable.
class ApiUtil : ApiHolderUtil<ApiHolder>(ApiHolder::class) {
companion object {
val apiUtil = ApiUtil()
val api = apiUtil.api
}
override fun invokeApi(api: Any, method: Method, args: Array<*>?): Any {
val observable = super.invokeApi(api, method, args) as Observable<*>
return observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
}
Dynamic update service address
Service addresses can also be dynamically updated, such as switching between test services and official services.
//update api baseUrl when needed
apiUtil.updateApi(ApiA::class, https://www.a2.com/)
Calling interface
api.methodA(1).subscribe()
api.methodB(1).subscribe()
Introducing dependency
dependencies {
implementation 'com.github.DonaldDu:ApiHolder:x.x.x'//JitPack version
}
Three-party library used in this project
OkHttp3 Retrofit2 rxjava3 (can be modified to rxjava2)
api 'com.squareup.okhttp3:okhttp:4.7.2'
api "com.squareup.retrofit2:retrofit:2.9.0"
api "com.squareup.retrofit2:converter-gson:2.9.0"
api "com.squareup.retrofit2:adapter-rxjava3:2.9.0"
api 'io.reactivex.rxjava3:rxandroid:3.0.0'
Other notes
rxjava3 -
>
rxjava2
It can be adjusted to rxjava2 as needed. It is recommended to use the latest one.
// Rewrite ApiHolderUtil The following method, RxJava3CallAdapterFactory ->RxJava2CallAdapterFactory That's enough.
protected open fun getRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder()
.validateEagerly(validateEagerly)
.addConverterFactory(getGsonConverterFactory())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.baseUrl("http://www.demo.com/")
.client(client)
.build()
}
Timeout
You can set different timeouts for each set of services
interface ApiA {
@GET("user/loginWithScanCode")
fun methodA(@Query("id") id: Int): Observable<ResponseBody>
}
interface ApiB {
@GET("user/loginWithScanCode")
fun methodB(@Query("id") id: Int): Observable<ResponseBody>
}
0