【Android入门】9、用 Retrofit 和 OkHttp 请求网络、JetPack 组件:ViewModel、LifeCycle、LiveData、Room、WorkManager

十一、网络

11.1 WebView

通过WebView可在自己的APP内嵌一个浏览器, 而不用跳转到系统浏览器, 也不需要自己造浏览器的轮子
新建一个WebViewTest项目, 布局如下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
    <uses-permission android:name="android.permission.INTERNET" />
class MainActivity : AppCompatActivity() {
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        webView.settings.javaScriptEnabled=true
        // 当需要从一个网页跳转到另一个网页时,我们希望目标网页仍然在当前WebView中显示,而不是打开系统浏览器
        webView.webViewClient = WebViewClient()
        webView.loadUrl("https://www.baidu.com")
    }
}

11.2 用 HTTP 访问网络

11.2.1 HttpURLConnection

新建一个NetWorkTest, 布局如下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="match_parent"
 android:layout_height="match_parent" >
	<Button
	android:id="@+id/sendRequestBtn"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:text="Send Request" />
	<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" />
	</ScrollView>
</LinearLayout>
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) {
    
    
        // 因为Android是不允许在子线程中进行UI操作的, 所以需用此函数指定线程
        runOnUiThread {
    
    
            // 在这里进行UI操作,将结果显示到界面上
            responseText.text = response
        }
    }
}

11.2.2 OkHttp 库

第三方的库, 比原生更好用, 是事实标准

dependencies {
	...
	implementation 'com.squareup.okhttp3:okhttp:4.1.0'
}
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("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) {
    
    
        // 因为Android是不允许在子线程中进行UI操作的, 所以需用此函数指定线程
        runOnUiThread {
    
    
            // 在这里进行UI操作,将结果显示到界面上
            responseText.text = response
        }
    }
}

11.3 网络请求的回调实现方式

object HttpUtil {
    
    
    fun sendHttpRequest(address: String, listener: HttpCallbackListener) {
    
    
        thread {
    
    
            var connection: HttpURLConnection? = null
            try {
    
    
                val response = StringBuilder()
                val url = URL(address)
                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()
            }
        }
    }
}

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

这样调用

object HttpUtil {
    
    
    ...
    fun sendOkHttpRequest(address: String, callback: okhttp3.Callback) {
    
    
        val client = OkHttpClient()
        val request = Request.Builder()
            .url(address) 
            .build()
        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) {
    
    
        // 在这里对异常情况进行处理
    }
})

11.4 Retrofit 网络库

  • 可以配置好一个根路径,然后在指定服务器接口地址时只需要使用相对路径即可,这样就不用每次都指定完整的URL地址了
  • 允许我们对服务器接口进行归类,将功能同属一类的服务器接口定义到同一个接口文件当中,从而让代码结构变得更加合理
  • 不用关心网络通信的细节,只需要在接口文件中声明一系列方法和返回值,然后通过注解的方式指定该方法对应哪个服务器接口,以及需要提供哪些参数。当我们在程序中调用该方法时,Retrofit会自动向对应的服务器接口发起请求,并将响应的数据解析成返回值声明的类型。这就使得我们可以用更加面向对象的思维来进行网络操作
dependencies {
	implementation 'com.squareup.retrofit2:retrofit:2.6.1'
	implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
}

布局如下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <Button
        android:id="@+id/getAppDataBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Get App Data" />
</LinearLayout>

调用方式如下

class App(val id: String, val name: String, val version: String)
interface AppService {
    
    
    // 当调用getAppData()方法时Retrofit会发起一条GET请求,请求的地址就是我们在@GET注解中传入的具体参数
    // 返回值必须声明成Retrofit中内置的Call类型,并通过泛型来指定服务器响应的数据应该转换成什么对象
    @GET("get_data.json")
    fun getAppData(): Call<List<App>>
}

class MainActivity : AppCompatActivity() {
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        getAppDataBtn.setOnClickListener {
    
    
            val retrofit = Retrofit.Builder()
                .baseUrl("http://10.0.2.2/")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
            val appService = retrofit.create(AppService::class.java)
            // 调用getAppData()会返回会返回Call<List<App>>对象
            // 再调用enqueue(), retrofit会根据注解中配置的服务器接口地址, 去请求网络
            appService.getAppData().enqueue(object : Callback<List<App>> {
    
    
                override fun onResponse(call: Call<List<App>>, response: Response<List<App>>) {
    
    
                    val list = response.body() // 得到retrofit解析后的对象, 即List<App>
                    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()
                }
            })
        }
    }
}

AndroidManifest.xml如下

    <uses-permission android:name="android.permission.INTERNET" />

11.4.1 复杂接口参数

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

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

// 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>
}

// 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>
}

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

通常用单例类实现, 只需要在调用create()方法时针对不同的Service接口传入相应的Class类型即可

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)
}

使用时, 这样调用

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

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

十二 Jetpack 组件

12.1 ViewModel

