Android入門|Activity篇

1.什麼是 Activity

Actuvity 是最容易吸引用戶的地方,它是一種能夠包含用戶界面的組件,主要用於和用戶進行交互。一個應用中能夠包含0個或多個 Activity,但不包含任何 Activity 的應用程序是沒法被用戶看見的。

2.Activity 的基本用法

2.1 手動建立 Activity

點擊 Empty Activity 建立名爲 FirstActivity 的 Activity
imagejava

image
勾選 Generate Layout File 表示會建立一個對應的佈局文件,勾選Launcher Activity 表示會將 FirstActivity 設置爲當前項目的主 Activity,點擊Finish。android

2.2 建立佈局和加載佈局

右鍵 app/src/main/res 目錄->New->Directory,會彈出一個新建目錄的窗口,如今這裏建立一個名爲 layout 的目錄,而後對着 layout 目錄右鍵->New->Layout resource file,優惠彈出一個新建佈局資源文件的窗口,咱們將這個佈局文件命名爲first_layout,根元素默認選擇爲 LinearLayout。編程

image

image
建立完成以後能夠在右側看到預覽窗口,能夠點擊對應按鈕切換到對應的頁面,在代碼區域會生成以下代碼數據結構

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

</LinearLayout>

2.2.1 在佈局中添加按鈕

image

2.2.2 加載佈局文件

class FirstActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 在此加載佈局文件
        setContentView(R.layout.first_layout)
    }
}

項目中每添加一個元素都會在 R 文件中響應的生成一個資源 id,所以剛纔添加的佈局文件的 id 就已經添加到了 R 文件中,因此能夠經過R.layout.first_layout找到 first_layout.xml 的 id,而後將這個值傳入到 setContentView() 方法便可。app

2.3 在 AndoirdManifest.xml 文件中註冊

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitytest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".FirstActivity"></activity>
    </application>

</manifest>

能夠看到,Activity 的註冊聲明要放在 <application> 標籤中,這裏經過 <activity> 標籤來對 Activity 進行註冊。在<activity>中經過 name 屬性指定具體註冊哪個 Activity,那麼這裏填入 .FirstActivity ,前面加 「.」 是由於最外層已經聲明瞭 package 屬性,在 name 的地方添加.FirstActivity 便可經過全類名找到 FirstActivity編程語言

通過了前面的步驟已經註冊了 Activity,可是還不能運行程序,由於須要配置 Activity,也就是說須要指定最早啓動哪一個 Activity。因此須要在 <activity> 標籤中添加 intent-filter 標籤,而後在 intent-filter 標籤中添加 <action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /> 兩行聲明便可,修改後的代碼以下所示:ide

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitytest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

這樣,FirstActivity 就成了這個應用的主 Activity 了,點擊應用圖標最早打開的就是這個 Activity。可是若是沒有在應用中聲明任何一個 Activity 做爲主 Activity,這個程序依然是能夠安裝的,只是沒法在啓動器中看到這個應用程序。這種程序一般做爲第三方服務供其它應用在內部進行調用。函數

運行效果:工具

image

2.4 在程序中使用 Toast

Toast 是 Adnroid 系統提供的一種很是好的提醒方式,在程序中可使用它將一些短小的信息通知給用戶,這些信息在一段時間內會自動消失,而且不會佔用任何屏幕空間。

2.4.1 定義彈出 Toast 的觸發點

class FirstActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 在此加載佈局文件
        setContentView(R.layout.first_layout)
        val button1: Button = findViewById(R.id.button1)
        button1.setOnClickListener {
            // 使用 Toast
            Toast.makeText(this, "You clicked Button 1", 
                Toast.LENGTH_SHORT).show()
        }
    }
}

