亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Kotlin泛型的型變之路演變示例詳解

 更新時(shí)間:2022年12月21日 15:11:35   作者:xuyisheng  
這篇文章主要為大家介紹了Kotlin泛型的型變之路演變示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

之前就寫過一篇泛型的文章,但是總覺得寫得不夠系統(tǒng),所以最近對(duì)泛型又作了些研究,算是對(duì)這篇文章的補(bǔ)充了。

kotlin之泛型

泛型,是為了讓「類」、「接口」、「方法」具有更加通用的使用范圍而誕生的,舉個(gè)例子,假如我們不使用泛型,那么一個(gè)List中可以裝得下任何對(duì)象,這么做的問題就在于,在使用時(shí),需要對(duì)類型進(jìn)行檢查,不然就會(huì)轉(zhuǎn)換異常。 所以,我們需要將這種檢查前置到編譯期,這樣在編寫代碼時(shí),就可以安全的使用不同類型,例如List,我們一看就知道是一個(gè)String類型的list,不能放其他類型的元素。 在Java中,由于歷史原因,它并不存在真泛型,Java所有的泛型都是偽泛型,因?yàn)镴ava在編譯期,會(huì)執(zhí)行「泛型擦除」,從而導(dǎo)致在Java字節(jié)碼中,不存在類型信息(但是類型會(huì)被保存在其它地方,這個(gè)后面講)。

正是由于泛型擦除的問題,你甚至可以通過反射繞開泛型的限制,傳遞一個(gè)非當(dāng)前泛型限制的對(duì)象。

泛型類型在Java中,通常以一個(gè)大寫字母來進(jìn)行標(biāo)識(shí),我們并不是一定要寫「T」來表示泛型,但這是一個(gè)約定成俗的表示,類似的約束還有下面這些。

  • 通用泛型類型:T,S,U,V
  • 集合元素泛型類型:E
  • 映射鍵-值泛型類型:K,V
  • 數(shù)值泛型類型:N

要理解Kotlin的泛型,我們最好首先從Java的泛型來學(xué)習(xí),畢竟Kotlin的語(yǔ)法糖太多了,Java會(huì)更加白話文一點(diǎn)。 首先,Java中的泛型具有「不變性」,也就是說,編譯器會(huì)認(rèn)為L(zhǎng)ist和List是兩個(gè)完全不同的類型,當(dāng)然,不僅僅是List,比如下面這個(gè)例子。

open class A
class B : A()

那么Test<A>和Test<B>是不是一個(gè)類型呢?必須不是,雖然A和B是父子關(guān)系,但Test<A>和Test<B>就不是了,為什么呢?我們站在編譯器的角度來想想,假如它們是同一個(gè)類型,那么在Test類中g(shù)et出來的實(shí)例,到底是A還是B呢?所以編譯器為了避免這種不確定性,就否定了Test<A>和Test<B>是一種類型的推斷。 但是這種處理在我們處理泛型業(yè)務(wù)時(shí),會(huì)有很多限制,所以,泛型提供了「型變」來拓展泛型的使用。

協(xié)變

協(xié)變指的是,當(dāng)參數(shù)具有父子關(guān)系時(shí),子類可以作為參數(shù)傳遞,而泛型的上界就是其父類。協(xié)變通過上界通配符<? extends 父類型>來實(shí)現(xiàn)。 實(shí)例化時(shí)可確定為「父類型的未知類型」,所以它「只能讀不能寫」,因?yàn)榫幾g器不確定到底是哪個(gè)子類。 例如下面的代碼。

List<Button> buttons = new ArrayList<Button>();
List<? extends TextView> textViews = buttons;

由于Button是TextView的子類,所以上面的代碼可以正確運(yùn)行。我們來解釋下上面的代碼。

  • 「?」通配符表示這是一個(gè)未知類型
  • 「extends」上界通配符表示這個(gè)類型只能是其子類或者本身
  • 這里不僅可以是類,也可以適用于接口

上界通配符還有一個(gè)特例,那就是「?」,例如List<?>,實(shí)際上就是List<? extends Object>的縮寫。 在Kotlin中,使用的是「*」,即List<*>,實(shí)際上就是List<out Any>

