西甲2015赛程表
最新安卓入门教程
扫码关注
获取更多技术干货

image

作者论坛ID:All_Blue    

https://github.com/Sakuragi

版权声明:本章节为安卓巴士博主原创文章,未经授权不得以?#25105;廡问?#36716;载发布/发表,违者将依法追究责任


2.1 活动概诉

前言

相信现在我们的安卓手机一定装着不少的应用,每当我们坐公交,地铁的时候我们会打开手机里的应用,这些应用有不同的界面,而这些在前台经常与我们用户直接打交道的正是活动(Activity)。本章,我们将会带你了解Activity。包括Activity的生命周期,以及Activity间的简单通信方式,还有Activity的四种启动模式。首先我们先来看看Activity是什么。

2.1 Activity是什么

如果说你打开一个应用见到的就是Activity,那么你可能大概已经猜到Activity是什么了吧?
没错,通俗的说Activity就是为应用程序提供一个窗口用于显示UI界面的。它可以包含显示界面的组件,用于与用户交互。Activity可以提供一个满屏的窗口,也可以提供一个浮动的窗口。一个应用可以包含0个或者多个Activity。

2.2 创建一个Activity

2.2 创建一个Activity

本小节,我们将会来学习如何创建一个Activity,并且将这个Activity运行到我们的手机上。

2.2.1创建一个Android工程

首先打开AS(Android Studio的简称,以下皆用此代替),选择Start a new Android Studio project,或者你可以在已经打开的项目里选择File->New->New Project

点击之后我们要给项目命名,这里我们命名为ActivityTest,项目命名尽量要有意义,与项目实际相符合。然后?#21069;?#21517;命名,这里我们命名为com.apkbus.activitytest完成后点击next

image

之后会出现选择SDK最低版本,这里我们选择16,继续点击next image

接下来是添加Activity,我们选Empty Activity,之后继续点击next:

image

之后出现的界面,我们点击finish就可以了。AS会自动帮我们创建一个MainActivity跟layout目录下的布局文件activity_main.xml

2.2.2创建一个新的Activity

经过上面的操作之后,我们的项目结构是这样的:

image

接下来我们要体验一下自己创建一个Activity,右键点击com.apkbus.activitytest包名,选择New->Kotlin File/Class;然后输入Name:SecondActivity

点击OK,你就会发现我们的Activity创建完成了。

2.2.3 给Activity创建一个布局文件

右键点击res目录下的layout,选择New->LayoutSource,如下图所示: image

我们命名为:activity_second,对于activity的布局命名我们都可以采用这种方式,activity_XXX的?#38382;健?#26368;后整个目录的结构是这样的:

app
 |---java
    |---MainActivity
    |---SecondActivity
 |---res
    |---layout
        |---activity_main.xml
        |---activity_second.xml

好,接下来我们打开我们的activity_second.xml文件,开始编写我们的布局。

<?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">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="This is the Second Activity!"/>


</LinearLayout>

很简单的一个布局,主要是在布局文件里增加了一个文?#31350;?#20214;TextView,并且空间显示内容为“This is the Second Activity!?#20445;?#25105;们可以点击右边的Preview预览一下:
image

LinearLayout我们先只要知道它是一个容器,可以存放其他控件。这里的TextView是一个文?#31350;?#20214;,它放置在LinearLayout这个容器里。TextView的几个属性解释如下:
layout_width:指定当前元素的宽度,match_parent表示与父容器一样宽,这里的父容器就是LinearLayout,对应的还有wrap_content,它表示当前元素的宽/高刚好能包围控件的内容就可以了。

layout_height:用于指定当前元素的高?#21462;?/p>

text:指定当前元素显示的文本内容。

创建好布局文件后,我们还需要在Activity中去加载这个布局文件。编辑SecondActivity,重写它的onCreate()方法,通过setContentView()方法,该方法可以接收一个int参数,我?#21069;?#24067;局ID给传入,就可以实现加载布局了:

class SecondActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)

    }

}

