改造通用服务接口

问题描述 投票:0回答:6

我正在为 Retrofit 创建一个通用 API 层

这是我的服务等级:

public interface ApiService {

    @POST("api/authenticate")
    Call<Class> postData(@Body Class postBody);

}

public  void  postRequest(String actionUrl,GenericModelClass postBodyModel){
    mApiService.postData(postBodyModel.getClass()).enqueue(new Callback<Class>() {


        @Override
        public void onResponse(Call<Class> call, Response<Class> response) {
            response.getClass().getComponentType();

            Log.d("TFFF", response.toString());
        }

        @Override
        public void onFailure(Call<Class> call, Throwable t) {
          Log.d("TFFF", t.toString());
        }
    });
}

但是这个给了我:

java.lang.UnsupportedOperationException:尝试序列化 java.lang.Class:a2a.rnd.com.a2ahttplibrary.retrofit.model.User。忘记注册类型适配器?

我想从泛型类型中获取

User
类型,但我得到了这个异常。

android generics retrofit retrofit2
6个回答
17
投票

你正在以一种没有意义的方式做这件事,这就是你得到的原因:

java.lang.UnsupportedOperationException:尝试序列化 java.lang.Class:a2a.rnd.com.a2ahttplibrary.retrofit.model.User。忘记注册类型适配器?

您的服务未指定类型参数。

Class
处理另一个目的:它是一个代表JVM加载的类的对象。序列化和反序列化
Class
实例的意义非常小(如果有的话),这就是 Gson 不提供它的原因。您想要的只是通用方法。互联网上有无数关于这个主题的文章。

接下来,Retrofit 不使用方法类型参数来显着简化底层的类型分析。没关系。

@GET("/")
<T> Call<T> get();

这行不通。那么您将如何传递必要的类型信息数据呢?我能想到的传递该信息的唯一方法是引入一个包装器来保存值及其类型(或类型令牌以简化 Gson)。

final class GenericBody<T> {

    final T body;
    final TypeToken<T> typeToken;

    GenericBody(final T body, final TypeToken<T> typeToken) {
        this.body = body;
        this.typeToken = typeToken;
    }

}

那么示例服务可能声明如下:

interface IGenericService {

    @POST("/")
    Call<Void> post(@Body @SuppressWarnings("rawtypes") GenericBody genericBody);

}

这里,

Call
被声明为不返回任何内容,并且
genericBody
被故意设为原始类型以使其通过Retrofit验证。

接下来是Gson部分。

final class GenericBodyTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory genericBodyTypeAdapterFactory = new GenericBodyTypeAdapterFactory();

    private GenericBodyTypeAdapterFactory() {
    }

    static TypeAdapterFactory getGenericBodyTypeAdapterFactory() {
        return genericBodyTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        if ( !GenericBody.class.isAssignableFrom(typeToken.getRawType()) ) {
            return null;
        }
        final TypeAdapter<GenericBody<T>> genericBodyTypeAdapter = new TypeAdapter<GenericBody<T>>() {
            @Override
            public void write(final JsonWriter out, final GenericBody<T> value)
                    throws IOException {
                final T body = value.body;
                final TypeAdapter<T> typeAdapter = gson.getDelegateAdapter(GenericBodyTypeAdapterFactory.this, value.typeToken);
                typeAdapter.write(out, body);
            }

            @Override
            public GenericBody<T> read(final JsonReader in) {
                throw new UnsupportedOperationException();
            }
        };
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) genericBodyTypeAdapter;
        return typeAdapter;
    }

}

它的作用是:

  • 检查它是否可以处理
    GenericBody
    实例;
  • 通过
    bound
    类型标记解析 <T> 的适当类型适配器;
  • 将通用主体值写入输出。

未实现任何读取。

使用示例(充满模拟(

staticResponse(applicationJsonMediaType, "OK")
),可以轻松翻译为您的代码):

private static final TypeToken<List<String>> stringListTypeToken = new TypeToken<List<String>>() {
};

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(getGenericBodyTypeAdapterFactory())
        .create();

private static final OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(staticResponse(applicationJsonMediaType, "OK"))
        .build();

private static final Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://whatever")
        .client(client)
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();

private static final IGenericService genericService = retrofit.create(IGenericService.class);

public static void main(final String... args)
        throws IOException {
    final GenericBody<List<String>> body = new GenericBody<>(asList("foo", "bar", "baz"), stringListTypeToken);
    genericService.post(body).execute();
}

这会将

["foo","bar","baz"]
写入输出流,尊重正确配置Gson(反)序列化策略。


1
投票

我认为我在这方面迟到了,但我的解决方案可能会帮助新程序员,因为它是我遇到过的最短的解决方案。 所以就这样了:

  1. 我们将使用 com.google.gson 中的 JsonObject,而不是使用特定的类作为服务中的输入参数

  2. 然后我们将使用一个辅助函数来调用该服务,辅助函数将接受我们的模型并将其序列化为等效的 json 对象,

  3. 之后,我们将使用 gson 反序列化将该 json 对象转换为 JsonObject。

  4. 将该参数传递给服务,它将完美运行。

    @POST
    suspend fun <V : Any> doPostRequest(
    @Header("Authorization") token: String = getToken(),
    @Url url: String,
    @Body inputModel: JsonObject
    ): Response<V>
    

这就是我们的服务函数的样子,然后我将辅助函数放入基础存储库中。

suspend fun <T : Any, V : Any> postRequest(
    apiName: String,
    model: T
): Flow<Resource<V>> {
    val gson = Gson()
    val body = gson.toJson(model)
    val jsonObject = gson.fromJson(body, JsonObject::class.java)
    return safeApiCall {
        sharedApiService.doPostRequest(
            url = apiName,
            inputModel = jsonObject
        )
    }
}