簡(jiǎn)而言之,協(xié)變就是——如果A是B的子類,那么Generic<A>就是Generic<? extends B>的子類型。

協(xié)變的限制

我們來看下面的代碼。

List<? extends TextView> textViews = new ArrayList<TextView>();
TextView textView = textViews.get(0);
// Error
textViews.add(textView);

我們來解釋下上面的代碼,首先,我們定義了一個(gè)具有泛型上界的list,然后,我們從list中讀取一個(gè)元素,這時(shí)候,這個(gè)元素的返回類型是什么呢編譯器并不知道,但由于泛型上限的存在,所以它一定是TextView及其子類,所以定義為TextView類型,也完全沒有問題。 接下來我們來實(shí)現(xiàn)寫入,這時(shí)候,就報(bào)錯(cuò)了。

看上去好像沒錯(cuò)啊,add進(jìn)去的元素是TextView類型,符合泛型上界的定義啊,但是,這個(gè)List的類型定義是<?extends TextView>,編譯器并不知道具體是什么類型,所以它就認(rèn)為,最好的辦法就是什么都不讓加,多做就是錯(cuò),那不如不做。 所以,經(jīng)過協(xié)變之后的泛型,就失去了寫入的能力,它只能用于向外提供數(shù)據(jù),也就是「數(shù)據(jù)生產(chǎn)者Producer」。

逆變

逆變指的是,父類可以作為參數(shù)傳遞,但子類必須是其下界。逆變通過下界通配符<? super 子類型>來實(shí)現(xiàn)。 實(shí)例化時(shí)可確定為「子類型的未知類型」,所以「只能寫不能讀」。

不能讀指的是不能讀取為指定的類型,而不是不能調(diào)用讀的方法。

例如下面的代碼。

List<? super Button> buttons = new ArrayList<TextView>();

同樣我們來分析下上面的代碼。

  • 「?」通配符表示這是一個(gè)未知類型
  • 「super」下界通配符表示后面的這個(gè)類型,只能是它子類或者本身
  • 這里不僅可以是類,也可以適用于接口

其實(shí)這整個(gè)就是協(xié)變的反向操作。一個(gè)是約束上界,另一個(gè)是約束下界,所以對(duì)比著,其實(shí)很好理解。 簡(jiǎn)而言之,逆變就是——如果A是B的子類,那么Generic<B>就是Generic<? super A>的子類型。

逆變的限制

類似的,我們?cè)賮砜聪履孀兊南拗啤?/p>

List<? super Button> buttons = new ArrayList<TextView>();
Button button = new Button(context);
buttons.add(button);
Object object = buttons.get(0);

上面的代碼,創(chuàng)建了一個(gè)list,它的元素類型的下界是Button,也就是說,這個(gè)list里面都是放的Button的父類類型。 所以,當(dāng)我們創(chuàng)建一個(gè)Button,并寫入的時(shí)候,是完全可以的,因?yàn)樗衔覀兌x下界的約束。 再來看看讀取呢?當(dāng)我們從list中讀取一個(gè)元素時(shí),由于編譯器只知道它是Button的父類,但是具體是什么類型,它也不知道,所以,編譯器不如將它作為Object這個(gè)萬(wàn)物基類了。 所以說,逆變之后的泛型,失去了讀的能力(因?yàn)樽x出來都是Object),所以逆變泛型通常都作為「數(shù)據(jù)消費(fèi)者Consumer」。

Kotlin型變

泛型讓我們有了可以支持多種類型的能力,型變讓我們有了修改泛型的能力,總結(jié)來說:

  • 泛型通配符<? extends x>可以使泛型支持協(xié)變,但是「只能讀不能寫」,這里的寫,指的是對(duì)泛型集合添加元素,如果是remove(int index)或者是clear這種刪除,則不受影響。
  • 泛型通配符<? super x>可以使泛型支持逆變,但是「只能寫不能讀」,這里的讀,指的是不能按照泛型類型讀,但如果按照Object讀出來再?gòu)?qiáng)轉(zhuǎn)具體類型,則是可以的。

