移动开发笔记(十三) 网络技术 Kotlin协程

网络技术

1.WebView的用法
首先我们创建一个WebViewTest新项目
修改activity_main.xml

<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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">
    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
></WebView>

</LinearLayout>

修改MainActivity‘

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        webView.settings.setJavaScriptEnabled(true)
        webView.webViewClient=WebViewClient()
        webView.loadUrl("https://www.youkuaiyun.com/")
    }
}

通过WebView的getSetting()方法,并传入了一个WebViewClient的实例。这段代码的作用是,当需要从一个网页跳转到另一个网页时,目标网页仍在当前WebView中显示,而不是打开系统浏览器。
调用setJavaScriptEnabled()方法,让WebView支持JavaScript脚本
另外我们需要声明访问网络的权限,再AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.webviewtest">
 <uses-permission android:name="android.permission.INTERNET"></uses-permission>
 </manifest>

2.使用HttpURLConnection
过去Android有两种方式:HttpURLConnection和HttpClient,由于HttpClient的API数量过多,扩展困难。在Android6.0系统中HttpClient的功能完全移除了。
1.首先我们要获取HttpURLConnection实例

val url = URL("https://www.baidu.com")
val connection = url.openConnection() as HttpURLConnection

2.得到HttpURLConnection之后,我们可以设置一下Http请求方式。GET/POST

connection.requestMethod = "GET"

3.接下来自由定制,设置连接超时,读取超时的毫秒数,以及服务器希望得到的一些消息头等

connection.connectTimeout=8000
connection.readTimeout=8000

4.之后再调用getInputStream()方法获取服务器返回的输入流

val input = connection.inputStream

5.最后调用disconnect()方法关闭HTTP连接

connection.disconnect()

**实例:**新建一个NetworkTest项目,修改activity_main.xml

<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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/sendRequestBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送请求"></Button>
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/responseText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            ></TextView>
    </ScrollView>
</LinearLayout>

修改MainActivity代码

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sendRequestBtn.setOnClickListener {
    sendRequestWithHttpURLConnection()
        }
    }
    private fun sendRequestWithHttpURLConnection(){
        //开启线程发起网络请求
        thread {
            var connection : HttpURLConnection?=null
            try {
                val response = StringBuilder()
                val url = URL("https://www.baidu.com/")
                connection = url.openConnection() as HttpURLConnection
                connection.connectTimeout=8000
                connection.readTimeout=8000
                val input = connection.inputStream
                //下面对获取到的输入流进行读取
                val reader = BufferedReader(InputStreamReader(input))
                reader.use {
                    reader.forEachLine {
                        response.append(it)
                    }
                }
                showResponse(response.toString())
            }catch (e:Exception){
                e.printStackTrace()
            }finally {
                connection?.disconnect()
            }
        }
    }
    private fun showResponse(response : String){
        runOnUiThread {
            //在这里进行UI操作,将结果显示到界面上
            responseText.text=response
        }
    }
}

最后再AndroidManifest.xml中声明网络权限

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.networktest">
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
   ...
</manifest>

那么如果我们想提交数据给服务器怎么办呢?

connection.requestMethod="POST"
val output = DataOutStream(connection.outputStream)
output.writeBytes("username=admin&password=123456")

3.使用OkHttp
OkHttp是由Square公司开发的,还开发了Retrofit,Picasso等知名的开源项目.
OkHttp的项目主页地址是:https://github.com/square/okhttp
使用OKHttp之前我们需要再项目中添加OkHttp库的依赖。编辑app/build.gradle文件

dependencies{
...
implementation 'com.squareup.okhttp3:okhttp:4.1.0'
}

1.首先我们需要创建一个OkHttpClient的实例

val client = OkHttpClient()

2.如果想发起一条HTTP请求,需要创建一个Request对象

val request = Request.Builder().build()

3.通过url()方法设置目标的网络地址

