一起来学Kotlin:概念:4. Kotlin 函数注解:Suppress,Volatile, Synchronized, Bindable, RequiresApi,SerializedName

这篇博客我们解释 Kotlin 函数注解:Suppress,Volatile, Synchronized, Bindable, RequiresApi,SerializedName 等。

文章目录

  • 一起来学Kotlin:概念:4. Kotlin 函数注解:Suppress,Volatile, Synchronized, Bindable, RequiresApi,SerializedName
  • 1. Deprecated
  • 2. Suppress
  • 3. Volatile
  • 4. Synchronized
  • 5. Bindable
  • 6. RequiresApi
  • 7. SerializedName
  • Reference

1. Deprecated

如果需要废弃一个方法,只需要在方法钱加上 @Deprecated 即可。

Kotlin 对于 @Deprecated 的定义:

/**
 * Marks the annotated declaration as deprecated.
 *
 * A deprecated API element is not recommended to use, typically because it's being phased out or a better alternative exists.
 *
 * To help removing deprecated API gradually, the property [level] could be used.
 * Usually a gradual phase-out goes through the "warning", then "error", then "hidden" or "removed" stages:
 * - First and by default, [DeprecationLevel.WARNING] is used to notify API consumers, but not to break their compilation or runtime usages.
 * - Then, some time later the deprecation level is raised to [DeprecationLevel.ERROR], so that no new Kotlin code can be compiled
 *   using the deprecated API.
 * - Finally, the API is either removed entirely, or hidden ([DeprecationLevel.HIDDEN]) from code,
 * so its usages look like unresolved references, while the API remains in the compiled code
 * preserving binary compatibility with previously compiled code.
 *
 * @property message The message explaining the deprecation and recommending an alternative API to use.
 * @property replaceWith If present, specifies a code fragment which should be used as a replacement for
 *  the deprecated API usage.
 * @property level Specifies how the deprecated element usages are reported in code.
 *  See the [DeprecationLevel] enum for the possible values.
 */
@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS)
@MustBeDocumented
public annotation class Deprecated(
    val message: String,
    val replaceWith: ReplaceWith = ReplaceWith(""),
    val level: DeprecationLevel = DeprecationLevel.WARNING
)

所以说,Deprecated的输入有三个,一个是message,解释弃用并建议使用替代 API 的信息;第二个是 replaceWith,指定可用于替换已弃用的函数,属性或类的代码片段;而 level 则指定如何在代码中报告已弃用的元素用法(WARNING, ERROR,HIDDEN三个选项)。后两个参数是有预设值的,第一个参数没有。所以,如果需要使用Deprecated函数注解的话,例如下面代码:

@Deprecated("xxx")
fun testKt(){

}

replaceWith的一个例子:

androidstudio kotlin 线程的开始和终止_API

2. Suppress

如果需要消除一些编译时的警告,可以使用 @Suppress("xxx")

比如:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        @Suppress("UNUSED VARIABLE")
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main)

    }
}

又比如:

fun unChecked(){
    val list: List<Any> = emptyList()

    @Suppress("UNCHECKED_CAST")
    list as List<String>
}

3. Volatile

为了强制变量中的更改立即对其他线程可见,我们可以使用注解 @Volatile,在以下示例中为:

@Volatile var shutdownRequested = false

这将保证在值更改后其他线程的更改可见性。 这意味着如果线程 X 修改了 shutdownRequested 的值,线程 Y 将能够立即看到更改。

@Volatile 的官方解释是:

Marks the JVM backing field of the annotated property as volatile, meaning that writes to this field are immediately made visible to other threads.

4. Synchronized

总结:Java 有 synchronized 关键字,可以将其应用于方法以确保一次只有一个线程可以访问它们。进入同步方法的线程获得锁(被锁定的对象是包含类的实例),并且在释放锁之前没有其他线程可以进入该方法。Kotlin通过 @Synchronized 注解提供了相同的功能。

在多线程的世界中,我们需要跨线程访问共享对象,如果我们不同步我们的工作,就会发生不希望的情况。比如下面例子:

