Android中的Activity

说明: 本文是郭霖《第一行代码-第3版》的读书笔记

Activity是包含用户界面的组件,主要用于和用户交互,一个应用程序中可以包含一个或多个Activity

3.2 基本用法

创建一个Empty Activity,之后自己来添加Activity

手动创建Activity

project模式下,在app/src/main/java/com.example.projectname目录下新建一个Activity。

项目的任何Activity都应该重写OnCreate()方法。

创建和加载布局

Android程序设计讲究逻辑和视图分离。最好每一个Activity都能对应一个布局

如果没有在创建Activity时勾选Generate Layout File,则需要我们在res目录下手动添加一个layout文件。

app/src/main/res/目录下创建layout文件夹,添加layout resource file,这是一个xml文件。

OnCreate()内通过setContentView()方法来传入布局文件,参数为布局文件的ID,如:

setContentView(R.layout.first_layout)

项目中添加的任何资源文件都会在R文件中生成一个相应资源的对应ID

在AndroidManifest文件中注册

所有的Activity都需要在AndroidManifest.xml中注册才能生效。但实际上Studio已经帮我们注册了FirstActivity。

除此之外,程序要想正常允许还需要配置主Activity。即在<activity>标签内添加<intent-filter>标签,然后加入actioncategoty的两句声明语句

此时程序就可以正常运行了。


Toast

Toast是Android提供的一种提醒方式,可以将一些短小的信息通知给用户。自动消失、不占屏幕空间

现在想点击button触发toast,首先需要找到定义在Layout文件中的Button,可以根据ID来找

  • findViewById()获取布局文件中控件的实例,但是在Kotlin中,会在app/buid.gradle文件头部引入一个kotlin-android-extensions插件,可以根据布局文件中控件的ID自动生成一个具有相同名称的变量,因此可以不需要调用findViewById()

var button1: Button = findViewById(R.id.button1)

//定义button的按下监听事件
button1.setOnClickListener{
    //使用Toast,Toast.makeText()静态方法创建一个Toast对象,再调用show()显示
    //Toast.makeText()需传入三个参数:1.Context,2.显示的文本内容, 3.Toast显示的时长(内置有Toast.LENGTH_SHORT和LONG)
    //Activity本身就是一个Content对象,所以这里可以直接传入this
    Toast.makeText(this, "You clicked button1", Toast.LENGTH_SHORT).show()
}

在Activity中使用Menu

首先在res文件夹下新建一个menu文件夹,新建一个Android resource file,这是一个xml文件,选择menu类型。然后可以在menu文件中添加item。可以定义itemidtitle,item相当于是一个菜单项,id是它唯一的标识

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

如何添加这个menu呢?

需要在主Activity中重写onCreateOptionsMenu()方法。这里可以使用Ctrl+O快捷键得到重写下拉表

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    // menuInflater实际上来自getMenuInflater(),是kotlin中的语法糖
    // 调用inflate()方法,给当前Activity创建菜单,第一个参数表示要使用哪一个menu文件,第二个参数指定这一个菜单项添加到哪一个
    // Menu对象中,这里直接添加到传入的这个menu中
	menuInflater.inflate(R.menu.menu, menu)
	return true //true表示允许这个menu显示
}

接下来可以定义菜单响应事件,

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    // 判断传入的是哪一个Item
	when(item.itemId) {
        // 如果item.itemId == R.id.add_item
		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
}

销毁一个Activity

只需要按一下Back键就可以销毁当前Activity, 代码实现是调用finish()方法

3.3 使用Intent在Activity之间穿梭

如果有多个Activity,如何从主Activity跳到另外一个Activity呢?

再建一个Activity和相应的布局文件,Android Studio已经帮我们在AndroidManifest.xml中注册了这个新建的Activity。

另外由于这个Activity不是主Activity,也不需要配置<intent-filter>

至此,这个新的Activity已创建完成,接下来是启动它。

Intent

1.显示Intent

Intent有多个构造函数的重载,其中一个是Intent(Context packageContext, Class<?> cls),构建出intent后,再用startActivity()方法接收intent参数,来启动Activity

// 这里的SecondActivity::class.java相当于Java中的SecondActivity::class
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)

2.隐式Intent

隐式Intent通过指定actioncategory等信息,交由系统分析启动哪个Activity

  • 只有<action><category>中的内容同时匹配Intent中指定的actioncategory时,该Activity才能响应Intent
// AndroidManifest.xml
<activity android:name=".SecondActivity">
	<intent-filter>
		<action android:name="com.example.activitytest.ACTION_START" />
		<category android:name="android.intent.category.DEFAULT"/>
	</intent-filter>
</activity>
// FirstActivity.kt
// 在xml中指定了SecondActivity可以响应"com.example.activitytest.ACTION_START"这个action(自己定义的)
// 而android.intent.category.DEFAULT是默认的category,调用startActivity()方法时会自动将其添加进intent中
val intent = Intent("com.example.activitytest.ACTION_START")
startActivity(intent)
  • 每个Intent只能指定一个action,但能指定多个category

此时如果我们在intent上添加一个categoty

