在本小节,我们将探究Activity。首先是Activity的基本用法:手动创建、加载布局、xml注册、使用Toast和menu以及如何销毁一个Activity。其次是使用显式Intent、隐式Intent(action和category匹配)、putExtra传递数据、返回数据startActivityForResult;再者是七个生命周期和四种Activity状态;启动模式包括了standard、singleTop、singleTask和singleInstance;知晓当前是哪一个Activity是继承自BaseActivity类。最后介绍了标准函数with、run、apply和静态方法(单例类、@JvmStatic和顶层方法)

3.1.Activity是什么
       用于和用户进行交互的包含用户界面的组件。一个程序包含一个或者多个Activity。
3.2.Activity的基本用法
3.2.1.手动创建Activity
       No Activity->Next->右击包名->New->Activity->Empty Activity。以下为该Activity中的代码:

package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
    //任何Activity需要重写父类的onCreate方法
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}

3.2.2.创建和加载布局
       Res目录->New->Directory->创建Layout目录->New->Layout Resourcce file->将布局文件命名为first_layout。点击右边的红色区域切换为XML编辑布局。这块我就不解释了,比较简单。代码示例如下:

《第一行代码》第三版之探究Activity(四)_Kotlin

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:text="Button 1"></Button>
</LinearLayout>

       随后在Activity中加载这个布局,使用setcontentView给当前加载一个布局,一般方法是传入布局文件的id。项目添加的任何资源都会在R文件中生成一个布局文件的id,因此上述xml文件的id已经添加进入了,只需要引用即可。

package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
    //任何Activity需要重写父类的onCreate方法
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.first_layout)
    }
}

3.2.3.在AndroidManifest.xml中注册
       Actvity的注册声明要放在application标签内,通过<Activity>对Activity进行注册,android:name用于指定哪一个Activity,这里采用了缩写,目前没有配置主程序Activity,因此仍然无法运行。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication">
     
    <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=".MainActivity"></activity>
    </application>

</manifest>

       android中配置主Activity的方案是内部加入<intent-filter>标签,然后在此标签中加入两句必要的声明,android:label指定了标题内容。修改后的代码如下所示:

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

    <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=".MainActivity"
            android:label="This is Activity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

3.2.4在Activity中使用Toast
      Toast是一种非常好的提醒方式,实现如下:

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import kotlinx.android.synthetic.main.first_layout.*

class MainActivity : AppCompatActivity() {
    //任何Activity需要重写父类的onCreate方法
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.first_layout)
        //findViewById获取布局文件中定义的元素,R.id.button1得到按键实例,返回一个继承自View的泛型变量
        //因为无法自动推导,所以显式声明为Button类型。
        val button1: Button = findViewById(R.id.button1)
        //得到按键实例后,我们调用setOnClickListener注册监听器,点击按钮会执行onclick方法
        button1.setOnClickListener {
            //静态方法makeText创建一个Toast对象,共三个参数,一个是上下文,Activity本身就是context对象;第二个是文本内容;第三个是Toast显示时长
            Toast.makeText(this, "you clicked button 1", Toast.LENGTH_SHORT).show()
        }
    }
}

      如果十个控件,Java需要十个findViewById才行,需要借助ButterKnife才能解决;在Kotlin中Android项目的gradle文件中默认引入Kotlin-android-extensions插件,会根据布局文件定义的控件id自动生成一个具有相同名称的变量,直接使用这一变量,删除findViewById这一行。推荐使用后者。
3.2.5.在Activity中引入Menu
       Menu是目录,不占用任何屏幕控件,创建如下:res目录->右击选New->Directory->输入文件名menu->右击选New->Menu resource file->输入文件名main。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- item用于创建某一个菜单项,指明唯一标识符和名称-->
    <item
        android:id="@+id/add_item"
        android:title="Add" />
    <item
        android:id="@+id/remove_item"
        android:title="remove" />
</menu>

      再回到mainActivity,重写onCreateOptionsMenu方法,编写如下方法:

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        //menuInflater调用了父类的getmenuInflater方法,在调用它的inflate方法就可以创建菜单了,
        // 两个参数:一个是哪一个资源文件的id;另一个是添加到哪一个Menu对象。
        menuInflater.inflate(R.menu.main, menu)
        //返回true显示出来,返回false则不显示
        return true
}

      插一点语法糖概念,自动将下面的代码转换为setPages方法和getPages方法。非常简单的Java类和调用的语法糖。