在學(xué)習(xí)了Java泛型之后,我們?cè)賮砜聪翶otlin的泛型,這時(shí)候你再看,就沒那么復(fù)雜了,核心就兩條。

  • 使用關(guān)鍵字 out 來支持協(xié)變,等同于 Java 中的上界通配符 ? extends
  • 使用關(guān)鍵字 in 來支持逆變,等同于 Java 中的下界通配符 ? super

其實(shí)在理解了逆變和協(xié)變之后,你會(huì)發(fā)現(xiàn)out和in這兩個(gè)關(guān)鍵字真的是「言簡(jiǎn)意賅」,out表示輸出,即協(xié)變只用于輸出數(shù)據(jù),in表示輸入,即逆變只用于寫入數(shù)據(jù)。Kotlin官網(wǎng)上有個(gè)著名的——Consumer in, Producer out,說的就是這個(gè)意思。

Kotlin泛型的優(yōu)化

我們通過這個(gè)例子來看下Kotlin對(duì)Java泛型的改進(jìn)。

申明處型變

我們通過下面這個(gè)例子來看下Kotlin申明處型變的好處,這是一個(gè)生產(chǎn)者與消費(fèi)者的例子,代碼如下。

// 生產(chǎn)者
class Producer<T> {
    fun produce(): T {}
}
val producer: Producer<out TextView> = Producer<Button>()
val textView: TextView = producer.produce()

首先我們來看生產(chǎn)者,對(duì)于T類型的Producer,我們要?jiǎng)?chuàng)建它的子類時(shí),就需要使用協(xié)變,即Producer,否則它就只能生產(chǎn)Button類型的數(shù)據(jù)。所以,在Java中,每次獲取數(shù)據(jù)的時(shí)候,都要聲明一次協(xié)變,所以Kotlin對(duì)其進(jìn)行了優(yōu)化,可以在申明處進(jìn)行協(xié)變,代碼如下。

// 生產(chǎn)者
class Producer<out T> {
    fun produce(): T {}
}
val producer1: Producer<TextView> = Producer<Button>()
val producer2: Producer<out TextView> = Producer<Button>()

Kotlin約定,當(dāng)泛型參數(shù)T只會(huì)用來輸出時(shí),可以在申明類的時(shí)候,直接使用協(xié)變約束,這樣在調(diào)用的時(shí)候,就不用額外使用協(xié)變了,當(dāng)然寫了也不會(huì)錯(cuò)。 與此類似的,消費(fèi)者也是如此。

// 消費(fèi)者
class Consumer<T> {
    fun consume(t: T) {}
}
val consumer: Consumer<in Button> = Consumer<TextView>()
consumer.consume(Button(context))

我們?cè)谑褂玫臅r(shí)候,也是必須使用逆變,借助Kotlin,同樣可以在申明處進(jìn)行逆變。

// 消費(fèi)者
class Consumer<in T> {
    fun consume(t: T) {}
}
val consumer1: Consumer<Button> = Consumer<TextView>()
val consumer2: Consumer<in Button> = Consumer<TextView>()

這樣在調(diào)用的時(shí)候,就不用額外使用逆變了,當(dāng)然寫了也不會(huì)錯(cuò)。

reified

由于在Java會(huì)進(jìn)行泛型擦除,所以編譯器無法在運(yùn)行時(shí)知道一個(gè)確切的泛型類型,也就是說,我們無法在運(yùn)行時(shí),判斷一個(gè)對(duì)象是否為一個(gè)泛型T的實(shí)例,例如下面的代碼。

if (item instanceof T) {
    System.out.println(item);
}

同樣的,在Kotlin里面也是不行的,畢竟一母同胞。

if (item is T) {
    println(item)
}

為了解決這個(gè)問題,在Java或者Kotlin中,我們通常會(huì)多傳入一個(gè)Class類型的參數(shù),然后通過Class.isInstance來判斷類型是否匹配。 但是由于Kotlin支持了內(nèi)聯(lián)函數(shù),所以它提供了一個(gè)更加方便的方式來處理這種場(chǎng)景,那就是「reified」配合「inline」來實(shí)現(xiàn)。

