第一行代码Android第三版学习——网络通信

网络通信

WebView的用法

  • 在layout文件下添加控件
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
  • 在Activity中进行设置
// 调用getSettings可以设置一些浏览器属性
// setJavaScriptEnabled(true)表示让WebView支持JavaScript脚本
webView.settings.setJavaScriptEnabled(true)
// 调用setWebViewClient方法并传入WebViewClient实例
// 使得跳转网页时,目标网页仍然在WebView中显示
webView.webViewClient = WebViewClient()
// 传入网址
webView.loadUrl("https://www.baidu.com")
  • 在AndroidManifest中加入权限声明
    <uses-permission android:name="android.permission.INTERNET" />

使用HTTP访问网络

使用HttpURLConnection
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()
                // 创建URL对象并传入网址
                val url = URL("https://www.baidu.com")
                // 调用openConnection获取HttpURLConnection实例
                connection = url.openConnection() as HttpURLConnection
                // 设置连接超时和读取超时的毫秒数
                connection.connectTimeout = 8000
                connection.readTimeout = 8000
                // 调用getInputStream获取返回的输入流
                val input = connection.inputStream
                showResponse(input.toString())
                // 下面对获取到的输入流进行读取
                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) {
        // 因为在不能在子线程中修改UI,所有要使用异步消息处理机制
        runOnUiThread {
            // 在这里进行UI操作,将结果显示到界面上
            responseText.text = response
        }
    }
}

如果忘记声明权限就运行程序了,要把APP卸载掉,再重新运行程序才能生效。

使用OkHttp

先在app/build.gradle中添加依赖
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
修改之前的代码用OkHttp的方式实现

private fun sendRequestWithOkHttp() {
        thread {
            try {
                // 创建OkHttpClient实例
                val client = OkHttpClient()
                // 创建Request用于发送HTTP请求
                val request = Request.Builder()
                        .url("https://www.baidu.com")
                        .build()    
                // 调用client.newCall创建Call对象
                // 并调用execute来发送请求并获取服务器返回的数据
                val response = client.newCall(request).execute()
                // 得到返回的具体内容
                val responseData = response.body?.string()
                if (responseData != null) {
                    showResponse(responseData)             }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

解析XML格式数据

  • 下载Apache HTTP Servier
    http://www.apachelounge.com/download/
  • 解压后打开\Apache24\conf\httpd.conf文件修改
    ServerRoot(apache目录)改为 Apache24的路径
    ServerName (服务器名称) 改为127.0.0.1
  • 安装apache
    安装/卸载命令(以管理员身份运行CMD)
    安装命令:”E:\apache\Apache24\bin\httpd.exe” -k install -n apache
    卸载命令:”E:\apache\Apache24\bin\httpd.exe” -k uninstall -n apache
    (每改一次.conf文件,都要卸载后重新安装)
  • 打开任务管理器,服务,开启apache
  • 打开浏览器输入127.0.0.1出现It works!证明安装成功。
    进入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>

打开浏览器输入127.0.0.1/get_data.xml即可看到内容。

Pull解析方式

修改url为http://10.0.2.2/get_data.xml
10.0.2.2对于模拟器来说就是本机的IP地址
加入parseXMLWithPull(responseData)方法
parseXMLWithPull方法如下

private fun parseXMLWithPull(xmlData: String) {
    try {
        // 创建XmlPullParserFactory实例来获得XmlPullParser对象
        val factory = XmlPullParserFactory.newInstance()
        val xmlPullParser = factory.newPullParser()
        // 将服务器返回的数据设置进去
        xmlPullParser.setInput(StringReader(xmlData))
        // 获得当前解析事件
        var eventType = xmlPullParser.eventType
        var id = ""
        var name = ""
        var version = ""
        // eventType != XmlPullParser.END_DOCUMENT说明解析没有完成
        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("MainActivity", "id is $id")
                        Log.d("MainActivity", "name is $name")
                        Log.d("MainActivity", "version is $version")
                    }
                }
            }
            // 进入下一个解析事件
            eventType = xmlPullParser.next()
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

在Logcat中查看解析出来的数据

SAX解析方式

新建一个类继承DefaultHandler,重写五个方法。

  • startDocument:开始XML解析时调用
  • startElement:开始解析某个节点时调用
  • characters:获取节点中内容时调用
  • endElement:完成解析某个节点时调用
  • endDocument:完成整个解析时调用
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) {
        // 根据当前的节点名判断将内容添加到哪一个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) {
            // 调用trim取出回车和换行符
            Log.d("ContentHandler", "id is ${id.toString().trim()}")
            Log.d("ContentHandler", "name is ${name.toString().trim()}")
            Log.d("ContentHandler", "version is ${version.toString().trim()}")
            // 最后要将StringBuilder清空掉
            id.setLength(0)
            name.setLength(0)
            version.setLength(0)
        }
    }

    override fun endDocument() {
    }

在Activity中加入parseXMLWithSAX(responseData)