这里我们传入的是R.layout.activity_second,这个R是com.apkbus.activitytest包对应的R,?#19978;低?#33258;动帮我们生成的,千万别选错了。如此一来,我们的Activity便可以去加载这个布局了。

2.2.4 在Manifest中注册Activity

前面我们已经完成了加载布局,但是大功还没有告成,此时的Activity还不能真正的运行。在Android中,所有的Activity要在AndroidManifest.xml文件中注册才?#34892;В?#25152;以我们创建完成后,需要在AndroidManifest.xml中注册:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.apkbus.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=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

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

</manifest>

增加:<activity android:name=".SecondActivity"/>
Activity的注册声明需要放在<application></application>里面,通过<activity> </activity>标签进行注册。我们来看看上面的属性:

android:name :用来指定注册的Activity的名称。上面的“.MainActivity”和“.SecondActivity"表示当前包名下对应的Activity,“.?#26412;?#30456;当于“com.apkbus.activitytest.”

intent-filter: 它包含一个过滤规则,这里我们先只要知道:

这俩个过滤规则将MainActivity设置为我们程序启动的主Activity,当我们打开一个程序时,首先出现的就是这个MainActivity。

如果此时我们运行这个应用(shift+F10),那么会出现的是MainActivity的界面:

image

现在我们要让程序一启动就跳转到我们的SecondActivity,那么我们该怎么做呢?

我们前面提到了intent-filter,就是通过修改过滤规则来实现,将上面AndroidManifest.xml文件修改?#19978;?#38754;这样:

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

        <!--增加SecondActivity的过滤规则,并删除MainActivity的过滤规则-->
        <activity android:name=".SecondActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

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


    </application>

</manifest>

此时我们再次启动程序,我们可以看到,出现在我们面前的就是SecondActivity的界面了:

image

2.3 使用Intent进行Activity之间的跳转

2.3使用Intent进行Activity之间的跳转

Intent(意图)主要是用来解决Android中各个组件之间的通信,是各个组件连接的桥梁。

2.3.1 使用显示Intent

首先我们先修改SecondActivity的布局文件:

<?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:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点击跳转到MainActivity"/>


</LinearLayout>

删除TextView控件,增加一个Button控件。

android:id 是给Button定义一个标识符,这样我们就可以通过这个标识符找到Button这个控件,在Activity中,我们可以通过使用findViewById,来找到对应id的控件,它返回一个View对象,所以我们需要将它向下转型为Button。

找到控件之后,我们为Button控件设置点击事件,通过setOnClickListener(),当按钮点击之后,就会执行Onclick()方法。我们要在这个方法里实现Activity的跳转,通过Intent来实现。

Intent有很多构造函数,Intent(Context packageContext, Class<?> cls)这个构造函数接收俩个参数,第一个参数是启动活动所需要的上下文,Activity就是一个上下文环?#24120;?#25152;以这里我们可以传入SecondActivity,第二个参数是活动要启动的目标,这里我们要启动的是MainActivity,所以当然传入MainActivity。

启动一个Activity,Activity中有一个startActivity()方法,它接收一个Intent参数,通过这个Intent意图来启动对应的Activity,这里我们传入构建好的Intent。完整代码如下:

class SecondActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        //初始化button
        val mButton = findViewById(R.id.btn) as Button
        //设置点击事件监听
        mButton.setOnClickListener(View.OnClickListener {
            //响应点击事件,启动MainActivity
            val intent = Intent([email protected], MainActivity::class.java)
            startActivity(intent)
        })
    }

}

接下来,我们启动程序:
image

点击按钮,我们就会看到这时活动从SecondActivity跳转到了MainActivity:

image

2.3.2 使用隐式Intent

使用隐式Intent,我们可以通过给Activity添加过滤规则,当Intent中添加的过滤规则与Activity的过滤规则一致时,便可以启动该Activity。

修改AndroidManifest.xml文件,为MainActivity添加匹配规则:

 <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="com.test.filter"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