val request = Request.Builder()
					.url("https://www.baidu.com")
					.build()

4.调用OkHttpClient的newCall()方法来创建一个Call对象,并调用它的execute()方法来发送请求并获取服务器返回的数据

val response = client.newCall(request).execute()

5.Response对象就是服务器返回的数据,如下写法得到返回的具体内容

val responseData = response.body?.string()

6.如果发送一条POST请求

val requestBody = FormBody.Builder()
							.add("username","admin")
							.add("password","123456")
							.build()

然后再Request.Builder调用一下post()方法,并将RequestBody对象传入

val request = new Request.Builder()
					.url("https://www.baidu.com")
					.post(requestBody)
					.build()

修改MainActivity代码

...
private fun sendRequestWithHttpURLConnection(){
        //开启线程发起网络请求
        thread {
       
            try {
                val client =  OkHttpClient()
                val request = Request.Builder()
                    .url("https://www.baidu.com/")
                    .build()
                val response = client.newCall(request).execute()
                val responseData = response.body?.string()
                if (responseData !=null){
                    showResponse(responseData)
                }
            }catch (e:Exception){
                e.printStackTrace()
            }
        }
    }

这里我使用的是jar包,需要OkHttp库和Okio库一起导入

4.解析XML格式数据

1.下载一个Apache服务器的压缩包
2.打开解压目录Apache24\conf\httpd.conf文件
修改第37行为解压目录
在这里插入图片描述
修改第60行为监听端口
在这里插入图片描述
用管理员方式打开CMD,安装服务

G:\Apache HTTP\Apache24\bin>httpd.exe -k install -n"Apache24"

在这里插入图片描述如果卸载服务的话

G:\Apache HTTP\Apache24\bin>httpd.exe -k uninstall -n"Apache24"

在这里插入图片描述
然后在Apache\htdocs目录下创建一个名为get_data.xml文件

<apps>
	<app>
		<id>1</id>
		<name>Google Maps</name>
		<version>1.0</version>
	</app>
	<app>
		<id>2</id>
		<name>Chrome</name>
		<version>2.1</version>
	</app>
	<app>
		<id>3</id>
		<name>Google Play</name>
		<version>2.3</version>
	</app>
</apps>

修改MainActivity代码

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sendRequestBtn.setOnClickListener {
    sendRequestWithHttpURLConnection()
        }
    }
    private fun sendRequestWithHttpURLConnection(){
        //开启线程发起网络请求
        thread {
            try {
                val client =  OkHttpClient()

                val request = Request.Builder()
                    .url("http://10.0.2.2/get_data.xml")
                    .build()
                val response = client.newCall(request).execute()
                val responseData = response.body?.string()
                if (responseData !=null){
                    showResponse(responseData)
                    parseXMLWithPull(responseData)
                }
            }catch (e:Exception){
                e.printStackTrace()
            }finally {
             //   connection?.disconnect()
            }
        }
    }

    private fun parseXMLWithPull(xmlData : String){
        try {
            val factory = XmlPullParserFactory.newInstance()
            val xmlPullParser = factory.newPullParser()
            xmlPullParser.setInput(StringReader(xmlData))
            var eventType=xmlPullParser.eventType
            var id =""
            var name =""
            var version =""
            while (eventType !=XmlPullParser.END_DOCUMENT){
                val nodeName = xmlPullParser.name
                when(eventType){
                //开始解析某个节点
                XmlPullParser.START_TAG -> {
                    when(nodeName){
                        "id" -> id=xmlPullParser.nextText()
                        "name" -> id=xmlPullParser.nextText()
                        "version" -> id=xmlPullParser.nextText()
                    }
                }
                    XmlPullParser.END_TAG ->{
                        if ("app" ==nodeName){
                            Log.d("MainActivity","id is $id")
                            Log.d("MainActivity","name is $name")
                            Log.d("MainActivity","version is $version")
                        }
                    }
                }
                eventType=xmlPullParser.next()
            }
        }catch (e:Exception){
            e.printStackTrace()
        }
    }
    ...
}