在 Activity 中經過 findViewById 找到佈局文件中定義的元素,因爲該方法會返回一個繼承自 View 的泛型對象,所以 Kotlin 沒法推導出獲得的是什麼類型的控件,因此須要將 button 顯式的聲明爲 Button 類型,接着經過 setOnClickListener 註冊一個監聽器,點擊按鈕就會觸發 onClick() 方法,因此在此書寫代碼邏輯,建立 Toast 首先須要傳入三個參數,第一個是 Context,在這裏傳入 this 便可,第二個參數是顯示的文本內容,第三個參數是 Toast 顯示的時長,點擊按鈕後會有以下效果:佈局

image

上面的代碼實現是經過findViewById找到的控件,可是當控件過多時就會頻繁的寫這段代碼,在使用 Java 開發時由於沒法避免這種寫法因此產生了 ButterKnife 之類的第三方開源庫,可是在 Kotlin 中這個問題就不復存在了,由於使用 Kotlin 編寫的 Android 程序在 app/build.gradle 文件中引入了 kotlin-android-extensions 插件,這個插件會根據佈局文件中定義的控件的 id 自動生成一個具備相同名字的變量,咱們能夠直接使用,從而替代 findViewById

image

class FirstActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 在此加載佈局文件
        setContentView(R.layout.first_layout)
//        val button1: Button = findViewById(R.id.button1)
        button1.setOnClickListener {
            // 使用 Toast
            Toast.makeText(this, "You clicked Button 1",
                Toast.LENGTH_SHORT).show()
        }
    }
}

2.5 在 Activity 中使用 Menu

由於手機屏幕不如電腦屏幕那麼大,因此爲了節省屏幕空間,Android 中提供了菜單這個功能,下面就來使用一下。

2.5.1 在 res 目錄下建立 menu 文件夾

  • 建立 menu 類型的文件夾,並建立 menu resource file 命名爲 main

image

  • 修改 main 的代碼

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item
            android:id="@+id/add_item"
            android:title="Add" />
        <item
            android:id="@+id/remove_item"
            android:title="Remove" />
    </menu>

    在這裏添加兩個菜單選項,其中 id 屬性時這個選項的惟一標識符,title是顯示的文本信息。

  • 在 FirstActivity 中重寫方法

    /**
*/
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    // 加載 menu 資源文件
    menuInflater.inflate(R.menu.main, menu)
    return true
}

/**
 * 經過 item.itemId 監聽點擊了哪一個 item,對點擊了的 item 作出提示
 */