修改SecondActivity中点击事件,在Intent中添加过滤规则,android.intent.category.DEFAULT是一种默认的category,即使我们的Intent中不添加这个category,它也默认存在,所以我们就不用再去添加这个category。

        //设置点击事件监听
        mButton.setOnClickListener{
            //响应点击事件,启动MainActivity
            val intent = Intent()
            intent.action = "com.test.filter"
            startActivity(intent)
        }

这样一来,我们startActivity(intent)的时候,它会去匹配Activity的过滤规则,匹配action跟category,如果Activity中的某组过滤规则与Intent中的一致,则可以启动该Activity。

我们运行程序,点击查看结果如下:
image

2.4 Activity之间的数据传递

2.4 Activity之间的数据传递

通过对前面的Intent的学习,我们对Intent已经有了一定的了解,通过Intent我们可以去启动一个Activity。而Intent作为通信的桥梁,它的功能当然不止这样,我们还可以用Intent进行活动之间的通信。

2.4.1 使用intent extra

为了让我们在活动之间进行通信,Intent中提供了一系列的putExtra()方法的重载,我们可以在启动活动的时候,通过putExtra()将数据放入Intent中,等到启动另外一个活动时,再通过getIntent()方法获取Intent,然后从Intent中取出数据就可以了。修改点击事件

#SecondActivity.java

...
 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        //初始化button
        val mButton = findViewById(R.id.btn) as Button
        //设置点击事件监听
        mButton.setOnClickListener {
            //响应点击事件,启动MainActivity
            val intent = Intent([email protected], MainActivity::class.java)
            intent.putExtra("data", "data from second activity")
            startActivity(intent)
        }
    }
...

我们通过显示的方式来启动MainActivity,通过putExtra(...)方法向待启动的Activity传入参数。Intent.putExtra()?#34892;?#22810;重载方法,但它们都接受俩个参数,一个参数为固定的String类型的key,另一个为对应的值,该值可以为多种数据类型。我们这里传入了一个为data的key,并对应了一个String类型的值。

把MainActivity转换成Kotlin代码

打开MainActivity文件,然后选择Code -> Convert Java File to Kotlin File。就可以把MainActivity转换成kotlin文件了。

接下来,我们要从MainActivity中取出这个值,通过getIntent可以获取到这个Intent。如下:

#MainActivity.java

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //这里简写了getIntent方法实际上这里的intent相当于通过getIntent调用获取activity中的intent
        val intent = intent
        val data = intent.getStringExtra("data")
        Log.d("MainActivity", data)
    }
}

通过getIntent()(Kotlin中的get和set方法都可以简写)方法,我们可以获取到启动这个Activity的Intent,然后通过getStringExtra()方法,传入对应的键值,这个键值我们在SecondActivity中传入的是“data?#20445;?#25152;以这边也传入这个键值,与其对应。打开日志输出,输出如下:

 /com.apkbus.activitytest D/MainActivity: data from second activity

可以看到输出:data from second activity。除了getStringExtra外,还有很多其他方法来获取不同的数据类型,比如getIntExtra()来获取整型数据,getBooleanExtra()来获取?#32423;?#31867;型数据。

2.4.2 从上一个Activity中返回结果

通过之前的学习,我们了解到可以通过Intent来向下一个活动传递数据,那么要怎么从上一个活动返回数据给下一个活动呢?
Activity中有个startActivityForResult()的方法,它也可以用于启动活动,不同于startActivity()的是这个方法启动的activity在销毁返回上一个activity时可以返回一个结果。
修改SecondActivity的点击事件:

#SecondActivity.java

...
        mButton.setOnClickListener {
            //响应点击事件,启动MainActivity
            val intent = Intent([email protected], MainActivity::class.java)
            startActivityForResult(intent, 1)
        }
...

我们通过startActivityForResult来启动MainActivity,使用startActivityForResult()启动的Activity,可以在返回上一个Activity时通过回调onActivityesult()方法传值给上一个Activity。startActivityForResult()这里我们传入俩个参数,第一个是Intent,第二个是请求码,当从下一个返回到上一个activity时,回调返回这个requestCode,可以用来区分数据要返回到哪个activty。这里要注意,requestCode必须要大于0时才能激活回调。