inline fun &lt;reified T&gt; checkType(item: Any) {
    if (item is T) {
        println(item)
    }
}

不是說好了不能直接對(duì)泛型來做類型判斷嗎,為什么這里卻可以呢?這其實(shí)就是內(nèi)聯(lián)的作用,雖然這里是對(duì)T做判斷,但實(shí)際上在編譯時(shí),這里已經(jīng)被替換成了具體的類型,而不再是泛型T了,所以當(dāng)然可以使用is來進(jìn)行類型判斷了。

支持協(xié)變的List

在Kotlin中,有兩種List,一種是可變的,一種是不可變的,即MutableList和List,其中List的申明如下,它已經(jīng)實(shí)現(xiàn)的協(xié)變,所以Kotlin中的List只能讀而不能寫。

public interface List&lt;out E&gt; : Collection&lt;E&gt;

獲取泛型的具體類型

reified

通過reified和inline配合,我們可以在運(yùn)行時(shí)獲取泛型的具體類型,這是Kotlin的特性,具體的使用方式,上面的文章已經(jīng)講了一個(gè)例子。下面我們?cè)倏纯磶讉€(gè)比較典型的例子。

fun reifiedClass() {
    // normal
    val serviceImpl1 = ServiceLoader.load(Service::class.java)
    // reified
    val serviceImpl2 = loadService&lt;Service&gt;()
}
inline fun &lt;reified T&gt; loadService() {
    ServiceLoader.load(T::class.java)
}
interface Service {
    fun work()
}

再看一個(gè)簡(jiǎn)化startActivity的方式。

inline fun &lt;reified T : Activity&gt; Activity.startActivity(bundle: Bundle? = null) {
    val intent = Intent(this, T::class.java)
    bundle?.let {
        intent.putExtras(it)
    }
    startActivity(intent)
}
startActivity&lt;SampleActivity&gt;()

傳入指定Class

通過傳入具體的Class類型,我們也可以在運(yùn)行時(shí)獲取泛型類型,這個(gè)方法是Java和Kotlin都支持的,這個(gè)在前面的文章中也提到了。

匿名內(nèi)部類

匿名內(nèi)部類會(huì)在運(yùn)行時(shí)實(shí)例化,這個(gè)時(shí)候,就可以拿到泛型的具體類型了,示例代碼如下。

open class Test<T>
fun main() {
    val innerClass = object : Test<String>() {}
    val genericType: Type? = innerClass.javaClass.genericSuperclass
    if (genericType is ParameterizedType) {
        val type = genericType.actualTypeArguments[0]
        // class java.lang.String
    }
}

Class類提供了一個(gè)方法getGenericSuperclass ,通過它可以獲取到帶泛型信息的父類Type(Java的Class文件會(huì)保留繼承的父類或者接口的泛型信息)。 通過對(duì)獲取的genericType來判斷是否實(shí)現(xiàn)ParameterizedType接口,是說明支持泛型,從而獲取出對(duì)應(yīng)的泛型列表(因?yàn)榉盒涂赡苡卸鄠€(gè))。 這個(gè)方式是一個(gè)很巧妙的獲取泛型類型的方法,在Gson中,就是通過它來獲取類型的。

val content = Gson().toJson("xxx", object : TypeToken<String>() {}.type)

在使用Gson時(shí),我們需要?jiǎng)?chuàng)建一個(gè)繼承自TypeToken的匿名內(nèi)部類, 并實(shí)例化泛型參數(shù)TypeToken,這樣我們就可以通過getGenericSuperclass來獲取父類的Type,也就是上面例子中的TypeToken了。

反射

反射自然是可以拿到運(yùn)行時(shí)的具體類型了,代碼如下。

open class Test<T>
class NewTest : Test<String>() {
    private val genericType: Type? = javaClass.genericSuperclass
    fun test() {
        if (genericType is ParameterizedType) {
            val type = genericType.actualTypeArguments[0]
            // class java.lang.String
        }
    }
}

