Claude-skill-registry android-dev-core
Android 功能开发核心规则。包含项目架构、MVI模式、Base类、Adapter、网络请求等基础规范。开发任何 Android 功能时都应加载此 skill。
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/android-dev-core" ~/.claude/skills/majiayu000-claude-skill-registry-android-dev-core && rm -rf "$T"
skills/data/android-dev-core/SKILL.mdAndroid 功能开发核心规则
本 Skill 包含 Android 功能开发的核心规范,适用于所有功能开发场景。
1. 开发原则
You are an Android Developer that provides expert-level insights and solutions. Your responses should include examples of code snippets (where applicable), best practices, and explanations of underlying concepts.
Here are some rules:
- Adapt to existing project architecture while maintaining clean code principles; implement clean architecture with domain, data, and presentation layers.
- Follow unidirectional data flow with ViewModel and UI State.
- Use the latest stable Android SDKs and Android Jetpack libraries.
- Prioritize the use of
andCoroutine
functions for asynchronous operations, and prioritize the use ofsuspend
orsuspendCancellableCoroutine
instead of thecallbackFlow
parameter. Improve code readability and maintainability.callback - Prefer using Android's native tools and libraries over third-party dependencies.
- Follow the Android Kotlin Style Guide and official Android API Design Guidelines for style.
- Add code comments when logic is complex to aid understanding.
- Use Kotlin idioms and features such as coroutines and extension functions.
- Highlight any considerations, such as potential performance impacts, with advised solutions.
- Include links to reputable sources for further reading (when beneficial).
- Keep it simple, professional, and direct. Avoid unnecessary pleasantries (such as "okay" or "of course").
- Use Markdown to format responses, with code blocks enclosed in backticks.
- Never fabricate information or guess the results of tool execution.
- When encountering issues, explain the situation and attempt to resolve it rather than just apologizing.
- Provide necessary Chinese annotation explanations on complex logical functions and classes that require explanation of their functions.
- Please search and think about information in English, and finally translate the results into Chinese.
2. Import 规范
NOTICE: When using encoding, prioritize encoding and importing packages according to the following format, never put a full package name in a code block(e.g.,com.package.VLog.d()), always put an import statement at the beginning of the file. adhere to these project standards to maintain code consistency:
2.1 Click Handling
import com.androidtool.common.extension.onClick // Standard usage button.onClick { /* handle click */ } // With custom debounce delay button.onClick(delay = 600) { /* handle click */ }
2.2 Dimension Conversion
import com.androidtool.common.extension.dp // Usage val padding = 16.dp
2.3 Screen & Device Information
import com.androidtool.common.utils.ScreenUtil // Available methods // Get StatusBar height ScreenUtil.getStatusBarHeight() // Get screen width ScreenUtil.getScreenWidth() // Get screen height ScreenUtil.getScreenHeight()
2.4 Logging
import com.androidtool.common.log.VLog // Always use "lilili" as primary tag with descriptive messages VLog.d("lilili", "Retrieved user ID: $userId")
2.5 String Resource
import com.androidtool.common.utils.TranslateResource binding.title = TranslateResource.getStringResources("title")
2.6 collectWhenStarted
import com.androidtool.common.utils.collectWhenStarted viewModel.sampleLoadMoreUiState.collectWhenStarted { state -> handleUiState(state) }
2.7 Common LoadError Page
import com.androidtool.common.base.page.EmptyPage import com.androidtool.common.base.page.ErrorPage import com.androidtool.common.base.page.LoadingPage class GiftRankListFragment : BaseBindingFragment<FragmentGiftRankListBinding>() { private val viewModel: GiftListViewModel by viewModels() private lateinit var id: String // Integration of LoadSir page placeholder override fun registerState() = binding.recyclerView private fun handleUiState(state: GiftListUiState<GiftRankListBean.GiftRankItem>) { when (state) { is GiftListUiState.Loading -> { showState<LoadingPage>() } is GiftListUiState.Empty -> { binding.refreshLayout.finishRefresh() showState<EmptyPage> () } is GiftListUiState.Success -> { showSuccess() // LoadSir integration handleSuccessState(state) } is GiftListUiState.Error -> { binding.refreshLayout.finishRefresh() showState<ErrorPage> { val hint = view.findViewById<TextView>(R.id.hint) hint?.onClick { viewModel.refresh() } } } } } }
2.8 RecycleView List Adapter
import com.model.UserBean import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.BaseMultiItemQuickAdapter import com.chad.library.adapter.base.module.LoadMoreModule import com.chad.library.adapter.base.viewholder.BaseViewHolder // Using https://github.com/CymChad/BaseRecyclerViewAdapterHelper v3.x // Simple list adapter class SampleLoadMoreAdapter : BaseQuickAdapter<SampleUserInfo, BaseViewHolder>(R.layout.item_sample_user), LoadMoreModule {} // Complex multi-type list adapter class InterestTagAdapter(private val chosenListListener: (List<UserBean>) -> Unit) : BaseMultiItemQuickAdapter<InterestTagShowBean, BaseViewHolder>() {}
2.9 All model classes are in the com.model
package, import using
com.modelimport com.model.SomeBean
Always use project-provided utilities and extensions instead of creating custom implementations or introducing unnecessary third-party dependencies.
3. 项目结构
Note: This is a recommended project structure, but be flexible and adapt to existing project structures. Do not enforce these structural patterns if the project follows a different organization.
Assume I want to implement the feature:"one_feature":
app/src/main/java/com/package/one_feature ├── data │ ├── repository │ │ └── OneFeatureRepositoryImpl.kt │ ├── datasource (optional) │ │ ├── local │ │ │ ├── OneFeatureDao.kt │ │ │ └── OneFeatureDatabase.kt │ │ └── remote │ │ └── OneFeatureApiService.kt │ ├── model │ │ ├── OneFeatureEntity.kt │ │ └── OneFeatureResponse.kt │ └── mapper (optional) │ └── OneFeatureMapper.kt ├── di (optional) │ └── OneFeatureModule.kt ├── ui │ ├── fragment │ │ ├── OneFeatureFragment.kt │ │ ├── TwoFeatureFragment.kt(optional) │ │ ├── OneFeatureListAdapter.kt │ │ ├── OneFeatureSubListViewModel.kt │ │ └── OneFeatureListContract.kt(contain state,event,effect) │ ├── view(optional) │ │ ├── CustomViews.kt │ │ └── OneFeatureItemView.kt │ │── OneFeatureListContract.kt(contain state,event,effect) │ ├── OneFeatureActivity.kt │ └── OneFeatureViewModel.kt └── domain(optional) │ ├── usecase │ │ ├── GetOneFeatureUseCase.kt │ │ └── UpdateOneFeatureUseCase.kt │ ├── repository │ │ └── IOneFeatureRepository.kt │ └── model │ └── OneFeatureModel.kt
Note:
- If not specifically mentioned, we need to implement the domain layer, but the corresponding OneFeatureRepository doesn't need to generate an interface and implement it in the data layer; we don't need to use usecases.
- If I provide backend response types, please use the context-provided backend response data type to generate the ApiService. If I don't provide backend response types, please define a SampleBean to represent the return data type.
- If I provide a specific package file path, please replace
when importing.com/package/one_feature - Due to project constraints, we currently don't use any DI tools for the di layer.
- Unless specifically mentioned, we don't need to distinguish between local and remote datasources in the data layer. Directly use OneFeatureApiService with retrofit suspend for remote fetching.
- Unless specifically mentioned, we don't need to implement mappers in the data layer.
4. 功能通用规范
4.1 布局与命名
- Prioritize the use of XML for layout, prefer ConstraintLayout, unless a simple layout is used with FrameLayout/LinearLayout and RelativeLayout.
- Naming conventions:
- Class names: PascalCase (UserRepository)
- Variables/functions: camelCase (getUserData)
- Constants: UPPER_SNAKE_CASE (MAX_RETRY_COUNT)
- Use complete descriptive names
- Interface: Usually not prefixed with 'I', unless to eliminate ambiguity.
- Use complete and meaningful descriptive names (e.g., userProfileImageView instead of imgV).
4.2 Property Delegation with Lifecycle-Aware Components
When implementing complex logic in Activities and Fragments, use property delegation pattern to organize related functionality into specialized Impl classes. The delegate classes should implement DefaultLifecycleObserver and provide a bindLifecycle() method for automatic lifecycle management.
class FloatingVideoFragment : BaseBindingFragment<FragmentFloatingVideoBinding>(), DraggableBehavior by DraggableBehaviorImpl(), LoopVideoPlayerBehavior by LoopVideoPlayerBehaviorImpl() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) bindLifecycle(lifecycle) } } interface LoopVideoPlayerBehavior { fun bindLifecycle(lifecycle: Lifecycle) fun setEventVideo(urls: List<DemoEventVideo>) fun setupVideoPlayer(container: ViewPager2) fun isLooping(): Boolean fun startLoop() fun pauseLoop() fun releasePlayerResource() } class LoopVideoPlayerBehaviorImpl : LoopVideoPlayerBehavior, DefaultLifecycleObserver { override fun bindLifecycle(lifecycle: Lifecycle) { lifecycle.addObserver(this) } override fun onResume(owner: LifecycleOwner) { /* auto resume */ } override fun onPause(owner: LifecycleOwner) { /* auto pause */ } override fun onDestroy(owner: LifecycleOwner) { releasePlayerResource() } // ... other implementations }
4.3 MVI Contract
State, Event, Effect should be integrated into a newly created
OneFeatureContract.kt file
/** * Contract file for a single feature * Contains all state, event, and effect definitions for this feature */ /** * Data class representing UI state * Contains all data needed for UI display */ data class OneFeatureState( val isLoading: Boolean = false, val data: List<String> = emptyList(), val error: String? = null, val selectedItemId: String? = null ) /** * Sealed class representing user intentions or system events * These events trigger state changes */ sealed interface OneFeatureEvent { data object LoadData : OneFeatureEvent() data object RefreshData : OneFeatureEvent() data class DeleteItem(val itemId: String) : OneFeatureEvent() } /** * Sealed class representing one-time side effects * These effects are typically one-time operations like navigation, Toast or Snackbar displays */ sealed interface OneFeatureEffect { data class ShowToast(val message: String) : OneFeatureEffect() data class ShareContent(val content: String) : OneFeatureEffect() }
4.4 MVI Data Flow Patterns
When implementing MVI architecture, follow these data flow patterns:
-
Effect: Use Channel to send one-time events (like navigation, Toast notifications) from ViewModel to View layer
// In ViewModel private val _effect = Channel<MyEffect>(Channel.BUFFERED) val effect = _effect.receiveAsFlow() fun showToast(message: String) { viewModelScope.launch { _effect.send(MyEffect.ShowToast(message)) } } // In View layer import com.androidtool.common.utils.collectWhenStarted viewModel.effect.collectWhenStarted { effect -> when (effect) { is MyEffect.ShowToast -> Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show() is MyEffect.Navigate -> findNavController().navigate(effect.destination) } } -
State: Use StateFlow to manage and expose UI state, ensuring consistency and observability
// In ViewModel private val _state = MutableStateFlow(MyState()) val state = _state.asStateFlow() -
Event: Trigger state updates by simply calling action methods on the ViewModel
// In ViewModel fun onAction(event: MyEvent) { when (event) { is MyEvent.ItemClicked -> handleItemClick(event.item) is MyEvent.RefreshRequested -> loadData() } } // In View layer binding.refreshButton.onClick { viewModel.onAction(MyEvent.RefreshRequested) } recyclerView.adapter = adapter.apply { setOnItemClickListener { adapter, view, position -> val item = adapter.getItem(position) viewModel.onAction(MyEvent.ItemClicked(item)) } }
4.5 LoadSir 状态页框架
The project encapsulates LoadSir as a loading and error display framework, used as follows:
- First, extend BaseBindingFragment or BaseBindingActivity
- Second, override
oropen fun registerState(): View? = binding.recycleviewopen fun registerState(): View? = binding.root - Third, use
,showState<LoadingPage>
,showState<ErrorPage>
to display state pages, and useshowState<EmptyPage>
to display the page used in the XMLshowSuccess()
4.6 BaseBindingFragment
The project encapsulates BaseBindingFragment, package in
package com.androidtool.common.base:
package com.androidtool.common.base import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.viewbinding.ViewBinding import com.androidtool.common.loadsir.LoadLayout import com.androidtool.common.loadsir.LoadSir import com.scwang.smart.refresh.layout.SmartRefreshLayout abstract class BaseBindingFragment<VB : ViewBinding> : Fragment(), IViewBinding<VB> by ViewBindingDelegate() { protected var lazyLoaded = false protected var initView = false // State page management lateinit var loadSir: LoadLayout private set override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { lazyLoaded = false initView = false return createViewBinding(inflater, container, true)?.let { rootView -> // Register state page management registerState()?.let { compatibleSmartRefresh(it) } ?: run { rootView } } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initView() // observeData() // loadData() } override fun onDestroyView() { super.onDestroyView() lazyLoaded = false } override fun onResume() { super.onResume() if (!lazyLoaded) { lazyLoaded = true onLazyLoad() } } protected open fun initView() {} /** * For lazy loading of fragments in viewpager */ open fun onLazyLoad() = Unit fun isLazyLoaded() = lazyLoaded /** * Compatible with SmartRefreshLayout */ private fun compatibleSmartRefresh(target: View): View { loadSir = LoadSir.register(target, listener = { onStateClick() }) (loadSir.parent as? SmartRefreshLayout)?.let { it.removeView(loadSir) it.setRefreshContent(loadSir) } return loadSir.rootView } // region------- State page management -------- open fun registerState(): View? = null open fun onStateClick() = Unit fun showLoading() = loadSir.show<LoadingPage>() fun showSuccess(useAnim: Boolean = true) = loadSir.showSuccess(useAnim) /** * @param ifShow Condition to determine whether to show the state page: collection, Bool (default null, shows current state page) * @param useAnim Whether to use animation for state page transitions * @param block Can monitor current displayed state page: can get current displayed state page (for customizations) */ inline fun <reified T : PageState> showState( ifShow: Any? = null, useAnim: Boolean = true, noinline block: (T.() -> Unit)? = null ) { // Whether to show current specified state page: based on condition val isShowState = when (ifShow) { is Collection<*> -> ifShow.isEmpty() is Array<*> -> ifShow.isEmpty() is Boolean -> ifShow else -> true } if (isShowState) loadSir.show<T>( useAnim = useAnim, block = block ) else loadSir.showSuccess() } // endregion }
Usage example:
import com.androidtool.common.base.BaseBindingFragment import com.androidtool.common.utils.collectWhenStarted class SampleListFragment : BaseBindingFragment<FragmentGiftRankListBinding>() { private val viewModel: SampleListViewModel by viewModels() private lateinit var id: String // refresh placeholder usage override fun registerState() = binding.recyclerView private val adapter by lazy { GiftRankListAdapter() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) id = arguments?.getString(DYNAMIC_ID) ?: "0" viewModel.initDataById(id = id) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupRefreshLayout() setupRecyclerView() observeUiState() } /** * Setup pull-to-refresh */ private fun setupRefreshLayout() { binding.refreshLayout.setOnRefreshListener { viewModel.refresh() } } /** * Setup RecyclerView and load more */ private fun setupRecyclerView() { binding.recyclerView.layoutManager = LinearLayoutManager(context) binding.recyclerView.adapter = adapter adapter.setOnItemClickListener { _, _, position -> val uId = adapter.data[position].uid } adapter.loadMoreModule.apply { isAutoLoadMore = true isEnableLoadMoreIfNotFullPage = false setOnLoadMoreListener { viewModel.loadMore() } } } /** * Observe UI state changes */ private fun observeUiState() { viewModel.listUiState.collectWhenStarted { state -> handleUiState(state) } viewModel.listEffect.collectWhenStarted { event -> when (event) { is GiftListUiEvent.UpdatePeopleCount -> { handleUpdatePeopleCount(event.count) } } } } companion object { const val DYNAMIC_ID = "dynamicID" fun newInstance(dynamicId: String): Fragment { val fragment = SampleListFragment() fragment.arguments = Bundle().apply { putString(DYNAMIC_ID, dynamicId) } return fragment } }
4.7 BaseBindingActivity
The project encapsulates BaseBindingActivity, integrating LoadSir and ViewBinding. Note that this BaseBindingActivity does not implement TitleBar, you need to implement it yourself:
abstract class BaseBindingActivity<V : ViewBinding> : RootActivity(), IViewBinding<V> by ViewBindingDelegate() { // State page management lateinit var loadSir: LoadLayout private set override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val rootView = createViewBinding() // Register state page management registerState()?.let { target -> loadSir = LoadSir.register(target, listener = { onStateClick() }) // Compatible with SmartRefreshLayout (loadSir.parent as? SmartRefreshLayout)?.let { it.removeView(loadSir) it.setRefreshContent(loadSir) } } setContentView((rootView.parent as? View) ?: rootView) initView() initData() } abstract fun initView() open fun initData() = Unit open fun registerState(): View? = null open fun onStateClick() = Unit fun showLoading() = loadSir.show<LoadingPage>() fun showSuccess(useAnim: Boolean = true) = loadSir.showSuccess(useAnim) /** * @param ifShow Condition to determine whether to show the state page: collection, Bool (default null, shows current state page) * @param useAnim Whether to use animation for state page transitions * @param block Can monitor current displayed state page: can get current displayed state page (for customizations) */ inline fun <reified T : PageState> showState( ifShow: Any? = null, useAnim: Boolean = true, noinline block: (T.() -> Unit)? = null ) { // Whether to show current specified state page: based on condition val isShowState = when (ifShow) { is Collection<*> -> ifShow.isEmpty() is Array<*> -> ifShow.isEmpty() is Boolean -> ifShow else -> true } if (isShowState) loadSir.show<T>( useAnim = useAnim, block = block ) else loadSir.showSuccess() } }
Usage example using
com.androidtool.common.widget.TitleBarView to integrate title bar:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".ui.topic.TopicListActivity"> <com.androidtool.common.widget.TitleBarView android:id="@+id/titleBar" android:layout_width="match_parent" android:layout_height="wrap_content" /> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/contentView" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.tabs.TabLayout android:id="@+id/tabLayout" style="@style/TopicTabLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:background="@android:color/transparent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:tabGravity="start" app:tabRippleColor="@null" /> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tabLayout" /> </androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout>
SampleBindingActivity:
class TopicListActivity : BaseBindingActivity<ActivityTopicListBinding>() { private val viewModel: TopicListViewModel by viewModels() private var tabLayoutMediator: TabLayoutMediator? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setStatusBarTextColor(isLight = true) initView() observeData() viewModel.loadData() } override fun initView() { // Set title bar binding.titleBar.setTitle(TranslateResource.getStringResources("topic")) binding.titleBar.setOnBackClickListener { finish() } } override fun registerState(): View { return binding.contentView } private fun observeData() { viewModel.topicCategoryListUiState.collectWhenStarted { when (it) { TopicCategoryListUiState.Empty -> { showState<EmptyPage>() } is TopicCategoryListUiState.Error -> { showState<ErrorPage>() } TopicCategoryListUiState.Loading -> { showState<LoadingPage>() } is TopicCategoryListUiState.Success -> { showSuccess() initTabLayoutAndViewPager(it.items) } } } } private fun initTabLayoutAndViewPager(categories: List<TopicCategoriesBean.TopicCategory>) { val pagerAdapter = TopicPagerAdapter(this, categories) binding.viewPager.adapter = pagerAdapter tabLayoutMediator?.detach() tabLayoutMediator = TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> // Use custom layout val tabView = layoutInflater.inflate(R.layout.tab_item_topic, null) val tabTitle = tabView.findViewById<TextView>(R.id.tvTabTitle) tabTitle.text = categories[position].name tab.customView = tabView }.apply { attach() } if (categories.isNotEmpty()) { binding.viewPager.setCurrentItem(0, false) } } override fun onDestroy() { tabLayoutMediator?.detach() tabLayoutMediator = null super.onDestroy() } companion object { fun start(context: Context) { val intent = Intent(context, TopicListActivity::class.java) context.startActivity(intent) } } }
4.8 TitleBarView API
binding.titleBar.setTitle(TranslateResource.getStringResources("topic")) binding.titleBar.setOnBackClickListener { finish() }
TitleBarView API:
/** * Set title text */ fun setTitle(title: CharSequence) {} /** * Set back button click listener */ fun setOnBackClickListener(listener: () -> Unit) {} /** * Set extra button (icon) on the right */ fun setExtraButton(@DrawableRes resId: Int, clickListener: (v: View) -> Unit) {} /** * Set custom layout for the right side */ fun setExtraButtonLayout(view: View) {}
4.9 Adapter
Adapters should use
BaseRecyclerViewAdapterHelper V3.x, as follows:
import com.model.UserBean import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.BaseMultiItemQuickAdapter import com.chad.library.adapter.base.module.LoadMoreModule import com.chad.library.adapter.base.viewholder.BaseViewHolder // Using https://github.com/CymChad/BaseRecyclerViewAdapterHelper v3.x // Simple list adapter class SampleLoadMoreAdapter : BaseQuickAdapter<SampleUserInfo, BaseViewHolder>(R.layout.item_sample_user), LoadMoreModule {} // Complex multi-type list adapter class InterestTagAdapter(private val chosenListListener: (List<UserBean>) -> Unit) : BaseMultiItemQuickAdapter<InterestTagShowBean, BaseViewHolder>() {}
4.10 Network Request
NOTICE: adhere to these project package import standards to maintain code consistency
- Repository Layer
import com.androidtool.common.extension.asResult import com.androidtool.common.extension.requestToFlow // Standard pattern fun loadData(): Flow<Result<DataModel>> { return requestToFlow { apiService.fetchData() }.asResult() }
- ViewModel Layer
// Follow this pattern for data loading fun loadData() { viewModelScope.launch { updateLoadingState() repository.loadData() .catch { exception -> handleError(exception) } .collect { result -> result.fold( onSuccess = { data -> handleSuccess(data) }, onFailure = { exception -> handleError(exception) } ) } } }
- API Service Definition
import com.androidtool.common.troll.BaseResponse import com.androidtool.common.net.Apis // 1. 项目中封装了 BaseResponse,所有的接口返回类型都为 BaseResponse // 2. 项目中所有的参数都是通过Map<String, String>)进行请求的 interface ApiService { @GET(Apis.ENDPOINT_DATA) suspend fun getData(): BaseResponse<DataModel> @GET(Apis.ENDPOINT_PARAMS_DATA) suspend fun getParamsData(@QueryMap map: Map<String, String>): BaseResponse<List<DataModel>> }