从Android9.0系统开始,应用程序默认只允许使用HTTPS类型的网络请求,HTTP类型的网络请求应为有安全隐患默认不再被支持,而我们搭建的Apache服务器现在使用的就是HTTP
右键res目录->New->Directory,创建一个xml目录,接着右击xml目录->New->File,创建一个newtwork_config.xml文件,修改其中的内容

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system"/>
        </trust-anchors>
    </base-config>
</network-security-config>

接下来在AndroidManifest.xml配置文件添加属性

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.networktest">
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        --------------------------------------------
        android:networkSecurityConfig="@xml/network_config"
        -------------------------------------------
        >
</manifest>

SAX解析方式
首先创建一个类继承自DefaultHandler

class MyHandler : DefaultHandler() {

    private var nodeName=""
    private lateinit var id:StringBuilder
    private lateinit var name:StringBuilder
    private lateinit var version:StringBuilder

    override fun startDocument() {
       id= StringBuilder()
        name= StringBuilder()
        version= StringBuilder()
    }

    override fun startElement(
        uri: String?,
        localName: String?,
        qName: String?,
        attributes: Attributes?
    ) {
        //记录当前节点名
        if (localName != null) {
            nodeName=localName
        }
        Log.d("MyHandler","uri is $uri")
        Log.d("MyHandler","localName is $localName")
        Log.d("MyHandler","qName is $qName")
        Log.d("MyHandler","attributes is $attributes")
    }

    override fun characters(ch: CharArray?, start: Int, length: Int) {
       //根据当前节点名判断将内容添加到哪一个StringBuilder对象中
        when(nodeName){
            "id"-> id.append(ch,start,length)
            "name"->name.append(ch,start,length)
            "version"->version.append(ch,start,length)
        }
    }

    override fun endElement(uri: String?, localName: String?, qName: String?) {
        if ("app"==localName){
            Log.d("MyHandler","id is ${id.toString().trim()}")
            Log.d("MyHandler","name is ${name.toString().trim()}")
            Log.d("MyHandler","version is ${version.toString().trim()}")
            //最后要将StringBuilder清空
            id.setLength(0)
            name.setLength(0)
            version.setLength(0)
        }
    }

    override fun endDocument() {
        super.endDocument()
    }
}

目前id,name,version中都可能是包含回车或换行符。因此在打印之前我们还需要调用一些trim()方法,并且打印完成后要将StringBuilder的内容情况。
修改MainActivity

  private fun sendRequestWithHttpURLConnection(){
        //开启线程发起网络请求
        thread {
            try {      
                val client =  OkHttpClient()
                val request = Request.Builder()
                    .url("http://10.0.2.2/get_data.xml")
                    .build()
                val response = client.newCall(request).execute()
                val responseData = response.body?.string()
                if (responseData !=null){
                    showResponse(responseData)        
                    parseXMLWithSAX(responseData)
                }
            }catch (e:Exception){
                e.printStackTrace()
            }finally {
            }
        }
    }
    private fun parseXMLWithSAX(xmlData: String){
        try {
            val factory = SAXParserFactory.newInstance()
            val xmlReader = factory.newSAXParser().xmlReader
            val handler=MyHandler()
            //将MyHandler的实例设置到XMLReader中
            xmlReader.contentHandler=handler
            //开始执行
            xmlReader.parse(InputSource(StringReader(xmlData)))
        }catch (e:Exception){
            e.printStackTrace()
        }
    }
    ...

5.解析JSON格式数据

首先我们在Apache\htdocs目录创建一个get_data.json的文件

[{"id":"5","version":"5.5","name":"Clash od Clans"},
{"id":"6","version":"7.0","name":"Boom Beach"},
{"id":"7","version":"3.5","name":"Clash Royale"}
]