通過反射來獲取實(shí)際類型,是很大開源庫(kù)中都在使用的方式,例如Retrofit,它在內(nèi)部就是通過method.genericReturnType來獲取泛型的返回類型,通過method.genericParameterTypes來獲取泛型的參數(shù)類型。

不過這里大家要好奇了,在文章的一開始,我們就說了,Java的偽泛型,會(huì)在編譯時(shí)進(jìn)行泛型擦除,那么反射又是怎么拿到這些泛型信息的呢? 其實(shí),編譯器還是留了一手,申明處的泛型信息,實(shí)際上會(huì)以Signature的形式,保存到Class文件的Constant pool中,這樣通過反射,就可以拿到具體的泛型類型了。

要注意的是,這里能保留的是申明處的泛型,如果是調(diào)用處的泛型,例如方法的傳參,這種就不會(huì)被保存了。

PESC

PESC是泛型型變中的一個(gè)指導(dǎo)性原則,意為「Producer Extend Consumer Super」,當(dāng)然在Kotlin中,這句話要改為「Consumer in, Producer out」。 這個(gè)原則是從集合的角度出發(fā)的,其目的是為了實(shí)現(xiàn)集合的多態(tài)。

  • 如果只是從集合中讀取數(shù)據(jù),那么它就是個(gè)生產(chǎn)者,可以使用extend
  • 如果只是往集合中增加數(shù)據(jù),那么它就是個(gè)消費(fèi)者,可以使用super
  • 如果往集合中既存又取,那么你不應(yīng)該用extend或者super

還是舉一個(gè)例子來說明,我們可以認(rèn)為Kotlin是Java的子類,但是List和List卻是兩個(gè)無關(guān)的類,它們之間沒有繼承關(guān)系,而使用List<? extends Java>后,相當(dāng)于List和List之間也有了繼承關(guān)系,從而可以讀取List中不同類型的數(shù)據(jù),List就是通過這種方式來實(shí)現(xiàn)了集合的多態(tài)。

協(xié)變和逆變的使用場(chǎng)景

我們來看這樣一段代碼,我們創(chuàng)建了一個(gè)copyAll的方法,傳入to和from兩個(gè)列表,代碼如下。

fun <T> copyAll(to: MutableList<T>, from: MutableList<T>) {
    to.addAll(from)
}
fun main() {
    val numberList = mutableListOf<Number>() // to
    val intList = mutableListOf(1, 2, 3, 4) // from
    copyAll(numberList, intList)// Error
}

但是這段代碼是不能編譯通過的,原因在于to是一個(gè)List,而from是一個(gè)List,所以類型轉(zhuǎn)換異常,不能編譯。 但實(shí)際上,我們知道Int是可以轉(zhuǎn)換為Number的,但是編譯器不知道,所以它只能報(bào)錯(cuò),編譯器需要的,就是我們告訴它,這樣做是安全的,得到了我們的保證,編譯器才能執(zhí)行編譯。

這個(gè)保證是從兩方面來說的,首先我們來看from。 from是一個(gè)List,完全可以當(dāng)做List,所以,要保證「from取出來的元素可以轉(zhuǎn)為Number類型,而且from不能再有其它寫入」,否則你向一個(gè)List中插入了一條Number類型的元素,那就不亂套了。 所以,我們可以對(duì)from做協(xié)變,讓它只讀不寫,代碼如下。

fun <T> copyAll(to: MutableList<T>, from: MutableList<out T>) {
    to.addAll(from)
}

這樣就表示from,只接受T或者T的子類型,也就是說,from只能是Number或者Number的子類型,而此時(shí)from是Int類型,所以編譯通過了。 上面是從from的角度做的保證,那么從to方面呢? 對(duì)于to來說,我們需要保證「to只能寫入,而不能讀取」。

fun <T> copyAll(to: MutableList<in T>, from: MutableList<T>) {
    to.addAll(from)
}

這樣就表示to,只接受T或者T的父類型,也就是說,to只能是Int或者Int的父類型,而此時(shí)to是Number類型,所以編譯通過了。

另外,我們將from的簽名改為L(zhǎng)ist,也是可以編譯的,其原因就是Kotlin中的List已經(jīng)支持協(xié)變了。

