复习笔记

1. 对变量延迟初始化

延迟初始化使用的是lateinit关键字,它可以告诉Kotlin编译器,我会在晚些时候对这个变量进行初始化,这样就不用在一开始的时候将它赋值为null。

当你对一个全局变量使用了lateinit关键字时,请一定要确保它在被任何地方调用之前已经完成了初始化工作,否则Kotlin将无法确保程序的安全性。

    private lateinit var adapter: TestAdapter

  另外,我们还可以通过代码来判断一个全局变量是否已经完成了初始化,这样在某些时候能够有效地避免重复对某一个变量进行初始化操作。

    private lateinit var adapter: TestAdapter
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_recycler)
     ...
        if (!::adapter.isInitialized){
            adapter = TestAdapter(data)
        }
        ...
    }

  具体的语法就是这样,::adapter.isInitialized可用于判断adapt变量是否已经初始化。虽然语法看上去有点奇怪,但这是固定的写法。

 2. by lazy

  • 延迟属性(lazy properties) 是 Kotlin 标准库中的标准委托之一,可以通过 by lazy 来实现
  • 其中,lazy() 是一个函数,可以接受一个 Lambda 表达式作为参数,第一次调用时会执行 Lambda 表达式,后续调用会直接返回之前的结果
val str: String by lazy{
    println("test")
    "test2"  // 最后一行为返回值
}

fun main(args: Array<String>) {
    println(str)
    println("-----------")
    println(str)
}

// 执行结果
test
test2
-----------
test2

把想要延迟执行的代码放到by lazy代码块中,这样代码块中的代码在一开始的时候就不会执行,只有当uriMatcher变量首次被调用的时候,代码块中的代码才会执行。

代码块中的代码只会执行一次。

3. 运行时权限

Android将常用的权限大致归成了两类,一类是普通权限,一类是危险权限。

这样我们就将拨打电话的功能成功实现了,并且在低于Android6.0系统的手机上都是可以正常运行的。但是如果我们在Android6.0或者更高版本系统的手机上运行,点击“Make Call”按钮就没有任何效果了,会报错。因为Android6.0及以上系统在使用危险权限时必须进行运行时权限处理。

 4. Service

它非常适合执行那些不需要和用户交互而且还要求长期运行的任务。Service的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,Service仍然能够保持正常运行。

不过需要注意的是,Service并不是运行在一个独立的进程当中的,而是依赖于创建Service时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行。

另外,也不要被Service的后台概念所迷惑,实际上Service并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,需要再Service的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况。

5. Android多线程编程之线程的基本用法

①定义一个线程的方式,一是新建一个类继承自Thread,然后重写父类的run()方法。

②当然,使用继承的方式耦合性有点高,更多地会选择使用实现Runnable接口的方式来定义一个线程。

③也可以使用Lambda的方式,这种写法更为常见。

            Thread{
            }.start()

以上几种线程的使用方式不陌生,因为在Java中创建和启动线程也是使用同样的方式。

④而Kotlin还提供了一种更加简单的开启线程的方式,连start()方法都不用调用,thread函数在内部帮我们全部都处理好了:

thread{ // 小写
// 编写具体的逻辑
}

6. Android多线程编程与Java多线程编程不同的地方之在子线程中更新UI

这样就掌握了Android异步消息处理的基本用法(handler+message),使用这种机制就可以出色地解决在子线程中更新UI的问题。

每个线程中只会有一个MessageQueue对象。

每个线程中只会有一个Looper对象。

7. Service的基本用法

在Activity中指挥Service去干什么,Service就去干什么。--借助onBind()方法。

任何一个Service在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以和任何一个其他的Activity进行绑定,而且在绑定完成后,它们都可以获取相同的DownloadBinder实例。

虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个Service只会存在一个实例。所以不管你调用了多少次startServcie()方法,只需调用一次stopService()和stopSelf()方法,Service就会停止。

 

11. 回调机制

P451. OKHttp在enqueue()方法的内部已经帮我们开好子线程了,然后会在子线程中执行http请求,并将最终的请求结果回调到okhttp3.Callback当中。

P445. 服务器响应的数据会回调到enqueue()方法中传入的Callback实现里面。

 

11.4 使用GSON解析JSON数组数据

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

需要借助TypeToken将期望解析成的数据类型传入fromJson()方法中,如下所示:

val typeOf = object : TypeToken<List<Person>>() {}.type

val people = gson.fromJson<List<Person>>(jsonData, typeOf)

 

11.6 使用Retrofit处理复杂接口地址类型

1. @Path注解和@query注解

2. ResponseBody是啥

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

在返回值声明的时候,我们将Call的泛型指定成了ResponseBody,这是什么意思呢?

由于POST、PUT、PATCH、DELETE这几种请求类型与GET请求不同,它们更多是用于操作服务器上的数据,而不是获取服务器上的数据,所以通常它们对于服务器响应的数据并不关心。这个时候就可以使用ResponseBody,表示Retrofit能够接受任意类型的响应数据,并且不会对响应数据进行解析。

3. @Body注解  P458

向服务器提交数据,使用POST请求,需要将数据放到HTTP请求的body部分,这个功能在Retrofit中可以借助@Body注解来完成:

interface ExampleService {
    @POST("data/create")
    fun createData(@Body data: Data): Call<ResponseBody>
}

当Retrofit发出POST请求时,就会自动将Data对象中的数据转换成JSON格式的文本,并放到HTTP请求的body部分,服务器在收到请求之后只需要从body中将这部分数据解析出来即可。

4. @Headers注解和@Header注解

有些服务器接口还可能会要求我们在HTTP请求的header中指定参数。这些header参数其实就是一个个的键值对,我们可以在Retrofit中直接使用@Headers注解来对它们进行声明。

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

但是这种写法只能进行静态header声明,如果想要动态指定header的值,则需要使用@Header注解:

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

 5. Service接口的动态代理对象是什么 P459

不熟悉动态代理也没关系,只需要知道有了动态代理对象之后,就可以随意调用接口中定义的所有方法,而Retrofit会自动执行具体的处理就可以了。

 

6. 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)
// 或者更加简洁,不带参数的create()方法
// inline fun <reified T> create(): T = create(T::class.java) }

 

posted @ 2024-05-11 17:30  touchmore  阅读(4)  评论(0编辑  收藏  举报