private fun parseXMLWithSAX(xmlData: String) {
    try {
        // 创建SAXParserFactory对象来获取xmlReader对象
        val factory = SAXParserFactory.newInstance()
        val xmlReader = factory.newSAXParser().xmlReader
        // 获取ContentHandler实例
        val handler = ContentHandler()
        // 将ContentHandler的实例设置到XMLReader中
        xmlReader.contentHandler = handler
        // 开始执行解析
        xmlReader.parse(InputSource(StringReader(xmlData)))
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

解析JSON格式数据

新建get_data.json文件

[
  {
    "id": "5",
    "version": "5.5",
    "name": "Clans of Clans"
  },
  {
    "id": "6",
    "version": "7.0",
    "name": "Boom Beach"
  },
  {
    "id": "7",
    "version": "3.5",
    "name": "Clans Royale"
  }
]
使用JSONObject

修改url为http://10.0.2.2/get_data.json
加入parseJSONWithJSONObject(responseData)方法

private fun parseJSONWithJSONObject(jsonData: String) {
    try {
        // 将服务器返回的数据传入JSON数组
        val jsonArray = JSONArray(jsonData)
        // 循环遍历数组
        for (i in 0 until jsonArray.length()) {
            // 取出JSONObject对象
            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: Exception) {
        e.printStackTrace()
    }
}
使用GSON

导入依赖implementation 'com.google.code.gson:gson:2.8.5'
新增App类

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

在Activity中加入parseJSONWithGSON(responseData)方法

private fun parseJSONWithGSON(jsonData: String) {
    // 创建Gson实例
    val gson = Gson()
    // 借助TypeToken将希望解析成的数据类型传入fromJson方法
    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}")
    }

网络请求回调的实现方式

新建object HttpUtil类加入以下方法

fun sendOkHttpRequest(address: String, callback: okhttp3.Callback) {
    val client = OkHttpClient()
    val request = Request.Builder()
        .url(address)
        .build()
    // enqueue方法会在内部开启子线程执行HTTP请求
    // 并将请求结果回调到okhttp3.Callback中
    client.newCall(request).enqueue(callback)
}

调用时就可以写成

HttpUtil.sendOkHttpRequest(address,object: Callback{
    override fun onResponse(call: Call, response: Response) {
        // 得到服务器返回的具体内容
        val responseData = response.body?.string()
    }

    override fun onFailure(call: Call, e: IOException) {
        // 在这里对异常情况进行处理
    }
})

最好用的网络库:Retrofit

添加依赖
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)

新建AppService接口

interface AppService {
    // 表示调用getAppData时Retrofit会发起一条GET请求
    // 请求的地址就是@GET中传入的参数
    // 这里只需要传入相对路径
    @GET("get_data.json")
    // getAppData的返回值必须声明成Retrofit中内置的Call类型
    // 并通过泛型来指定数据应该转换成什么对象
    fun getAppData(): Call<List<App>>

}

在布局文件中添加一个Button
在Activity中修改代码

getAppDataBtn.setOnClickListener {
    // 创建retrofit对象
    val retrofit = Retrofit.Builder()
            // 指定跟路径
            .baseUrl("http://10.0.2.2/")
            // 指定解析数据时使用的转换库
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    // 调用create方法并传入Service接口对应的Class类型,创建一个该接口的动态代理对象
    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 list = response.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(call: Call<List<App>>, t: Throwable) {
            t.printStackTrace()
        }
    })
}
处理复杂的接口地址类型

假设地址为http://example.com/

  • 当含有页数时,如http://example.com/<page>
// @Path将page参数自动替换到{page}的位置来表示页数
@GET("{page}/get_data.json")
fun getData(@Path("page")page: Int):Call<Data>
  • 当含有参数时,如http://example.com/get_data.json?u=<user>&t=<token>
@GET("get_data.json")
fun getData(@Query("user") user: String, @Query("token") token: String): Call<Data>
  • 当要删除数据时,如http://example.com/data/<id>
// ResponseBody表示能够接受任意类型的数据,并不会对其进行解析
@DELETE("data/{id}")
fun deleteData(@Path("id") id: String): Call<ResponseBody>
  • 提交数据时,如http://example.com/data/create
// 使用@Body将数据放到HTTP请求的body部分
@POST("data/create")
fun createData(@Body data: Data): Call<ResponseBody>
  • 有些服务器接口还可能会要求我们在HTTP请求的header中指定参数,如

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

@GET("get_data.json")
fun getData(@Header("User-Agent") userAgent: String, @Header("Cache-Control") cacheControl: String): Call<Data>
Retrofit构建器的最佳写法

创建单例类

object ServiceCreator {
    // 指定根路径
    private const val BASE_URL = "http://10.0.2.2/"
    // 构建retrofit对象
    private val retrofit = Retrofit.Builder()
        .baseUrl(BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    // 创建相应Service接口的动态代理对象
    fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)
    // 进一步简化代码
    inline fun <reified T> create(): T = create(T::class.java)

}

之后就可以使用如下方法来获取AppService接口的动态代理对象了
val appService = ServiceCreator.create<AppService>()

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值