5.1使用JSONObject
修改MainActivity中的代码

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sendRequestBtn.setOnClickListener {
    sendRequestWithHttpURLConnection()
        }
    }
    private fun sendRequestWithHttpURLConnection(){
        //开启线程发起网络请求
        thread {
            try {
                val client =  OkHttpClient()

                val request = Request.Builder()
                    .url("http://10.0.2.2/get_data.json")
                    .build()
                val response = client.newCall(request).execute()
                val responseData = response.body?.string()
                if (responseData !=null){
                    showResponse(responseData)
                    parseJsonWithJSONObject(responseData)
                }
            }catch (e:Exception){
                e.printStackTrace()
            }finally {     
            }
        }
    }
    private fun parseJsonWithJSONObject(jsonData:String){
        try {
            val jsonArray = JSONArray(jsonData)
            for (i in 0 until jsonArray.length()){
                val jsonObject = jsonArray.getJSONObject(i)
                val id=jsonObject.getString("id")
                val name=jsonObject.getString("name")
                val version=jsonObject.getString("version")
                Log.d("MainActivity","id is $id")
                Log.d("MainActivity","name is $name")
                Log.d("MainActivity","version is $version")
            }
        }catch (e:java.lang.Exception){
            e.printStackTrace()
        }
    }
 
...
}

5.2使用GSON
首先编辑aapp/build.graadle文件,在dependencies闭包中添加如下内容

dependencies{
  implementation 'com.google.code.gson:gson:2.8.5'
...
}

如果一段JSON格式的数据如下

{"name":"Tom","age":20}

定义一个Person,并加入name和age两个字段,如下代码将JSON数据解析成一个Person对象

val gson = Gson()
val person = gson.fromJson(jsonDaataa,Person::class.java)

如果需要解析一段JSON数组,会稍微麻烦一点

[{"name":"Tom","age":20},{"name":"Jack","age":25},{"name":"Lily","age":22}]
val typeOf = object : TypeToken<List<Person>>(){}.type
val people = gson.fromJson<List<Person>>(jsonData,typeOf)

最后修改MainActivity中的代码

 private fun sendRequestWithHttpURLConnection(){
        //开启线程发起网络请求
        thread {
            try {
                val client =  OkHttpClient()

                val request = Request.Builder()
                    .url("http://10.0.2.2/get_data.json")
                    .build()
                val response = client.newCall(request).execute()
                val responseData = response.body?.string()
                if (responseData !=null){
                    showResponse(responseData)
                    parseJSONWithGSON(responseData)
                }
            }catch (e:Exception){
                e.printStackTrace()
            }finally {
            }
        }
    }
    private fun parseJSONWithGSON(jsonData : String){
        val gson = Gson()
        val typeOf = object :TypeToken<List<App>>(){}.type
        val appList =gson.fromJson<List<App>>(jsonData,typeOf)
        for (app in appList){
            Log.d("MainActivity","id is ${app.id}")
            Log.d("MainActivity","name is ${app.name}")
            Log.d("MainActivity","version is ${app.version}")
        }
    }

6.网络请求回调的实现方式

我们应该及那个这些通用的网络操作提取到一个公共的类力,并提取一个通用的方法,当想要发起网络请求时,简单调用一下这个方法即可。

object HttpUtil{
    fun sendHttpRequest(adress:String) :String{
        var connection : HttpURLConnection? = null
        val response = StringBuilder()
        try {
            val url = URL(adress)
            connection=url.openConnection() as HttpURLConnection
            connection.connectTimeout=8000
            connection.readTimeout=8000
            val input = connection.inputStream
            val reader=BufferedReader(InputStreamReader(input))
            reader.use {
                reader.forEachLine {
                    response.append(it)
                }
            }
        }catch (e:Exception){
            e.printStackTrace()
        }finally {
            connection?.disconnect()
        }
        return response.toString()
    }
}

