Activity的活動頁面跳轉是App最常用的功能之一,在前幾章的demo源碼中便多次見到了,常常是點擊界面上的某個按鈕,然后跳轉到與之對應的下一個頁面。對於App開發者來說,該功能的實現非常普通,使用Java編碼不過以下兩行代碼而已:
Intent intent = new Intent(MainActivity.this, LinearLayoutActivity.class);
startActivity(intent);
上面代碼的關鍵之處在於Intent的構造函數,其中第一個參數指定了頁面跳轉動作的來源,即MainActivity這個源頁面,MainActivity.this通常簡寫為this;構造Intent的第二個參數則表示頁面跳轉動作的目的地,即LinearLayoutActivity這個目標頁面。倘若把這兩行Java代碼轉換為Kotlin代碼(復制這兩行然后粘貼到kt文件中,Android Studio就會自動完成轉換),則可看到活動跳轉的Kotlin代碼如下所示:
val intent = Intent(this@MainActivity, LinearLayoutActivity::class.java)
startActivity(intent)
對比之下,這里的Kotlin代碼與Java代碼主要有兩點不同之處:
1、在類內部指代自身的this關鍵字,Java的完整寫法是“類名.this”,而Kotlin的完整寫法是“this@類名”,當然二者均可簡寫為“this”;
2、獲取某個類的class對象,Java的寫法是“類名.class”,而Kotlin的寫法是“類名::class.java”,一看便知帶有濃濃的Java風味;
看起來,Kotlin代碼與Java代碼半斤八兩,未有明顯的簡化,令人產生小小的失望。但細心的讀者也許已經注意到了,本書附錄源碼里的活動跳轉,並非上述的Kotlin正宗寫法,而是下面這種簡化版的寫法:
startActivity<LinearLayoutActivity>()
究其原因,乃是Anko庫利用Kotlin的擴展函數,給Context類新增了名為startActivity的新方法。故而使用簡化版的寫法之前,必須先導入Anko庫的指定文件,即在kt文件頭部添加下面一行導入語句:
import org.jetbrains.anko.startActivity
活動頁面跳轉的時候,往往還要攜帶一些請求參數,如果使用Java編碼,可以很輕松地調用Intent對象的putExtra方法,通過“putExtra(參數名, 參數值)”的方式傳遞消息,就像下面代碼那樣:
Intent intent = new Intent(this, ActSecondActivity.class);
intent.putExtra("request_time", DateUtil.getNowTime());
intent.putExtra("request_content", et_request.getText().toString());
startActivity(intent);
如果使用Anko的簡化寫法,其實也很容易,只要在startActivity后面的括號中依次填上每個參數字段的字段名和字段值,具體的Kotlin跳轉代碼如下所示:
//第一種寫法,參數名和參數值使用關鍵字to隔開
startActivity<ActSecondActivity>(
"request_time" to DateUtil.nowTime,
"request_content" to et_request.text.toString())
注意到上面的寫法使用關鍵字to隔開參數名和參數值,感覺不夠美觀,而且容易使人迷惑,to后面究竟要跟着字段名還是字段值呢?所以Anko庫提供了另一種符合習慣的寫法,也就是利用Pair類把參數名和參數值進行配對,Pair的第一個參數為字段名,第二個參數為字段值。據此改寫后的Kotlin跳轉代碼如下所示:
//第二種寫法,利用Pair把參數名和參數值進行配對
startActivity<ActSecondActivity>(
Pair("request_time", DateUtil.nowTime),
Pair("request_content", et_request.text.toString()))
不管哪種寫法,在下一個活動中解析請求參數的方式都一樣,都得先獲取Bundle對象,然后分別根據字段名稱獲取對應的字段值。具體的請求參數解析代碼如下所示:
class ActSecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_act_second)
val bundle = intent.extras
val request_time = bundle.getString("request_time")
val request_content = bundle.getString("request_content")
tv_response.text = "收到請求消息:\n請求時間為${request_time}\n請求內容為${request_content}"
}
}
下面通過測試界面觀察一下消息數據發送之前和發送之后的效果,如下面左圖所示,這時第一個頁面准備跳轉到第二個頁面;如下面右圖所示,這是跳轉后的第二個頁面,界面上展示了第一個頁面傳遞過來的參數信息。


Activity之間傳遞的參數類型,除了整型、浮點數、字符串等基本數據類型,還允許傳遞序列化結構如Parcelable對象。這個Parcelable對象可不是簡單的實體類,而是實現了Parcelable接口的實體類,實現接口意味着該類必須重寫接口定義的所有方法,不管你願不願意都得老老實實地照貓畫虎。譬如前面的活動跳轉傳遞了兩個字段數據,如果把這兩個字段放到Parcelable對象中,僅僅包含兩個字段的Parcelable類對應的Java代碼也如下面這般冗長:
public class MessageInfo implements Parcelable {
public String content;
public String send_time;
// 寫數據
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(content);
out.writeString(send_time);
}
// 例行公事實現createFromParcel和newArray
public static final Parcelable.Creator<MessageInfo> CREATOR
= new Parcelable.Creator<MessageInfo>() {
// 讀數據
public MessageInfo createFromParcel(Parcel in) {
MessageInfo info = new MessageInfo();
info.content = in.readString();
info.send_time = in.readString();
return info;
}
public MessageInfo[] newArray(int size) {
return new MessageInfo[size];
}
};
@Override
public int describeContents() {
return 0;
}
}
看看這架勢,如此簡單的自定義Parcelable類,就得重寫包括writeToParcel、createFromParcel、newArray、describeContents在內的四個方法,可謂是興師動眾。由此可見這里又是Java的一個痛點,正適合Kotlin施展拳腳、好好改進。在第五章的類和對象中,介紹了Kotlin對數據類的寫法,在類名前面關鍵字data,Kotlin即可自動提供get/set、equals、copy、toString等諸多方法。那么序列化對象的改造也相當簡單,僅需在類名之前增加一行注解“@Parcelize”就好了,整個類的Kotlin代碼只有下面寥寥幾行:
@Parcelize
data class MessageInfo(val content: String, val send_time: String) : Parcelable {
}
不過若想正常編譯,還需修改模塊的編譯文件build.gradle,在文件末尾添加下面幾行,表示增加安卓插件的編譯支持:
//@Parcelize標記需要設置experimental = true
androidExtensions {
experimental = true
}
編譯文件修改完畢,現在能在Kotlin中使用序列化對象的注解了。雖然自定義的MessageInfo類內部沒有任何一行代碼,但是它除了具備數據類的所有方法,也自動實現了Parcelable接口的幾個方法。接下來就可以利用該類傳輸活動跳轉的序列化數據了,下面是改寫后的Kotlin跳轉代碼:
val request = MessageInfo(et_request.text.toString(), DateUtil.nowTime)
startActivity<ParcelableSecondActivity>("message" to request)
跳轉后的下一個頁面,調用getParcelable即可正常獲得原始的序列化數據,具體的數據解析代碼如下所示:
class ParcelableSecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_parcelable_second)
val request = intent.extras.getParcelable<MessageInfo>("message")
tv_response.text = "收到打包好的請求消息:\n請求時間為${request.send_time}\n請求內容為${request.content}"
}
}
同樣通過測試界面觀察序列化對象的打包和解包效果,如下面左圖所示,這時第一個頁面准備跳轉到第二個頁面;如下面右圖所示,這是跳轉后的第二個頁面,界面上展示了第一個頁面傳遞過來的序列化數據。


