“我报名参加金石方案1期应战——瓜分10万奖池,这是我的第37篇文章,点击检查活动概况”
本章记录一个根底的 demo 项目,运用 kotlin+协程+retrofit+okhttp3+MVVM 完成。
功能需求
调用气候 api,在主页显现气候情况。
大致流程
- api 恳求及实体剖析
- 网络恳求权限
- 增加 kotlin,协程,网络结构等依靠
- 网络结构 Retrofit+okhttp3
- 主页页面制作
- 根底类构建
- 调用接口并显现在当时页面
api 恳求及实体剖析
这里运用万维易源的数据源,首要注册并登录账号。
- 进入气候预报入口。
- 购买一个月的气候预报 api ,这里运用地址查询当时气候作为比如。
- api 恳求示例。
http[s]://route.showapi.com/9-2?showapi_appid=替换自己的值&showapi_sign=替换自己的值&area=“杭州” - 进入控制台,能够检查自己的 appId 和 sign。
- 若是没有,则增加,并创立 app,可调用接口选择全部。
- 构建 api 接口,拜访得到回来数据如下所示。
- 剖析回来数据。
外层是固定格式,能够统一封装,需要获取当时的气候情况,取关键字 now 的内容即可。归纳总结为三个实体,具体内容如下:
通用网络恳求实体 CResponse
/**
* 通用网络恳求
*/
data class CResponse<T>(
@SerializedName("showapi_res_error")
val msg: String,//错误提示
@SerializedName("showapi_res_code")
val code: Int,//错误码
@SerializedName("showapi_res_body")
val data: T//数据
)
气候实体
/**
* 气候
*/
data class Weather(
val time: Long,//预报发布时刻
val now: WeatherDetail//气候概况
)
气候概况实体
/**
* 气候概况
*/
data class WeatherDetail(
val aqi: String,//空气指数
val rain: String,//下雨时刻点
val sd: String,//空气湿度
val temperature: String,//气温
@SerializedName("temperature_time")
val temperatureTime: String,//取得气温的时刻
val weather: String,//气候
@SerializedName("weather_pic")
val weatherPic: String,//气候小图标
@SerializedName("wind_direction")
val windDirection: String,//风向
@SerializedName("windPower")
val windPower: String//风力
)
网络恳求权限
在 AndroidManifest.xml 中增加网络恳求权限,代码如下:
<!--网络权限-->
<uses-permission android:name="android.permission.INTERNET" />
增加 kotlin,协程,网络结构等依靠
在 build.gradle 文件中增加依靠,代码如下:
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.7.10"
// 协程中心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
// 协程Android支撑库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"
implementation "androidx.activity:activity-ktx:1.2.3"
implementation "androidx.fragment:fragment-ktx:1.3.5"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
// okhttp
implementation "com.squareup.okhttp3:okhttp:4.9.0"
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'//日志拦截器
// retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"//支撑回来结果是string
implementation "com.squareup.retrofit2:converter-gson:2.9.0"//支撑回来结果是实体
网络结构 Retrofit+okhttp3
网络恳求封装,增加根底拜访地址,拦截器等,最终目的是能有个能够得到 service 的办法。
- 首要需要一个类,存放静态常量,例如根底拜访地址,appId 以及 sign。
/**
* 通用数据
*/
object HttpConstant {
/**
* 拜访地址
*/
const val BASE_HTTP = "https://route.showapi.com"
/**
* appID
*/
const val APP_ID = "替换为万维易源的appId"
/**
* app_sign
*/
const val APP_SIGN = "替换为万维易源的sign"
}
- 从恳求地址剖析,有两个公共参数(showapi_appid 和 showapi_sign)能够封装,构建通用参数拦截器。
恳求地址
http[s]://route.showapi.com/9-2?showapi_appid=替换自己的值&showapi_sign=替换自己的值&area=“杭州”
/**
* 通用参数拦截器
*/
class CommonInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val oldRequest = chain.request()
val httUrl = oldRequest.url
val urlBuilder = httUrl.newBuilder()
/** 增加公共参数 */
urlBuilder.addQueryParameter("showapi_appid", HttpConstant.APP_ID)
urlBuilder.addQueryParameter("showapi_sign", HttpConstant.APP_SIGN)
val request = oldRequest
.newBuilder()
.url(urlBuilder.build())
.build()
return chain.proceed(request)
}
}
- 构建 Retrofit 管理类,得到 service 办法。
/**
* Retrofit管理类
*/
object RetrofitManager {
/**
* okhttpClient
*/
private val okhttpClient: OkHttpClient
get() = OkHttpClient.Builder()
.addInterceptor(CommonInterceptor())//通用参数拦截器
.addInterceptor(HttpLoggingInterceptor())//日志拦截器
.followRedirects(true)
.build()
/**
* 构建service
*/
fun <T> getService(serviceClass: Class<T>): T {
val retrofit = Retrofit.Builder().apply {
baseUrl(HttpConstant.BASE_HTTP)//根底拜访地址
client(okhttpClient)
addConverterFactory(GsonConverterFactory.create())//Gson转换工厂
addConverterFactory(ScalarsConverterFactory.create())//String转换工厂
}.build()
return retrofit.create(serviceClass)
}
}
主页页面制作
主界面显现当时的气候情况即可,运用 databinding 办法,代码如下。
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="viewModel"
type="com.elaine.little_project.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.weatherData.weather}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
根底类构建
在项目构架过程中,都会习惯性地将一些公共办法或者属性进行封装操作,便利后续运用。
- 抽象类 BaseViewModel,承继 ViewModel(),定义一个初始化数据的办法。
/**
* 根底ViewModel
*/
abstract class BaseViewModel : ViewModel() {
/** 初始化数据 */
abstract fun start()
}
- 抽象类 BaseActivity,自定绑定 ViewModel 和 databinding,避免过多重复代码,其他的 Activity 承继 BaseActivity 即可。
/**
* 根底类Activity
*/
abstract class BaseActivity<VM : BaseViewModel, VB : ViewDataBinding>(private val contentViewResId: Int) :
AppCompatActivity() {
lateinit var mViewModel: VM
lateinit var mBinding: VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initViewModel()
initDataBinding()
initView()
initData()
}
/**
* 初始化ViewModel
*/
private fun initViewModel() {
//注意:actualTypeArguments[0] 0-->指上面BaseActivity<VM : BaseViewModel, VB : ViewDataBinding>的VM放在第一个
val type: Class<VM> =
(this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<VM>
mViewModel = ViewModelProvider(this).get(type)
mViewModel.start()
}
/**
* 初始化dataBinding
*/
private fun initDataBinding() {
mBinding = DataBindingUtil.setContentView(this, contentViewResId)
mBinding.apply {
//绑定生命周期
lifecycleOwner = this@BaseActivity
//mBinding绑定viewModel
setVariable(BR.viewModel, mViewModel)
}
}
/**
* 初始化UI
*/
abstract fun initView()
/**
* 初始化数据
*/
abstract fun initData()
}
调用接口并显现在当时页面
- 接口文件 Api,这里是挂起函数。
/**
* api接口
*/
interface Api {
/**
* 恳求气候
* @param area 地址 eg:杭州
*/
@FormUrlEncoded
@POST("/9-2")
suspend fun getWeather(
@Field("area") area: String,
): CResponse<Weather>
}
- api 接口完成类 WeatherRepository,通过 Retrofit 管理器获取 service,然后调用气候接口。
/**
* api接口完成类
*/
object WeatherRepository : Api {
/** 获取service */
private val service by lazy { RetrofitManager.getService(Api::class.java) }
/**
* 恳求气候
* @param area 地址 eg:杭州
*/
override suspend fun getWeather(area: String): CResponse<Weather> {
return service.getWeather(area)
}
}
- MainActivity 对应的 MainViewModel,利用 viewModelScope 进行网络恳求,其是一个协程。
/**
* viewModel
*/
class MainViewModel : BaseViewModel() {
/** 气候数据 */
var weatherData: MutableLiveData<WeatherDetail> = MutableLiveData()
override fun start() {
}
/**
* 获取气候数据
*/
fun getWeather() {
viewModelScope.launch {
val result = WeatherRepository.getWeather("杭州")
weatherData.value = result.data.now
}
}
}
- 主页 MainActivity,主页就是恳求接口即可。
/**
* 主页
*/
class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(R.layout.activity_main) {
override fun initView() {
}
override fun initData() {
getData()
}
/**
* 获取数据
*/
private fun getData() {
mViewModel.getWeather()
}
}
项目效果
项目 github 地址
github.com/ElaineTaylo…
具体内容在 little_project 项目中