#MainActivity.java
...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val intent = Intent()
        intent.putExtra("return_data", "data from MainActivity")
        setResult(Activity.RESULT_OK, intent)
    }

...

在MainActivity中通过setResult()方法设置返回结果,当从MainActivity返回SecondActivity时,将会返回setResult所设置的结果。setResult这里接收俩个参数,第一个是resultCode,一般来说resultCode有:Activity.RESULT_OK , Activity.RESULT_CANCELED,第二个是Intent,可以从Intent中获取需要的返回的数据。如果没有设置setResult(),当用户按下了返回键,则返回父Activity的resultCode会是Activity.RESULT_CANCELED。

在SecondActivity中重写onActivityResult()方法来获取从MainActivity返回的值:
onActivityResult(int requestCode, int resultCode, Intent data)
该方法有三个参数,第一个为requestCode,即我们在启动活动时传入的的请求码,第二个为resultCode,这里我们在setResult()方法中设置为RESULT_OK返回了。第三个是Intent,这个Intent可以带有数据返回。整体代码如下:

class SecondActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        //初始化button
        val mButton = findViewById(R.id.btn) as Button
        //设置点击事件监听
        mButton.setOnClickListener {
            //响应点击事件,启动MainActivity
            val intent = Intent([email protected], MainActivity::class.java)
            startActivityForResult(intent, 1)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        when (requestCode) {
            1 -> if (resultCode == Activity.RESULT_OK) {
                val str = data.getStringExtra("return_data")
                Log.d("SecondActivity", str)
            }
            else -> {
            }
        }
    }
}

然后我们点击按钮启动MainActivity后,再按Back键返回SecondActivity,查看日志输出:

/com.apkbus.activitytest D/SecondActivity: data from MainActivity

可以看到,我们能成功的接收到了MaiaActivity的返回数据。

2.5 Activity的生命周期

2.5 Activity的生命周期

每一个Activity实例都有它自己的生命周期,在它的生命周期内可能存在停止,运行,暂停,销毁四个状态。Activity的生命周期提供了七个回调方法:
onCreate()
Activity第一次创建的时候会调用这个方法,我们可以在这个方法里面做一些初始化的操作。比如加载布局,初始化View等?#21462;?/p>

onStart()
Activity正在被启动,已经可见,但是还没位于前台。

onResume()
Activity位于前台,并且可以与用户交互了。

onPause():
Activity处于正在停止的状态,通常当要离开这个Activity的时候会被调用。接下去onStop()马上会被调用,如果是弹出一个对话框,那么onStop不会被调用。

onStop():
Activity即将停止,Activity完全不可见。

onRestart():
这个方法表示Activity正在重新启动,活动由停止状态?#25351;?#20026;运行状态,通常由上一个Activity返回到这个Activity时,这个Activity会调用此方法。

onDestroy():
活动完全销毁前调用,可以在这做一些资源释放的操作。

见下图Activity的生命周期过程,可以帮我们更好的理解这整个过程: image
Activity的生命周期通常分为四个状态:

运行状态: 在该状态下活动对于用户来说是可见的,并且活动位于前台。 比如说activity的OnResume()回调后,活动就处于在运行状态。

暂停状态: 活动离开了前台,但是依然可见。对应于上图onPause()回调后。

停止状态: 活动离开了前台,并且不可见对应于上图onStop()回调后。

销毁状态: 活动完成了或被销毁了,比如说activity回调了onDestroy()方法后。

我们上面总是提到前台,那么活动位于前台是什么意思呢? Android是通过任务栈来管理Activity的,栈是一种后进先出的数据结构。在默认的情况下,我们每启动一个活动就会被加入栈顶,在栈顶的Activity就是我们可见的位于前台的Activity。

2.5.1 跟踪Activity的生命周期

接下来我们将通过代码来跟踪Activity的生命周期,以更好的理解Activity的生命周期。我们在SecondActivity中添加生命周期方法,代码如下:

#SecondActivity.java

class SecondActivity : AppCompatActivity() {

    //设置TAG,用于Log打印日志TAG
    private val TAG = SecondActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        Log.d(TAG,"onCreate");
        //初始化button
        val mButton = findViewById(R.id.btn) as Button
        //设置点击事件监听
        mButton.setOnClickListener {
            //响应点击事件,启动MainActivity
            val intent = Intent([email protected], MainActivity::class.java)
            startActivityForResult(intent, 1)
        }
    }

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

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

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

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

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


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

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        when (requestCode) {
            1 -> if (resultCode == Activity.RESULT_OK) {
                val str = data.getStringExtra("return_data")
                Log.d("SecondActivity", str)
            }
            else -> {
            }
        }
    }
}

我们启动应用程序,查看日志:

D/SecondActivity onCreate
D/SecondActivity onStart
D/SecondActivity onResume

Activity的生命周期在启动后经历了onCreate()-->onStart()-->onResume(),然后Activity显示在前台,可以供用户交互了。

接下去我们点击跳转到MainActivity,然后再按back键返回到SecondActivity:

D/SecondActivity onCreate
D/SecondActivity onStart
D/SecondActivity onResume

D/SecondActivity onPause
D/SecondActivity onStop
D/SecondActivity onRestart
D/SecondActivity onStart
D/SecondActivity onResume

当将要离开这个Activity的时候会执行onPause()方法,然后执行onStop()方法,此时Activity已经不可见,当从MainActivity返回到SecondActivty时,SecondActivty的onStart()方法又会被调用,接着回调onResume()方法,Activity?#31181;?#26032;处于可见状态。

然后我们继续点击back键,销毁SecondActivty,此时onDestroy()方法被回调:

D/SecondActivity onCreate
D/SecondActivity onStart
D/SecondActivity onResume

D/SecondActivity onPause
D/SecondActivity onStop
D/SecondActivity onRestart
D/SecondActivity onStart
D/SecondActivity onResume


D/SecondActivity onPause
D/SecondActivity onStop
D/SecondActivity onDestroy

即将离开Activity,onPause()回调,然后onStop(),最后回调onDestroy(),Activity被销毁。

2.5.2 异常状态下的生命周期

试想一下,如果我们启动了Activity A,然后在A上又启动了Activity B,此时A处于不可见状态,突然内存不足,A被销毁回收了,此时我们从B返回A,那么A会怎么样?
答案是A还是会正常显示,Activity提供了onSaveInstanceState()方法供我们在异常状态下保存一些数据。当Activity被异常终止的时候,?#20302;?#20250;调用onSaveInstanceState()方法,来保存当前Activity的一些状态。然后重新创建Activity,调用onRestoreInstanceState()方法,这个方法传入一个Bundle参数,这个Bundle参数就是在onSaveInstanceState()里面保存的Bundle。此时的生命周期大致如下:

image

除了因为内存不足导致的Activity异常终止,还有资源配置上的变化?#19981;?#23548;致Activity的异常终止,比如说屏幕旋转。我们通过代码来重现下这个过程。在SecondActivity中添加如下代码:

 #SecondActivity.java

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        val saveData = "save data"
        outState.putString("key", saveData)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        val getData = savedInstanceState.getString("key")
        Log.d(TAG, getData)
    }

在onSaveInstanceState()里存储的Bundle数据可以在onRestoreInstanceState或者onCreate()中获取得到,这俩者不同的是,只要onRestoreInstanceState被调用,则Bundle参数传入的值一定有数据。onCreate()则可以没有数据,接下来,我们旋转屏幕,观察打印的数据。

image

看红色的方框,我们可以看到我们保存的数据被打印出来了。

2.6 Activity的四种启动模式

2.6 Activity的四种启动模式

Android有四种启动模式:standard,singleTop,singleTask,singleInstance。通过Activity标签下的android:lanchMode属性可以指定。

2.6.1 standard