override fun onOptionsItemSelected(item: MenuItem): Boolean {
    when (item.itemId) {
        R.id.add_item ->
            Toast.makeText(this, "You clicked Add",
                Toast.LENGTH_SHORT).show()
        R.id.remove_item ->
            Toast.makeText(this, "You clicked Remove",
                Toast.LENGTH_SHORT).show()
    }
    return true
}
```
在`onCreateOptionsMenu`方法中須要加載菜單資源文件,並指明菜單項添加到哪一個 menu 中,最後要返回 `true`,若是返回 `false` 菜單就沒法顯示了,除此以外還要根據用戶點擊了的 `item` 作對應的邏輯處理,因此須要重寫 `onOptionsItemSelected` 方法,而後經過 `itemId` 判斷,並作出提示。

2.6 銷燬一個 Activity

銷燬 Activity 只須要在對應的方法中調用 finish() 便可。

button1.setOnClickListener {
    finish()
}

點擊按鈕會觸發監聽事件,而後回調用 finish() 方法完成 Activity 的銷燬。

3.使用 Intent 在 Activity 之間穿梭

Intent 是 Android 中各組件之間進行交互的一種重要方式,它不只能夠置名當前組件想執行的動做,還能夠在不一樣的組件之間傳遞數據。Intent 通常可用於啓動 Activity、啓動 Service以及發送廣播等場景。Intent 大體分爲兩種:顯式和隱式。

3.1 使用顯示 Intent

3.1.1 建立 SecondActivity 並修改佈局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".SecondActivity">

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button 2" />
</LinearLayout>

3.1.2 修改 FirstActivity

button1.setOnClickListener {
    val intent = Intent(this, SecondActivity::class.java)
    startActivity(intent)
}

在點擊事件中首先須要建立一個 Intent 對象,而後在第一個參數中指出當前的環境上下文,第二個參數指定要跳轉的頁面,這裏的SecondActivity::class.java 和 Java 中的 SecondActivity.class 做用一致。

3.2 使用隱式 Intent

隱式 Intent 要比顯示 Intent 含蓄的多,他並不明確指定要啓動哪一個 Activity,而是經過指定 actioncategory 的信息,讓系統去分析這個 Intent,並找出合適的 Activity 去啓動。
  • 修改 SecondActivity 的配置信息
<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
  • 修改 Intent 的內容
button1.setOnClickListener {
    val intent = Intent("com.example.activitytest.ACTION_START")
    startActivity(intent)
}

要想使用隱式 Intent 啓動 Activity,則必須匹配設置的 <action><category> ,在這裏指定配置文件中 SecondActivity 中<action>標籤的內容,因爲 <category> 的值是默認的,因此在這裏不須要指定。

注意:一個 Intent 中只能夠指定一個 <action> 可是能夠指定多個 <category>

3.2.1 指定多個 category

button1.setOnClickListener {
    val intent = Intent("com.example.activitytest.ACTION_START")
    intent.addCategory("com.example.activitytest.MY_CATEGORY")
    startActivity(intent)
}

注意:在指定多個 category 的同時,不要忘記在配置文件中添加 <category>標籤,不然會報錯。

3.2.2 更多隱式 Intent 的用法

隱式 Intent 不只能夠用來啓動本身的 Activity,還能夠啓動其它程序的 Activity,這就讓多個應用程序之間有了共享的可能。
  • 使用 Intnet 代開百度

    button1.setOnClickListener {
        val intent = Intent(Intent.ACTION_VIEW)
        intent.data = Uri.parse("http://www.baidu.com")
        startActivity(intent)
    }

    在這裏將 action 指定爲 ACTION_VIEW,並將須要跳轉的 URI 進行解析,設置到 data 屬性中,能夠實現點擊按鈕跳轉到這個 URL 界面的效果。與此對應,還能夠在 <intent-filter> 標籤中再配置一個 <data> 標籤,爲了更精準的響應數據,還能夠在 <data> 標籤中設置以下屬性:

    image
    只有當 <data> 標籤中指定的內容和 Intent 中攜帶的 Data 數據徹底一致時,當前 Activity 纔會響應該 Intent,不過 <data> 標籤中通常不會指定過多的內容。

  • 使用 Intent 打開撥號頁面

    button1.setOnClickListener {
        val intent = Intent(Intent.ACTION_DIAL)
        intent.data = Uri.parse("tel:10086")
        startActivity(intent)
    }

    action 指定爲 DIAL,並設置號碼便可。

3.2.3 向下一個 Activity 傳遞數據

  • 修改 FirstActivity 代碼

    button1.setOnClickListener {
        val data = "Hello SecondActivity"
        val intent = Intent(this, SecondActivity::class.java)
        intent.putExtra("extra_data", data)
        startActivity(intent)
    }

    在這裏使用 putExtra 方法一鍵值對的形式傳遞數據。

  • 修改 SecondActivity 代碼

    val extraData = intent.getStringExtra("extra_data")
    Log.d("SecondActivity", "extra data is $extraData")

image

3.2.4 返回數據給上一個 Activity

  • 修改 FirstActivity

    button1.setOnClickListener {
        val intent = Intent(this, SecondActivity::class.java)
        startActivityForResult(intent, 1)
    }
  • 修改 SecondActivity

    button2.setOnClickListener { 
        val intent = Intent()
        intent.putExtra("data_return", "Hello FirstActivity")
        setResult(RESULT_OK, intent)
        finish()
    }

    這裏須要在 setResult 方法中傳入狀態碼和intent對象,並將 Activity 銷燬掉

  • 在 FirstActivity 中重寫方法

    /**
     * 使用 startActivityForResult 啓動的 Activity 被銷燬後會回調
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    when (requestCode) {
        1 -> if (resultCode == Activity.RESULT_OK) {
            val returnedData = data?.getStringExtra("data_return")
            Log.d("FirstActivity", "returned data is $returnedData")
        }
    }
}
```
因爲在一個 Activity 中可能會調用 `startActivityForResult` 方法去啓動不少不一樣的 Activity,每一個 Activity 返回的數據都會回調到 `onActivityResult` 方法,因此須要使用經過檢查 `requestCode` 的值判斷數據來源。
![image](https://upload-images.jianshu.io/upload_images/15402014-bf2fc13fb662e38c?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

注意:按照上面的書寫方式用戶必須點擊按鈕才能夠將數據返回,若是點擊了 back 鍵則不會返回數據,要想改變這個狀況,須要在 SecondActivity 中再重寫一個方法。

  • 重寫方法處理 back 鍵問題

    /**
     * 在用戶點擊返回鍵後回調的方法,
*/
override fun onBackPressed() {
    val intent = Intent()
    intent.putExtra("data_return", "Hello FirstActivity")
    setResult(Activity.RESULT_OK, intent)
    finish()
}
```

4.Activity 的生命週期

4.1 返回棧

Android 是使用任務來管理 Activity 的,一個任務就是一組存放在棧裏的 Activity 的集合,這個棧也被稱爲返回棧(back stack)。棧是一種後進先出的數據結構,在默認狀況下,每當咱們啓動新的 Activity,它就會在返回棧中入棧,並處於棧頂的位置。而每當咱們按下 Back 鍵或調用 finish() 方法後,處於棧頂的 Activity 就會出棧,前一個入棧的 Activity 就會從新處在棧頂的位置,下圖展現了返回棧如何管理 Activity 入棧出棧操做。

image

4.2 Activity 狀態

  • 運行狀態

    當一個 Activity 處於棧頂時,Activity 就處於運行狀態,系統最不肯回收的就是處於運行狀態的 Activity,由於這會帶來很是差的用戶體驗。

  • 暫停狀態

    當一個 Activity 再也不處於棧頂的位置,但仍然可見,Activity 就進入了暫停狀態。例如在 Activity 上面顯示了一個對話框,可是並無徹底遮擋住 Activity,系統也不肯意回收這種 Activity,可是會在內存極低的狀況下會考慮進行回收。

  • 中止狀態

    當一個 Activity 再也不處於棧頂位置,而且徹底不可見的時候,就進入了中止狀態。系統仍然會爲這種 Activity 保存相應的狀態和成員變量,可是這並非徹底可靠的,當其它地方須要內存時,處於中止狀態的 Activity 有可能會被系統回收。

  • 銷燬狀態

    一個 Activity 從返回棧中移除後就變成了銷燬狀態。系統最傾向於回收這種狀態的 Activity,以保證手機的內存充足。

4.3 Activity 的生存期

Activity 類中定義了7個回調方法,覆蓋了 Activity 生命週期的每個環節,下面就來介紹一下這7個方法。

  • onCreate()

    該方法會在 Activity 第一次建立時進行調用,在這個方法中一般會作 Activity 初始化相關的操做,例如:加載佈局、綁定事件等。

  • onStart()

    這個方法會在 Activity 由不可見變爲可見的時候調用。

  • onResume()

    這個方法在 Activity 準備好和用戶進行交互時調用,此時的 Activity 必定會位於棧頂,並處於運行狀態。

  • onPause()

    這個方法在系統準備去啓動或者恢復另外一個 Activity 的時候調用。咱們一般會在這個方法中將一些消耗 CPU 的資源釋放掉,以及保存一些關鍵數據,但這個方法的執行速度必定要快,否則會影響新的棧頂 Activity 的使用。

  • onStop()

    這個方法會在 Activity 徹底不可見的時候調用。它和 onPause() 的主要區別在於,若是啓動的新 Activity 是一個對話框式的 Activity,那麼 onPause() 方法會獲得執行,而 onStop() 方法並不會執行。

  • onDestroy()

    這個方法在 Activity 銷燬以前調用,以後 Activity 的狀態將變成銷燬狀態。

  • onRestart()

    這個方法在 Activity 由中止狀態變爲運行狀態以前調用,也就是 Activity 被從新啓動了。

注意:以上7個方法除了 onRestart() 方法,其它的都是兩兩相對的,從而又能夠將 Activity 分爲一下3種生存期。

  • 完整生存期:

    Activity 在 onCreate() 方法和 onDestroy() 方法之間所經歷的就是完整生存期,通常狀況下,一個 Activity 會在 onCreate() 方法中完成各類初始化操做,而在 onDestroy() 方法中完成釋放內存的操做。

  • 可見生存期:

    Activity 在 onStart() 方法和 onStop() 方法之間所經歷的就是可見生存期。在可見生存期內,Activity 對於用戶老是可見的,即使有可能沒法和用戶進行交互,咱們也能夠經過這兩個方法合理的管理那些對用戶可見的資源。好比在 onStart() 方法中對資源進行加載,而在 onStop() 方法中對資源進行釋放,從而保證處於中止狀態的 Activity 不會佔用過多內存。

  • 前臺生存期:

    Activity 在 onResume() 方法和 onPause() 方法之間所經歷的就是前臺生存期,在前臺生存期內,Activity 老是處於運行狀態,此時的 Activity 是能夠和用戶進行交互的,平時咱們接觸最多的就是這個狀態下的 Activity。

Activity 生命週期示意圖:

image

4.4 體驗 Activity 的生命週期

4.4.1 建立兩個 Activity 並修改佈局文件爲一些標識

4.4.2 修改配置文件

image

將 DialogActivity 的主題設置爲 Dialog 風格。

4.4.3 修改 MainActivity 的佈局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/startNormalActivity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start NormalActivity" />
    
    <Button
        android:id="@+id/startDialogActivity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start DialogActivity" />

</LinearLayout>

4.4.4 修改 MainActivity 代碼

class MainActivity : AppCompatActivity() {

    private val tag = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(tag, "onCreate")
        setContentView(R.layout.activity_main)
        startNormalActivity.setOnClickListener{
            val intent = Intent(this, NormalActivity::class.java)
            startActivity(intent)
        }
        startDialogActivity.setOnClickListener{
            val intent = Intent(this, DialogActivity::class.java)
            startActivity(intent)
        }
    }

    override fun onStart() {
        super.onStart()
        Log.d(tag, "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d(tag, "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(tag, "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d(tag, "onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(tag, "onDestroy")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d(tag, "onRestart")
    }
}

程序啓動後:

image
點擊 normal button

image
點擊返回按鈕

image

點擊 dialog button 後

image
點擊返回按鈕後

image
在 MainActivity 中點擊 back 鍵

image

4.5 Activity 被回收了怎麼辦

當咱們在 ActivityA中進行了一些操做後,須要跳轉到 ActivityB,這時 ActivityA 就進入了中止狀態,在內存不足時可能會被回收,當用戶從 ActivityB 返回後,ActivityA 就會被從新建立,這樣以前保存的數據也就沒有了,這顯然是不合理的。

在 Android 中提供了一個 onSaveInstanceState() 方法,會在 ActivityA 被回收以前調用,所以咱們能夠經過這個方法保存數據。保存的數據會存儲在 Bundle 中,咱們能夠經過 Bundle 對象取出數據。

4.5.1 使用 Bundle 對象

  • 重寫方法

    /**
*/
override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    Log.d(tag, "onCreate")
    val tempData = "Something you Just typed"
    outState.putString("data_key", tempData)
}
```
  • 修改 onCreate 方法

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(tag, "onCreate")
        setContentView(R.layout.activity_main)
        if (savedInstanceState != null) {
            val tempData = savedInstanceState.getString("data_key")
            Log.d(tag, tempData)
        }
    }

    這裏在 Bundle 不爲空的狀況下獲取其中存儲的數據並進行打印。