以后每当需要发起一条HTTP请求的时候

val address = "https://www.baidu.com"
val response = HttpUtill.sendHttpRequest(address)

但是网络请求是耗时操作,sendHttpRequest()没有开启线程
如何使用回调机制
1.首先定义一个接口,将它命名成HttpCallbackListener

interface HttpCallbackListener{
	fun onFinish(response:String)
	fun onError(e:Exception)
}

onFinish()方法表示当服务器成功响应我们请求时候调用,onError()表示镜像网络操作出现错误时候调用
修改HttpUtil中的代码

object HttpUtil{
    fun sendHttpRequest(adress:String,listener:HttpCallbackListener) {
        Log.d("HttpUtil","已经执行")
        thread{
            Log.d("HttpUtil","进入线程")
            val response = StringBuilder()
            var connection : HttpURLConnection? = null
        try {
            val url = URL(adress)
            connection=url.openConnection() as HttpURLConnection
            connection.connectTimeout=8000
            connection.readTimeout=8000
            val input = connection.inputStream
            val reader=BufferedReader(InputStreamReader(input))
            reader.use {
                reader.forEachLine {
                    response.append(it)
                }
            }
            //回调onFinish()方法

            listener.onFinish(response.toString())
        }catch (e:Exception){
            e.printStackTrace()
            //回调onError()方法
            listener.onError(e)
        }finally {
            connection?.disconnect()
        }
    }

    }
}

再修改MainActivity中的代码,添加如下代码

 HttpUtil.sendHttpRequest("http://10.0.2.2/get_data.json",object:HttpCallbackListener{
                     override fun onFinish(response: String) {
                         //得到服务器返回的具体内容
                         showResponse(response)
                         parseJSONWithGSON(response)
                     }

                     override fun onError(e: java.lang.Exception) {
                        //在这里对异常情况进行处理
                         e.printStackTrace()
                     }
                 })

6.1Retrofit基本用法
首先我们需要在项目中添加必要的依赖库

dependencies {
 implementation 'com.squareup.retrofit2:retrofit:2.6.1'
  implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
}

接下来我们需要新增一个APP类

class App (val id:String,val name:String,val version:String)

接下来我们可以根据服务器接口的功能进行归类,创建不同种类的接口文件,并在其中定义对应具体服务器接口的方法。

interface AppService {
    @GET("get_data.json")
    fun getAppData(): Call<List<App>>
}

