《第一行代码 第3版》学习笔记——第十一章 网络技术

1 webview用法

class MainActivity : ComponentActivity() {
    @SuppressLint("SetJavaScriptEnabled")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NetWorkDemoTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    AndroidView(factory = { context ->
                        WebView(context).apply {
                            webViewClient = object : WebViewClient() {
                                override fun shouldOverrideUrlLoading(
                                    view: WebView?,
                                    request: WebResourceRequest?
                                ): Boolean {
                                    try {
                                        if (url!!.startsWith("baiduboxapp://")) {
                                            val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
                                            startActivity(intent)
                                            return true
                                        }
                                    } catch (e: Exception) {
                                        return false
                                    }
                                    view?.loadUrl(url!!)
                                    return true
                                }
                            }
                            settings.javaScriptEnabled = true
                            loadUrl("https://www.baidu.com/")
                        }
                    })
                }
            }
        }
    }
}

Compose没有WebView控件,使用传统的WebView控件,创建一个WebViewClient对象,用于展示百度首页。loadUrl函数加载百度首页数据。javaScriptEnabled用于加载JavaScript样式
由于baidu有自定义scheme,所以这里做了特殊处理

2 使用http访问网络

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NetWorkDemoTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    color = MaterialTheme.colorScheme.background
                ) {
                    ShowHttp()
                }
            }
        }
    }
}

@Composable
fun ShowHttp() {
    var response by remember {
        mutableStateOf("")
    }
    LazyColumn {
        item {
            Button(
                onClick = {
                    thread {
                        var conn: HttpURLConnection? = null
                        try {
                            val res = StringBuilder()
                            val url = URL("https://www.baidu.com")
                            conn = url.openConnection() as HttpURLConnection
                            conn.connectTimeout = 8000
                            conn.readTimeout = 8000
                            val input = conn.inputStream
                            val reader = BufferedReader(InputStreamReader(input))
                            reader.use {
                                reader.forEachLine {
                                    res.append(it)
                                }
                            }
                            response = res.toString()
                            Log.i("TAG", "response = $response ")
                        } catch (e: Exception) {
                            e.printStackTrace()
                        } finally {
                            conn?.disconnect()
                        }
                    }
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "request")
            }
        }
        item {
            Button(
                onClick = {
                    thread {
                        try {
                            val client = OkHttpClient()
                            val request = Request.Builder()
                                .url("https://www.baidu.com")
                                .build()
                            val res = client.newCall(request).execute()
                            val responseData = res.body?.string()
                            if (responseData != null) {
                                response = responseData
                            }
                        } catch (e: Exception) {
                            e.printStackTrace()
                        }
                    }
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "request from okhttp")
            }
        }
        item {
            Text(text = response)
        }
    }
}

这里创建两个按钮,一个数据展示的空间。两个按钮是两种使用http访问网络的方式,第一种是Java自带的HttpURLConnection相关的API,第二种是使用okhttp这个开源框架。
下面是访问baidu之后的打印界面
在这里插入图片描述

3 解析xml数据

网络上的数据经常使用xml或json进行传输,需要学习怎么对xml和json类型数据进行解析
这个使用pull和sax方式解析xml