5.Activity 的啓動模式

Activity 的啓動模式一共有4種,分別是:standard、singleTop、singleTask、singleInstance,具體使用哪一種啓動模式要根據項目特定的需求,咱們能夠在 AndroidManifest.xml 配置文件中經過給 <activity> 標籤指定 android:launchMode 屬性設置具體的啓動模式。

5.1 standard

standard 模式是 Activity 默認的啓動模式,在不進行顯式指定的狀況下,全部 Activity 都會自動使用這種啓動模式,在 standard 模式下,每當啓動一個新的 Activity,他就會在返回棧中入棧,並處於棧頂的位置,對於使用 standard 模式的 Activity,系統不會在意 Activity 是否已經在返回棧中存在,每次啓動都會建立一個該 Activity 的新實例。

5.1.1 演示 standard 模式

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.first_layout)
    button1.setOnClickListener {
        val intent = Intent(this, FirstActivity::class.java)
        startActivity(intent)
    }
}

在這裏每點擊一次按鈕就會啓動一個新的 Activity。

image
由於點擊了3次按鈕,因此又建立了3個新的 Activity,因此須要點擊4次back鍵才能夠退回到桌面

image

5.2 singleTop

上面的 standard 模式不管須要啓動的 Activity 是否在棧頂,都會被從新建立,這或者有些不合理,可是使用 singleTop 模式會檢查棧頂 Activity 是否是要啓動的 Activity,若是是就不會再從新建立了。