是Jetpack最重要的组件, 为了解决传统的Activity需要处理逻辑/UI展示/网络回调的太耦合的问题, 其专门存放UI相关的数据
当手机横竖屏旋转时, Activity会被重新创建而使UI数据丢失, 而ViewModel可保证跟随Activity生命周期同步, 避免因横竖屏导致UI数据的丢失
在这里插入图片描述

dependencies {
	implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
}

每一个Activity和Fragment都创建一个对应的ViewModel
我们为MainActivity创建一个对应的MainViewModel类,并让它继承自ViewModel

class MainViewModel : ViewModel() {
    
    
}
<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical">
	<TextView
	android:id="@+id/infoText"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_gravity="center_horizontal"
	android:textSize="32sp"/>
	<Button
	android:id="@+id/plusOneBtn"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:layout_gravity="center_horizontal"
	android:text="Plus One"/>
</LinearLayout>
class MainViewModel : ViewModel() {
    
    
    var counter = 0
}

class MainActivity : AppCompatActivity() {
    
    
    private lateinit var viewModel: MainViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel = ViewModelProvider(this)[MainViewModel::class.java]
        plusOneBtn.setOnClickListener {
    
    
            viewModel.counter++
            refreshCounter()
        }
        refreshCounter()
    }

    private fun refreshCounter() {
    
    
        infoText.text = viewModel.counter.toString()
    }
}

在这里插入图片描述

  • Factory向ViewModel传参数
class MainViewModel(countReserved: Int) : ViewModel() {
    
    
    var counter = countReserved
}

class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory {
    
    
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
    
    
        return MainViewModel(countReserved) as T
    }
}

class MainActivity : AppCompatActivity() {
    
    
    lateinit var viewModel: MainViewModel
    lateinit var sp: SharedPreferences
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sp = getPreferences(Context.MODE_PRIVATE)
        val countReserved = sp.getInt("count_reserved", 0)
        // 通过ViewModelProvider来获取ViewModel的实例, 可保证ViewModel有独立的声明周期且长于Activity
        viewModel = ViewModelProvider(this)[MainViewModel::class.java]
        plusOneBtn.setOnClickListener {
    
    
            viewModel.counter++
            refreshCounter()
        }
        refreshCounter()
    }

    private fun refreshCounter() {
    
    
        infoText.text = viewModel.counter.toString()
    }

    override fun onPause() {
    
    
        super.onPause()
        sp.edit {
    
    
            putInt("count_reserved", viewModel.counter)
        }
    }
}

12.2 Lifecycles

在编写Android应用程序的时候,可能会经常遇到需要感知Activity生命周期的情况。比如说,
某个界面中发起了一条网络请求,但是当请求得到响应的时候,界面或许已经关闭了,这个时
候就不应该继续对响应的结果进行处理。因此,我们需要能够时刻感知到Activity的生命周期,
以便在适当的时候进行相应的逻辑控制。

感知Activity的生命周期并不复杂,早在第3章的时候我们就学习过Activity完整的生命周期流
程。但问题在于,在一个Activity中去感知它的生命周期非常简单,而如果要在一个非Activity
的类中去感知Activity的生命周期,应该怎么办呢?

这种需求是广泛存在的,同时也衍生出了一系列的解决方案,比如通过在Activity中嵌入一个隐
藏的Fragment来进行感知,或者通过手写监听器的方式来进行感知,等等

class MyObserver {
    
    
    fun activityStart() {
    
    }
    fun activityStop() {
    
    }
}

// 这里我们为了让MyObserver能够感知到Activity的生命周期,需要专门在
// MainActivity中重写相应的生命周期方法,然后再通知给MyObserver。这种实现方式虽然是
// 可以正常工作的,但是不够优雅,需要在Activity中编写太多额外的逻辑
class MainActivity : AppCompatActivity() {
    
    
    lateinit var observer: MyObserver
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        observer = MyObserver()
    }
    override fun onStart() {
    
    
        super.onStart()
        observer.activityStart()
    }
    override fun onStop() {
    
    
        super.onStop()
        observer.activityStop()
    }
}

而Lifecycles组件就是为了解决这个问题而出现的,它可以让任何一个类都能轻松感知到
Activity的生命周期,同时又不需要在Activity中编写大量的逻辑处理。

下面我们就通过具体的例子来学习Lifecycles组件的用法。新建一个MyObserver类,并让
它实现LifecycleObserver接口,代码如下所示:

// 这里我们为了让MyObserver能够感知到Activity的生命周期,需要专门在
// MainActivity中重写相应的生命周期方法,然后再通知给MyObserver。这种实现方式虽然是
// 可以正常工作的,但是不够优雅,需要在Activity中编写太多额外的逻辑
class MainActivity : AppCompatActivity() {
    
    
    lateinit var observer: MyObserver
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 只要添加这样一行代码,MyObserver就能自动感知到Activity的生命周期了
        lifecycle.addObserver(MyObserver())
    }
}