package com.example.myapplication;

public class Book {
    private int pages;

    public int getPages() {
        return pages;
    }

    public void setPages(int pages) {
        this.pages = pages;
    }
}

val book = Book()
book.pages = 500
val bookPages = book.pages

         另外,仅显示还不够,必须要有相应的点击事件。复写onOptionsItemSelected即可。

    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
        //语法糖的小应用,调用了item的getItemId方法
        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
}

3.2.6.销毁一个Activity
        Back键或者使用finish()方法。
3.3.使用Intent在Activity之间穿梭
3.3.1.显式Intent

       创建第二个Activity,并在其中加入一个按钮。Intent是Android各组件进行交互的一种重要方式,可以不同组件传递数据、指明要执行动作。譬如:启动Activity、启动Service、发送广播等。Intent有两种,一种是显式,一种是隐式。下面介绍前者。

 button1.setOnClickListener {
            //Intent接受两个参数:第一个参数context提供了启动Activity的上下文;第二个参数是要启动的目标Activity
            //this是当前上下文,第二个SecondActivity::class.java相当于Java的SecondActivity.class
            val intent = Intent(this, SecondActivity::class.java)
            //启动Activity,接受一个Intent参数
            startActivity(intent)
        }

3.3.2.隐式Intent
       隐式Intent通过指定一系列更为抽象的action和category等信息去决定启动哪一个Activity。首先在AndroidManifest.xml中的intent-filter中声明action和category。如下所示:

   <activity android:name=".SecondActivity">
            <intent-filter>
                <action android:name="com.example.myapp.ACTION_START"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
   </activity>

       action标明做啥动作,category是附加信息。只有两者内容同时匹配Intent的指定时,才能响应Intent。响应代码如下所示:

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

       刚不是说两个同时匹配么?这是因为android.intent.category.DEFAULT是默认的category,会将其自动添加进去。每个Intent有一个action和多个category。

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

       Intent中添加了category,但xml中的<intent-filter>没有,需要再添加一个category的声明。在xml中声明。

        <activity android:name=".SecondActivity">
            <intent-filter>
                <action android:name="com.example.myapp.ACTION_START" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="com.example.myapp.my_category" />
            </intent-filter>
        </activity>

3.3.3.更多隐式Intent的用法
       隐式Intent不仅启动程序内的Activity,还可以启动其他程序的Activity。举例来讲,应用程序内展示一个网页百度。

        button1.setOnClickListener {
            //action是Intent.ACTION_VIEW,安卓内置动作android.intent.action.VIEW
            val intent = Intent(Intent.ACTION_VIEW)
            //Uri.parse将字符串解析成Uri对象,然后再使用setData传入,这里使用了语法糖,使得看起来是给Intent的data赋值
            intent.data = Uri.parse("https://www.baidu.com")
            startActivity(intent)
        }

       另外,<data>标签用于指定数据协议之类的,书中讲的比较粗略,除了https协议,仍有geo地理位置、tel打电话等。譬如,打电话给10086。

        button1.setOnClickListener {
            //内置动作,拨号
            val intent = Intent(Intent.ACTION_DIAL)
            //data部分指定了协议是tel,号码是10086
            intent.data = Uri.parse("tel:10086")
            startActivity(intent)
        }

3.3.4.向下一个Activity传递数据
       Intent在启动Activity过程中可以传递数据。Intent中提供了putExtra方法进行重载,举例来讲:将字符串从一个Activity中传递至第二个Activity中。

        button1.setOnClickListener {
            val data = "Hello SecondActivity"
            val intent = Intent(this, SecondActivity::class.java)
            //putExtra接受的是键值对,第一个参数是键,用于后面取值;第二个是真正要传递的数据
            intent.putExtra("extra_data", data)
            startActivity(intent)
        }

      在第二个Activity中将传递的数据拿出,并将其打印出来。

class SecondActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_second)
        //intent调用的是getIntent方法,会获取用于启动SecondActivity的Intent,getStringExtra获取到传递的数据
        //getIntExtra拿到的是整形;getBooleanExtra拿到的是布尔类型
        val extradata = intent.getStringExtra("extra_data")
        Log.d("SecondActivity", "extra data is $extradata")
    }
}