class MainActivity : ComponentActivity() {
    private final val TAG = "MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NetWorkDemoTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    XmlPage { sendRequestForXml() }
                }
            }
        }
    }

    private fun sendRequestForXml() {
        thread {
            try {
                val client = OkHttpClient()
                val request = Request.Builder()
                    .url("http://192.168.12.148:12345/get_data_xml")
                    .build()
                val response = client.newCall(request).execute()
                val responseData = response.body?.string()
                if (responseData != null) {
                    parseXmlDataWithPull(responseData)
                }
                if (responseData != null) {
                    parseXmlWithSax(responseData)
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    private fun parseXmlDataWithPull(responseData: String) {
        try {
            val factory = XmlPullParserFactory.newInstance()
            val xmlPullparser = factory.newPullParser()
            xmlPullparser.setInput(StringReader(responseData))
            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" -> name = xmlPullparser.nextText()
                            "version" -> version = xmlPullparser.nextText()
                        }
                    }

                    XmlPullParser.END_TAG -> {
                        if ("app" == nodeName) {
                            Log.d(TAG, "id = $id, name = $name, version = $version")
                        }
                    }
                }
                eventType = xmlPullparser.next()
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun parseXmlWithSax(responseData: String) {
        try {
            val factory = SAXParserFactory.newInstance()
            val xmlReader = factory.newSAXParser().xmlReader
            val handler = ContentHandler()
            xmlReader.contentHandler = handler
            xmlReader.parse(InputSource(StringReader(responseData)))
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

@Composable
fun XmlPage(
    sendRequest: () -> Unit
) {
    LazyColumn {
        item {
            Button(
                onClick = {
                    sendRequest()
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "GetXml")
            }
        }
    }
}

SAX方式解析需要继承DefaultHandler并重写其中的方法

class ContentHandler : 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
    ) {
        nodeName = localName
    }

    override fun characters(ch: CharArray, start: Int, length: Int) {
        when (nodeName) {
            "id" -> id.appendRange(ch, start,  length)
            "name" -> name.appendRange(ch, start, length)
            "version" -> version.appendRange(ch, start, length)
        }
        Log.d("ContentHandler", "id = $id")
        Log.d("ContentHandler", "name = $name")
        Log.d("ContentHandler", "version = $version")
    }

    override fun endElement(uri: String?, localName: String?, qName: String?) {
        if ("app" == localName) {
            Log.d("endElement", "id = $id")
            Log.d("endElement", "name = $name")
            Log.d("endElement", "version = $version")
            Log.d("ContentHandler", "id is $id, name is $name, version is $version")
            id.setLength(0)
            name.setLength(0)
            version.setLength(0)
        }
    }

    override fun endDocument() {
    }
}

点击之后会打印

id = 1, name = Google Maps, version = 1.0
id = 2, name = Chrome, version = 2.1
id = 3, name = Google Play, version = 3.2

4 解析Json数据

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sendRequestForJson()
        setContent {
            NetWorkDemoTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {

                }
            }
        }
    }

    private fun sendRequestForJson() {
        thread {
            val client = OkHttpClient()
            val request = Request.Builder()
                .url("http://192.168.12.148:12345/get_data_json")
                .build()
            val response = client.newCall(request).execute()
            val responseData = response.body?.string()
            if (responseData != null) {
                parseJsonWithJsonObject(responseData)
                parseJsonWithGson(responseData)
            }
        }
    }

    private fun parseJsonWithJsonObject(responseData: String) {
        try {
            val jsonArray = JSONArray(responseData)
            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(
                    "parseJsonWithJsonObject",
                    "id = ${id.trim()}, name = ${name.trim()}, version = ${version.trim()}"
                )
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun parseJsonWithGson(responseData: String) {
        val gson = Gson()
        val typeOf = object : TypeToken<List<App>>() {}.type
        val appList = gson.fromJson<List<App>>(responseData, typeOf)
        for (app in appList) {
            Log.d(
                "parseJsonWithGson", "id = ${app.id.trim()}, name = ${app.name.trim()} " +
                        ", version = ${app.version.trim()}"
            )
        }
    }
}

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

使用JSONArray和Gson解析,Gson可以直接解析成对象。
打印如下

id = 5, name = Clash of Clans, version = 5.5
id = 6, name = Boom Beach, version = 7.0
id = 7, name = Clash Royale, version = 3.5
id = 5, name = Clash of Clans , version = 5.5
id = 6, name = Boom Beach , version = 7.0
id = 7, name = Clash Royale , version = 3.5

5 使用回调

由于网络请求是耗时的操作,在子线程中操作,无法准确知道结果什么时候返回,所以可以通过回调的方式来返回结果。

HttpCallbackListener

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

HttpUtils

object HttpUtils {
    private const val TAG = "HttpUtils"
    fun sendHttpRequest(address: String, listener: HttpCallbackListener) {
        thread {
            var connect: HttpURLConnection? = null
            try {
                val response = StringBuilder()
                val url = URL(address)
                connect = url.openConnection() as HttpURLConnection
                connect.connectTimeout = 8000
                connect.readTimeout = 8000
                val inputStream = connect.inputStream
                val reader = BufferedReader(InputStreamReader(inputStream))
                reader.use {
                    reader.forEachLine {
                        response.append(it)
                    }
                }
                listener.onFinish(response.toString())
            } catch (e: Exception) {
                e.printStackTrace()
                listener.onError(e)
            } finally {
                connect?.disconnect()
            }
        }
    }

    fun sendHttpRequest(address: String, callback: okhttp3.Callback) {
        thread {
            val client = OkHttpClient()
            val request = Request.Builder()
                .url(address)
                .build()
            client.newCall(request).enqueue(callback)
        }
    }

    fun parseXmlWithPull(response: String): String {
        try {
            val factory = XmlPullParserFactory.newInstance()
            val parser = factory.newPullParser()
            parser.setInput(StringReader(response))
            var eventType = parser.eventType
            val responseData = StringBuilder()
            var id = ""
            var name = ""
            var version = ""
            while (eventType != XmlPullParser.END_DOCUMENT) {
                val nodeName = parser.name
                when (eventType) {
                    XmlPullParser.START_TAG -> {
                        when (nodeName) {
                            "id" -> id = nodeName
                            "name" -> name = nodeName
                            "version" -> version = nodeName
                        }
                    }

                    XmlPullParser.END_TAG -> {
                        if ("app" == nodeName) {
                            val text = "id = ${id.trim()}, name = ${name.trim()}," +
                                    " version = ${version.trim()}\n"
                            Log.d(TAG, text)
                            responseData.append(text)
                        }
                    }
                }
                eventType = parser.next()
            }
            return responseData.toString()
        } catch (e: Exception) {
            e.printStackTrace()
            return ""
        }
    }

    val localIpv4Address: String?
        get() {
            val en = NetworkInterface.getNetworkInterfaces()
            while (en.hasMoreElements()) {
                val netInterface = en.nextElement()
                val enIpAddress = netInterface.inetAddresses
                while (enIpAddress.hasMoreElements()) {
                    val inetAddress = enIpAddress.nextElement()
                    if (!inetAddress.isLoopbackAddress && inetAddress is Inet4Address) {
                        return inetAddress.hostAddress!!.toString()
                    }
                }
            }
            return null
        }
}

MainActivity

const val TAG = "MainActivity"

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NetWorkDemoTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    RequestPage()
                }
            }
        }
    }
}

