本文的网络请求使用了Retrofit2 + okhttp,因为使用的是协程,就再也不需要回调地狱了,所以抛弃了Rxjava
1.先集成相关sdk
在app模块目录build.gradle中添加
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
..........
dependencies {
implementation 'androidx.core:core-ktx:1.1.0-alpha04'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0-beta01'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
implementation 'com.google.code.gson:gson:2.6.2'//解析接口返回的数据我用到了这个,如果你是用fastjson的话,可以忽略掉这个,继续用fastjson就行了
implementation 'com.squareup.retrofit2:retrofit:2.9.0' //这个版本很重要,必须要大于2.6.0,如果你的项目是低于2.6.0的话,更新一下版本,否则实际运行时会出现错误
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
}
2.创建Retrofit公共请求类
通常我们同一个项目,和服务器后端应该存在一些固定约束,比如接口返回的固定字段,会有code、message、data这些,那么发送给接口的请求,也应该有一些固定约束,比如固定的header等,所以就需要将网络请求进行一层封装
class RetrofitClient {
//实际项目应用中,应该存在至少dev环境和idc线上环境,笔者这里还有test环境,如果你的项目没有这些环境,那么可以直接return 唯一的地址,甚至这个方法都可以不需要,直接使用唯一的环境即可
fun getCoroutineServiceApi():ServiceApi{
if(HttpApi.baseIp == "xxx" || HttpApi.baseIp == "xxx"){
return coroutineServiceApiDev
}else{
return coroutineServiceApiOnLine
}
}
private val coroutineServiceApiDev: ServiceApi by lazy { //不清楚by lazy的自行去百度,这是委托模式,可以延迟加载,并且只在首次访问时计算值
val retrofitClient = Retrofit.Builder()
.baseUrl(HttpApi.baseIp)
.client(OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message ->
Log.i(TAG, message)
}).setLevel(HttpLoggingInterceptor.Level.BODY)
).addInterceptor(Interceptor { chain ->
//这里就可以添加一些通用请求头了
val request: Request = chain.request()
.newBuilder()
.addHeader("Content-Type", "application/json")
.addHeader("version", MyApplication.getInstances().getVersion())
.addHeader("deviceId", MyApplication.getInstances().getDeviceid())
.addHeader("osType", MyApplication.osType)
.addHeader("token", token)
.addHeader("channelCode",MyApplication.getInstances().getChannelNo()+MyApplication.getInstances().getHumeChannel())
.addHeader("androidId",MyApplication.getInstances().getAndroidid())
.addHeader("ua",URLEncoder.encode(MyApplication.mUa,"UTF-8"))
.addHeader("oaid",MyApplication.getInstances().getOaid())
.addHeader("imei",MyApplication.getInstances().getIMEI())
.addHeader("uuid",MyApplication.getInstances().getuniqueId())
.addHeader("vendor",Build.MANUFACTURER)
.build()
KLogger.e("xiaolitest","当前uuid:"+MyApplication.getInstances().getuniqueId())
Log.e("xiaolitest","当前渠道:"+MyApplication.getInstances().getChannelNo())
chain.proceed(request)
}).build())
.addConverterFactory(DsGsonConverterFactory.create())//这里我是把接口返回的值序列化,就像上面说的,同一个项目,后台和客户端的数据返回应该有一些固定约束
.build()
retrofitClient.create(ServiceApi::class.java)
}
//coroutineServiceApiOnLine我就不贴了,写法完全一样,只是baseUrl不一样而已,一个测试环境的,一个线上环境的
}
3.创建ServiceApi
上文中创建了Retrofit的属性委托,返回的对象都是ServiceApi,那么就需要写这个ServiceApi了
interface ServiceApi {
@POST("card-user/xxx") //这个不用描述吧?懂retrofit的都知道,代表的是请求方式以及请求地址
suspend fun getUserGotoTest():BaseResult<UserJumpConfigBean> //注意到开头的suspend关键字了吗?它很重要,因为协程体调用外部的方法,它必须是suspend的,否则会报错
}
//贴一下BaseResult代码,大部分的约束也应该如此
data class BaseResult <T>(
val code :String,
val success:Boolean,
val message:String,
val time:String,
val data: T
)
4.创建viewmodel,并在里面生命协程作用域
viewmodel并不是协程所必须创建的,但属于lifecycle的viewmodel,能感知生命周期,在生命周期的末尾取消掉协程,都不用去管内存泄漏这些问题了,它不香么?
class MyViewModel:ViewModel() {
/**
* 这是此 ViewModel 运行的所有协程所用的任务。
* 终止这个任务将会终止此 ViewModel 开始的所有协程。
*/
private val viewModelJob = SupervisorJob()
val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
val ioScope = CoroutineScope(Dispatchers.IO + viewModelJob)
override fun onCleared() { //不清楚的去百度一下viewmodel生命周期
super.onCleared()
viewModelJob.cancel()
}
//协程一共有几种调度器,是有对应区别的,如下:
/*
Dispatchers 决定协程在哪个线程或线程池上运行(启动和恢复)。最重要的选项有:
Dispatchers.Defualt:被用来执行 CPU 密集型操作
Dispatchers.Main:被我们用来访问主线程,例如在 Android、Swing 或者 JavaFX 上
Dispatchers.Main.immediate:它和 Dispatchers.Main 运行在同一个线程上,但如果没有必要,它不会重新调度
Dispatchers.IO:被用来执行一些阻塞线程的操作
调用了 limitParallelism的 Dispatchers.IO 或者带有自定义线程池的 Dispatcher.IO:我们用来处理大量的阻塞调用
调用了 limitParallelism 并设置为1的 Dispatchers.Default 或 Dispatchers.IO,或具有单个线程的自定义调度器:被用来修改共享状态
Dispatchers.Unconfined:当我们不关心协程在哪个线程上被挂起时使用
*/
}
5.创建一个BaseActivity吧,通常我们的项目不都有它么,在里面设置我们将要用到的viewmodel以及serviceapi
abstract class BaseActivity : AppCompatActivity(){
//熟悉吧,又用到了委托属性,它只会加载一次,不用每次都计算,不香么
val myViewModel: MyViewModel by lazy {
ViewModelProviders.of(this).get(MyViewModel::class.java)
}
val coroutineServiceApi: ServiceApi by lazy {
RetrofitClient.getRetrofitClient().getCoroutineServiceApi()
}
//其它的代码我就省略了,不重要
}
6.一切都有了,那么使用吧!
class TestKT :BaseActivity(){
override fun getLayoutId(): Int {
return R.layout.item_hot_cake_text
}
override fun initView() {
myViewModel.ioScope.launch{ //这里我用的是Dispatchers.IO调度器,因为只是请求网络读写数据呀,不用放到主线程
var data = coroutineServiceApi.getUserGotoTest()
initData(data)
}
}
//留意到了吗?又是suspend关键字,协程体调用外部方法必须是suspend的,否则会报错
suspend fun initData(data: BaseResult<UserJumpConfigBean>){
withContext(Dispatchers.Main){ //因为上面的调度器是IO线程的,但UI只能在主线程更新,所以这里要生命Main调度器,否则会报错,如果上面调用的是uiScope那么这里就可以不用写这个了
tv_content.text = data.message
}
}
}
NOTO::写在最后,至此,只要你有kotlin基础,用过retrofit +okhttp,那么你现在已经可以将网络请求应用到实际项目中了。但是!但是!这还远远不够的,本文的目的是期望你能快速上手运用协程,将异步的代码用同步的逻辑来写,这样会使得代码更简洁,可读性也更高,而且完全废弃掉了Rxjava,也不用管什么回调地狱了,但你在会使用协程之后,就应该去关注一下更细节的东西;
比如:协程的作用域、协程里面需要再启动协程、必要时取消协程等等。举个实际项目中的例子,假设我们有两个网络请求,分别是接口A和接口B,接口B的请求依赖于接口A返回的字段,那么如果不用协程,我们是不是要写接口A的回调,然后在接口A里面去调用接口B。如果不是两个网络请求,是三个或者更多呢?代码是不是看起来就很臃肿?那么协程就真香了,因为协程有async/await来处理并发,试想一下,原本繁重的一个接口回调后再调另外一个接口,变成了如下代码,是不是代码量减少了,可读性也越高了?
coroutineScope.launch(Dispatchers.IO) {
val a1 = async{ 接口A() }
val userInfo = a1.await()
val a2 = async{ 接口B(userInfo.token) }
val msgList = a2.await()
}
还有上面我不止一次提到的suspend关键字,它到底有什么用呢?
大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje
我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击
在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
我正在处理旧代码的一部分。beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)endRubocop错误如下:Avoidstubbingusing'allow_any_instance_of'我读到了RuboCop::RSpec:AnyInstance我试着像下面那样改变它。由此beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)end对此:let(:sport_
我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b
我收到格式为的回复#我需要将其转换为哈希值(针对活跃商家)。目前我正在遍历变量并执行此操作:response.instance_variables.eachdo|r|my_hash.merge!(r.to_s.delete("@").intern=>response.instance_eval(r.to_s.delete("@")))end这有效,它将生成{:first="charlie",:last=>"kelly"},但它似乎有点hacky和不稳定。有更好的方法吗?编辑:我刚刚意识到我可以使用instance_variable_get作为该等式的第二部分,但这仍然是主要问题。
当我使用has_one时,它工作得很好,但在has_many上却不行。在这里您可以看到object_id不同,因为它运行了另一个SQL来再次获取它。ruby-1.9.2-p290:001>e=Employee.create(name:'rafael',active:false)ruby-1.9.2-p290:002>b=Badge.create(number:1,employee:e)ruby-1.9.2-p290:003>a=Address.create(street:"123MarketSt",city:"SanDiego",employee:e)ruby-1.9.2-p290
网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识