5.2.1 修改配置文件

image
這樣設置以後會發現不管怎麼點擊都不會新建新的 Activity,由於在這個模式下會先對須要啓動的 Activity 進行檢查,看看它是否是在棧頂。並且在這個模式下只須要點擊一次 back 鍵就能夠回退到桌面。

5.2.2 修改 FirstActivity 和 SecondActivity 的代碼

button1.setOnClickListener {
    val intent = Intent(this, SecondActivity::class.java)
    startActivity(intent)
}

button2.setOnClickListener {
    val intent = Intent(this, FirstActivity::class.java)
    startActivity(intent)
}

在這裏只須要修改點擊事件中須要跳轉的頁面。

image
在這裏點擊了兩次按鈕,第一次由於 SecondActivity 不在棧頂,因此會建立 SecondActivity,當再次點擊時會發現 FirstActivity 不在棧頂,因此又會新建 FirstActivity,當點擊返回時,會首先回退到 SecondActivity,再次點擊會回退到 FirstActivity,再點擊一次 back 纔會返回到桌面。
image

5.3 singleTask

前面使用的 singleTop 模式會判斷要啓動的 Activity 是否在棧頂,若是不在會進行建立,可是這樣也會形成屢次建立的問題。可是使用 singleTask 模式就能夠解決這個問題,它會判斷返回棧中是否又要啓動的 Activity 的實例,若是有則直接使用該實例,並將該 Activity 上的 Activity 實例所有出棧。

