Activity的生命周期
返回栈 Android是使用任务(task)来管理Activity的,一个任务就是一组存放在栈里的Activity的集合,这个栈也被称为返回栈。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的Activity,它就会在返回栈中入栈,并处于栈顶的位置。每当我们按下Back键或调用finish()方法销毁一个Activity时,处于栈顶的Activity就会出栈,前一个入栈的Activity就会重新处于栈顶的位置,系统总会显示处于栈顶的Activity给用户。
Activtiy的四种状态: 1 运行状态 当Activity位于栈顶时,Activity就处于运行状态。 回收会造成很差的用户体验(如游戏闪退)
2暂停状态 当Activity不位于栈顶,但仍然可见时,Activity就进入了暂停状态。 因为有些Activity可能只会占据屏幕的部分区域,所以导致前一个Activity并没有被完全覆盖,即不位于栈顶,依然可见。比如:对话框形式的Activity,虽然位于栈顶,但前一个Activity仍然可见。所以系统也不愿意回收暂停状态的Activity,(回收可见的Activity会对用户造成很不好的体验)。只有内存极低,系统才会回收
3停止状态 当Activity不再处于栈顶,并且完全不可见,Activity为停止状态。比如Activity被新的Activity完全覆盖,系统会为这种Activity保持相应的状态和成员变量。但一旦其他地方需要内存,系统很可能会回收。(类似于手机一键清理操作,清理时,只有栈顶的Activity不会被回收,停止状态的Activity会被回收)
4销毁状态 Activity从返回栈中被移除后就变成了销毁状态。系统最倾向于回收这种状态的Activity保证内存充足
Activity的生存周期 Activity类中定义了7个回调方法,覆盖了Activtiy生命周期的每一个环节。
onCreate(). 每个Activity中都重写了这个方法,它会在Activity第一次被创建的时候调用。一般在这个方法中完成Activity的初始化操作,比如加载布局,绑定事件等 onStart(). 这个方法在不可见变成可见的时候调用 onResume().这个方法在Activity准备好和用户进行交互的时候调用。调用后,Activity一定位于返回栈的栈顶,并处于运行状态 onPause().这个方法在系统准备去启动或者恢复另一个Activity的时候调用。通常在这个方法中,将一些消耗的CPU资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然就会影响到新的栈顶Activity的使用。调用后,Activity不再位于前台。 onStop(),这个方法在Activity完全不可见的时候调用。 和OnPause的区别是,如果启动的新Activity是对话框式的Activity,那么onPause方法会执行,onStop不会执行。因为onStop()之后代表Activity完全不可见。 onDestroy(),这个方法在Activty被销毁之前调用,调用之后Activity的状态变成销毁状态。 onRestrat()。这个方法在Activity由停止状态变为运行状态之前调用,也就是Activity被重新启动了。(调用此方法相当于回到了刚刚调用onCreate方法之后。并且onCreate没有被再调用,因为Activity并没有重新创建)
可以将Activity分成3种生存期:
完整生存期:Activity在onCreate()方法和onDestory()方法之间所经历的就是完整生存期。一般情况下,一个Activity会在onCreate()方法中完成各种初始化操作,在onDestory方法中,完成释放内存 的操作。
可见生存期:Activity在onStart()方法和onStop()方法经历的是可见生存期,在期间内,Activity对于用户总是可见的,即便可能无法与用户交互(被新Activity不完全覆盖).可以通过通过这两个方法合理管理那些对用户可见的资源。比如在onStart中对资源加载,在onStop方法中对资源进行释放,从而保证处于停止状态的Activity不会占用过多的内存。
前台生存期:Activity在onResume()方法和onPause()方法经历的是前台生存期,在前台生存期,Activity总是处于运行状态,并且可以和用户进行交互。
创建两个子Activity,一个普通Activity,一个对话框式的Activity 体验Activity的生命周期:
<?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>
<activity android:name=".DialogActivity"
android:theme="@style/Theme.AppCompat.Dialog">
</activity>
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")
}
}
在AndroidManifest.xml中,使用 android:theme属性为当前Activity指定主题,内置有很多的主题可以选择,也可以定制自己的主题。这里的@style/Theme.AppCompat.Dialog就是让我们使用对话框式的主题的意思。
Activity被回收了怎么办? 在ActivityA的基础上启动ActivityB。在内存不足的情况下,系统将A回收。此时用户如果按下Back键返回ActivityA,会正常显示。 不过此时执行的不是onRestart方法而是执行onCreate方法,即在这种情况下A被重新创建了一次,因为A已经被回收过了。 问题是A中可能存在临时的数据和状态,比如A中有一个文本输入框,输入一段文字后,启动了B。这时如果A由于系统不足被回收了。当你通过Back键回到A时,发现输入的文字没了。这是因为MainActivity被重新创建了。 Activity中提供了一个onSaveInstanceState()回调方法,这个方法可以保证Activity被回收之前一定调用。 onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法保存数据。比如使用putStirng()保存字符串,putInt保存整数,以此类推。 每个保存方法要传入两个参数,第一个是键,用于后面从Bundle中取值。第二个参数是真正保存的内容。
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
super.onSaveInstanceState(outState, outPersistentState)
val tmpData =" My some tempdatas"
outState.putString("data_key",tmpData)
}
保存好了数据,在哪里恢复呢? 一直使用的onCreate()方法有一个Bundle类型的参数。这个参数一般情况都是null。 但是如果在Activity被系统回收之前,通过onSaveInstanceState()方法保存数据,这个参数就会带有之前保存的全部数据,只需要再通过取值方法把数据取出即可。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if(savedInstanceState!=null)
{
val temData= savedInstanceState.getString("data_key")
Log.d(tag,"tempData is $temData")
}
}
当ActivityA被系统将要回收时,通过onSaveInstanceState()将数据保存在Bundle中。这样,从B返回A时,由于创建了新的A,所以会再次调用A的onCreate()方法。此时的Bundle并不为空,所以能从Bundle中把之前存储的临时值取出。注意:一定是因为A被系统回收了,要创建的ActivityA才会再次调用onCreate方法。如果只是常规的按Back键返回,A调用的是onRestart()方法再调用onStart和onResume,而不是onCreate()方法。这样savedInstanceState这个Bundle的值仍然为空
Bundle保存和数据的的用法和Intent类似,Bundle也可以和Intent结合一起使用,将需要传递的数据保存在Bundle的对象中,再将Bundle的对象存放在Intent中。到了目标的Activity后,先从Intent中取出Bundle,再从Bundle中取出数据。
**另外当手机屏幕发生旋转的时候,Activity也会经历一个重新创建的过程,因此在这种情况下,Activity的临时数据也会丢失。**虽然也可以通过onSaveInstanceState()方法解决,但是不太建议,对于横竖屏旋转的情况,后续有更加优雅的解决方案(ViewModel)。
Activity的启动模式
Activity一共有四种启动模式,分别是standard,sigleTop,sigleTask,和sigleInstance
standard standard是Activity的默认启动模式,在不进行显式指定的情况下,所有Activity都会用这种启动模式。Android是使用返回栈来管理Activity的,standard模式下,每当启动一共新的Activty的时候,它就会在返回栈中入栈,并处于栈顶的位置对于使用standard模式的Activity,系统不会在乎Activity是否已经在返回栈中存在,每次启动都会创建一个该Activity的新实例。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.first_layout)
Log.d("FirstActivity",this.toString())
button1.setOnClickListener {
val intent =Intent (this,FirstActivity::class.java)
startActivity(intent)
}
}
在FirstActivity的基础上启动FirstActivity,逻辑上讲没有意义。但从打印信息上可以看出,每点击一次按钮,就会创建出一个新的FirstActivity实例,此时返回栈中也会存在3个FirstActivity的实例,因此需要按三次Back键才能退出程序。
singleTop
singleTop模式,当Activity启动模式指定为singleTop时,在启动Activity时发现返回栈的栈顶已经是该Activity,则认为可以直接使用它,不会再创建新的Activity实例。
<activity android:name=".FirstActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
运行程序,发现已经创建了一个FristActivity的实例,但之后无论点击多少次都不会再出现打印信息。是因为FirstActivity已经处于返回栈的栈顶,每当想要再启动一个FirstActivity时,都会直接使用栈顶的Activity,因此FistsActivity只会有一个实例,仅按一次Back键就可以退出程序。 但要注意的是,当FirstActivity并未处于栈顶位置时,再启动FirstActivity还是会创建新的实例。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.first_layout)
Log.d("FirstActivity",this.toString())
button1.setOnClickListener {
val intent =Intent (this,SecondActivity::class.java)
startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
Log.d("SecondActivity",this.toString())
button2.setOnClickListener {
val intent = Intent(this,FirstActivity::class.java)
startActivity(intent)
}
}
运行完程序,发现系统创建了两个不同的FirtstActivity实例,这是因为SecondActivity再次启动FirstActivity时,栈顶的Activity是SecondActivity,因此会创建一个新的FirstActivity实例。现在按Back会返回到SecondActivity,再按下Back键又会回到FirstActivity,再一次Back才会退出程序。
singleTask
使用singleTop可以很好地解决重复创建栈顶Activity问题,但如果该Activity并没有处于栈顶位置,那么singleTop启动模式下的Activity还是会创建多个Activity实例。那怎么能让某个Activity在整个应用程序中的上下文中都只存在一个实例呢? 那就是singleTask启动模式,当Activity的启动模式指定为singleTask时,每次启动该Activity,系统首先会在返回栈中检查该Activity的实例,如果发现已经存在,那么直接使用该实例,并把在这个Activity之上(之下的不会)的所有其他Activity统统出栈,如果没有发现就会创建一个新的Activity实例
<activity android:name=".FirstActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
override fun onRestart() {
super.onRestart()
Log.d("FirstActivity","onRestart")
}
override fun onDestroy() {
super.onDestroy()
Log.d("SecondActivity","onDestory")
}
在SecondActivity中启动FirstActivity时,会发现返回栈中已经存在一个FirstActivity的实例,并且在SecondActivty下面。于是SecondActivity会从返回栈中出栈,而FirstActivity重新成为了栈顶。故FirstActivity的onRestart()方法和SecondActivity的onDestory方法都会得到执行。 现在返回栈中只剩下FisrtActivity的实例,按一下Back键即可退出程序
singleInstance
不同于上面3种启动模式,指定为singleInstance模式的Activity会启用一个新的返回栈俩管理这个Activity。(如果singleTask模式下指定了不同的taskAffinity,也会启动一个新的返回栈)。
这个启动模式有什么意义呢?假设我们程序中有一个Activity是允许其他程序调用的,如果想实现其他程序和我们的程序共享这个Activity的实例,如何实现呢? 使用前面的启动模式无法做到,因为每一个应用程序有自己的返回栈,同一个Activity在不同的返回栈中入栈时必然创建了新的实例。而使用singleInstance模式可以解决。
在singleInstance的这种模式下,会有一个单独的返回栈来管理这个Activity,不管是哪个应用程序来访问这个Activity,都共用一个返回栈,也就解决了共享Activity实例的问题。
<activity android:name=".SecondActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
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)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
Log.d("SecondActivity","Task id is $taskId")
button2.setOnClickListener {
val intent = Intent(this,ThirdActivity::class.java)
startActivity(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("ThirdActivity","Task id is $taskId")
setContentView(R.layout.activity_third)
}
发现SecondActivity的Task id不同于FirstActivity 和ThirdActivity,说明SecondActivity是存放在单独的返回栈里的,而且这个栈里面只有SecondActivity这一个Activity。(注意如果有多个Activity的启动方式都是设置的singleInstance模式,那么这些Activity都会有自己的一个单独的返回栈)
按下Back键进行返回,发现ThirdActivity竟然返回到了FirstActivity,再按Back,返回到SecondActivity,再按Back退出程序。因为FirstActivity和ThirdActivity是存放在同一个栈里的,所以在ThirdActivity是栈顶时按下Back时,ThirdActivity出栈,那么FirstActivity变成了栈顶Activity。再按Back时,此时当前返回栈已经空了,所以显示了另一个返回栈的栈顶Activity,也就是SecondActivity。最后按下Back,所有返回栈都空了,也就退出了程序。
Activity的实践技巧
关于Activity的一些实践技巧
知晓当前是在哪一个Activity?
根据程序当前的界面就能判断出这是哪一个Activity.接手别人代码的时候,可以通过这个办法,快速找到界面对应的Activity是哪一个。
新建一个BaseActivity类,这个类和普通的Activity的创建方式不一样,因为不需要让BaseActivity在AndroidManifest.xml中注册,所以选择创建一个普通的Kotlin类就可以了。然后让BaseActivity继承自AppcompatActivity,重写onCreate方法即可。
open class BaseActivity :AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("BaseActivity",javaClass.simpleName)
}
}
在onCreate方法中加了一行日志,用于打印当前实例的类名。注意,Kotlin中的javaClss表示获取当前实例的class对象,相当于在java中调用getClass()方法,而Kotlin中的BaseActivity::class.java表示获取BaseActivity类的class对象,相当于在Java中调用BaseActivity.class. 在上面的代码中,我们先是获取了当前实例的Class对象,再调用simpleName获取当前实例的类名。
我们需要让BaseActivity成为Activity项目中所有Activity的父类,在BaseActivity类名前面加关键字open,让BaseActivity可以被继承。如何修改所有普通Activity的继承结构,让它们不再继承自AppcompatActivity,而是继承自BaseActivity。由于BaseActivity又是继承AppcompatActivity的,所有项目中所有Activity的现有功能并不受影响,它们仍然继承Activity的所有特性。
这样,每当我们进入一个Activity的界面,该Activity的类名就会被打印出来,这样我们就可以时刻知晓当前界面对应的是哪一个Activity了。
如何随时随地退出程序? 有时候想退出程序很不方便,如前面singleInstance那段代码示例,要按三次Back键才能退出。按Home键只能把程序挂起,并没有退出程序。如果我们的程序需要注销或者退出功能,那又如何实现呢?需要一个能随时随地退出程序的方案才行。 其实只需要一个专门的集合来对所有的Activity进行管理,配合BaseActivity即可
新建一个单例类ActivityCollector作为Activity的集合
object ActivityCollector {
private val activites = ArrayList<Activity>()
fun addActivity(activity :Activity)
{
activites.add(activity)
}
fun removeActivity(activity: Activity)
{
activites.remove(activity)
}
fun finishAll()
{
for (activity in activites)
{
if(!activity.isFinishing)
{
activity.finish()
}
}
activites.clear()
}
}
使用了一个单例类,是因为全局只需要一个Activity集合。在集合中,通过一个ArrayList来暂存Activity。 提供一个addActivity()方法,用于向ArrayList中添加Activity; 提供一个removeActivity()方法用于从ArrayList中移除Activity; 最后提供一个finishAll()方法,用于将Arraylist中存储的Activity全部销毁。
注意,在销毁Activity之前,我们需要先调用activity.isFinishing来判断Activity是否正在销毁中,因为Activity还有可能通过按下Back键等方式销毁Activity,如果该Activity没有正在销毁中,我们再调用它的finish()方法来销毁它。
open class BaseActivity :AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("BaseActivity",javaClass.simpleName)
ActivityCollector.addActivity(this)
}
override fun onDestroy() {
super.onDestroy()
ActivityCollector.removeActivity(this)
}
}
class ThirdActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_third)
button3.setOnClickListener {
ActivityCollector.finishAll()
}
}
}
在BaseActivy的onCreate方法中调用了ActivityCollector的addActivity()方法,表明将当前正在创建的Activity添加到集合里。(每当新建一个Activity由于普通Activity中的onCreate()方法都被调用,故都会将其添加到集合里面) 然后在BaseActivity中重写onDestroy()方法,并调用了ActivityColler()的removeActivity()方法,表明从集合里移除一个马上要销毁的Activity。(只要有Activity调用了onDestroy()方法,集合中就会移除这个Activity) 后面不管想在什么地方退出程序,只需要调用 ActivityCollector.finishAll()方法就可以了。
还可以在销毁所有Activity的代码后面再加上杀掉当前进程的代码,以保证程序完全退出,杀掉进程的代码如下:
android.os.Process.killProcess(android.os.Process.myPid())
killProcess()用于杀掉一个进程,它接收一个进程id参数,我们可以通过mypid()方法来获得当前程序的进程id。需要注意的是killProcess()只能用于杀掉当前程序的进程,不能用于杀掉其他程序。
启动Activity的最佳方法? 启动Activity的方法已经很熟悉了,首先通过Intent构建出当前的“意图”,然后调用startActivity()或者startActivityForResult()方法将Activity启动起来,如果有数据需要也可以在Activity之间传递,也可以借助Intent来完成。 假如SecondActivity中需要用到两个非常重要的字符串参数,在启动Activity必须传递过来的时候,通常会这么写:
val intent= Intent(this,SecondActivity::class.java)
intent.putExtra("param1",data1)
intent.putExtra("param2",data2)
context.startActivity(intent)
这样写虽然正确,但是实际上再项目开发中会考虑到对接的问题。比如自己开发的部分需要启动SecondActivity,但是不清楚SecondActivity需要传递哪些数据。这样通常只有两个办法,一个是自己阅读SecondActivity的代码,另一个问负责编写SecondActivity的人。换一种写法就能轻松解决这个问题:
class SecondActivity : BaseActivity() {
...
companion object
{
fun actionStart(context: Context,data1:String, data2:String)
{
val intent= Intent(context,SecondActivity::class.java)
intent.putExtra("param1",data1)
intent.putExtra("param2",data2)
context.startActivity(intent)
}
}
}
这里用了一个新的语法结构companion object,并在companion object中定义了一个actionStart()方法。之所以这样写,是因为Kotlin规定,所有定义在companion object中的方法,都可以使用类似于静态方法的调用。 看actionStart()方法,在这个方法中完成了Intent的构建,另外所有SecondActivity中需要的数据都是通过actionStart()方法的参数传递过来的,然后把它们存储知道Intent中,最后调用startActivity()启动SecondActivity。(注意这里构建Intent的第一个参数是上下文,所有对应的Activity调用此方法时,用对应的上下文即可。常规Activity填this就好,fragment中要另作考虑)
重要的是一目了然,SecondActivity需要的数据在方法的参数中全部都体现出来了,所有可以清晰地知道启动SecondActivity需要传递哪些数据。另外,简化了启动Activity的代码。
button1.setOnClickListener {
SecondActivity.actionStart(this,"data1","data2")
}
养成一个好的习惯,给编写的每个Activity都添加类似的启动方法,这样可以让启动Activity变得更加简单,还可以节省询问时间。
Kotlin:标准函数with,run,apply
Kotlin中的标准函数指的是Standard.kt文件中定义的函数,任何Kotlin代码都可以自由地调用所有的标准函数。
with函数
with函数接收两个参数:第一个参数可以是任意类型的对象,第二个参数是一个Lambda表达式。with函数会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回。
val result = with(obj) { //这里是obj的上下文 “value” //with函数的返回值 }
with函数作用:它可以在连续调用同一个对象多个方法时,让代码变得更加精简
val list = listOf("1","2","3")
val builder= StringBuilder()
builder.append("start\n")
for (num in list)
{
builder.append(num).append("\n")
}
builder.append("end")
val result =builder.toString()
println(result)
代码逻辑很简单,就是用StringBuider来构建字符串,最后将结果打印。但是发现连续调用了很多次builder对象的方法,这样我们可以考虑用with函数让代码更加精简。
fun AddString()
{
val list = listOf("1","2","3")
val result = with(StringBuilder())
{
append("start\n")
for (num in list)
{
append(num).append("\n")
}
append("end")
toString()
}
println(result)
}
这里首先给with函数的第一个参数传入一个StringBuilder对象,那么接下来整个Lambda表达式的上下文就会是这个StirngBuilder对象。于是我们在Lmabda表达式就不要那样调用 builder.append() 和 builder.toString()方法了,而是可以直接调用append()和toString()方法。( 也就是不需要用实例化的对象来直接调用方法,在with函数里面的Lambda表达式中,将可以直接调用传入参数的那个对象的方法)
run函数
run函数的用法和使用场景和with函数是非常类似的,只是语法有所改动。run函数通常不会直接调用,而是要在某个对象的基础上调用;第二run函数值接收一个Lambda参数,并且会在Lambda表达式中提供调用对象的上下文。其他方面和with函数一致。包括会使用Lambda表达式中最后一行的代码作为返回值返回
val result = obj.run{ //这里是obj的上下文 “value” //run函数的返回值 }
用run函数实现上面那段代码
val list = listOf("1","2","3")
val result = StringBuilder().run {
append("start\n")
for (num in list)
{
append(num).append("\n")
}
append("end")
toString()
}
println(result)
总体来说变化很小,只是将调用with函数并传入StringBuilder对象改成了调用StringBuilder对象的run方法。
apply函数 apply函数和run函数相类似,都是要在对象上调用,并且只能接受一个Lambda参数,也会在Lmabda表达式中提供调用对象的上下文,但是apply函数无法指定返回值,而是会自动返回调用对象本身。
val result = obj.apply{ //这里是obj的上下文 } //result==obj
val list = listOf("1","2","3")
val result = StringBuilder().apply{
append("start\n")
for (num in list)
{
append(num).append("\n")
}
append("end")
}
println(result.toString())
注意,由于apply函数无法指定返回值,只能返回调用对象本身,因此这里的result实际上是一个StringBuilder对象,所以我们在最后打印的时候还要调用它的toString()方法才行。
标准函数大多数情况下可以相互转换,但是还是要掌握好区别,以便编程的时候,作出最佳选择。
val intent= Intent(context,SecondActivity::class.java)
intent.putExtra("param1",data1)
intent.putExtra("param2",data2)
context.startActivity(intent)
这里每传一个参数,就要调用一次intent.putExtra()方法,所以可以用标准函数来精简。
val intent= Intent(context,SecondActivity::class.java).apply {
putExtra("param1",data1)
putExtra("param2",data2)
}
context.startActivity(intent)
由于Lambda表达式的中的上下文就是Intent对象,所以可以直接调用putExtra()方法。
Kotlin:定义静态方法
静态方法在某些语言中又叫类方法,指的是那种不需要创建实例就能调用的方法,所有主流的编程语都支持静态方法这个特性。
在java中定义一个静态方法很简单,只需要在方法上声明一个static关键字就可以了
public class Util
{
public static void doAction()
{
System.out.prinln("do action");
}
}
这是一个非常简单的工具类,上面的doAction()方法就是一个静态方法。调用静态方法不需要创建类的实例,而是可以直接以Util.doAction()这种写法来调用。因而静态方法非常适合于编写工具类,因为工具类通常没有创建实例的必要,基本上全局通用。
Kotiln弱化了静态方法的概念,因为Kotlin有比静态方法更好的语法特性,单例类。
像工具类这种功能,在Kotlin中,就非常推荐用单例类的方式来实现,如上面的工具类,用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")
}
}
}
这里让Util先变成一个普通类,然后在类中直接定义了doAction1()方法,在 companion object中定义doAction2()方法。这样两个方法就有了本质的区别。因为doAction1()方法一定要先创建Util的实例才能调用,但是doAciton2()方法可以直接通过Util.doAction2()的方式来调用(类似于静态方法)
不过doAciton2()方法并不是静态方法,companion object这个关键字实际上会在Util类的内部创建一个伴生类,而doAction2()方法就是定义在这个伴生类里面的实例方法。只是Kotlin会保证Util类始终只存在一个伴生类对象,因此调用Util.doAction2()方法,实际上就是调用了Util类伴生对象的doAction2()方法。 Kotlin中没有直接定义静态方法的关键字,但是提供一些语法特性支持类似于静态方法的调用。
如果要定义真正的静态方法,Kotlin提供了两种实现方式,注解和顶层方法。
先是注解,前面的单例类和companion object只是在语法的形式上模仿了静态方法的调用方式,实际上它们都不是静态方法,所以在Java代码汇中,以静态方法的形式去调用,发现这些方法并不存在。而如果我们给单例类或companion object中的方法加上 @JvmStatic注解,那么Kotlin编译器就会把这些方法编译成真正的静态方法。
companion object
{
@JvmStatic
fun doAction2()
{
println("do Action2")
}
}
注意, @JvmStatic注解只能加在单例类或companion object 中的方法上,如果尝试加在普通方法上,会提示语法错误。
由于doAction2()方法,已经成为了真正的静态方法,那么现在不管Kotlin中还是在Java中,都可以使用Util.doAciton2()的写法来调用了。
顶层方法
**顶层方法值的是那些没有定义在任何类中的方法。**比如之前编写的main()方法。Kotlin会将所有的顶层方法都全部编译成静态方法,因此只要你定义了一个顶层方法,那么它就一定是静态方法。
想要定义一个顶层方法,首先创建一个Kotlin文件,(创建类型选择File)。这里创建一个名字为Helper.kt的文件。这样我们在这个文件中定义的任何方法都是顶层方法。比如这里定义一个doSomething()的方法。
fun doSomething()
{
println("do Something")
}
如果是在Kotlin代码中调用的话,由于顶层方法可以在任何位置被直接调用,不用管包名路径,也不用创建实例,直接输入doSomething()即可调用。
**但如果在Java代码中,发现找不到doSomething()方法,因为Java中没有顶层方法这个概念,所有的方法必须定义在类中。**那么doSomething()方法到底被藏在哪里呢? 我们刚才创建的Kotlin文件叫Helper.kt,于是Kotlin编译器会自动创建一个叫HelperKt的Java类,doSomething()方法就是以静态方法的形式定义在HelperKt类里面的,因此在Java中使用HelperK.doSomething()的写法来调用就可以了。
public class JavaTest {
public static void main(String[]args)
{
HelperKt.doSomething();
}
}
|