3.3.5.返回数据给上一个Activity
       既可以传递数据给下一个Activity,那么将数据返回给一个Activity也是可行的。

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

       startActivityForResult方法接受两个参数:第一个参数是Intent,第二个参数是请求码。用于在之后的回调中判断数据来源。

class SecondActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_second)
        button2.setOnClickListener {
            //Intent传递数据,没有任何意图,只需要将数据存放进去
            val intent = Intent()
            intent.putExtra("data_return", "Hello FirstActivity")
            //setResult接受两个参数:第一个是用于向上一个Activity返回处理结果,一般是RESULT_OK或者RESULT_CANCELED
            //第二个是带有数据的intent传递过去。最后调用finish销毁。
            setResult(Activity.RESULT_OK, intent)
            finish()
        }
    }
}

       最后需要在第一个Activity重写方法得到返回的数据。

  //第一个参数requestCode是启动Activity时传入的请求码;第二个参数resultcode是返回数据传入的处理结果
    //第三个参数是data数据,携带intent。
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        //requestcode是判断数据来源
        when (requestCode) {
            //resultcode是判断处理结果是否成功与否。
            1 -> if (resultCode == Activity.RESULT_OK) {
                val returnedData = data?.getStringExtra("data_return")
                //将data值打印出来
                Log.d("MainActivity", "returned data is $returnedData")
            }
        }
}

       那么如果用户不是通过点击事件返回,而是点击Back键回到第一个Activity,如何处理?通过在第二个Activity中重写onBackPressed方法来解决这个问题。

    override fun onBackPressed() {
        val intent = Intent()
        intent.putExtra("data_return", "Hello FirstActivity")
        setResult(Activity.RESULT_OK, intent)
        finish()
}

3.4.Activity的生命周期
3.4.1.返回栈

        栈后进先出,这个栈在Android中被称为返回栈。默认情况,启动Activity入栈并处于栈顶位置,back键或者finish方法时,栈顶将会被移除。
3.4.2.Activity状态
        Activity有四种状态:1.运行状态:当Activity处于栈顶时;2.暂停状态:不再处于栈顶但可见,并不是每一个Activity都会占满屏幕。eg:对话框形式的Activity占用屏幕中间部分区域,下面的Activity是暂停的,一般不会回收;3.停止状态:不再处于栈顶且完全不可见。当其他地方需要内存时,该状态的Activity可能会被回收;4.销毁状态:从返回栈中移除,系统会回收该部分。
3.4.3.Activity生存期
        七个回调方法:1.onCreate:第一次创建时使用;2.onStart:由不可见变为可见;3.onResume:准备和用户进行交互时,此时处于栈顶位置;4.onPause:系统准备去启动或者恢复另一个Activity时调用;5.onStop:完全不可见时调用。如果启动的是Dialog时,onPause会被调用,onStop不会被调用;6.onDestroy:销毁时调用。7.onRestart:停止状态到运行状态。
        一共三种生存期:1.完整生存期:onCreate->onDestroy;2.可见生存期:onStart->onStop,可见;3.前台生存期:onResume->onPause,可交互。

                                                  《第一行代码》第三版之探究Activity(四)_Activity_02
3.4.4.体验Activity生命周期
        建立三个Activity,一个主入口MainActivity、一个DialogActivity和一个NormalActivity。DialogActivity是一个弹窗,其他与NormalActivity相同,唯一不同的是在xml中的声明,如下所示:

        <activity
            android:name=".DialogActivity"
            android:theme="@style/Theme.AppCompat.Dialog"></activity>
        <!--android:theme指定主题,使用@style/Theme.AppCompat.Dialog是使用对话框式的主题-->

        为了体验生命周期,MainActivity代码如下所示:

package com.example.myapplication

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*

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 onDestroy() {
        super.onDestroy()
        Log.d(tag,"onDestroy")
    }

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

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

   启动程序:

《第一行代码》第三版之探究Activity(四)_Android_03
   启动NormalActivity:

《第一行代码》第三版之探究Activity(四)_android_04
   返回MainActivity:

《第一行代码》第三版之探究Activity(四)_Android_05
   启动DialogActivity:

《第一行代码》第三版之探究Activity(四)_Kotlin_06
   点击Back返回:

《第一行代码》第三版之探究Activity(四)_Android_07
   Back键销毁:

《第一行代码》第三版之探究Activity(四)_Android_08
3.4.5.Activity被回收了怎么办?
        考虑一个问题,Activity进入停止状态是有可能被回收的,假设可能被回收的Activity有用户输入的文字之类的,系统回收之后没了,很影响用户体验,在这里我们使用onSavedInstance方法来保证Activity被回收之前一定会被调用。
       onSavedInstance方法携带一个Bundle类型的数据,Bundle提供了保存数据的一系列方法,putString保存字符串,putInt保存整数,这方法有两个参数:键和内容。

    override fun onSaveInstanceState(outState: Bundle?, outPersistentState: PersistableBundle?) {
        super.onSaveInstanceState(outState, outPersistentState)
        val tempData = "Something u just typed"
        outState?.putString("data_key", tempData)
}

        onCreate方法中调用、恢复:

        if (savedInstanceState != null) {
            val tempData = savedInstanceState.getString("data_key")
            Log.d(tag, tempData)
        }

       举一反三:(1)Intent也可以结合Bundle一起传递数据。Bundle对象置于intent当中进行传递;(2)横竖屏旋转也使得Activity经历重建过程,可以通过onSavedInstance保存数据,当然也有更优的方法。
3.5.Activity的启动模式
       启动模式有四种:standard、singleTop、singleTask、SingleInstance。通过xml中<activity>标签指定android:launhMode来选择启动模式。
3.5.1.standard
      默认启动模式,不显式指定采用这种。每当启动一个Activity,在返回栈入栈并处于栈顶位置。并不在乎是否已经在栈中存在,每次都会创建新实例。

《第一行代码》第三版之探究Activity(四)_Kotlin_09
3.5.2.singleTop
       Standard不太合理的地方在于Activity明明处于栈顶了,但再次启动还要创建新的实例。在这里我们选择SingleTop。SingleTop是在启动Activity时发现返回栈的栈顶已经是该Activity,则直接使用它,不用创建新实例。若该Activity虽然存在,但不是处于栈顶位置,仍然会创建新的实例。

《第一行代码》第三版之探究Activity(四)_android_10
3.5.3.singleTask
     singleTop的问题是若该Activity未处于栈顶位置,仍然会创建多个Activity实例。singleTask首先会检查返回栈中是否该实例,若已经存在则直接使用该实例,并将Activity之上的所有其他Activity统统出栈,若没有则创建新的Activity实例。

《第一行代码》第三版之探究Activity(四)_android_11
3.5.4.singleInstance
       singleInstance模式的Activity会启用一个新的返回栈来管理这个Activity。假设我们的程序中有一个Activity是允许其他程序调用,如果两个程序共享该实例,前三种无法做到。因为每个程序都会有自己的返回栈,同一个Activity在不同的返回栈入栈时必然会创建新的实例,在这种模式下,会有一个单独的任务栈来管理这个Activity,不管哪个程序访问,都共用同一个返回栈,解决了共享Activity实例的问题。
       举例来讲:有三个Activity,A跳B、B跳C,再点击两个back键。将B的launchmode设置为singleInstance,打印其TaskID。
A跳B、B跳C显示如下:

《第一行代码》第三版之探究Activity(四)_Android_12
       第一次Back键C跳A,因为处于同一个任务栈;第二次Back键,A跳B,因为当前返回栈清空,显示另一个返回栈栈顶B;最后一次Back键退出。

《第一行代码》第三版之探究Activity(四)_Android_13
3.6.Activity的最佳实践
3.6.1.知晓当前在哪一个Activity

       看别人代码往往不知道当前界面对应的Activity是哪一个,因此需要知晓当前是哪一个Activity?

package com.example.myapplication

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

//此代码无需注册,创建普通类,继承自AppCompatActivity并重写onCreate方法
open class BaseActivity:AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //JavaClass获取当前实例的class对象,然后使用simpleName获取当前实例的类名
        Log.i("BaseActivity",javaClass.simpleName)
    }
}

       接下来让BaseActivity称为所有Activity的父类,修改其他Activity继承结构,使它们不再继承AppCompatActivity,而是继承BaseActivity。又因为BaseActivity已经继承过了,所以其他功能不受影响。