class MyObserver(val lifecycle: Lifecycle) : LifecycleObserver {
    
    
    // 上使用了@OnLifecycleEvent注解,并传入了一种生命周期事件。
    // 生命周期事件的类型一共有7种:ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、ON_STOP和ON_DESTROY
    // 分别匹配Activity中相应的生命周期回调;另外还有一种ON_ANY类型,表示可以匹配Activity的任何生命周期回调。
    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun activityStart() {
    
    
        Log.d("MyObserver", "activityStart")
    }
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun activityStop() {
    
    
        Log.d("MyObserver", "activityStop")
    }
}

可在任何地方调用lifecycle.currentState来主动获知当前的生命周期状态, 其返回值为如下枚举有INITIALIZED、DESTROYED、CREATED、STARTED、RESUMED这5种

在这里插入图片描述

12.3 LiveData

响应式组件, 可包含任意类型的数据, 当数据变化时通知观察者, 其特别适合于ViewModel结合使用

class MainViewModel(countReserved: Int) : ViewModel() {
    
    
    // MutableLiveData是一种可变的LiveData,它有3种读写数据的方法,分别是getValue()、setValue()和postValue()方法
    val counter = MutableLiveData<Int>()
    init {
    
    
        counter.value = countReserved
    }

    fun plusOne() {
    
    
        val count = counter.value ?: 0
        counter.value = count + 1
    }

    fun clear() {
    
    
        counter.value = 0
    }
}
  • LiveData的map()方法,可将实际包含数据的LiveData和仅用于观察数据的LiveData进行转换。
data class User(var firstName: String, var lastName: String, var age: Int)
class MainViewModel(countReserved: Int) : ViewModel() {
    
    
    private val userLiveData = MutableLiveData<User>()
    // 第一个参数是原始的LiveData对象;第二个参数是一个转换函数
    // 当userLiveData的数据发生变化时,map()方法会监听到变化并执行转换函数中的逻辑,然后再将转换之后的数据通知给userName的观察者
    val userName: LiveData<String> = Transformations.map(userLiveData) {
    
     user ->
        "${
      
      user.firstName} ${
      
      user.lastName}"
    }
}
  • LiveData的switchMap()方法可以调其他接口
class MainViewModel(countReserved: Int) : ViewModel() {
    
    
	private val userIdLiveData = MutableLiveData<String>()
	val user: LiveData<User> = Transformations.switchMap(userIdLiveData) {
    
     userId ->
		Repository.getUser(userId)
	}
	fun getUser(userId: String) {
    
    
		userIdLiveData.value = userId
	}
}

12.3 Room

要由Entity、Dao和Database这3部分组成

  • Entity。用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并
    且表中的列是根据实体类中的字段自动生成的。
  • Dao。是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际
    编程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。
  • Database。用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提
    供Dao层的访问实例。
@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {
    
    
	@PrimaryKey(autoGenerate = true)
	var id: Long = 0
}

@Dao
interface UserDao {
    
    
    @Insert
    fun insertUser(user: User): Long
    @Update
    fun updateUser(newUser: User)
    @Query("select * from User")
    fun loadAllUsers(): List<User>
    @Query("select * from User where age > :age")
    fun loadUsersOlderThan(age: Int): List<User>
    @Delete
    fun deleteUser(user: User)
    @Query("delete from User where lastName = :lastName")
    fun deleteUserByLastName(lastName: String): Int
}

12.4 WorkManager

用于处理一些要求定时执行的任务,它可以根据操作系统的版本自动选择底层是使用AlarmManager实现还是JobScheduler实现
WorkManager和Service并不相同,也没有直接的联系。

  • Service是Android系统的四大组件之一,它在没有被销毁的情况下是一直保持在后台运行的。
  • WorkManager只是一个处理定时任务的工具,它可以保证即使在应用退出甚至手机重启的情况下,之前注册的任务仍然将会得到执行
    • 因此WorkManager很适合用于执行一些定期和服务器进行交互的任务,比如周期性地同步数据,等等
    • WorkManager注册的周期性任务不能保证一定会准时执行,这并不是bug,而是系统为了减少电量消耗,可能会将触发时间临近的几个任务放在一起执行,这样可以大幅度地减少CPU被唤醒的次数,从而有效延长电池的使用时间
dependencies {
	implementation "androidx.work:work-runtime:2.2.0"
}
class SimpleWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    
    
	// doWork()方法不会运行在主线程当中,因此你可以放心地在这里执行耗时逻辑,不过这里简单起见只是打印了一行日志。
	override fun doWork(): Result {
    
    
		Log.d("SimpleWorker", "do work in SimpleWorker")
		// 成功就返回Result.success(),失败就返回Result.failure()
		return Result.success()
	}
}
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()
val request = PeriodicWorkRequest.Builder(SimpleWorker::class.java, 15, TimeUnit.MINUTES).build()
WorkManager.getInstance(context).enqueue(request)

链式任务: 假设这里定义了3个独立的后台任务:同步数据、压缩数据和上传数据。现在我们想要实现先同
步、再压缩、最后上传的功能,就可以借助链式任务来实现,代码示例如下

val sync = ...
val compress = ...
val upload = ...
WorkManager.getInstance(this)
 .beginWith(sync)
 .then(compress)
 .then(upload)
 .enqueue()

猜你喜欢

转载自blog.csdn.net/jiaoyangwm/article/details/127042850