在了解启动模式之前,我们先来了解一下安卓中活动的管理,在安卓中,是以任务栈来管理Activity的,栈是一种先入后出的数据结构。每次启动一个活动,都会把Activity放入一个指定的栈当中,一般情况下,任务栈只有一个。每当按下back键就会有一个活动从栈中弹出,直到栈中没有任何的活动,?#20302;?#23601;会回收这个任务栈。一个任务栈可以有多个实例,每一个实例也可以属于不同的任务栈。

那么当我们每次启动一个Activity的时候,都会把这个Activity压入栈中置于栈顶。当我们重?#21019;?#24314;同一个Activity的时候,?#20302;?#37117;会把这些实例一一放入任务栈中,而我们的启动模式就可以选择Activity压入任务栈的场景。

standard为Activity的默认启动默认,即如果你不指定启动模式,则Activity都以这种模式启动。当Activty设置为standrad的时候,每次启动一个Activity都会重新创建一个实例压入任务?#24674;小?#36890;过代码来实践下这个过程,修改布局文件activity_second.xml中的Button的text:

#activity_second.xml
<?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:id="@+id/btn"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Button"/>


</LinearLayout>

然后修改SecondActivity中的点击事件,使其点击按钮重?#21019;?#24314;当前的Activity:

#SecondActivity.java
...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        Log.d(TAG, "onCreate")
        //通过id来找到对应的控件
        val mButton = findViewById(R.id.btn) as Button
        //设置点击事件监听
        mButton.setOnClickListener{
            //响应点击事件,重复启动当前的Activity
            val intent = Intent([email protected], SecondActivity::class.java)
            startActivity(intent)
        }
    }
...

然后启动Activity,点击俩次按钮,然后我?#21069;?#19979;Back键,会发现当前任务栈中有三个SecondActivity的实例。即我?#21069;?#19977;次回退键才能从SecondActivity中退出。

2.6.2 singleTop

singleTop模式下如果发现任务栈栈顶已经存在这个实例了,那么就不会重新创建这个实例,并?#19968;?#22238;调onNewIntent()方法。此时,这个Activity的onCreate、onStart方法不会被调用。

我们修改AndroidManifest.xml文件,为SecondActivity指定启动模式:

#AndroidManifest.xml
...
 <activity android:name=".SecondActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

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

修改SecondActivity中的点击事件,并且重写它的onNewIntent方法:

#SecondActivity.java
...
  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        Log.d(TAG, "onCreate")
        //通过id来找到对应的控件
        val mButton = findViewById(R.id.btn) as Button
        //设置点击事件监听
        mButton.setOnClickListener{
            val intent = Intent([email protected], SecondActivity::class.java)
            intent.putExtra("key", "singleTop")
            startActivity(intent)
        }
    }


    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        Log.d(TAG, "onNewIntent,get data: " + intent.getStringExtra("key"))
    }
...

然后启动应用程序,点击俩次button,然后按下Back键,会发现按下一次back键就退出了应用,?#24471;?#29616;在任务栈中一直复用着这个Activity,并没有重?#21019;?#24314;新的Activity。查看日志:

 23:11:13.165 29686-29686/? D/SecondActivity: onCreate
 23:11:13.169 29686-29686/? D/SecondActivity: onStart
 23:11:13.169 29686-29686/? D/SecondActivity: onResume
 23:11:30.573 29686-29686/com.apkbus.activitytest D/SecondActivity: onPause
 23:11:30.573 29686-29686/com.apkbus.activitytest D/SecondActivity: onNewIntent,get data: singleTop
 23:11:30.573 29686-29686/com.apkbus.activitytest D/SecondActivity: onResume
 23:11:31.257 29686-29686/com.apkbus.activitytest D/SecondActivity: onPause
 23:11:31.257 29686-29686/com.apkbus.activitytest D/SecondActivity: onNewIntent,get data: singleTop
 23:11:31.257 29686-29686/com.apkbus.activitytest D/SecondActivity: onResume