5.3.1 修改啓動模式

image

5.3.1 在 FirstActivity 中重寫方法

override fun onRestart() {
    super.onRestart()
    Log.d("FirstActivity", "onRestart")
}

5.3.2 在 SecondActivity 中重寫方法

override fun onDestroy() {
    super.onDestroy()
    Log.d("SecondActivity", "onDestroy")
}

image
經過運行結果能夠看出,在 SecondActivity 中啓動 FirstActivity 時,首先會對返回棧中是否有 FirstActivity 的實例進行檢查,在這裏確定是有的,因此就會將 SecondActivity 的實例出棧,將 FirstActivity 的實例放在棧頂,點擊一次back鍵就能夠退回到桌面。

image

5.4 singleInstance

singleInstance 啓動模式和其它啓動模式不一樣的是它會啓動一個新的返回棧來管理這個 Activity(若是 singleTask 模式指定了不一樣的 taskAffinity,也會啓動一個新的返回棧)。該啓動模式主要是爲了解決共享 Activity 實例的問題。

5.4.1 修改啓動模式

image

5.4.2 在 Activity 中打印 taskId

  • FirstActivity

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.first_layout)
        Log.d("FirstActivity", "Task id is $taskId")
        button1.setOnClickListener {
            val intent = Intent(this, SecondActivity::class.java)
            startActivity(intent)
        }
    }
  • SecondActivity

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("SecondActivity", "Task id is $taskId")
        setContentView(R.layout.activity_second)
        button2.setOnClickListener {
            val intent = Intent(this, ThirdActivity::class.java)
            startActivity(intent)
        }
    }
  • ThirdActivity

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("ThirdActivity", "Task id si $taskId")
        setContentView(R.layout.activity_third)
    }

