目录
ViewModel
ViewModel其实就是一个类,只不过它主要用于存放与界面相关的数据和数据处理,原则上所有与界面相关的数据都应该存放在View Model中而不是activity中,这样可以减少activity中的逻辑,并且每个activity都应该建立一个对应的ViewModel,因为ViewModel的生命周期比activity的生命周期要长,所以对于数据的处理部分推荐都放在View Model中,比如创建一个方法在需要的地方调用一下就行
基本用法:
dependencies {
...
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
}
创建一个类,命名推荐使用对应界面的名字+viewmodel,并让它继承ViewModel
class MainViewModel : ViewModel() {
val count = 0
}
在activity中使用它
class MainActivity : AppCompatActivity() {
lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
将界面与viewmodel绑定
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
//使用ViewModel中的count变量
infoText.text = viewModel.count.toString()
}
}
到这里应该可以获取到View Model中的数据了,但如果想向view Model中传递数据可以定义一个形参,通过ViewModelProvider.Factory,这里不在赘述,详情可以去看第一行代码第三版,如果想要优雅的传递数据,继续向下
ViewModel+Livedata
你在敲代码时是不是常常遇到异步操作时需要更新数据的问题,这时一般会通过接口写一个回调方法,但是如果数据一多,这样的方法就太麻烦了,怎么办?答案是使用Livedata,如果你学习过观察者模式,你应该知道它的描述是当一个对象的状态发生改变时,所有依赖与它的对象都会得到消息并自动更新,怎么在安卓实现呢?使用Livedata,LiveData可以包含任何类型的数据,并在数据发生变化的时候通知给观察者。也就是说,如果我们将View Model中的数据使用LiveData来包装,然后在Activity中去观察它,就可以 主动将数据变化通知给Activity
基本用法
首先改造ViewModel
class MainViewModel : ViewModel() {
val count = MutableLiveData<Int>()
}
将count变量修改成了一个MutableLiveData对象,并指定它的泛型为Int,表 示它包含的是整型数据。MutableLiveData是一种可变的LiveData,它的用法很简单,主要 有3种读写数据的方法,分别是getValue()、setValue()和postValue()方法。 getValue()方法用于获取LiveData中包含的数据;setValue()方法用于给LiveData设置数 据,但是只能在主线程中调用;postValue()方法用于在非主线程中给LiveData设置数据
在activity中创建ViewModel中viewModel.count的observe()方法来观察数据的变化,无论在哪里改变view Model中的变量的value都会被它的observe()方法检测到并立即执行observe方法,然后你就可以在里面编写具体的处理逻辑
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?){
viewModel.count.observe(this, Observer { count ->
infoText.text = count.toString()
})
}
}
Retrofit
Retrofit的用法就是,首先我们配置好一个根路径,然后在指定服务器接口地址时只需要使用相对路径即可,这样就不用每次都指定完整的URL地址了。 另外,Retrofit允许我们对服务器接口进行归类,将功能同属一类的服务器接口定义到同一个接 口文件当中,从而让代码结构变得更加合理。我们也完全不用关心网络通信的细节,只需要在接口文件中声明一系列方法和返回值, 然后通过注解的方式指定该方法对应哪个服务器接口,以及需要提供哪些参数。当我们在程序 中调用该方法时,Retrofit会自动向对应的服务器接口发起请求,并将响应的数据解析成返回值 声明的类型。这就使得我们可以用更加面向对象的思维来进行网络操作。
基本用法
编辑app/build.gradle文件,在 dependencies闭包中添加如下内容
dependencies {
...
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
//解析后端服务器发送下来的json格式数据
implementation 'com.google.code.gson:gson:2.8.5'
}
新建AppService接口,代码如下所示(注:下面返回和解析的是JSON格式的数据,如果你不是可以自行搜索)
interface AppService {
@Headers("Content-Type: application/json")
@GET("get_data.json")
fun getAppData(): Call<ResponseBody>
}
上述代码中有两点需要我们注意。第一就是在getAppData()方法上面添加的注解,这里使用 了一个@GET注解,表示当调用getAppData()方法时Retrofit会发起一条GET请求,请求的地址就是我们在@GET注解中传入的具体参数,如果发起的是POST请求使用@POST注解,参数一样
第二就是getAppData()方法的返回值必须声明成Retrofit中内置的Call类型,并通过泛型来 指定服务器响应的数据应该转换成什么对象。@Headers()用来指定内容类型为json
由于在接口中我们会创建很多的方法去获取后台的不同数据,所以一般推荐创建一个接口的实现类,并在这个实现类中创建方法去实现接口中定义的方法
class AppServiceImpl(){
fun getdata(){
}
}
创建Retrofit对象并创建AppService中方法的回调方法,这个方法会在retrofit发起请求后,收到后台返回的数据时调用
class AppServiceImpl(){
fun getdata(){
val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2/") //URL中的端口号那一部分
.addConverterFactory(GsonConverterFactory.create())
.build()
val appService = retrofit.create(AppService::class.java)
appService.getAppData().enqueue(object : Callback<ResponseBody>> {
override fun onResponse(call: Call<ResponseBody>,response:
Response<ResponseBody>){
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
Log.d("queryAirConList", "激活回调:onFailure")
}
}
}
}
使用Retrofit.Builder来构建 一个Retrofit对象,其中baseUrl()方法用于指定所有Retrofit请求的根路径, addConverterFactory()方法用于指定Retrofit在解析数据时所使用的转换库,这里指定成 GsonConverterFactory。注意这两个方法都是必须调用的。 有了Retrofit对象之后,我们就可以调用它的create()方法,并传入具体Service接口所对应 的Class类型,创建一个该接口的动态代理对象。如果你并不熟悉什么是动态代理也没有关 系,你只需要知道有了动态代理对象之后,我们就可以随意调用接口中定义的所有方法,而 Retrofit会自动执行具体的处理就可以了
需要注意retrofit中.baseUrl中传入的URL+接口方法注解中传入的URL应该等于你要访问的URL的全路径,注意baseURL尾或注解URL首保证有一个应该有"/"符号
在onResponse方法中我们可以用response方法的isSuccessful来判断回调是否成功
if (response.isSuccessful) {
Log.d("queryAirConList", "激活回调:onResponse==成功")
}
解析JSON数据
如果JSON数据时这样:
{
"httpStatus":200,
"code":"000000",
"message":"success",
"description":"成功",
"data":{
"pageSize":10,
"pageNo":1,
"rowCount":1,
"pageCount":1,
"entityList":[
Object{...}
]
}
}
如果是 { } 以大括号包住的可以先转化为一个map,在onResponse方法中先用isSuccessful判断回调是否成功,成功后就可以开始解析了
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
val responsedata = response.body()?.string()
val dataMap = Gson().fromJson(responsedata, Map::class.java)
//这一步就已经将最外层的数据转为map格式
val code = dataMap["code"] as String //这样就可以获得json中的code的值
//但是你如果想获得data中的数据,你就会发现data中的数据又是一个{}包裹的json
//但你直接这样读取时val data = dataMap["data"] 会发现不报错但是这个map中的
//数据读取不出来,因为Kotlin自己转化的map类型不对,没关系强转一下
val data = dataMap["data"]as LinkedTreeMap<String,Any>
//这里的Any表示任意类型,因为我们可以发现在data中的entityList中数据是[{...}]
//这表示它是一个jsonList,但是其余的值都是String,所以使用Any
val pageSize = data["pageSize"] as String //这样就可以获取json中的pageSize的值
}
}
展开entityList后的json,注意在未展开的json中一个object{...}表示一个列表项,演示的只有一个
"pageSize":10,
"pageNo":1,
"rowCount":1,
"pageCount":1,
"entityList":[
{
"deleteFlag":"0",
"deleteTime":null,
"rowState":"",
"id":"10086",
"device":Object{...},
"orgCode":"master",
}
]
继续解析
//前面说了data中entityList是一个list,同样的道理继续强转一下就行
val entityList = data["entityList"] as ArrayList<LinkedTreeMap<String, Any>>
//同样的道理Any还是因为在entityList 中的device又是一个JSON,如果没有继续嵌套使用String就行
//然后遍历entityList 获取每个entity
if (entityList.size!=0) { //该空间有entity
for (entity in entityList) {
val id = entity["id"] as String //这时就获取到了entityList 中一个子项的id值
//同样的道理只要一层层解析就可以获取到所有的数据了
}
}
使用Retrofit+Apifox对接后台接口
query
body
然后再Apifox中传入参数就可以点击发送来测试接口了
Retrofit接口中的方法代码
@Headers("Content-Type: application/json")
@POST("/airconditioner/airconditionerDevice/queryAirconditionerDeviceList")
//Apifox中需要传入的参数,一个query参数使用@Query注解,一个Body参数使用@Body注解
fun queryAirConList(@Query("access_token") access_token: String,@Body
params:HashMap<String,String>):Call<ResponseBody>
需要调用的实际URL为:
注意在url中用?连接了access_token,在实际需求中如果在这里连接了两个参数,那么接口方法中分别用@Query注解传入两个参数
比如:
fun getCurtainDetail(@Query("id") id :String,@Query("access_token") access_token: String) :Call<ResponseBody>
然后在该方法的实现位置传入值
fun queryAirConList(){
val params = HashMap<String, String>()
//这里就是你要传入body的map
params["spaceId"]="64197bc85caef34d5e8aa1fe" //空间id 64197bc85caef34d5e8aa1fe
val retrofit = Retrofit.Builder().baseUrl("http://172.17.36.201:8012")
.addConverterFactory(GsonConverterFactory.create()).build()
val service = retrofit.create(BoardRoomControlService::class.java)
//Apifox上显示要传入的两个参数
service.queryAirConList(access_token, params).enqueue(object:Callback<ResponseBody>
{
override fun onResponse(call:Call<ResponseBody>,response:Response<ResponseBody>)
{
。。。
}
}
}