@Composable
fun RequestPage() {
    var result by remember {
        mutableStateOf("")
    }
    LazyColumn {
        result = ""
        item {
            Button(
                onClick = {
                    val listener = object : HttpCallbackListener {
                        override fun onFinish(response: String) {
                            result = "HttpURLConnection data: \n"
                            result += HttpUtils.parseXmlWithPull(response)
                        }

                        override fun onError(e: Exception) {
                            Log.d(TAG, "onError: ")
                            result = "HttpURLConnection request failed"
                        }

                    }
                    val ip = HttpUtils.localIpv4Address
                    val url = "http://$ip:12345/get_data_xml"
                    HttpUtils.sendHttpRequest(url, listener)
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "request for xml with HttpURLConnection")
            }
        }
        item {
            Button(
                onClick = {
                    result = ""
                    val callback = object : Callback {
                        override fun onFailure(call: Call, e: IOException) {
                            Log.d(TAG, "onFailure: ")
                            result = "okhttp request failed"
                        }

                        override fun onResponse(call: Call, response: Response) {
                            result = "okhttp data: \n"
                            result += HttpUtils.parseXmlWithPull(response.body?.string().toString())
                        }
                    }
                    val ip = HttpUtils.localIpv4Address
                    val url = "http://$ip:12345/get_data_xml"
                    HttpUtils.sendHttpRequest(url, callback)
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "request for xml with okhttp")
            }
        }
        item {
            Text(text = result)
        }
    }
}

这里展示了HttpURLConnection和okhttp使用回调的方式,HttpURLConnection需要自己创建回调接口,okhttp则有自带的callback接口。

6 Retrofit

6.1 Retrofit使用

Retrofit基于以下几点设计:

  • 同一款应用程序中所发起的网络请求绝大多数指向的是同一个服务器域名
  • 服务器提供的接口通常是可以根据功能来归类的
  • 开发者肯定更加习惯于“调用一个接口,获取它的返回值”这样的编码方式
class App(val id: String, val name: String, val version: String)

创建一个App类用于存储数据,Retrofit可以通过Gson直接将xml数据解析成对象进行存储

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

创建一个AppService 接口,定义一个函数做网络请求的入口,使用GET注解表示一个是get类型的请求,由于Retrofit可以设置baseurl,所以这里只需要设置相对的资源路径

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NetWorkDemoTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    GetXmlData()
                }
            }
        }
    }
}

@Composable
fun GetXmlData() {
    var xmlData by remember {
        mutableStateOf("")
    }
    LazyColumn {
        item {
            Button(
                onClick = {
                    val ip = HttpUtils.localIpv4Address
                    val retrofit = Retrofit.Builder()
                        .baseUrl("http://$ip:12345/")
                        .addConverterFactory(GsonConverterFactory.create())
                        .build()
                    val appService = retrofit.create(AppService::class.java)
                    appService.getAppData().enqueue(object : Callback<List<App>> {
                        override fun onResponse(
                            call: Call<List<App>>,
                            response: Response<List<App>>
                        ) {
                            val result = StringBuilder()
                            val list = response.body()
                            if (list != null) {
                                for (app in list) {
                                    result.append(Gson().toJson(app).toString() + "\n")
                                }
                            }
                            xmlData = result.toString()
                        }

                        override fun onFailure(call: Call<List<App>>, t: Throwable) {
                            xmlData = "request failed"
                        }

                    })
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text = "get xml data")
            }
        }
        item {
            Text(text = xmlData)
        }
    }
}

首先创建Retrofit对象,设置baseurl,并设置Gson作为转换工具。
然后创建AppService子类对象,调用getAppData方法,并调用enqueue开始发起网络请求。后面传入一个回调作为参数,请求的response返回后直接触发回调
请求后效果如下:
在这里插入图片描述

6.2 其他请求方式

如果需要复杂参数传递,可以参考

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

如果存在页面参数,可以通过传入一个int值并使用Path注解修饰

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

这个是带参数查询的写法

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

这个是delete请求

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

这个是post请求

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

这种是静态的Header中添加数据

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

这种是动态的Header中添加数据

6.3 最佳实践

val ip = HttpUtils.localIpv4Address
val retrofit = Retrofit.Builder()
    .baseUrl("http://$ip:12345/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()
val appService = retrofit.create(AppService::class.java)

这段代码可以优化,创建一个ServiceCreator 类

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

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

    fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)
    inline fun <reified T> create(): T = create(T::class.java)
}

使用泛型,并创建一个内联函数,使用reified修饰,可以访问泛型的真实类型来进一步简化

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

没有加入inline函数可以调用上面的,加入inline之后,更为简化

  • 26
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值