欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

Android中的Activity

时间:2023-06-18

说明: 本文是郭霖《第一行代码-第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。即在标签内添加标签,然后加入action和categoty的两句声明语句

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


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。可以定义item的id和title,item相当于是一个菜单项,id是它唯一的标识

<?xml version="1.0" encoding="utf-8"?>

如何添加这个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 { // 判断传入的是哪一个Itemwhen(item.itemId) { // 如果item.itemId == R.id.add_itemR.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,也不需要配置

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

Intent

1.显示Intent

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

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

2.隐式Intent

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

只有和中的内容同时匹配Intent中指定的action和category时,该Activity才能响应Intent

// AndroidManifest.xml// 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")// 添加categoryintent.addCategory("com.example.activitytest.MY_CATEGORY")startActivity(intent)

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

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

更多隐式Intent的用法

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

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

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

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

// 调用系统拨打电话界面val intent = Intent(Intent.ACTION_DIAL)intent.data = Uri.parse("tel:1008") //指定协议是telstartActivity(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的IntentLog.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状态

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

Activity的生存期

小结:

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

可以在onStart()内加载用户可见的资源,在onStop()内销毁用户可见的资源可以在onResume()内加载和用户交互的资源,在onStop()内销毁和用户交互的资源

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

使用对话框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有四种启动模式。

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

使用taskId查看当前返回栈的id,本质上是getTaskID()方法的语法糖

启动模式在AndroidManifest.xml中更改

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

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

随时随地退出程序

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

object ActivityCollector { private val activities = ArrayList() 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")

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。