1. 程式人生 > >kotlin專案開發總結

kotlin專案開發總結

前言

最近都是在用Kotlin開發Android專案,總結了一些心得在這裡和大家分享

1. 定義變數

kotlin定義變數有三種形式

1)使用var定義可修改變數,最常見的用法,也是很靈活,

private var point: Point? = null

//使用的時候,因為point是可空的,所以有兩種用法
println(point?.x) //如果你不能確保point是否為空
println(point!!.x) //如果你能確保point一定不為空,否則point為空,這裡會報執行時空指標

//下面用法會報編譯時錯誤,因為point是Point?型別,所以point有可能是null

println(point.x)

從上面的註釋,我們可以發現,kotlin從編譯時預防了空指標的可能性,VeryGood的特性,能夠避免很多人為導致的空指標錯誤

2)使用val定義不可修改的變數

private val point = Point(20, 20)

這種情況下point一定不會為空,因為val定義的變數必須要初始化,從這個角度來看,又避免了人為導致的空指標錯誤
比如下面會產生編譯時錯誤

point = Point(30, 30)

需要注意的是,point不能被修改,但是Point類裡面的成員變數是可修改的,所以下面操作是允許的

point.x = 30
point.y = 30

3)使用lateinit var,可以使得變數的初始化可以延遲到需要的時候
比如在使用dagger的時候,需要inject,如下

@Inject lateinit var mPresenter: MapHomePresent

但是需要慎用lateinit, 因為你可能後面忘記了初始化,但是編譯器又不會報錯提醒。只有執行之後才會檢測到,如下

fun main(args: Array<String>) {
    var test = Test()
    println(test.a)
}

class Test {
    lateinit var
a: String }

會報錯如下:

Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property a has not been initialized
    at Test.getA(Simplest version.kt:14)
    at Simplest_versionKt.main(Simplest version.kt:10)

2.定義方法

注意下面是兩種型別

var a: String? = null
var b: String = null

b可以自動轉換成a, a需要使用a!!轉換成b,這種在定義方法的時候特別有用,如下舉例

fun test(a: String) {
a.apply{
print(this)
}
}

上面這個方法在傳入引數時,必須保證這個引數不為空,比如下面會報編譯錯誤

var temp: String? = null
test(temp)//這裡會報編譯錯誤,L型別自動轉換
test(temp!!)//ok,但是需要在別的地方把temp重新賦值為非null

反過來,下面是可以呼叫的

fun test(a: String?) {
    a?.apply{
        print(this)
    }
}

var temp: String = null 
test(temp)//會自動轉換

3. 通過data class 定義entity

可以看下data class的定義,它是專門用來儲存資料的,很適合entity的場景,比如從伺服器拉取資料。

4. 多使用Kotlin提供的標準函式

比如使用apply函式,可以使得程式碼看起來非常簡潔

mOption.apply {
     locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy//可選,設定定位模式,可選的模式有高精度、僅裝置、僅網路。預設為高精度模式
     isNeedAddress = false
     /**
      * 設定是否優先返回GPS定位結果,如果30秒內GPS沒有返回定位結果則進行網路定位
      * 注意:只有在高精度模式下的單次定位有效,其他方式無效
      */
     isGpsFirst = false // GPS優先會導致反應速度很慢
     // 設定是否開啟快取
     isLocationCacheEnable = true
     // 設定是否單次定位
     isOnceLocation = true
     //設定是否等待裝置wifi重新整理,如果設定為true,會自動變為單次定位,持續定位時不要使用
     isOnceLocationLatest = true
     //設定是否使用感測器
     isSensorEnable = true
     interval = 1000
     // 設定網路請求超時時間
     httpTimeOut = 60000
     //設定是否開啟wifi掃描,如果設定為false時同時會停止主動重新整理,停止以後完全依賴於系統重新整理,定位位置可能存在誤差
     isWifiScan = true
}

對比下,下面的普通用法

mOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy//可選,設定定位模式,可選的模式有高精度、僅裝置、僅網路。預設為高精度模式
mOption.isGpsFirst = false//可選,設定是否gps優先,只在高精度模式下有效。預設關閉
mOption.httpTimeOut = 30000//可選,設定網路請求超時時間。預設為30秒。在僅裝置模式下無效
mOption.interval = 2000//可選,設定定位間隔。預設為2秒
mOption.isNeedAddress = true//可選,設定是否返回逆地理地址資訊。預設是true
mOption.isOnceLocation = false//可選,設定是否單次定位。預設是false
mOption.isOnceLocationLatest = false//可選,設定是否等待wifi重新整理,預設為false.如果設定為true,會自動變為單次定位,持續定位時不要使用
AMapLocationClientOption.setLocationProtocol(AMapLocationClientOption.AMapLocationProtocol.HTTP)//可選, 設定網路請求的協議。可選HTTP或者HTTPS。預設為HTTP
mOption.isSensorEnable = false//可選,設定是否使用感測器。預設是false
mOption.isWifiScan = true //可選,設定是否開啟wifi掃描。預設為true,如果設定為false會同時停止主動重新整理,停止以後完全依賴於系統重新整理,定位位置可能存在誤差
mOption.isLocationCacheEnable = true //可選,設定是否使用快取定位,預設為true

一對比發現,上面這種寫法真的是很簡潔

5. 巧用“?:”

?: 的意思是,左邊的表示式沒有成功,則使用右邊的結果;如下,person是null,所以person?.name不會執行,所以最終a == "null"

var person: Person? = null
var a = person?.name ?: "null"

配合閉包也可以使用,如下程式碼:

screenMarker?.apply {
   val point = aMap!!.projection.toScreenLocation(position)
   point.y -= SizeUtils.sp2px(125f)
   val target = aMap!!.projection.fromScreenLocation(point)
   val animation = TranslateAnimation(target)
   animation.setInterpolator { input ->
       // 模擬重加速度的interpolator
       if (input <= 0.5) {
           (0.5f - 2.0 * (0.5 - input) * (0.5 - input)).toFloat()
       } else {
           (0.5f - Math.sqrt(((input - 0.5f) * (1.5f - input)).toDouble())).toFloat()
       }
   }
   //整個移動所需要的時間
   animation.setDuration(600)
   //設定動畫
   setAnimation(animation)
   //開始動畫
   startAnimation()
}?:KLog.d(TagObject.TAG, "screenMarker is null")

是不是看上去,程式碼連貫性很強。

6. 使用companion object

當你的類包含太多的東西,你想把它們隔離到另外一個類,又不想使用類引用的方式,你就可以使用companion object,如下

class AndroidFragment : MainFragment() {
    override fun getAdapter(list: ArrayList<FuckGoods>): BaseBindingAdapter<*> {
        return FuckGoodsAdapter(list)
    }

    override fun getType(): String {
        return ANDROID
    }


    //companion object的好處是,外部類可以直接訪問物件,不需要通過物件指標
    companion object {
        val ANDROID = "ANDROID"
        fun newInstance(): Fragment {
            val fragment = AndroidFragment()
            val bundle = Bundle()
            fragment.arguments = bundle
            return fragment
        }
    }
}

可以結合一起使用,又能實現程式碼分離,增強程式碼的可讀性

7. 通過閉包減少介面類

有些時候你不想使用定義新的介面去實現回撥,那就可以考慮使用閉包。如下程式碼:

protected fun <T> submit(observable: Observable<JsonResult<T>>, block: (T) -> Unit) {
        addDisposable(
              observable.observeOn(AndroidSchedulers.mainThread())
                   .subscribe(
                           {
                               if(it != null && !it.error && it.results != null) {
                                   block(it.results)
                               }else if (it == null){
                                   KLog.d(TagObject.TAG_Release, "result is null")
                               }else {
                                   KLog.d(TagObject.TAG_Release, "res.error:" + it.error + ",res.results:" + it.results)
                               }
                           },
                           {
                               KLog.d(TagObject.TAG_Release, "error android Presenter" + it.message)
                           }
                   )
      )
}

呼叫如下:

submit(mModel.getData(page, type)){
   view.setData(it)
}

這樣寫程式碼,會顯示非常簡潔,也避免了建立大量的介面回撥類。

8. Java類轉換成kotlin

首先選中Java類,然後按ctrl+shift+A,彈出一個“enter action or option name”的對話方塊,然後輸入”convert Java file to kotlin file”,就像下面的截圖:
這裡寫圖片描述

點選“Convert java File to Kotlin File”(也可以使用快捷鍵),IDE就自動幫我們把Java檔案轉換成kotlin, 一般情況下,轉換出來的檔案,但是不排除需要做一些修改。但是相對來說也省事多了