import kotlinx.coroutines.*
fun main() = runBlocking {
    var sharedCounter = 0
    val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) // We want our code to run on 4 threads
        scope.launch {
            val coroutines = 1.rangeTo(1000).map { //create 1000 coroutines (light-weight threads).
            launch {
                for(i in 1..1000){ // and in each of them, increment the sharedCounter 1000 times.
                    sharedCounter++
                }
            }
        }

        coroutines.forEach {
            corotuine->
                corotuine.join() // wait for all coroutines to finish their jobs.
        }
    }.join()
  
    println("The number of shared counter should be 1000000, but actually is $sharedCounter")
}

在上面的例子中,我们在 4 个线程上启动了 1000 个协程,并且每个协程将 sharedCounter 递增 1000 倍,所以 sharedCounter 的最终值应该是 1000000。但我们运行上述代码后会发现,并不会输出这个值。

在我们从技术上解释这里发生了什么之前,假设有一个思考室(柜台),很多人(线程)想要使用它,但一次只允许一个人。 这个房间有一扇门,当房间被占用时,它是关闭的。 在这种情况下发生的情况是,当一个人在房间内时,其他人也可以打开门,进来使用房间。 但是门需要锁!

在上面的代码中,为了增加 sharedCounter,每个线程都尝试执行以下操作以在内部增加 sharedCounter 值:

  • 获取其当前值,
  • 将其存储在临时变量中,并将临时变量加 1,
  • 将临时变量保存到 sharedCounter。

但是,如果一个线程获取当前值,并且由于我们处于多线程世界中,另一个线程跳入并尝试获取当前值怎么办?他们都将获得相同的价值!因此,它们中的每一个都将该值增加 1,并存储相同的值。

这个问题可能以类似的其他方式发生,例如,一个线程超过了第二个级别,但在存储它之前,其他线程递增并保存 sharedCounter 值,当第一个线程跳转到它的第三步时,它保存了一个旧的 sharedCounter 的版本。这就是为什么最终值不是我们所期望的。

如果我们使用 Synchronized,代码如下:

import kotlinx.coroutines.*
var sharedCounter = 0
@Synchronized fun updateCounter(){
    sharedCounter++
}
fun main() = runBlocking {
    val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) // We want our code to run on 4 threads
        scope.launch {
            val coroutines = 1.rangeTo(1000).map { //create 1000 coroutines (light-weight threads).
            launch {
                for(i in 1..1000){ // and in each of them, increment the sharedCounter 1000 times.
                    updateCounter() // call the newly created function that is now synchronized
                }
            }
        }

        coroutines.forEach {
            corotuine->
                corotuine.join() // wait for all coroutines to finish their jobs.
        }
    }.join()
  
    println("The number of shared counter is $sharedCounter")
}

如我们所见,输出值始终是正确的。 在这个场景中,Synchronized 类似于门上的锁,只有一把钥匙,人们需要用它来开门和锁门。 因此,当一个人(一个线程)进入房间使用时,除非该人离开房间并归还钥匙,否则其他人都无法进入。

其他例子:

@Database(entities = [Note::class], version = 1)
abstract class NoteDatabase : RoomDatabase() {

    abstract fun noteDao(): NoteDao

    companion object {
        private var instance: NoteDatabase? = null

        @Synchronized
        fun getInstance(ctx: Context): NoteDatabase {
            if(instance == null)
                instance = Room.databaseBuilder(ctx.applicationContext, NoteDatabase::class.java,
                    "note_database")
                    .fallbackToDestructiveMigration()
                    .addCallback(roomCallback)
                    .build()

            return instance!!

        }

        private val roomCallback = object : Callback() {
            override fun onCreate(db: SupportSQLiteDatabase) {
                super.onCreate(db)
                populateDatabase(instance!!)
            }
        }

        private fun populateDatabase(db: NoteDatabase) {
            val noteDao = db.noteDao()
            subscribeOnBackground {
                noteDao.insert(Note("title 1", "desc 1", 1))
                noteDao.insert(Note("title 2", "desc 2", 2))
                noteDao.insert(Note("title 3", "desc 3", 3))

            }
        }
    }
}

5. Bindable

数据绑定有两种与数据交互的基本方式:BaseObservable 类及其关联的 @Bindable 注释,以及 LiveData 可观察包装器(LiveData observable wrapper)。