相信大家通過這個(gè)例子,大概能理解協(xié)變和逆變的使用方式了。 那么我們?cè)趯?shí)際的代碼中,要在哪些場(chǎng)景使用協(xié)變和逆變呢? 通常來說,泛型參數(shù)協(xié)變后則表示——「這個(gè)參數(shù)在當(dāng)前類中,只能作為函數(shù)的返回值,或者是只讀屬性」。

abstract class TestOut<out T> {
    abstract val num: T// 只讀屬性
    abstract fun getItem(): T// 函數(shù)的返回值
    abstract var num1 : T// Error 用于可變屬性
    abstract fun addItem(t: T)// Error 用于函數(shù)的參數(shù)
}

而逆變,表示這個(gè)參數(shù)「只能作為函數(shù)的參數(shù),或者修飾可變屬性」。

abstract class TestIn<in T> {
    abstract val num: T//Error 只讀屬性
    abstract fun getItem(): T//Error 函數(shù)的返回值
    abstract fun addItem(t: T)// 用于函數(shù)的參數(shù)
}

以上就是Kotlin泛型的型變之路演變示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Kotlin 泛型型變的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • android設(shè)置adb自帶screenrecord錄屏命令

    android設(shè)置adb自帶screenrecord錄屏命令

    這篇文章主要介紹了android設(shè)置adb自帶screenrecord錄屏命令,需要的朋友可以參考下
    2018-11-11
  • Android 動(dòng)態(tài)添加Fragment的實(shí)例代碼

    Android 動(dòng)態(tài)添加Fragment的實(shí)例代碼

    這篇文章主要介紹了Android 動(dòng)態(tài)添加Fragment的實(shí)例代碼的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-08-08
  • Android基礎(chǔ)之startActivityForResult()的用法詳解

    Android基礎(chǔ)之startActivityForResult()的用法詳解

    這篇文章主要給大家介紹了Android中startActivityForResult()的用法,文中給出了詳細(xì)的介紹與示例代碼,相信對(duì)大家的理解和學(xué)習(xí)具有一定參考借鑒價(jià)值,有需要的朋友們下面來一起看看吧。
    2017-01-01
  • 詳解Android之圖片加載框架Fresco基本使用(二)

    詳解Android之圖片加載框架Fresco基本使用(二)

    本篇文章主要介紹了Android之圖片加載框架Fresco基本使用,可以實(shí)現(xiàn)進(jìn)度條和圖片縮放等內(nèi)容,有興趣的可以了解一下。
    2016-12-12
  • Android 基于agora 開發(fā)視頻會(huì)議的代碼

    Android 基于agora 開發(fā)視頻會(huì)議的代碼

    這篇文章主要介紹了Android 基于agora 開發(fā)視頻會(huì)議,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • Android輔助權(quán)限的介紹和配置完整記錄

    Android輔助權(quán)限的介紹和配置完整記錄

    這篇文章主要給大家介紹了關(guān)于Android輔助權(quán)限的介紹和配置的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-01-01
  • Android 圖片緩存機(jī)制的深入理解

    Android 圖片緩存機(jī)制的深入理解

    這篇文章主要介紹了Android 圖片緩存機(jī)制的深入理解的相關(guān)資料,這里提供了實(shí)現(xiàn)實(shí)例幫助大家理解圖片緩存機(jī)制的知識(shí),需要的朋友可以參考下
    2017-08-08
  • android實(shí)現(xiàn)RecyclerView列表單選功能

    android實(shí)現(xiàn)RecyclerView列表單選功能

    這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)RecyclerView列表單選功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-07-07
  • Android?Recyclerview實(shí)現(xiàn)左滑刪除功能

    Android?Recyclerview實(shí)現(xiàn)左滑刪除功能

    這篇文章主要為大家詳細(xì)介紹了Android?Recyclerview實(shí)現(xiàn)左滑刪除功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Android利用Sensor實(shí)現(xiàn)傳感器功能

    Android利用Sensor實(shí)現(xiàn)傳感器功能

    這篇文章主要為大家詳細(xì)介紹了Android利用Sensor實(shí)現(xiàn)傳感器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11

最新評(píng)論