《第一行代码》第三版之探究Activity(四)_Kotlin_14
3.6.2.随时随地退出程序
       目前程序停留在ThirdActivity,退出程序需要按好几次Back键。需要一个随时随地能够退出程序的方案。
       第一步:单例类管理Activity集合。

package com.example.myapplication

import android.app.Activity

object ActivityController {
    //单例类只需要一个Activity集合,通过ArrayList进行暂存
    private val activities = ArrayList<Activity>()

    //向ArrayList中添加
    fun addActivity(activity: Activity) {
        activities.add(activity)
    }

    //移除Activity
    fun removeActivity(activity: Activity) {
        activities.remove(activity)
    }

    //用于将所有Activity销毁
    fun finishAll() {
        for (activity in activities) {
            //需要判断Activity是否正处于销毁状态
            if (!activity.isFinishing)
                //最后进行销毁
                activity.finish()
        }
        activities.clear()
    }
}

       第二步:BaseActivity中添加或者移除Activity。

package com.example.myapplication

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

//此代码无需注册,创建普通类,继承自AppCompatActivity并重写onCreate方法
open class BaseActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //JavaClass获取当前实例的class对象,然后使用simpleName获取当前实例的类名
        Log.i("BaseActivity", javaClass.simpleName)
        ActivityController.addActivity(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        ActivityController.removeActivity(this)
    }
}

       第三步:何时何地的退出只需要调用ActivityController.finishAll()即可。

        button4.setOnClickListener{
            ActivityController.finishAll()
            //你甚至可以杀死一个进程,通过myPid获取当前进程id。killProcess只能杀死当前程序的进程,不能杀死其他程序
            android.os.Process.killProcess(android.os.Process.myPid())
        }
3.6.3.启动Activity的最佳写法
   刚才提到,Intent构建意图,startActivity或者startActivityForResult启动,使用putExtra携带多个键值对数据进行传递。简单来说如下:
        button3.setOnClickListener {
            val intent = Intent(this, ThirdActivity::class.java)
            intent.putExtra("param1", "data1")
            intent.putExtra("param2", "data2")
            startActivity(intent)
        }

3.6.3.启动Activity的最佳写法
       刚才提到,Intent构建意图,startActivity或者startActivityForResult启动,使用putExtra携带多个键值对数据进行传递。简单来说如下:

        button3.setOnClickListener {
            val intent = Intent(this, ThirdActivity::class.java)
            intent.putExtra("param1", "data1")
            intent.putExtra("param2", "data2")
            startActivity(intent)
        }

       为了使得同事之间协同更方便,了解传递了那些数据,我们采用了companion语法结构(类似于Java静态方法形式的调用)。更一目了然的代码如下:

package com.example.myapplication

import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_normal.*

class NormalActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_normal)
        button3.setOnClickListener {
            NormalActivity.actionStart(this,"data1","data2")
        }
    }

    companion object {
        fun actionStart(context: Context, data1: String, data2: String) {
            val intent = Intent(context, ThirdActivity::class.java)
            intent.putExtra("param1", data1)
            intent.putExtra("param2", data2)
            context.startActivity(intent)
        }
    }
}

3.7.Kotlin课堂:标准函数和静态方法
3.7.1.标准函数with、tun和apply

        Kotlin标准函数是Standard.kt文件中定义的函数,任何Kotlin代码可自由调用所有标准函数。
        with函数能够精简代码。接受两个参数,一个是任意类型的对象,另一个是lambda表达式。With函数会在Lambda表达式中提供第一个参数对象的上下文,并将该表达式最后一行代码作为返回值返回。实例代码如下:

Val result = with(obj){
//这里是obj上下文
“value”//with函数的返回值
}

       举例来讲:吃完水果列表的所有水果并打印出来,Kotlin代码如下所示:

//StringBuilder构建吃水果的字符串,最后将结果打印出来。
fun eatfruits() {
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    var builder = StringBuilder()
    builder.append("Starting eat fruit\n")
    for (fruit in list) {
        builder.append(fruit).append("\n")
    }
    builder.append("Ate all fruits.")
    val result = builder.toString()
    println(result)
}

       利用with简化后的代码如下所示:

//多次调用StringBuilder代码,考虑使用with方法使得代码更加精简
fun eatfruits_1() {
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    //with第一个参数传入StringBuilder对象,接下来Lambda的上下文都是该StringBuilder对象。
    val result = with(StringBuilder()) {
        append("Starting eat fruit\n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.\n")
        //Lambda的最后一行作为with函数的返回值返回,最终将结果打印出来
        toString()
    }
    println(result)
}

       标准函数Run函数与with非常类似,run函数不能直接调用,一定要调用某个对象的run函数,其次run函数只接受一个Lambda参数,并且会在Lambda表达式中提供调用对象的上下文,并将最后一行代码作为返回值返回。实例代码如下:

Val result = obj.run{
//这里是obj的上下文
“value”//run函数的返回值
}

       使用run函数修改吃水果的代码,如下所示:

fun eatfruits_2() {
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    val result =StringBuilder().run {
        append("Starting eat fruit\n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.\n")
        toString()
    }
    println(result)
}

       最后我们要介绍的是apply函数。apply函数与run函数极其类似,都是在某对象上调用,然后接受一个Lambda参数,也会提供响应上下文。不同的是apply函数无法指定返回值,而是会自动返回调用对象本身。实例代码如下:

val result = obj.apply{
   //这里是obj的上下文
}

       使用apply函数修改吃水果代码:

fun eatfruits_3() {
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    val result = StringBuilder().apply {
        append("Starting eat fruit\n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.\n")
        toString()
    }
    println(result.toString())
}

      实战一下,将下列代码转换为apply标准函数的代码进行精简。简化前:

     fun actionStart(context: Context, data1: String, data2: String) {
            val intent = Intent(context, ThirdActivity::class.java)
            intent.putExtra("param1", data1)
            intent.putExtra("param2", data2)
            context.startActivity(intent)
        }

      简化后的代码如下所示:

       fun actionStart1(context: Context, data1: String, data2: String) {
            val intent = Intent(context, ThirdActivity::class.java).apply {
                putExtra("param1", data1)
                putExtra("param2", data2)
            }
            context.startActivity(intent)
        }

3.7.2.定义静态方法
       静态方法又被称为类方法,无需创建实例就能调用。Java只要在方法上声明一个static关键字即可。与绝大多数编程语言不同的是,Kotlin极度弱化静态方法。Kotlin提供了比静态方法更好用的语法特性,这就是单例类。将如下的Java静态方法转换为Kotlin来实现。

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

       Kotlin实现:

//单例类
object Util {
    //doAction虽然不是静态方法,但仍然可以使用Util.doAction来实现
    fun doAcction() {
        println("do action")
    }
}

       使用单例类会将整个类中的所有方法都变成类似于静态方法的调用方法,若果只希望其中的一个是静态方法,就需要用到companion object关键字了。

package com.company
//1.将Util从单例类改成普通类
class Util {
    fun doAction1() {
        println("do action1")
    }
    //2.companion object里面的方法可以直接调用Util.doAction2()调用,前者必须要先创建Util类的实例才能调用
    companion object {
        fun doAction2() {
            println("do action2")
        }
    }
}

       本质上讲doAction2不是静态方法,componion object会在Util内部创建一个伴生类,doAction2其实就是伴生类的实例方法。Kotlin保证了Util类只会有一个伴生类对象,因此该调用实质上是Util类中伴生对象的doAction2方法。那么如果提供真正的静态方法?两种方案:注解和顶层方法。
       注解是在单例类或者ompanion object中的方法加上@JvmStatic注解。Kotlin编译器会将这些方法编译成真正的静态方法。如果加在普通方法上,会提示语法错误。如下所示:

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

       顶层方法是那些没有定义在任何类中的方法,譬如在main方法,Kotlin编译器会将所有顶层方法编译成静态方法。首先创建Kotlin文件,注明类型为File,此时出现一个KT结尾的文件,这文件中定义的任何方法都是顶层方法,都是静态方法。譬如创建一个Helper.Kt文件,里面内容如下:

package com.example.myapplication

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

       调用时直接调用doSomething即可。