使用网络技术
WebView的用法
只是先简单用用
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
···>
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/webView"/>
</LinearLayout>
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.webView.settings.javaScriptEnabled=true
binding.webView.webViewClient=WebViewClient()//表示当需要从一个网页跳转到另一个网页的时候,我们需要目标网页是在webView中打开也不是打开系统浏览器
binding.webView.loadUrl("https://www.bilibili.com")//在webView中加载一个Url网址字符串的对应网页的内容,
}
}
网络权限要申请
<uses-permission android:name="android.permission.INTERNET"/>
谈到网络就避不开http
此处的http只是了解一下,正经还得去看书
http协议大致的工作逻辑就是:
客户端向服务器发出一条请求,服务器收到请求后就返回一些数据给客户端,然后客户端对这些数据处理
而我们刚才的webView显示阿b的首页
客户端向b站服务器发出一条请求,b站服务器收到请求后了解到我们想要的是首页就把首页的HTML代码进行返回,然后客户端浏览器对解析HTML代码,把页面显示出来
WebView帮我们处理好了发送HTTP请求,接收服务器响应,解析返回数据,展示最终的页面。但是由于webView封装的太好了,所以我们无法直观的看到整个工作流程
手动发送Http请求深入了解整个过程
使用httpURLConnection
-
Android6.0以前有两种发送HTTP请求的方式
- httpURLConnection
- HttpClient 由于API复杂,于Android6.0时代废弃
-
httpURLConnection使用流程
- 获取一个httpURLConnection实例,一般只需要常见一个url对象,并且传入目标的网络地址,然后调用一下openConnection()方法即可
val url=URL("https://www.baidu.com") val connection=url.openConnection() as HttpURLConnection
- 得到一个httpURLConnection之后设置一下Http请求所使用的方法。常用的是post和get
- post 提交数据给服务器
- get 从服务器接收数据
connection.requestMethod="GET"
- 自由的定制功能,比如:设置连接超时,读取的超时的毫秒数,以及服务器希望得到的一些消息头
connection.connectTimeout=8000 connection.readTimeout=8000
- 调用getInputStream()方法就可以获取到服务器返回的输入六,剩下的输入流进行读取
val input = connection.inputStream
- 最后通过disconnect()方法将这个HTTP连接关闭
connection.disconnect
- 获取一个httpURLConnection实例,一般只需要常见一个url对象,并且传入目标的网络地址,然后调用一下openConnection()方法即可
还是实践才能出真知
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
···
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/sendRequestBtn"
android:text="发送请求"/>
<!--由于屏幕的空间有限,所以通过ScrollView可以用滚动的方式查看屏幕外的内容-->
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/responseText"
android:text="返回数据"/>
</ScrollView>
</LinearLayout>
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.sendRequestBtn.setOnClickListener {
Toast.makeText(this,"点击了发送请求",Toast.LENGTH_SHORT).show()
sendRequestWithHttpURLConnection()
}
}
private fun sendRequestWithHttpURLConnection(){
//开启线程发起网络请求
thread {
var connection:HttpURLConnection?=null
try {
var response=StringBuilder()//创建一个StringBuilder来存储返回的数据
val url= URL("https://www.baidu.com")
val 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操作,这个函数自己已经封装了异步消息处理机制
binding.responseText.text=response
}
}
}
网络权限
<uses-permission android:name="android.permission.INTERNET"/>
使用okHttp
开源的okHttp完全可以替代httpURLConnection
- okHttp的使用流程
- 安装依赖 dependencies { ··· implementation 'com.squareup.okhttp3:okhttp3:4.1.0' } - 创建一个okHttpClient实例 val client= OkHttpClient() - 想要发起一个HTTP请求,就需要创建一个Request对象 val request=Request.Builder() .url("https://www.baidu.com") .build() - 调用OkHttpClient的newCall()方法来创建一个call对象,并且调用的execute()方法来发送请求并获取服务器返回的数据 val response=client.newCall(request).execute() - response就是返回的数据 val responseData=response.body?.string() - 如果发送的是一个post请求 就要先创建一个Request Body对象来存放待提交的参数 val requestBody=FormBody.Builder() .add("username","admin") .add("password","123446") .build() - 然后在Request。Builder中调用一下post()方法,并且将RequestBody()对象传入 val request=Request.Builder() .url("https://www.baidu.com") .post(requestBody) .build() - 接下来的操作就和GET请求一样了
实操
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.sendRequestBtn.setOnClickListener {
Toast.makeText(this,"点击了发送请求",Toast.LENGTH_SHORT).show()
sendRequestWithOkHttp()
}
}
private fun sendRequestWithOkHttp(){
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()
}
}
}
private fun showResponse(response:String){
runOnUiThread{//这个函数是使用异步消息处理机制来对UI操作,这个函数自己已经封装了异步消息处理机制
binding.responseText.text=response
}
}
}
解析XML格式数据
在网络上传输数据主要有两种方式:
- XML:有两种解析方式
- Pull解析
- SAX解析
- Dom解析
- JSON
准备好的xml数据
<apps>
<app>
<id>1</id>
<name>google maps</name>
<version>1.0</version>
</app>
<app>
<id>2</id>
<name>edge</name>
<version>2.1</version>
</app>
<app>
<id>3</id>
<name>google player</name>
<version>3.2</version>
</app>
</apps>
Pull解析
class MainActivity : AppCompatActivity() {
···
private fun sendRequestWithOkHttp(){
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)
parseXMLWithPull(responseData)
}catch (e:Exception){
e.printStackTrace()
}
}
}
private fun parseXMLWithPull(xmlData: String) {//这里传过来的是xml中的所有数据
try {
val factory = XmlPullParserFactory.newInstance()//获取一个Xml抽取分割工厂的实例
val xmlPullParser=factory.newPullParser()//通过Xml拉取分割工厂对象new一个Xml抽取解析器
xmlPullParser.setInput(StringReader(xmlData))//StringReader把xml数据字符串转化成字符串流,然后设置到解析器中
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()//xml抽取解析器用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")
Log.d("MainActivity","===========================")
}
}
}
eventType=xmlPullParser.next()//一个解析事件完了就解析下一个解析时间
}
}catch (e:Exception){
e.printStackTrace()
}
}
}
<!--在android9.0时代开始,应用程序默认只允许https类型的网络请求
http有安全隐患不能用,但是但我们现在用的apache服务器就是http
下面就是配置为了能使用http
这段配置的意思就是允许我们以明文的方式在网络上传输数据,而http就是用明文传输数据-->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:android="http://schemas.android.com/apk/res/android">
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>
还需要在AndroidManifest.xml中启用配置文件
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bo.a2_learnnetwork">
<uses-permission android:name="android.permission.INTERNET"/>
<application
···
android:networkSecurityConfig="@xml/network_config">
···
</application>
</manifest>
SAX
解析方式
要用SAX
解析,就需要新建一个类去继承DefaultHandler
类,并且重写父类的5个方法
class MyHandler :DefaultHandler(){
override fun startDocument() {
xml开始解析的时候用
super.startDocument()
}
override fun startElement(
uri: String?,
localName: String?,//当前节点的名字
qName: String?,
attributes: Attributes?
) {
xml开始解析某个节点的时候用
super.startElement(uri, localName, qName, attributes)
}
override fun characters(
ch: CharArray?,
start: Int,
length: Int
) {
获取节点里面具体的内容
super.characters(ch, start, length)
}
override fun endDocument() {
结束解析的时候用
super.endDocument()
}
override fun endElement(
uri: String?,
localName: String?,
qName: String?
) {
结束某个节点解析的时候用
super.endElement(uri, localName, qName)
}
}
具体实例
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?
) {
//记录当前节点名
if (localName != null) {
nodeName=localName
}
Log.d("ContentHandler","uri is $uri")
Log.d("ContentHandler","localName is $localName")
Log.d("ContentHandler","qName is $qName")
Log.d("ContentHandler","attributes is $attributes")
}
override fun characters(ch: CharArray?, start: Int, length: Int) {
when(nodeName){
"id"->id.append(ch,start,length)
"name"->name.append(ch,start,length)
"version"->version.append(ch,start,length)
}
}
override fun endDocument() {
super.endDocument()
}
override fun endElement(uri: String?, localName: String?, qName: String?) {
if("app"==localName){
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)
}
}
}
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.sendRequestBtn.setOnClickListener {
Toast.makeText(this,"点击了发送请求",Toast.LENGTH_SHORT).show()
/*sendRequestWithHttpURLConnection()*/
sendRequestWithOkHttp()
}
}
private fun sendRequestWithOkHttp(){
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)
parseXMLWithSAX(responseData)
}catch (e:Exception){
e.printStackTrace()
}
}
}
private fun parseXMLWithSAX(xmlData: String) {
try {
val factory=SAXParserFactory.newInstance()
val xmlReader=factory.newSAXParser().xmlReader
val handler=ContentHandler()
//将ContentHandler的实例设置到xmlReader中
xmlReader.contentHandler=handler
//开始解析
xmlReader.parse(InputSource(StringReader(xmlData)))
}catch (e:Exception){
e.printStackTrace()
}
}
}
Dom解析方式
节点包括:元素节点、属性节点、文本节点。元素一定是节点,但是节点不一定是元素。
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.sendRequestBtn.setOnClickListener {
Toast.makeText(this,"点击了发送请求",Toast.LENGTH_SHORT).show()
/*sendRequestWithHttpURLConnection()*/
sendRequestWithOkHttp()
}
}
private fun sendRequestWithOkHttp(){
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)
parseXMLWithDom(responseData)
}catch (e:Exception){
e.printStackTrace()
}
}
}
private fun parseXMLWithDom(xmlData: String) {
val factory = DocumentBuilderFactory.newInstance()
try {
val builder = factory.newDocumentBuilder();
val dom = builder.parse(InputSource(StringReader(xmlData)))
val root = dom.documentElement;
val items = root.getElementsByTagName("app");//查找所有app节点
for (i in 0 until items.length) {//遍历所有的app节点
val appNode = items.item(i) as Element
val childNodes = appNode.childNodes//获得app节点的子节点
for (j in 0 until childNodes.length) {//遍历app节点的子节点
val node = childNodes.item(j)
if(node.nodeType == Node.ELEMENT_NODE){//判断这个节点是不是元素节点
val e = node as Element//是元素节点就把节点转型成元素
when(e.nodeName){//判断元素的名字
"id"->Log.d("MainActivity","id is ${e.firstChild.nodeValue}")//
"name"->Log.d("MainActivity","name is ${e.firstChild.nodeValue}")
"version"->Log.d("MainActivity","version is ${e.firstChild.nodeValue}")
}
}
}
}
}catch (e:Exception){
e.printStackTrace()
}
}
解析Json
格式数据
json
的有点是体积小,在网络上传输用的流量少
但是相对的他的语义性较差,看起来不如xml直观
json中的数据其实都是键值对
主流的有四种:
- jsonObject
- gson
- jackson
- fastson
准备好的json数据
[
{
"id": "5",
"version": "5.5",
"name": "Clash of Clans"
},
{
"id": "6",
"version": "6.5",
"name": "Boom Beach"
},
{
"id": "7",
"version": "7.5",
"name": "Clash Royale"
}
]
jsonObject
解析
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.sendRequestBtn.setOnClickListener {
Toast.makeText(this,"点击了发送请求",Toast.LENGTH_SHORT).show()
/*sendRequestWithHttpURLConnection()*/
sendRequestWithOkHttp()
}
}
private fun sendRequestWithOkHttp(){
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)
parseJSONWithJSONObject(responseData)
}catch (e:Exception){
e.printStackTrace()
}
}
}
private fun parseJSONWithJSONObject(jsonData: String) {
try {
val jsonArray=JSONArray(jsonData)//获取的数据会根据{}分组,一个{}是一组数据(也就是一个jsonObject)
for (i in 0 until jsonArray.length()){
val jsonObject=jsonArray.getJSONObject(i)//获取到每个jsonObject
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")
Log.d("MainActivity","=======================================")
}
}catch (e:Exception){
e.printStackTrace()
}
}
}
GSON解析数据
添加依赖
dependencies {
···
implementation 'com.google.code.gson:gson:2.8.5'
}
GSON会把一段json格式的字符串自动映射成一个对象,
比如
如果有一段json数据是:
{"name":"tom","age":"12"}
那么我们就可以定义一个Person类(有name和age两个字段),然后简单的调用
val gson=Gson()
val person=gson.fromJson(jsonData,Person::class.java)
就可以把json数据解析成person对象了
如果是json数组,比如
{{"name":"tom","age":"12"},{"name":"jom","age":"13"}}
就可以用
val typeof=object:TypeToken<List<Person>>(){}.type
val personList=gson.formGson<List<Person>>(jsonData,typeof)
用json文件中的数据试试吧
需要一个Bean类来接收json中的数据
class App(val id:String,val name:String,val version:String) {
}
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.sendRequestBtn.setOnClickListener {
Toast.makeText(this,"点击了发送请求",Toast.LENGTH_SHORT).show()
/*sendRequestWithHttpURLConnection()*/
sendRequestWithOkHttp()
}
}
private fun sendRequestWithOkHttp(){
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)
parseJSONWithGSON(responseData)
}catch (e:Exception){
e.printStackTrace()
}
}
}
private fun parseJSONWithGSON(jsonData: String) {
val gson= Gson()
val typeOf=object:TypeToken<List<App>>(){}.type//利用TypeToken获取到期望解析成的数据的类型
val apps=gson.fromJson<List<App>>(jsonData,typeOf)//把jsonData解析成期望类型
for (i in apps.indices){
Log.d("MainActivity","id is ${apps.getOrNull(i)?.id}")
Log.d("MainActivity","name is ${apps.getOrNull(i)?.name}")
Log.d("MainActivity","version is ${apps.getOrNull(i)?.version}")
Log.d("MainActivity","=======================================")
}
}
}
jackson解析数据
jackson是java语言的一个流行的JSON函数库.
在android开发使用过程中,主要包含三部分:
- Jackson可以轻松的将Java对象转换成json对象和xml文档,
- 同样也可以将json、xml转换成Java对象,
- 他是基于事件驱动,与GSON相同
- 流程:
- 先创建一个对应于JSON数据的JavaBean类就可以通过简单的操作解析出所需JSON数据。
- 但和Gson解析不同的是,GSON可按需解析,即创建的JavaBean类不一定完全涵盖所要解析的JSON数据,按需创建属性
- 但Jackson解析对应的JavaBean必须把Json数据里面的所有key都有所对应,即必须把JSON内的数据所有解析出来,无法按需解析。但Jackson的解析速度和效率都要比GSON高
jackson解析时需要数据类有无参构造
导包
dependencies {
···
implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.0.pr3'
implementation 'com.fasterxml.jackson.core:jackson-core:2.9.0.pr3'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.0.pr3'
}
数据类
class App {
var id=""
var name=""
var version=""
constructor(i:String,n:String,v: String){
id=i
name=n
version=v
}
constructor(){}
}
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.sendRequestBtn.setOnClickListener {
Toast.makeText(this,"点击了发送请求",Toast.LENGTH_SHORT).show()
/*sendRequestWithHttpURLConnection()*/
sendRequestWithOkHttp()
}
}
private fun sendRequestWithOkHttp(){
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)
parseJSONWithJackson(responseData)
}catch (e:Exception){
e.printStackTrace()
}
}
}
private fun parseJSONWithJackson(jsonData: String) {
val mapper = ObjectMapper()
var apps: ArrayList<App>? = null
try {
val typeOf = mapper.typeFactory.constructParametricType(
List::class.java,
App::class.java
)
apps = mapper.readValue(jsonData,typeOf)
if (apps != null) {
for (app in apps) {
Log.d("MainActivity","id is ${app.id}")
Log.d("MainActivity","name is ${app.name}")
Log.d("MainActivity","version is ${app.version}")
Log.d("MainActivity","=======================================")
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
fastson解析数据
- 反序列化
public static final Object parse(String text); // 把JSON字符串转化为JSONObject或者JSONArray
public static final JSONObject parseObject(String text); // 把JSON字符串转化为JSONObject
public static final <T> T parseObject(String text, Class<T> clazz); // 把JSON文本parse为JavaBean
public static final JSONArray parseArray(String text); // 把JSON文本parse成JSONArray
public static final <T> List<T> parseArray(String text, Class<T> clazz); //把JSON字符串转化为JavaBean集合
- 序列化
public static final String toJSONString(Object object); // 将JavaBean序列化为JSON文本
public static final String toJSONString(Object object, boolean prettyFormat); // 将JavaBean序列化为带格式的JSON字符串
public static final Object toJSON(Object javaObject); //将JavaBean转换为JSONObject或者JSONArray
fastjson解析时需要数据类有无参构造
导包
dependencies {
···
implementation 'com.alibaba:fastjson:1.2.32'
}
class App {
var id=""
var name=""
var version=""
constructor(i:String,n:String,v: String){
id=i
name=n
version=v
}
constructor(){}
}
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.sendRequestBtn.setOnClickListener {
Toast.makeText(this,"点击了发送请求",Toast.LENGTH_SHORT).show()
/*sendRequestWithHttpURLConnection()*/
sendRequestWithOkHttp()
}
}
private fun sendRequestWithOkHttp(){
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)
parseJSONWithFastJson(responseData)
}catch (e:Exception){
e.printStackTrace()
}
}
}
private fun parseJSONWithFastJson(jsonData: String) {
try {
val apps= parseArray(jsonData,App::class.java)
for (app in apps) {
Log.d("MainActivity","id is ${app.id}")
Log.d("MainActivity","name is ${app.name}")
Log.d("MainActivity","version is ${app.version}")
Log.d("MainActivity","=======================================")
}
}catch (e:Exception){
e.printStackTrace()
}
}
}
网络请求回调的实现方式
从上面我们尝试几种解析方式的实践中可以发现,每次我们在发送请求的时候只有网址和解析的代码和函数不一样,其他的代码大量的多次写到,这不是个好习惯,我们应该把相同代码封装起来
object HttpUtil {
fun sendHttpRequest(address:String):String{
var connection:HttpURLConnection?=null
try {
val response=StringBuilder()//把返回的信息存到一个StringBuilder中(因为要多次拼接,单线程下超快的,也没有安全问题)
val url=URL(address)//把字符串解析成URL对象
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)
}
}
return response.toString()
}catch (e:Exception){
e.printStackTrace()
return e.message.toString()
}finally {
connection?.disconnect()
}
}
}
当想要发起一条http请求的时候
val address="http://www.baidu.com"
val response=HttpUtil.sendHttpRequest(address)
就可以了
但是网络请求是一个耗时的工作,所以我们需要再子线程里面进行这个工作,而我们又不能直接在这个工具类里面使用子线程,因为整个耗时逻辑都是在子线程中,有可能服务器响应之前sendHttpRequest()执行完了,当然也就无法返回数据了
所以我们需要借助kotlin的回调机制
定义一个接口
interface HttpCallbackListener {
fun onFinish(response: String)//服务器成功响应请求的时候调用,response是成功返回的数据
fun onError(e:Exception)//服务器失败响应请求的时候调用,e中是失败的信息
}
object HttpUtil {
fun sendHttpRequest(address:String,listener: HttpCallbackListener){
thread {
var connection:HttpURLConnection?=null
try {
val response=StringBuilder()//把返回的信息存到一个StringBuilder中(因为要多次拼接,单线程下超快的,也没有安全问题)
val url=URL(address)//把字符串解析成URL对象
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)
}
}
listener.onFinish(response.toString())
}catch (e:Exception){
e.printStackTrace()
listener.onError(e)
}finally {
connection?.disconnect()
}
}
}
}
因为sendHttpRequest有一个参数是接口,所以调用的时候需要实现以下
HttpUtil.sendHttpRequest("https://www.baidu.com", object : HttpCallbackListener {
override fun onFinish(response: String) {
TODO("Not yet implemented")
}
override fun onError(e: Exception) {
TODO("Not yet implemented")
}
})
但是okHttp其实已经帮我们做好这个功能了
object HttpUtil {
fun sendOkHttpRequest(address:String,callback: okhttp3.Callback){//callback其实也是一个接口
val client=OkHttpClient()
val request=Request.Builder()
.url(address)
.build()
client.newCall(request).enqueue(callback)
//okhttp3会在enqueue这个方法中帮我们开好子线程,然后在子线程中发送请求,并将结果回调到okHttp.CallBack中
}
HttpUtil.sendOkHttpRequest(address, object : Callback {
override fun onFailure(call: Call, e: IOException) {//其实这个方法还是在子线程中运行的
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
responseData=response.body?.string()
Log.d("MainActivity", responseData!!)
}
})
值得注意的是接口中的回调方法还是在子线程中执行的,所以里面是不能进行UI操作的
最好的网络库:Retrofit
- okHttp侧重于底层通信的实现
- Retrofit侧重于上层接口的封装
Retrofit的基本用法
retrofit的设计基于以下几个事实
- 同一款应用程序中所发起的网络请求绝大多数指向的是一个服务器的域名
- 服务器提供的接口通常是按功能来分类的
- 开发者肯定更加习惯于调用一个接口,获取他的返回值
设计
- Retrofit需要首先配置好一个根路径,然后指定服务器接口地址时只需要使用相对路径即可
- retrofit允许我们对服务器接口进行归类,将功能属于一类的服务器接口定义在一个接口文件中,从而使代码结构更加合理
- 我们也不需要关心网络通信的细节,只需要在接口文件中声明一系列方法和返回值
- 通过注解的方式指定该方法对应哪个服务器接口
- 需要提供哪些参数
- 当我们在程序中调用这个方法的时候,Retrofit会自动向对应的服务器接口发起请求
试试就逝世
导包
dependencies {
···
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
//这一条是引入retrofit和okHttp和okio库
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
//这一条是引入转换库,retrofit会把服务器返回的数据借助GSON解析
}
网络权限
<uses-permission android:name="android.permission.INTERNET"/>
数据类
class App(val id:String,val name:String,val version:String ) {
}
接口
interface AppService {
@GET("get_data.json")//这条注解是在注解下面这个方法
fun getAppData(): Call<List<App>>
//当调用这个方法的时候Retrofit发起了一条GET请求,请求的地址就是括号里的相对地址
//方法的返回值必须声明成Retrofit中的内置的Call参数,并通过泛型来指定数要转化成什么对象
}
调用
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding= ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.getAppDataBtn.setOnClickListener {
val retrofit= Retrofit.Builder()//构建一个Retrofit对象
.baseUrl("http://10.0.2.2")//设置根路径
.addConverterFactory(GsonConverterFactory.create())//指定解析数据时的转换库
.build()
val appService=retrofit.create(AppService::class.java)//创建AppService这个接口的动态代理对象
appService.getAppData().enqueue(object : Callback<List<App>> {//重写Callback回调
/*enqueue会开启一个子线程,当数据回调到CallBack中之后又自动切回主线程*/
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()
}
})
}
}
}
处理复杂的接口地址类型
实际上的网址是千变万化的,我们需要使用Retrofit来应对这种情况
普通
interface ExampleService {
@GET("get_data.json")
fun getData():Call<Data>
}
在很多场景下路径是不断动态变化的
比如:http://example.com/<page>/get_data.json
在这个接口中<page>页数是不断变化的
那么应该怎么写呢?
interface ExampleService {
@GET("{page}/get_data.json")//这样就指定了page是一个变量,可以当作参数传递过来
fun getData(@Path("page") page:Int):Call<Data>
}*/
或者还会传递一系列的参数
比如:http://example.com/get_data.json?u=<user>&t=<token>
interface ExampleService{
@GET("get_data.son")
fun getData(@Query("u") user:String,@Query("t") token:String):Call<Data>
@Query注解对u和t声明,那么Retrofit在就会自动按参数GET请求的格式将这两个参数构建到请求地址当中
}
Retrofit对所有常用的Http请求类型进行了支持
- @GET
- @POST
POST http://example.com/data/create {"id":1,"content":"123456"} interface ExampleService{ @POST("data/create") fun createData(@Body data: Data):Call<ResponseBody> ResponseBody表示能够接收任意类型的数据 } 让retrofit发出POST请求的时候,就会自动将Data对象中的数据转换成Json格式的文本,然后放到HTTP请求的body部分,服务器收到请求之后只需要从body中将这部分数据解析出来即可
- @PUT
- @PATCH
- @DELETE: 删除一条指定的信息
/*DELETE http://example.com/data/<id>*/ interface ExampleService{ @DELETE("data/{id}") fun deleteData(@Path("id") id: String):Call<ResponseBody> /*ResponseBody表示能够接收任意类型的数据*/ }
要求HTTP请求的Header中指定参数
比如
GET http://example.com/get_data.json
User-Agent:okHttp
Cache-Control:max-age=0
interface ExampleService{
@Header("User-Agent":"okHttp","Cache-control":"max-age=0")
@GET("get_data.json")
fun getData():Call<Data>
}
动态来写
interface ExampleService{
@GET("get_data.json")
fun getData(
@Header("User-Agent") user-Agent:String,
@Header("Cache-Control" cache-Control:String)
):Call<Data>
}
Retrofit构建器的最佳写法
val retrofit= Retrofit.Builder()//构建一个Retrofit对象
.baseUrl("http://10.0.2.2/")//设置根路径
.addConverterFactory(GsonConverterFactory.create())//指定解析数据时的转换库
.build()
val appService=retrofit.create(AppService::class.java)//创建AppService这个接口的动态代理对象
动态代理对象这样生成,但其实生成出来的Retrofit对象是通用的,所以没有必要每次要得到代理对象的时候都这样写一遍,可以把他封装成单例类
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)
//或者用内联函数来实化泛型
inline fun <reified T> create(): T = create(T::class.java)
}
leService{
@Header("User-Agent":"okHttp","Cache-control":"max-age=0")
@GET("get_data.json")
fun getData():Call<Data>
}
动态来写
interface ExampleService{
@GET("get_data.json")
fun getData(
@Header("User-Agent") user-Agent:String,
@Header("Cache-Control" cache-Control:String)
):Call<Data>
}
Retrofit构建器的最佳写法
val retrofit= Retrofit.Builder()//构建一个Retrofit对象
.baseUrl("http://10.0.2.2/")//设置根路径
.addConverterFactory(GsonConverterFactory.create())//指定解析数据时的转换库
.build()
val appService=retrofit.create(AppService::class.java)//创建AppService这个接口的动态代理对象
动态代理对象这样生成,但其实生成出来的Retrofit对象是通用的,所以没有必要每次要得到代理对象的时候都这样写一遍,可以把他封装成单例类
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)
//或者用内联函数来实化泛型
inline fun <reified T> create(): T = create(T::class.java)
}