val intent = Intent("com.example.activitytest.ACTION_START")
// 添加category
intent.addCategory("com.example.activitytest.MY_CATEGORY")
startActivity(intent)

此时程序就相应不了了,因为我们的SecondActivity并没有这个Category

我感觉是<Intent-filter>中可以指定多个category,只要能满足intent的category就可以响应该intent,但不能少于intent的category

更多隐式Intent的用法

使用隐式的Intent,不仅可以启动本程序的Activity,也可以启动其他程序的Activity,使多个程序之间的功能共享成为可能。

// 指定Intent的Action,这是Android系统内置的Action
val intent = Intent(Intent.ACTION_VIEW)
// 通过Uri.Parse()方法将网址字符串解析为Uri对象,再调用Intent的setData()方法将Uri对象传进去
intent.data = Uri.parse("https://www.baidu.com/") //指定协议是https
startActivity(intent) //start的是哪个activity?

也可以创建一个新的Activity,指定其Action为android.intent.action.VIEW,以及data来响应这个intent:

<activity android:name=".ThirdActivity">
	<intent-filter tools:ignore="AppLinkUrlError">
		<action android:name="android.intent.action.VIEW"/>
		<category android:name="android.intent.category.DEFAULT"/>
		<data android:scheme="https"/>
	</intent-filter>
</activity>

除了https协议外,还可以指定其他协议,比如geo为地理位置,tel为拨打电话。

// 调用系统拨打电话界面
val intent = Intent(Intent.ACTION_DIAL)
intent.data = Uri.parse("tel:1008") //指定协议是tel
startActivity(intent) //start的是哪个activity?

如何向下一个Activity传递数据

思路:Intent中提供了一系列putExtra()方法的重载,因此可以把数据先暂存在Intent中,启动另一个activity后,再将数据取出。

//传递数据
val data = "Hello, SecondActivity!"
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("extra_data", data)

//------------------------------------------
//另一个Activity中取出数据
val extraData = intent.getStringExtra("extra_data") // intent其实是getIntent(),即启动第二个Activity的Intent
Log.d("SecondActivity","extra data is $extraData")

返回数据给上一个Activity

// 首先需要在启动Activity的时候选择这个方法,这样在此Activity销毁的时候才能够回调onActivityResult方法
val intent = Intent(this, SecondActivity::class.java)
startActivityForResult(intent, 1) // 第二个参数是请求码,是唯一值就可以

//------------------------------------------
// 点击button2让其返回数据
val intent = Intent() //创建一个空的Intent保存数据
intent.putExtra("data_return", "Hello FirstActivity") //将要返回的数据保存到intent中
setResult(Activity.RESULT_OK, intent) //这个方法专门用于向上一个Activity返回数据,第二个参数就是把intent给传递回去
finish() //销毁当前Activity, 从而回调onActivityResult()方法

//------------------------------------------
// 第一个Activity中重写onActivityResult()方法
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    // 首先根据请求码判断数据来源,再根据resultCode判断结果是否处理正确
    when (requestCode) {
        1 -> if (resultCode == Activity.RESULT_OK) {
            val returnData = data?.getStringExtra("data_return") //这里不能用intent来获取数据
            Log.d("FirstActivity", "returned data is $returnData")
        }
    }
}

上述的onActivityResult()方法是在activity销毁的时候才调用的,为了点击Back键也能保存要返回的数据,需要重写OnBackPressed()方法

override fun onBackPressed() {
    val intent = Intent()
    intent.putExtra("data_return", "Hello FirstActivity")
    setResult(Activity.RESULT_OK, intent)
	//finish()
    super.onBackPressed() //如果自己不调用这个父类的处理方法,你就要自己写 finish()
}

3.4 Activity的生命周期

返回栈

Android的Activity是可以层叠的,放在了栈中,启动新的Activity的时候入栈,Back的时候出栈,栈顶的Activity显示在当前界面。

Activity状态

  1. 运行状态,位于返回栈的栈顶时处于该状态
  2. 暂停状态,不处于栈顶,但仍然可见时,如对话框弹出的背景Activity。此时Activity是仍然存活的,系统一般不会回收。
  3. 停止状态,当Activity不处于栈顶,完全不可见的时,有可能被系统回收
  4. 销毁状态,当Activity从返回栈中pop后就处于该状态,系统最倾向于回收这种状态的Activity

Activity的生存期

在这里插入图片描述

小结:

  1. 当Activity对用户可见时,是在onStart()onStop()
  2. 当Activity准备好和用户交互时(当然是可见的),是在onResume()onPause()
  3. onCreate()onDestroy()经历的是完整的生命周期,前者进行各种初始化操作,后者释放资源
  • 可以在onStart()内加载用户可见的资源,在onStop()内销毁用户可见的资源
  • 可以在onResume()内加载和用户交互的资源,在onStop()内销毁和用户交互的资源

感觉生命周期还是挺重要的,可以帮助管理好资源

使用对话框Activity

 <! -- 在AndroidManifest.xml中配置对话框Activity-->
<activity android:name=".DialogActivity"
	android:theme="@style/Theme.AppCompat.Dialog">
</activity>

