Android入门 | Activity篇

2020-04-16  本文已影响0人  青年心路

1.什么是 Activity

Actuvity 是最容易吸引用户的地方,它是一种可以包含用户界面的组件,主要用于和用户进行交互。一个应用中可以包含0个或多个 Activity,但不包含任何 Activity 的应用程序是无法被用户看见的。

2.Activity 的基本用法

2.1 手动创建 Activity

点击 Empty Activity 创建名为 FirstActivity 的 Activity


image image

勾选 Generate Layout File 表示会创建一个对应的布局文件,勾选Launcher Activity 表示会将 FirstActivity 设置为当前项目的主 Activity,点击Finish。

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() 方法即可。

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" /> 两行声明即可,修改后的代码如下所示:

<?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 文件夹

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 去启动。

<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.example.activitytest.ACTION_START" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
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,这就让多个应用程序之间有了共享的可能。

3.2.3 向下一个 Activity 传递数据

image

3.2.4 返回数据给上一个 Activity

注意:按照上面的书写方式用户必须点击按钮才可以将数据返回,如果点击了 back 键则不会返回数据,要想改变这个情况,需要在 SecondActivity 中再重写一个方法。

4.Activity 的生命周期

4.1 返回栈

Android 是使用任务来管理 Activity 的,一个任务就是一组存放在栈里的 Activity 的集合,这个栈也被称为返回栈(back stack)。栈是一种后进先出的数据结构,在默认情况下,每当我们启动新的 Activity,它就会在返回栈中入栈,并处于栈顶的位置。而每当我们按下 Back 键或调用 finish() 方法后,处于栈顶的 Activity 就会出栈,前一个入栈的 Activity 就会重新处在栈顶的位置,下图展示了返回栈如何管理 Activity 入栈出栈操作。

image

4.2 Activity 状态

4.3 Activity 的生存期

Activity 类中定义了7个回调方法,覆盖了 Activity 生命周期的每一个环节,下面就来介绍一下这7个方法。

注意:以上7个方法除了 onRestart() 方法,其它的都是两两相对的,从而又可以将 Activity 分为一下3种生存期。

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 对象

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

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 函数的返回值
}

它可以在连续调用同一个对象的多个方法时让代码变得更加精简

需求:有一个水果列表,现在我们想吃完所有水果,并将结果打印出来

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 中可以以注解和顶层方法实现。

上一篇下一篇

猜你喜欢

热点阅读