在MainActivity中添加代码

   getAppDataBtn.setOnClickListener {
        val retrofit = Retrofit.Builder()
            .baseUrl("http://10.0.2.2/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
         val appService=retrofit.create(AppService::class.java)
         appService.getAppData().enqueue(object :Callback<List<App>>{
             override fun onResponse(p0: Call<List<App>>, p1: Response<List<App>>) {
                 val list=p1.body()
                 if(list!=null){
                     for (app in list){
                         Log.d("MainActivity","id is ${app.id}")
                         Log.d("MainActivity","name is ${app.name}")
                         Log.d("MainActivity","version is ${app.version}")
                     }
                 }

             }

             override fun onFailure(p0: Call<List<App>>, p1: Throwable) {
                 p1.printStackTrace()
             }
         })
        }

当调用了AppService的getAppData()方法时,会返回一个Call<List< App>>对象,这时我们再调用一下它的enqueue()方法,Retrofit就会根据注解中配置的服务器接口地址去进行网络请求。
注意,当发起请求的时候,Retrofit会自动在内部开启子线程,当数据回到Callback中之后,Retrofit又会自动切换回主线程,整个过程我们不需要考虑线程切换的问题
6.2处理复杂的接口地址类型
为方便先定义一个Data类,并包含id和content这两个字段如下

class Data(val id : String,val content : String)

1.在很多场景下,接口地址中的部分内容可能时动态变化的,比如

GET http://example.com/<page>/get_data.json
interface ExampleService {
    @GET("{page}get_data.json")
    fun getData(@Path("page") page: Int): Call<Data>
}

2.很多服务器要求我们传入一系列参数

GET http://example.com/get_data.json?u=<user>&t=<token>
interface ExampleService {
    @GET("get_data.json")
    fun getData(@Query("u") user:String,@Quert("t") token:String): Call<Data>
}

HTTP不仅只有GET这一种请求方式,常见的又GET,POST,PUT,PATCH,DELETE这几种
1.比如根据id删除一条指定的数据

DELETE http://example.com/data/<id>
interface ExampleService {
    @DELETE("data/{id}")
    fun deleteData(@Path("id") id : String): Call<ResponseBody>
}

2.使用POST请求来提交数据,将数据放在HTTP请求的body部分,借助Retrofit中@Body注解来完成

POST http://example.com/data/create
{"id" : 1,"content" : "The description for this data."}
interface ExampleService {
    @DELETE("data/create")
    fun createData(@Body data : Data): Call<ResponseBody>
}

最后有些服务器接口还需要我们在HTTP请求的header中指定参数

GET http://example.com/get_data.json
User-Agent:okhttp
Cache-Control : max-age=0

我们可以在Retrofit中直接使用@Headers注解来对他们进行声明(静态header声明)

interface ExampleService {
	@Headers("User-Agent : okhttp","Cache-Control : max-age=0")
    @GET("get_data.json")
    fun getData() : Call<Data>
}

动态指定header的值,则需要使用@Header

interface ExampleService {
    @GET("get_data.json")
    fun getData(@Header("User-Agent") userAgent : String,@Header("Cache-Control") cacheControl : String) : Call<Data>
}

6.3Retrofit构建器的最佳写法
新建一个ServiceCreator单例类

object ServiceCreator {
    private const val BASE_URL = "http://10.0.2.2/"

    private val retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    fun <T> create(serviceClass: Class<T>) : T= retrofit.create(serviceClass)
}

经过这样的封装后,我们可以这样获取一个AppService接口的动态代理对象

val appService = ServiceCreator.create(AppService::class.java)

还可以继续优化一些(根据上一节泛型实体化)

object ServiceCreator{
	...
	inline fun <reified T> create() : T = create(T::class.java)
}
val appService = ServiceCreator.create<Appservice>()

Kotlin协程编写高效的并发程序

1.协程的基本用法
首先在app.build.gradle中添加如下依赖库

dependencies{
...
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
}

第二个依赖库是在Android项目中才会用到
接下来创建一个CoroutinesTest.kt文件,并定义一个main()函数

fun main(){
	GlobalScope.launch{
	println("codes run in coroutine scope")
	}
}

GlobaalScope.launch函数每次创建都是一个顶层协议
delay()函数可以让当前协程延迟指定时间后再运行,但它和Tread.sleep()方法不同。delay()函数是一个非阻塞式挂起函数,它只会挂起当前协程并不会影响其他协程的运行。
2.让应用程序在协程中所有代码都运行完之后再结束(借助runBlocking函数实现这个功能)

fun main(){
	runBlocking{
	println("codes run in coroutine scope")	
	delay(1500)
	println("codes run in coroutine scope finished")
}
}

runBlocking函数通常只应该在测试环境下使用,在正式环境中使用容易产生一些性能上的问题
创建多个协程只需要使用launch函数就可以了

fun main(){
	runBlocking{
	launch{
	println("lauch1")	
	delay(1500)
	println("lauch1 finished")
				}
	launch{
	println("lauch2")	
	delay(1500)
	println("lauch2 finished")
				}
}
}

3.kotlin提供了一个suspend关键字,使用它可以将任意函数声明成挂起函数,挂起函数之间可以互相调用

suspend fun printDot(){
	println(".")
	delay(1000)
}

在任何其他挂起函数中调用coroutineScope函数.它的特点式会继承外部的协程作用于并创建一个子作用域。

suspend fun printDot() = coroutineScope{
	launch{
	println(".")
	delay(1000)
	}
}

coroutineScope函数只会阻塞当前协程,既不影响其他协程,也不影响任何线程,因此不会造成任何性能上的问题。而runBlocking函数会阻塞当前线程,如果在主线程中调用了它的话,可能会导致界面卡死的情况
4.更多的作用域构建器
不管GlobalScope.launch函数还是launch函数,他们都会返回一个Job对象,只需要调用Job对象的cancel()方法就可以取消协程

val job=GlobalScope.launch{
	//处理具体的逻辑
}
job.cancel()

项目中常用的写法:

val job = Job()
val scope = CoroutineScope(job)
scope.launch{
	//处理具体逻辑
	}
job.cancel()
}