可以看当设置为singleTop时,当栈顶复用了这个Activity时不会执行Activity的onCreate和onStart方法,同时会回调onNewIntent方法。

2.6.3 singleTask

singleTop可以复用栈顶的Activity,倘若Activity不处于栈顶,而是位于栈中,那么singleTop就无法解决Activity重?#21019;?#24314;的问题了。那么我们就要使用singleTask了,当活动设置为singleTask启动模式,那么只要指定的这个栈中存在这个Activity的实例,那么就会弹出这个栈中这个Activity上面的所有实例,然后把这个Activity置顶,singleTask有clear top的效果。并?#19968;?#20250;执行onNewIntent方法。

上面提到指定栈,默认情况下,Activity的任务栈名都为应用的包名。而当我们的Activity启动模式为singleTask时,我们可以通过设置taskAffinity属性,这个属性值不能与包名相同。通过设置这个属性值,该Activty将会启动在拥有这个属性?#24471;?#30340;任务?#24674;小?/p>

接下来我们修改AndroidManifest.xml文件,将MianActivity设置为singleTask,并且将其设置为默认启动Activity,如下:

#AndroidManifest.xml

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

    <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:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

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

</manifest>

修改activity_main.xml文件,增加一个按钮:

#activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    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"
    tools:context="com.example.administrator.activitylifecycletest.MainActivity">

    <Button
        android:id="@+id/btn_mian"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button in MainActivty"/>

</android.support.constraint.ConstraintLayout>

然后在MainActivty中添加点击事件,使其跳转到SecondActivity:

#MainActivty.java
...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val mButton = findViewById(R.id.btn_mian) as Button
        mButton.setOnClickListener {
            val intent = Intent([email protected], SecondActivity::class.java)
            startActivity(intent)
        }
    }
    ...

修改SecondActivity中的点击事件:

#SecondActivity.java
...
   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        Log.d(TAG, "onCreate")
        //通过id来找到对应的控件
        val mButton = findViewById(R.id.btn) as Button
        //设置点击事件监听
        mButton.setOnClickListener{
            val intent = Intent([email protected], MainActivity::class.java)
            intent.putExtra("key", "singleTop")
            startActivity(intent)
        }
    }
    ...

通过前面的学习,我们先来猜测一下,当我们当我们从MainActivty中点击跳转到SecondActivity,再从SecondActivity跳转到MainActivty时,我们点击back键会出现什么现象呢?这时应该不会退回到SecondActivity,而是直接退出应用。

我们启动应用程序,来验证结果。点击MainActivty的按钮跳转到SecondActivity,再点击SecondActivity中的按钮跳转回MainActivty,按下Back键,直接退出应用,我们的假设得到验证。

如果是standrad模式,那么情况又会是怎么样呢?若为Standrad模式那?#21019;?#26102;任务栈应该有三个Activty实例,那么我们得按下三次才会退出应用。

2.6.4 singleInstance

这个模式可以理解为加强版的singleTask,,拥有?#22235;?#24335;的Activty将会单独的位于一个任务?#24674;小?#21516;时它也拥有singleTask的其他特性。

新增ThirdActivty,以及其布局文件,暂且不为其设置点击事件:

#activity_third.xml
<?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:id="@+id/btn_third"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="this is 3rd btn"/>

</LinearLayout>
#ThirdActivty.java
class ThirdActivty : AppCompatActivity() {

    override fun onCreate( savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_third)
        Log.d("TAG", "ThirdActivty onCreate task id  " + getTaskId())
    }
}

修改SecondActvity的点击事件,并且打印TaskId:

@SecondActvity.java
...
  @Override
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        //打印TaskId
        Log.d("TAG", "SecondActivity onCreate getTaskId " + getTaskId());
        //通过id来找到对应的控件
        val mButton = findViewById(R.id.btn) as Button;
        //设置点击事件监听
        mButton.setOnClickListener {
            val intent = Intent([email protected], ThirdActivty::class.java)
            startActivity(intent)
        }
    }
    ...