0
投票

找了好久终于找到了正确的方法。您可以使用这些代码发送具有完全通用的 url 和参数的 GET 和 POST 请求。

当然,你必须实现一些依赖项才能使用它(例如,Retrofit、Gson、Hilt...),并且你必须编辑一些东西。

祝你编码愉快✌🏻


HttpApiModule.kt

@Module
@InstallIn(SingletonComponent::class)
object HttpApiModule {

    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        val interceptor = HttpLoggingInterceptor()
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
        val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
        return Retrofit.Builder()
            .baseUrl("http://192.168.1.25:8000/")
            .client(client)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(CoroutineCallAdapterFactory())
            .build()
    }


    @Provides
    @Singleton
    fun provideHttpApiService(retrofit: Retrofit): HttpApiService = retrofit.create()
}

HttpApiService.kt

interface HttpApiService {
    @POST
    suspend fun <R : Any> doPostRequest(
        @Header("Authorization") token: String,
        @Url path: String,
        @Body inputModel: JsonObject
    ): Response<R>


    @GET
    suspend fun <R : Any> doGetRequest(
        @Header("Authorization") token: String,
        @Url path: String,
        @QueryMap params: Map<String, String>
    ): Response<R>
}

数据模型扩展.kt

val gson = Gson()

//convert a data class to a map
fun <T> T.serializeToMap(): Map<String, String> {
    return convert()
}

//convert a map to a data class
inline fun <reified T> Map<String, String>.toDataClass(): T {
    return convert()
}

//convert an object of type I to type O
inline fun <I, reified O> I.convert(): O {
    val json = gson.toJson(this)
    return gson.fromJson(json, object : TypeToken<O>() {}.type)
}

HttpRequest.kt

class HttpRequest @Inject constructor(
    private val httpApiService: HttpApiService,
) {
    suspend fun <RQ : Any, RS : Any> postRequest(
        context: Context,
        path: String,
        requestModel: RQ
    ): Flow<Resource<RS>> = flow {
        val gson = Gson()
        val bodyJson = gson.toJson(requestModel)
        val jsonObject = gson.fromJson(bodyJson, JsonObject::class.java)
        coroutineScope {

            emit(Resource.Loading)

            if (context.isOnline()) {
                val call = httpApiService.doPostRequest<RS>(
                    inputModel = jsonObject,
                    token = "[TOKEN]",
                    path = path
                )
                call.run {
                    if (call.isSuccessful) {
                        body()?.let {
                            emit(Resource.Success(it))
                        } ?: kotlin.run {
                            emit(Resource.Error(BaseError(errorMessage = call.message())))
                        }
                    } else {
                        emit(Resource.Error(BaseError(errorMessage = "The http request is not successful!")))
                    }
                }
            } else {
                emit(Resource.Error(BaseError(errorMessage = "Internet connection error!")))
            }

        }
    }

    suspend fun <RQ : Any, RS : Any> getRequest(
        context: Context,
        path: String,
        requestModel: RQ
    ): Flow<Resource<RS>> = flow {
        val params = requestModel.serializeToMap()
        coroutineScope {

            emit(Resource.Loading)

            if (context.isOnline()) {
                val call = httpApiService.doGetRequest<RS>(
                    params = params,
                    token = "[TOKEN]",
                    path = path
                )
                call.run {
                    if (call.isSuccessful) {
                        body()?.let {
                            emit(Resource.Success(it))
                        } ?: kotlin.run {
                            emit(Resource.Error(BaseError(errorMessage = call.message())))
                        }
                    } else {
                        emit(Resource.Error(BaseError(errorMessage = "The http request is not successful!")))
                    }
                }
            } else {
                emit(Resource.Error(BaseError(errorMessage = "Internet connection error!")))
            }
        }
    }
}

你的ViewModel.kt

@HiltViewModel
class DashboardViewModel @Inject constructor(
    private val httpApiService: HttpApiService,
    @SuppressLint("StaticFieldLeak") @ApplicationContext private val context: Context
) : BaseViewModel() {

    suspend fun httpRequest() {
        val httpRequest = HttpRequest(httpApiService)

        // Get
        httpRequest.getRequest<QuestionsRequest, QuestionsResponse>(
            context = context,
            path = "api/v1/get/questions",
            requestModel = QuestionsRequest()
        ).collectLatest {
            log("getRequest state: $it")
        }
        
        // Post
        httpRequest.postRequest<QuestionsRequest, QuestionsResponse>(
            context = context,
            path = "api/v1/get/questions",
            requestModel = QuestionsRequest()
        ).collectLatest {
            log("postRequest state: $it")
        }
    }

    data class QuestionsRequest(
        val count: 7,
    )

    data class QuestionsResponse(
        val success: Boolean,
        val size: Int,
        val questions: List<QuestionResponse>
    )

}

仅供参考@compaq-le2202x


0
投票

在 APIManager 类中尝试一下

suspend inline fun <reified RS : Any> postRequest(
    @Url url: String,
    @Body body: RequestBody,
    @HeaderMap headerMap: Map<String, String>
): RS = Gson().fromJson(apiService.postRequest(url, body, headerMap).body().toString(), RS::class.java)

0
投票

查看此通用代码以使用 retofit2 访问任何 api

https://github.com/Mohammad-Taqi/Generic-Retrofit-Implementation/tree/main


-2
投票

认为您的 postrequest 方法中缺少以下代码行

ApiService mApiService = APIUtil.getApiService().create(ApiInterface.class);
© www.soinside.com 2019 - 2024. All rights reserved.