image
根據運行結果能夠看出 SecondActivity 的返回棧 id 和 其它兩個不一樣,這說明 SecondActivity 處於單獨的一個返回棧。

點擊 back 鍵後會發現直接返回到了 FirstActivity 中,再按 back 會返回到 SecondActivity,再次返回纔會退出應用程序。產生這種結果是由於 FirstActivity 和 ThirdActivity 處於同一個返回棧,當這個返回棧爲空後因而就會顯示另外一個返回棧棧頂的 Activity。

image

6.Kotlin 課堂

6.1 標準函數 with、run、apply

Kotlin 的標準函數指的是 Standard.kt 文件中定義的函數,任何 Kotlin 代碼均可以自由地調用全部的標準函數。

6.1.1 with 函數

with 函數接收兩個參數,第一個參數能夠是一個任意類型的對象,第二個參數是一個 Lambda 表達式。with 函數會在 Lambda 表達式中提供第一個參數對象的上下文,並使用 Lambda 表達式中的最後一行代碼做爲返回值返回

val result = with(obj) {
    // 這裏是 obj 的上下文
    "value" // with 函數的返回值
}

它能夠在連續調用同一個對象的多個方法時讓代碼變得更加精簡

需求:有一個水果列表,如今咱們想吃完全部水果,並將結果打印出來

  • 傳統寫法

    val list = listOf<String>("Apple", "Banana", "Orange", "Pear", "Grape")
    val builder = StringBuilder()
    builder.append("Start eating fruit. \n")
    for (fruit in list) {
        builder.append(fruit).append("\n")
    }
    builder.append("Ate all fruits")
    
    val result = builder.toString()
    println(result)
  • 使用 with 函數

    val list = listOf<String>("Apple", "Banana", "Orange", "Pear", "Grape")
    val result = with(StringBuilder()) {
        append("Start eating fruit. \n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
        toString()
    }
    println(result)

    這裏在上下文的位置傳入了 StirngBuilder 對象,那麼接下來整個 Lambda 表達式的上下文就是 StringBuilder,在 Lambda 表達式中不須要寫 builder.append() 或者 builder.toString(),只須要簡寫成 append()toString() 便可,而且 Lambda 的最後一行代碼會做爲 with 函數的返回值。

6.1.2 run 函數

run 函數的使用場景和 with 函數的使用場景很是相似,只是稍微作了一些語法改動而已。首先 run 函數時不能直接調用的,必需要調用某個對象的 run 函數才行,其次 run 函數只接受一個 Lambda 參數,而且會在 Lambda 表達式中提供調用對象的上下文。其它方面和 with 函數是同樣的,包括也會使用 Lambda 表達式中的最後一行代碼做爲返回值。

val result = obj.run {
    // 這裏的 obj 是上下文
    "value" // run 函數的返回值
}

需求:使用 run 函數實現吃水果的代碼

val list = listOf<String>("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().run {
    append("Start eating fruit. \n")
    for (fruit in list) {
        append(fruit).append("\n")
    }
    append("Ate all fruits.")
    toString()
}
println(result)

整體變化很是小,作出的改變只是須要使用對象調用 run 函數,參數只有 Lambda。

6.1.3 apply 函數

apply 函數和 run 函數也是極其相似的,都要在某個對象上調用,而且只接受一個 Lambda 參數,也會在 Lambda 表達式中提供調用對象的上下文,可是 apply 函數沒法指定返回值,而是會自動返回調用對象自己。

val list = listOf<String>("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().apply {
    append("Start eating fruit. \n")
    for (fruit in list) {
        append(fruit).append("\n")
    }
    append("Ate all fruits.")
}
println(result.toString())

這裏與 run 函數的不一樣體如今返回值須要本身在輸出語句中調用,其它的都是相似的。

6.2 定義靜態方法

靜態方法在某些編程語言裏面又叫作類方法,指的就是這種不須要建立實例就能調用的方法,全部主流的編程語言都會支持靜態方法這個特性。

在 Java 中定義一個靜態方法很是簡單,只須要在方法上加一個 static 關鍵字便可

public class Util {
    public static void doAction() {
        System.out.println("do action");
    }
}

這是一個簡單的工具類,這個類中的方法只須要使用Util.doAction()調用便可,於是靜態方法很是適合編寫工具類,覺得工具類一般沒有建立實例的必要,基本是全局通用的。

在 Kotlin 中要想實現這種功能則須要使用單例類的方式實現,好比上述的Util工具類使用 Kotlin 要這樣寫

object Util {
    fun doAction() {
        println("do action")
    }
}

雖然這裏的 doAction() 方法並非靜態方法,可是咱們仍然可使用 Util.doAction() 的方式來調用,這就是單例類所帶來的便利性。

可是這麼寫會將單例類中的全部方法變成相似於靜態方法的調用方式,若是隻但願讓某一個方法變成靜態方法的調用方式就可使用 companion object

class Util {
    fun doAction1() {
        println("do action1")
    }

    companion object {
        fun doAction2() {
            println("do action2")
        }
    }
}

這裏首相將 單例類改爲了一個普通類,在普通類中的 doAction1() 方法必需要經過實例化 Util 類才能夠調用,而在companion object 中定義的 doAction2() 方法則能夠直接經過Util.doAction2()的形式進行調用。

不過,doAction2() 方法其實也並非靜態方法,companion object這個關鍵字實際上會在 Util 類的內部建立一個伴生類,而 doAction2() 方法就是定義在這個伴生類裏面的實例方法。只是 Kotlin 會保證 Util 類始終只會存在一個伴生類對象,所以調用 Util.doAction2() 方法實際上就是調用了Util 類中伴生對象的 doAction2() 方法

因而可知,Kotlin 確實沒有直接定義靜態方法的關鍵字,可是提供了一些語法特性來支持相似於靜態方法調用的寫法。若是確實須要定義真正二點靜態方法的話,在 Kotlin 中能夠以註解和頂層方法實現。

  • 註解方式

    class Util {
        fun doAction1() {
            println("do action1")
        }
    
        companion object {
            @JvmStatic
            fun doAction2() {
                println("do action2")
            }
        }
    }

    經過添加 @JvmStatic 註解就可讓 Kotlin 編譯器將這個方法編譯成真正的靜態方法了。

    注意@JvmStatic 註解只能加在單例類和companion object中的方法上。

  • 頂層方法

    頂層方法是指那些沒有定義在任何類中的方法,好比 main() 方法,Kotlin 會將全部的頂層方法所有編譯成靜態方法,所以只要你定義了一個頂層方法,那麼它必定是靜態的

    fun doSomething() {
        println("do something")
    }

    這個方法定義在一個新的 .kt 文件中,那麼這個方法就是一個頂層方法。

    在 Kotlin 中要是想調用頂層方法,直接輸入函數名便可。

    image

    若是想在 Java 文件中調用這個方法這麼寫是調用不到的,由於 Java 中的方法必須寫在類裏。

    可是 Kotlin 的編譯器會根據 Kotlin 文件的名稱生成一個 Java 文件,文件名爲 Kotlin文件名+Kt.java,因此能夠像下面這種方式進行調用。

    image

相關文章
相關標籤/搜索