async函数必须在协程作用域当中才能调用,他会创建一个新的子协程并返回一个Defered对象。如果我们想获取async函数代码块的执行结果,只需要调用Deferred对象的await()

fun main(){
	runBlocking{
	val result = async{
	5+5
}.await()
println(result)
}

}

两个async函数完全可以同时执行从而提高运行效率

fun main(){
	runBlocking{
		val deferred1 =async{
		delay(1000)
		5+5
		}
		val deferred2=async{
		delay(1000)
		4+6
		}
		println("result is ${deferred1.await() + deferred2.await()}.")
}	

}

一个比较特殊的作用域构建器:withContext()函数。withContext()函数是一个挂起函数,大体可以理解成async函数的一种简化版写法

fun main(){
	runBlocking{
	va; result = withContext(Dispatchers.Default){
5+5
	}
	println(result)
}
}

线程参数主要有以下3种可选:Dispatchers.Default,Dispatchers.IO和Dispatchers.Main。
Dispatchers.Default表示会使用一种默认低并发的线程策略
Dispatchers.IO表示会使用一种高并发的线程策略
Dispatchers.Main表示不会开启子线程,而是在Android主线程种执行代码,只能在Android项目中使用
5.使用协程简化回调的写法
回调机制基本上是依靠匿名类来实现的,但匿名类的写法比较繁琐
suspendCoroutine函数必须在协程作用域或挂起函数种才能调用。它接收一个Lambda表示式参数,主要作用是将当前协程立即挂起,然后在普通的线程中执行。

suspend fun request(address : String) : String{
	return suspendCoroutine { continuation ->
	HttpUtil.sendHttpRequest(address,object : HttpCallbackListener{
		override fun onFinish(response : String){
		continuation.resume(response)
	}
		override fun onError(e:Exception){
		continuation.resumeWithException(e)
	}
	})
	}
}

HttpUtil.sendCoroutine方法发起网络请求,并通过回调的方式监听请求结果。如果成功就调用Continuation函数的返回值。如果请求失败就调用Continuation的resumeWithException()恢复被挂起的协程。

suspend fun getBaiduResponse(){
	try{
		val response = request("https://www.baidu.com")
		//对服务器响应的数据进行处理
	}catch(e : Exception){
		//对异常情况进行处理
	}
}

简化Retrofit来发起网络请求

	suspend fun<T> Call <T>.await(): T{
	return suspendCoroutine{ continuation ->
		enqueue(object : Callback<T> {
			val body = response.body()
			if(body != null) continuation.resume(body)
			else continuation.resumeWithException(
				RuntimeException("response body is null"))
		}
		override fun onFailure(call : Call<T>, t : Throwable){
			continuation.resumeWithException(t)
	}
	})
	}
}

有了await()函数以后,我们调用所有Retrofit的Service接口都会变得极其简单

suspend fun getAppData(){
	try{
	val appList = ServiceCreator.create<AppService>().getAppData().await()
	//对服务器响应的数据进行处理
	}catch(e : Exception){
	//对异常情况进行处理
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一天发火两次

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值