Activity被回收了怎么办

当Activity进入Stop状态时是有可能被系统回收的,此时再返回该Activity,临时数据和状态就会被销毁。此时执行的是onCreate()方法。我们可以重写onSaveInstanceState()方法,此方法在Activity在销毁前一定会被调用。

// 接受Bundle类型的参数,提供了一系列的方法用来保存数据
override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    val tempData = "Something you just typed."
    val intData = 1024
    outState.putString("data_key", tempData)
    outState.putInt("data2_key", intData)
}

如何取出数据呢?如果内存被回收,则返回Activity时会调用onCreate()方法,此时就可以在onCreate()中加载保存的数据

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    Log.d(tag, "onCreate() called")
    // 加载保存的数据
    if (savedInstanceState != null) {
        val tempData = savedInstanceState.getString("data_key")
        val intData = savedInstanceState.getInt("data2_key")
        Log.d(tag, "tempData is $tempData and $intData")
    }
}

这里需要注意两点:

  • Intent可以和Bundle一起用来传递数据,将数据存入Bundle里,再将Bundle传入Intent中,传给下一个Activity。
  • 当手机的屏幕发生旋转时,Activity也会经历一个重新创建的过程,虽然也可用上述的onSaveInstanceState()方法来保存数据,但是有更优雅的解决方案。

3.5 Activity的启动模式

Activity有四种启动模式。

  1. standard,是默认的启动模式。系统不在乎返回栈中是否已有Activity,每次启动都会创建一个该Activity的新实例。
  2. singleTop,该模式下,若返回栈的栈顶已经是该Activity,则直接使用,不会创建新的实例。
  3. singleTask, 该模式下,整个返回栈中只会存在该Activity的一个实例,启动该Activity时,系统会先检查返回栈中是否存在该Activity的实例,若存在,则直接使用该实例,并将在其之上的其他Activity统统出栈
  4. singleInstance,该模式下,会有一个单独的返回栈管理这个Activity,便于不同应用程序共享Activity。前面三种模式,都是一个应用程序有一个返回栈。按Back返回时,会先返回该返回栈中的Activity,直到本返回栈为空,才返回下一个返回栈中的Activity。当全部返回栈为空时,就退出了应用程序。
  • 使用taskId查看当前返回栈的id,本质上是getTaskID()方法的语法糖

启动模式在AndroidManifest.xml中更改

<activity
    android:name=".FirstActivity"
    <!-- 设置启动模式 -->  
    android:launchMode="singleTop" 
    android:label="This is FirstActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

3.6 Activity的最佳实践

如何判断当前处于哪个Activity

当阅读别人的项目时,有时我们无法知晓各个界面对应的是哪个Activity,这时有一个小技巧。

编写一个BasicClass,让其打印所在类的名称:

package com.example.activitytest

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

open class BasicActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // javaClass表示获取当前实例的Class对象,相当于JAva中的getClass()方法,simpleName获取当前实例类的类名
        // 而BaicActivity::class.java表示获取BasicActivity类的Class对象
        Log.d("BaseActivity", javaClass.simpleName)
    }
}

再让所有的Activity继承这个Basic类而非之前的AppCompatActivity

这样每当我们进入一个界面时就会打印当前界面对应的Activity类名。

随时随地退出程序

当我们打开了多个Activity,需要一个一个Back才能退出,这样会显得很麻烦。可以通过写一个单例类来管理这些Activity并提供一个finishAll()方法。

object ActivityCollector {
    private val activities = ArrayList<Activity>()

    fun addActivity(activity: Activity) {
        activities.add(activity)
    }

    fun removeActivities(activity: Activity) {
        activities.remove(activity)
    }

    fun finishAll() {
        for (activity in activities) {
            // 判断Activity是否正在销毁
            if (!activity.isFinishing) {
                activity.finish()
            }
        }
        activities.clear()
    }
}

接下来修改之前写的BasicActivity,在onCreate()onDestroy()方法内添加单例类的add和remove:

open class BasicActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("BaseActivity", javaClass.simpleName)
		
        ActivityCollector.addActivity(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        ActivityCollector.removeActivities(this)
    }
}

现在想随时退出程序,只需要调用ActivityCollector单例类的finnishAll()方法即可。

button3.setOnClickListener {
    //点击button3直接全部退出
    ActivityCollector.finishAll()
    // 为保证程序安全退出,可以杀掉当前进程,killProcess()只能用于杀掉当前程序的进程。
    android.os.Process.killProcess(android.os.Process.myPid())
}

启动Activity的最佳写法

前面学过,通过构建Intent再调用startActicity()或者startActivityForResult()来启动Activity,数据传递也可以借用Intent和Bundle完成。

问题:当某个Activity不是由你开发的,但启动它需要传参,怎么传参呢?

要么询问作者,要么自己看代码。为了方便别人,可以给每个Activity启动时都编写启动方法如下:

// 在你写的这个Activity类中加入
// companion object 使得其内的方法变为静态方法
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)
    }
}

// 那在启动这个Activity的时候可以简化成如下形式,则传参一目了然。
SecondActivity.actionStart(this, "data1", "data2")
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值