#Kotlin# Activity 之朝花夕拾

對於上一期文章末尾講的 companion 還遺留了一些需要解釋的地方。

  • 上期最後 Parcelable 類的 @JvmStatic 註解詞已糾正為更適合的 @JvmField 。@JvmFieldCREATOR 變數註解為純 Java 的 field,去掉 kotlin 的特性(例如自動生成 getter, setter),直接暴露 CREATOR 變數給 Java 代碼。

  • @JvmStatic 通常用來註解函數(必須定義在 companion object 或 named object 類內),註解後的函數可以直接在 Java 中通過類名訪問:

KotlinClass.doSomething()

  • 前綴為 Jvm 的註解詞,是在需要和 Java 代碼進行交互的地方才用到,會失去大部分 kotlin 中的特性,非不得已的話,還是少用為妙。

  • companion object 在 Java 中的等價實現,實際上是一個 static 修飾的內部 class。就算不藉助 Jvm/java-interop 註解詞,我們也可以在 Java 代碼中,通過 class 內的 Companion 內部類,來訪問 companion object 代碼塊中的變數:

KotlinClass.Companion.doSomething()

另外,上期文章末尾在對 companion 做解釋時,提到了 named object,下面粗略提一下。

named object 定義一個全局單例

例如,我們可以在 Model 類內定義一個實名 object 對象 ModelList,並在每次新建 Model 的時候,將實例加入到 ModelList 中的 list 中。

data class Model(var test1: Int, var test2: Int): Parcelable { constructor(source: Parcel): this(source.readInt(), source.readInt()) { ModelList.list.add(this) } // ... object ModelList { val list = arrayListOf<Model>() }}

這樣我們就維護了一個 全局的 ModelList 單例。當然,named object 還有其他用處,這裡暫且就不細說了。

很抱歉拖了很長一段時間的稿,最近工作較忙,空閑時間較少,故而一直未跟進該系列專欄(好吧,其實是懶)。

另,如果時間充足的話,筆者會考慮再開一個 『使用 python 開發 Web』 系列專欄,初步構想是講解tornado 的一些初、中級知識。專欄也會涉獵到,但不限於 「爬蟲、Web Front-end」 等領域。

當然一切只是空想,事實上有出入的話請別打我 ╮(╯_╰)╭ 筆者是個懶人。

回到正文

這節將對 kotlin_android_base_framework(以下簡稱 base_framework) 中的 activity/helper 進行講解。

base_framework 使用的一個包結構如下:

com.nekocode.baseframework├─ data│ ├─ net│ └─ local│ ├─ presenter│ ├─ ui│ ├─ activity│ ├─ adapter│ ├─ fragment│ └─ view│ ├─ utils│ ├─ App.kt└─ Config.kt

為了方便,並沒有將 kotlin 源碼文件提取到新的 kotlin source 文件夾中。目前暫時混合儲存在 java source 文件夾中。

BaseActivity.kt

得益於 Java 的繼承機制,在 base_framework 中,我們可以實現一個抽象的 BaseActivity 類,並在其中實現一些 Activity 通用的方法(例如實現「應用內 UI 風格統一」),這樣我們在添加新 Activity 時,只需要繼承 BaseActivity 就能獲取統一的 features 和介面。

BaseActivity.kt 中已經實現的通用介面:

  • 維護一個全局 Handler 來解決「 Activity 中」非同步通知,以及「 Activity 間」的通信問題

使用 sendMsg()sendMsgDelayed() 以及 runDelayed() 來實現 Activity 中非同步通知。而繼承 BaseActivity 的子類都 必須實現抽象的 handler() 方法,來處理非同步傳來的消息,例如:

public class MainActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sendMsgDelayed(msg(MSG.SHOW_HELLO), 1000) } object MSG { const val SHOW_HELLO = 0 } override fun handler(msg: Message) { when(msg.what) { MSG.SHOW_HELLO -> showToast("hello") } }}

這樣,在 Activity 創建後,會延遲一秒再發送 SHOW_HELLO 消息給自己,這個在很多非同步場景下十分實用。例如進行一次非同步網路請求,或者其他非同步耗時操作時,在操作完成後,可以使用 sendMsg() 通知 Activity 已完成操作。注意:handler() 中的消息處理是在 UI Thread 中完成的。

另外,上面的 showToast() 的用法,可以使用 kotlin 的 Extensions 語法糖來實現:

public fun Context.showToast(any: Any) { when(any) { is Int -> Toast.makeText(this, any, Toast.LENGTH_SHORT).show() is String -> Toast.makeText(this, any, Toast.LENGTH_SHORT).show() }}

這裡,我們為 Context 類拓展了 showToast 外部函數,在 showToast() 內可以通過 this 變數來訪問「 showToast() 當前所在 Context 的實例」,於是就可以在所有 Context 類及其子類下,使用我們自定義的 showToast 語法糖:

class ContextA : Context() { fun toast() { // normal Toast.makeText(ContextA.this, "hello", Toast.LENGTH_SHORT).show() // used extensions showToast("hello") }}

其他的一些關於 Extensions 語法糖的例子可以參考 Sugar.kt(會不斷完善),這是 kotlin 中十分強大的 feature,通過為類實現 Extensions 方法,創建各種語法糖,極大地縮減了代碼量。

而 Activity 間,可以使用 broadcast() 來廣播消息。假如,我們實現了一個 LogoutActivity 用來處理用戶的賬戶登出交互。在登出成功後,可以使用 broadcast() 廣播一條 LOGOUT 消息給所有 Activity,通知 Activity 賬戶已經登出,以便進行相應操作。

public class LogoutActivity : BaseActivity() { fun logoutSuccess() { broadcast(msg(BaseActivity.MSG.LOGOUT)) } override fun handler(msg: Message) { }}

回到 BaseActivity.kt 中,我們來看看核心 MyHandler 的具體實現:

abstract class BaseActivity : AppCompatActivity() { companion object { private val handlers = arrayListOf<MyHandler>() class MyHandler : Handler { private val mOuter: WeakReference<BaseActivity> constructor(activity: BaseActivity) { mOuter = WeakReference(activity) } override fun handleMessage(msg: Message) { if(mOuter.get() == null) { BaseActivity.deleteHandler(this) return } else { // ... mOuter.get().handler(msg) } } } }}

這裡,MyHandler 被定義為 MainActivity 的內部類,關於 kotlin 中內部類的詳細說明可以看 這裡,在 kotlin 中,默認不加任何修飾詞的內部類,都相當於 Java 中的靜態內部類,也就是該內部類不持有外部類的實例。而添加了 inner 修飾詞的內部類,會持有外部類的實例,可以通過BaseActivity.this 來訪問外部類的實例。

  • 默認內部類靜態化,可以一定程度上降低初學者發生內存泄露的概率。很多時候內部類僅被用來存儲數據,並不需要訪問外部類中的屬性。

我們在 companion object 中維護了一個靜態全局 MyHandler 列表,在 Activity 執行 onCreate() 和 finish() 時分別入列和出列自己的 handler。這樣我們就獲得了一個動態儲存所有運行中 Activity 的 Handler 列表。

當然,維護這個列表是為了實現 Activity 間 boradcast 功能。MyHandler 更詳細的邏輯實現請自行看完整代碼。

下面,我們來看看 runDelayed() 的具體實現:

public fun runDelayed(runnable: ()->Unit, delayMillis: Int) { val msg = Message() msg.what = -101 msg.arg1 = -102 msg.arg2 = -103 msg.obj = runnable handler.sendMessageDelayed(msg, delayMillis.toLong())}

這是一個 高階函數,它把 runnable()匿名函數 當做參數傳入。注意,runnable 的類型定義為 ()->Unit。這個類型表示為:傳參為空,返回為 Unit(空返回) 的匿名函數。

  • 匿名函數是使用 Lambda 表達式的方法來書寫的,所以也可以叫做『Lambda 表達式』

這樣,我們就可以在 runDelayed() 中用使用 Lambda 表達式 :

runDelayed({ showToast("hello") }, 1000)

相信有不少朋友使用過 gradle-retrolambda 插件,但這是一種侵入式的 lambda 實現模擬。相對來說,kotlin 的原生 lambda 支持顯得更為徹底,用起來也更加爽快,要知道 「在 kotlin 中,函數和其他值類型一樣是一等公民」

kotlin-android-extensions 已經默認為我們將大部分以前使用匿名類傳參的各種系統介面,添加上 lambda 的支持,Cool!(??`ω′?)

另外,在這裡解釋一下,我們利用 android.os.Message 中的三個參數來標誌這是個帶 runnable 的消息,在 MyHandler 處理消息時,通過三個參數標誌來判斷是否是個帶 runnable 的消息,如果是的話,就執行該 runnable 函數:

if (msg.what == -101 && msg.arg1 == -102 && msg.arg2 == -103) { val runnable = msg.obj as ()->Unit runnable.invoke() return}

SingleFragmentActivity.kt

這是一個含有單個 ToolbarFragment 的 Activity 抽象類。在很多場景下,我們僅需要含有單個 Fragment 的 Activity,這時候就可以繼承 SingleFragmentActivity:

public class MainActivity : SingleFragmentActivity() { override val fragmentClass = TestFragment::class.java override val fragmentBundle = { null } override fun afterCreate() { toolbar.title = "TestActivity" } override fun handler(msg: Message) { }}

是的,僅僅需要這幾行代碼就可以創建一個用 TestFragment 填充的 Activity。

我們來看看布局的具體實現:

relativeLayout { include<Toolbar>(R.layout.toolbar) { id = id_toolbar }.lparams(width = matchParent, height = dip(50)) frameLayout { id = id_fragment_content }.lparams(width = matchParent, height = matchParent) { below(id_toolbar) }}

我們使用了 anko 庫來用 DSL 描述我們 SingleFragmentActivity 的 UI 布局,關於 anko 更詳細介紹請查閱 該處。

我們在 SingleFragmentActivity 中定義了兩個抽象變數 fragmentClassfragmentBundle(在 kotlin 中,支持在抽象類中定義抽象變數)。這樣,我們在子類 override 這兩個變數,就可以指定使用哪個 Fragment 來填充 Activity 了。

另外需要說明的兩個地方是:

  1. TestFragment::class.java 返回的是 TestFragment 的 java Class 對象,不添加.java 的話返回的是 kotlin Class 對象。在這裡,因為 Fragment.instantiate() 傳入的是 java Class 對象,所以使用::class.java
  2. override val fragmentBundle = { null } 表示一個返回為 null 的匿名函數。在 lambda 表達式中,默認省去 return 關鍵字的最後的變數,表示為返回值。

結語

感覺這篇廢話有些多了(kotlin 以外的東西講得有些多),以後會多抽精華的地方來講。心情好的話,也會講一下 Android 上的一些東西。但是大家都知道的東西,就不多講了。

順便解釋下,標題亂起的 =_=


推薦閱讀:

Kotlin 資源大全 - 如何學習 Kotlin?
如何評價開中文C# issue的人到kotliner.cn論壇提中文版Kotlin?
哪一些大公司在使用 kotlin 開發應用?

TAG:Android开发 | Kotlin |