首页 > 解决方案 > 改造第一次调用会冻结 UI

问题描述

我在我的 Android 应用程序中使用 Retrofit 和 TMDB API。改造第一次调用会冻结 UI。我几乎尝试了所有方法,但没有奏效。(我试图将所有动作都放在协程中)它在第一次调用时一直冻结。非常感谢您的帮助。请检查我的代码:

TmdbService.kt

object TmdbService {

fun getClient(): TmdbApi {

    val requestInterceptor = Interceptor {
        val url = it.request()
            .url()
            .newBuilder()
            .addQueryParameter("api_key", API_KEY)
            .build()

        val request = it.request()
            .newBuilder()
            .url(url)
            .build()

        return@Interceptor it.proceed(request)
    }

    val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(requestInterceptor)
        .connectTimeout(60, TimeUnit.SECONDS)
        .build()

    val moshi = Moshi.Builder()
        .add(KotlinJsonAdapterFactory())
        .build()

    return Retrofit.Builder()
        .client(okHttpClient)
        .baseUrl(BASE_URL)
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .build()
        .create(TmdbApi::class.java)

    }

}

TmdbAPI.kt

interface TmdbApi {
@GET("search/movie")
suspend fun getMovie(
    @Query("query") query: String,
    @Query("page") page: Int
): Response<MovieResult>

@GET("search/tv")
suspend fun getSeries(
    @Query("query") query: String,
    @Query("page") page: Int
): Response<SeriesResult>
}

电影列表视图模型.kt

class MovieListViewModel : ViewModel() {

//########## TMDB API
private val api: MutableLiveData<TmdbApi> by lazy { MutableLiveData<TmdbApi>() }


//########## Progress MaterialTextView text
private var _progressMTVText = MutableLiveData<String>()
val progressMTVText: LiveData<String>
    get() = _progressMTVText

fun setProgressMTVText(text: String) {
    _progressMTVText.value = text
}


//########## Progress LinearLayout gone
private var _progressLLGone = MutableLiveData<Boolean>()
val progressLLGone: LiveData<Boolean>
    get() = _progressLLGone

fun isProgressLLGone(gone: Boolean) {
    _progressLLGone.value = gone
}


//########## Get data
fun getData(query: String, adapter: MovieListAdapter) {

    val listData = Pager(PagingConfig(pageSize = PAGE_SIZE)) {
        MovieDataSource(query, api.value!!)
    }.flow.cachedIn(viewModelScope)

    viewModelScope.launch {

        listData.collect {
            adapter.submitData(it)
        }

    }

}


init {

    viewModelScope.launch {
        api.value = TmdbService.getClient()
    }

}

}

电影数据源.kt

class MovieDataSource(private val query: String, private val api: TmdbApi) :
PagingSource<Int, Movie>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> {

    return try {
        val currentLoadingPageKey = params.key ?: 1
        val responseData = mutableListOf<Movie>()
        responseData.addAll(
            api.getMovie(query, currentLoadingPageKey).body()?.results ?: emptyList()
        )
        
        LoadResult.Page(
            data = responseData,
            prevKey = if (currentLoadingPageKey == 1) null else currentLoadingPageKey - 1,
            nextKey = currentLoadingPageKey.plus(1)
        )
    } catch (e: Exception) {
        LoadResult.Error(e)
    }

}

override fun getRefreshKey(state: PagingState<Int, Movie>): Int? {
    return state.anchorPosition
}

}

电影列表片段.kt

class MovieListFragment : Fragment(), MovieListAdapter.ItemClickListener {

private lateinit var viewModel: MovieListViewModel

private lateinit var binding: FragmentMovieListBinding

private lateinit var adapter: MovieListAdapter

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View {

    viewModel = ViewModelProviders.of(this).get(MovieListViewModel::class.java)

    binding =
        DataBindingUtil.inflate(inflater, R.layout.fragment_movie_list, container, false)

    binding.let {
        it.viewModel = viewModel
        it.lifecycleOwner = viewLifecycleOwner
    }

    observe()

    setRecyclerView()

    viewModel.isProgressLLGone(true)

    return binding.root

}

private fun observe() {

    searchResult.observe(viewLifecycleOwner, {

        if (networkAvailable) {

            if (!it.isNullOrEmpty()) {

                with(viewModel) {
                    setProgressMTVText("${getString(R.string.searching_for)} 
                     \"${searchResult.value}\" ...")
                    isProgressLLGone(false)
                }

                CoroutineScope(Dispatchers.Main).launch {
                    delay(500)
                    viewModel.getData(it, adapter)
                }

            }

        } else R.string.no_internet_connection.showToast(requireContext())

    })

}

private fun setRecyclerView() {

    adapter = MovieListAdapter(this)

    binding.fragmentMovieListMainRV.let {
        it.layoutManager = LinearLayoutManager(context)
        it.adapter = adapter
    }

    CoroutineScope(Dispatchers.Main).launch {

        adapter.loadStateFlow.collectLatest {

            with(viewModel) {

                when (it.append) {

                    is LoadState.Loading -> {
                        setProgressMTVText("${getString(R.string.searching_for)} 
                       \"${searchResult.value}\" ...")
                        isProgressLLGone(false)
                    }

                    is LoadState.NotLoading -> {
                        setProgressMTVText("")
                        isProgressLLGone(true)
                    }

                    is LoadState.Error -> {
                        setProgressMTVText("")
                        isProgressLLGone(true)
                        if (!networkAvailable) R.string.no_internet_connection.showToast(
                            requireContext()
                        )
                    }

                }

            }

        }

    }

}

override fun onItemClick(movie: Movie) {
    startActivity(Intent(context, MovieDetailActivity::class.java).putExtra("movie", movie))
}

}

标签: androidandroid-studiokotlinretrofitcoroutine

解决方案


推荐阅读