在MainActivty中也打印出Task id:

 @Override
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("TAG","MainActivity onCreate getTaskId "+getTaskId());
        val mButton = findViewById(R.id.btn_mian) as Button
        mButton.setOnClickListener {
            val intent = Intent([email protected], SecondActivity::class.java)
            startActivity(intent)

        }
    }

最后修改AndroidManifest.xml,注册ThirdActivity,以及修改SecondActivity启动模式为singleInstance:

...
 <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">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name=".SecondActivity"
                  android:launchMode="singleInstance">
        </activity>

        <activity android:name=".ThirdActivty"/>

    </application>
    ...

过滤TAG为“TAG”的日志,启动应用,查看打印出来的日志:


D/TAG: MainActivity onCreate getTaskId 42
com.apkbus.activitytest D/TAG: SecondActivity onCreate getTaskId 43
com.apkbus.activitytest D/TAG: ThirdActivty onCreate task id 42

通过Task Id我们发现MainActivity与ThirdActivity处于同一个任务栈,SecondActivity位于单独的一个Activity,这时我?#21069;聰路?#22238;键,会发现直接跳到了MainActivity,这是因为MainActivity与ThirdActivity处于同一个任务栈,弹出ThirdActivity后,可见的就是MainActivty了,再按下Back键,任务栈中没有实例,那么就被回收了,可见的就是位于单独任务栈中的SecondActivity了

2.7 Activity的最佳实践

启动活动的最佳写法

2.7.1 启动活动的最佳写法

在我们实?#22763;?#21457;中,我们往往要向一个Activity里传入参数,所以我们需要封装一个方法,以让我们清晰的知道要传给这个Activity什么参数。比如我们要启动ThirdActivty:

#ThirdActivty.java

    companion object{
        fun start(activty: Activity, data: Int) {
            val intent = Intent(activty, ThirdActivty::class.java)
            intent.putExtra("data", data)
            activty.startActivity(intent)
        }
    }

然后我们在其他的Activity中就可以通过ThirdActivty.start()方法,传入一个Activty参数,而且,可以清楚的知道启动该Activity该传入什么数据。传入参数后我们就可以启动ThirdActivty了。

2.7.2 BaseActivty来管理Activty

有时候我们希望结束某个Activity的时候就结束这个应用,而不是一个一个的回退Activity退出,那么我们就可以写一个BaseActivity,使其他的Activity?#22363;?#33258;这个Activity,来对Activity进行统一的管理,如下:

open class BaseActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activityStack.add(this)
    }

    fun finishActivity(simpleName: String) {
        for (activity in activityStack) {
            if (activity.javaClass.getSimpleName() == simpleName) {
                activity.finish()
                activityStack.remove(activity)
                break
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        activityStack.remove(this)
    }

    companion object {
        //存放activity的栈
        val activityStack = Stack<Activity>()

        fun put(a: Activity) {
            activityStack.add(a)
        }
        /**
         * 结束所有的Activity
         */
        fun finishAll() {
            while (!activityStack.empty()) {
                activityStack.pop().finish()
            }
        }
    }
}

在上面我们用一个栈来存放Activity,每当启动一个activity的时候,我们都会在onCreate中将其压入栈中,销毁的时候,将其移除。另外我们提供了结束所有Activity的方法,这个方法判断栈中是否还存在Activity,如果存在则弹出逐一销毁。此外还提供了一个销毁特定Activity的方法,通过变量栈中的Activity,找到对应名称的Activity,然后销毁。

通过这个BaseActivity我们可以更方便的管理Activity


欢迎大家对本教程的内容进行刊错和纠正,点击此处?#24052;?#35770;坛给你?#19981;?#30340;文章作者给予支持鼓励,若有问题反馈和建议请注明具体章节
西甲2015赛程表 墨尔本胜利蒙通联 急速赛车开奖记录 西班牙人巴拉多利德回放 巴塞罗那vs皇家马德里 一分赛车开奖统一吗 伯恩利vs切尔西比赛 弗赖堡天气 第戎天气 美因茨vs霍芬海姆 七乐彩开奖查询结果