如果您从 BaseObservable 继承您的 ViewModel,那么您注释为 @Bindable 的所有变量都将添加到 BR 全局引用对象中。 BR 是可绑定资源(Bindable Resource)。 与您的 R 引用对象类似,这只是引用特定视图的静态整数的集合。

比如,我们在build.gradle(:app)中,我们先设置dataBinding:

buildFeatures{
    dataBinding = true
}

fragment_login.xml中,我们设置了需要需要binding的ViewModel:

<data>
    <variable
        name="myLoginViewModel"
        type="com.example.mydatabaseapp.login.LoginViewModel" />
</data>

我们在LoginFragment.kt

loginViewModel = ViewModelProvider(this, factory).get(LoginViewModel::class.java)
binding.myLoginViewModel = loginViewModel

最后,我们在 LoginViewModel.kt 中使用 @bindable

@Bindable
val inputUsername = MutableLiveData<String>()

@Bindable
val inputPassword = MutableLiveData<String>()

6. RequiresApi

在我们写代码的时候,我们可能会碰到一个问题,需要调用一些之前版本的API,比如下面这个例子:

val dateTime = LocalDateTime.now()
val currentTime = dateTime.format(DateTimeFormatter.ofPattern("M/d/y H:m:ss"))

如果我们不加 @RequiresApi(Build.VERSION_CODES.O) 的话,会出现下面这类报错:

Call requires API level 26 (current min is 21): java.time.LocalDateTime#now

所以,我们需要调用API 26,Android 8 (Oreo),即,在函数的上一行加上 @RequiresApi(Build.VERSION_CODES.O) 即可。

Android API与Android版本对应关系

androidstudio kotlin 线程的开始和终止_Kotlin_02

7. SerializedName

我们通常使用@SerializedName批注来映射JSON字段。比如下面的代码:

data class Country(
    // 我们使用@SerializedName批注来映射JSON字段,
    // name是JSON字段的,我们将其映射为countryName,用于我们这个项目
    @SerializedName("name")
    val countryName: String?,

    @SerializedName("capital")
    val capital: String?,

    @SerializedName("flagPNG")
    val flag: String?
)

我们读取的JSON文件长这样:

[
  {
    "alpha2Code": "AF",
    "alpha3Code": "AFG",
    "altSpellings": [
      "AF",
      "Af\u0121\u0101nist\u0101n"
    ],
    "area": 652230,
    "borders": [
      "IRN",
      "PAK",
      "TKM",
      "UZB",
      "TJK",
      "CHN"
    ],
    "callingCodes": [
      "93"
    ],
    "capital": "Kabul",
    "currencies": [
      {
        "code": "AFN",
        "name": "Afghan afghani",
        "symbol": "\u060b"
      }
    ],
    "demonym": "Afghan",
    "flagPNG": "https://raw.githubusercontent.com/DevTides/countries/master/afg.png",
    "gini": 27.8,
    "languages": [
      {
        "iso639_1": "ps",
        "iso639_2": "pus",
        "name": "Pashto",
        "nativeName": "\u067e\u069a\u062a\u0648"
      },
      {
        "iso639_1": "uz",
        "iso639_2": "uzb",
        "name": "Uzbek",
        "nativeName": "O\u02bbzbek"
      },
      {
        "iso639_1": "tk",
        "iso639_2": "tuk",
        "name": "Turkmen",
        "nativeName": "T\u00fcrkmen"
      }
    ],
    ...
]

我们希望将JSON文件中的namecapitalflagPNG的值分别提取出来,然后放到我们自己的数据类 Country 里,所以我们使用@SerializedName来将name映射到countryNamecapital映射到capitalflagPNG映射到flag

另外一个例子,比如我们这个json数据长这样:

//json1
{
    "class":"1"
    "public":"2"
}

//json2
{
    "a":"1"
    "b":"2"
}

如 json1 中数据我们如果建立一个相同字段名的实体类显然是不可行的,因为class和public都是java/kotlin语言中的关键字,@SerializedName注解就起到作用了。

public class Json1{

    @SerializedName("class")
    private String cla;
    @SerializedName("public") 
    private String pub;

}

Reference