【Android -- 四大组件】Activity

660 阅读13分钟

一、Activity的生命周期

1.1 生命周期图

1.2 可见状态图

1.3 周期图

1.4 状态解释

生命周期方法作用说明
onCreate正在被创建activity 被创建时调用,一般在这个方法中进行活动的初始化工作,如设置布局工作、加载数据、绑定控件等。
onRestart正在重新启动这个回调代表了 Activity 由完全不可见重新变为可见的过程,当 Activity 经历了 onStop() 回调变为完全不可见后,如果用户返回原 Activity,便会触发该回调,并且紧接着会触发 onStart() 来使活动重新可见。
onStart正在被启动经历该回调后,Activity 由不可见变为可见,但此时处于后台可见,还不能和用户进行交互。
onResume已经可见已经可见的 Activity 从后台来到前台,可以和用户进行交互。
onPause正在停止当用户启动了新的 Activity ,原来的 Activity 不再处于前台,也无法与用户进行交互,并且紧接着就会调用 onStop() 方法,但如果用户这时立刻按返回键回到原 Activity ,就会调用 onResume() 方法让活动重新回到前台。而且在官方文档中给出了说明,不允许在 onPause() 方法中执行耗时操作,因为这会影响到新 Activity 的启动。
onStop即将停止这个回调代表了 Activity 由可见变为完全不可见,在这里可以进行一些稍微重量级的操作。需要注意的是,处于 onPause() 和 onStop() 回调后的 Activity 优先级很低,当有优先级更高的应用需要内存时,该应用就会被杀死,那么当再次返回原 Activity 的时候,会重新调用 Activity 的onCreate()方法。
onDestroy即将被销毁来到了这个回调,说明 Activity 即将被销毁,应该将资源的回收和释放工作在该方法中执行。

二、 生命周期分析

2.1 常见情况下生命周期的回调

情况回调
第一次启动onCreate() -> onStart() -> onResume()
从 A 跳转到 BA_onPause() -> B_onCreate() -> B_onStart() -> B_onResume() -> A_onStop()
从 B 再次回到 AB_onPause() -> A_onRestart() -> A_onStart() -> A_onResume() -> B_onStop()
用户按 home 键onPause() -> onStop()
按 home 键后回到应用onRestart() -> onStart() -> onResume()
用户按电源键屏保onPause() -> onStop()
用户按电源键亮屏onRestart() -> onStart() -> onResume()
用户按 back 键回退onPause() -> onStop() -> onDestroy()

2.2 关于生命周期常见问题

问题回调
由活动 A 启动活动 B时,活动 A 的 onPause() 与 活动 B 的 onResume() 哪一个先执行?活动 A 的 onPause() 先执行,活动 B 的 onResume() 方法后执行
标准 Dialog 是否会对生命周期产生影响没有影响
全屏 Dialog 是否会对生命周期产生影响没有影响
主题为 Dialog 的 Activity 是否会对生命周期产生影响有影响,与跳转 Activity 一样

2.3 异常情况:资源配置相关

  • 资源相关的系统配置发生改变导致Activity被杀死并重新创建

分析:当系统配置发生更改后,Activity会被销毁,其onPause、onStop、onDestroy都会被调用,由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态(这个方法只会出现在Activity异常终止的情况下,正常情况下不会调用这个方法)。

Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数传递给onRestoreInstanceStateonCreate方法。

因此,可以通过onRestoreInstanceStateonCreate方法来判断Activity是否被重建了,如果被重建了,我们就可以取出之前保存的数据并恢复,从时序上来说,onRestoreInstanceState的调用时机在onStart之后。

2.4 异常情况:资源内存不足

  • 资源内存不足导致优先级低的Activity被杀死

Activity优先级从高到低可以分为以下三种:

  • 前台Activity ——正在和用户交互的Activity,优先级最高。

  • 可见但并非前台Activity——比如Activity中弹出一个对话框,导致Activity可见,但是位于后台无法和用户交互。

  • 后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。

当系统内存不足时,系统就会按照上述优先级去杀死目标Activity所在的进程,并在后续onSaveInstanceStateonRestoreInstanceState来存储和恢复数据。

2.5 异常情况下的处理

  在发生异常情况后,用户再次回到 Activity,原 Activity 会重新建立,原已有的数据就会丢失,比如用户操作改变了一些属性值,重建之后用户就看不到之前操作的结果,在异常的情况下如何给用户带来好的体验,有两种办法。

2.5.1 数据保存

  第一种就是系统提供的 onSaveInstanceState 和 onRestoreInstanceState 方法,onSaveInstanceState 方法会在 Activity 异常销毁之前调用,用来保存需要保存的数据,onRestoreInstanceState 方法在 Activity 重建之后获取保存的数据。

  在活动异常销毁之前,系统会调用 onSaveInstanceState,可以在 Bundle 类型的参数中保存想要的信息,之后这个 Bundle 对象会作为参数传递给 onRestoreInstanceState 和 onCreate 方法,这样在重新创建时就可以获取数据了。

  关于 onSaveInstanceState 与 onRestoreInstanceState 方法需要注意的一些问题:

  1. onSaveInstanceState 方法的调用时机是在 onStop 之前,与 onPause 没有固定的时序关系。而 onRestoreInstanceState 方法则是在 onStart 之后调用。

  2. 正常情况下的活动销毁并不会调用这两个方法,只有当活动异常销毁并且有机会重现展示的时候才会进行调用,除了资源配置的改变外,activity 因内存不足被销毁也是通过这两个方法保存数据。

  3. 在 onRestoreInstanceState 和 onCreate 都可以进行数据恢复工作,但是根据官方文档建议采用在 onRestoreInstanceState 中去恢复。

  4. 在 onSaveInstanceState 和 onRestoreInstanceState 这两个方法中,系统会默认为我们进行一定的恢复工作,具体地讲,默认实现会为布局中的每个 View 调用相应的 onSaveInstanceState() 方法,让每个视图都能提供有关自身的应保存信息。Android 框架中几乎每个小部件都会根据需要实现此方法,以便在重建 Activity 时自动保存和恢复付 UI 所做的任何可见更改。例如 EditText 中的文本信息、ListView 中的滚动位置等。也可以通过 android:saveEnabled 属性设置为 “false” 或通过调用 setSaveEnabled() 方法显式阻止布局内的视图保存其状态,通常不会将该属性停用,除非想要以不同方式恢复 Activity IU 的状态。

  5. onSveInstanceState() 常见的触发场景有:横竖屏切换、按下电源键、按下菜单键、切换到别的 Activity 等;onRestoreInstanceState() 常见的触发场景有:横竖屏切换、切换语言等等。

2.5.2 防止重建

  在默认情况下,资源配置改变会导致活动的重新创建,但是可以通过对活动的 android:configChanges 属性的设置使活动防止重新被创建。

属性值含义
mccSIM 卡唯一标识IMSI(国际移动用户标识码)中的国家代码,由三位数字组成,中国为:460,这里标识 mcc 代码发生了变化
mncSIM 卡唯一标识 IMSI(国际移动用户标识码)中的运营商代码,有两位数字组成,中国移动 TD 系统为 00 ,中国联通为 01,电信为 03,此项标识 mnc 发生了改变
locale设备的本地位置发生了改变,一般指的是切换了系统语言
touchscreen触摸屏发生了改变
keyboard键盘类型发生了改变,比如用户使用了外接键盘
keyboardHidden键盘的可访问性发生了改变,比如用户调出了键盘
navigation系统导航方式发生了改变
screenLayout屏幕布局发生了改变,很可能是用户激活了另外一个显示设备
fontScale系统字体缩放比例发生了改变,比如用户选择了个新的字号
uiMode用户界面模式发生了改变,比如开启夜间模式 -API8 新添加
orientation屏幕方向发生改变,比如旋转了手机屏幕
screenSize当屏幕尺寸信息发生改变(当编译选项中的 minSdkVersion 和 targeSdkVersion 均低于 13 时不会导致 Activity 重启 ) API 13 新添加
smallestScreenSize设备的物理尺寸发生改变,这个和屏幕方向没关系,比如切换到外部显示设备 -API13 新添加
layoutDirection当布局方向发生改变的时候,正常情况下无法修改布局的 layoutDirection 的属性 -API17 新添加

可以在属性中声明多个配置值,方法使用 “|” 字符分割这些配置值。

三、启动模式

3.1 区别

3.2 设置

1. 在 AndroidMainifest 设置

<activity
android:launchMode="启动模式"
//属性
//standard标准模式
//singleTop栈顶复用模式
//singleTask栈内复用模式
//singleInstance单例模式
//如不设置Activity的启动模式默认为**标准模式standard)**
</activity>

2.通过Intent设置标志位

Intent inten = new Intent (ActivityA.this,ActivityB.class);
intent,addFlags(Intent,FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

3.3 standard

  • 简单的栈的进栈和出栈,先进后出

3.4 singleTop

  • 栈顶复用模式意思就是说如果这个activity是在栈顶,再次启动这个activity就直接复用,不创建新的activity

3.5singleTask

  • 与singleTop类似,只不过singleTop只是复用栈顶的元素,而singleTask可以复用栈内的所有元素,当启动的activity在栈内已经存在时,singleTask模式直接弹出目标activity之上的所有元素,讲目标元素置于栈顶。

activity1是singleTask模式,当再次启动activity1的时候,栈内已经存在目标activity(activity1),则弹出activity1之上的所有activity,讲目标置于栈顶。

应用场景
程序主界面,我们肯定不希望主界面被多创建,而且在主界面退出的时候退出整个App是最好的设想。
耗费系统资源的Activity:对于那些及其耗费系统资源的Activity,我们可以考虑将其设为singleTask模式,减少资源耗费(在创建阶段耗费资源的情况,个人理解-。+)

3.6 singleInstance

在该模式下,我们会为目创建一个新的Task栈,将目标Activity放入新的Task,并让目标Activity获得焦点。新的Task有且只有这一个Activity实例。 如果已经创建过目标Activity实例,则不会创建新的Task,而是将以前创建过的Activity唤醒(对应Task设为Foreground状态)。
为了方便理解,也为了与standard模式融会贯通,我在下图中activity1和activity2使用的是standard模式,而activity3使用的是singleInstance模式

四、Intent

在Android中,Activity的启动是通过Intent来表达的,Intent是组件之间通信的媒介,专门提供组件互相调用的相关信息

4.1 显示启动

第一种:class 跳转

// 1. 实例化显式Intent & 通过构造函数接收2个参数
// 参数1 = Context:启动活动的上下文,一般为当前Activity 
// 参数2 = Class:是指定要启动的目标活动
Intent intent = new Intent(Activity.this,Activity2.class);

// 2. 通过Activity类的startActivity()执行该意图操作(接收一个Intent对象)
// 将构建好的Intent对象传入该方法就可启动目标Activity
startActivity(intent);

第二种:包名.类名跳转

Intent intent = new Intent(); 
intent.setClassName(FirstActivity.this,"com.xiaozeng.launchapplication.SecondActivity");
startActivity(intent);

第三种:ComponentName跳转

Intent intent = new Intent()
ComponentName componentName = new ComponentName(FirstActivity.this,SecondActivity.class);
intent.setComponent(componentName);
startActivity(intent);

4.2 隐式启动

隐式启动并不明确指出想要启动的哪一个活动,而是指定了一系列的action和category等信息,然后由系统去分析这个Intent,并帮我们找出合适的活动去启动

第一步:在AndroidManifest.xml文件中 定义action和category属性

第二步:在java 文件中写入逻辑代码 截屏2023-06-27 19.45.39.png

详细说明
声明条件含:动作(Action)、类型(Category)、数据(Data)

五、 数据传递

5.1 使用方式

  • startActivity();
  • startActivityForResult();
  • 自定义方法 actionStart(), 最佳数据传值方法
  • 通过 Bundle 传递数据

5.2 可传递的数据类型

  • 8 种基本数据类型(boolean、 byte、 char、 short、 int、 long、 float、 double)、String
  • Intent、Bundle
  • Serializable对象、Parcelable及其对应数组、CharSequence 类型
  • ArrayList,泛型参数类型为:、<? Extends Parcelable>

5.3 startActivity

// 在MainActivity中定义如下代码 
button1.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
			Intent intent = new 
			Intent(MainActivity.this,TwoActivity.class);
	        //在Intent对象当中添加一个键值对
		    intent.putExtra("key","value");                 
		    startActivity(intent);
	}
});
// 在TwoActivity中定义如下代码
public class TwoActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		button2.setOnClickListener(new OnClickListener() {
	
			@Override
			public void onClick(View v) {
				//取得从上一个Activity当中传递过来的Intent对象
				Intent intent = getIntent();
				//从Intent当中根据key取得value
				if (intent != null) {
					String value = intent.getStringExtra("key");
				}
			}
		});
	}
}

5.4 startActivityForResult

	button1.setOnClickListener(new OnClickListener() {
		@Override
		public void onClick(View v) {
             Intent intent = new Intent(MainActivity.this,TwoActivity.class);
             intent.putExtra("key", "value");
             // 第二个参数是请求码,只要是一个唯一值
             startActivityForResult(intent, 1234);
		}
	});

 	// 由于我们是使用startActivityForResult()方法来启动TwoActivity的,在TwoActivity被销毁之后会回调上一个活动的onActivityResult()方法
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        switch (requestCode) {
            case 1234:
                if (resultCode == RESULT_OK) {
					//接收对象
					//Bundle bundle = data.getExtras();
                    //AddressBean addressBean = (AddressBean) bundle.getSerializable(Constant.ADDRESSBEAN);

                    String returnedData = intent.getStringExtra("key1");
                }
                break;
                
            default:
        }
    }

public class TwoActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		//接收从MainaActivity传递的数据
		Intent intent = getIntent();
		if (intent != null) {
			String value = intent.getStringExtra("key");
		}
		
		button2.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
			    //传递对象
				//Intent intent = new Intent();
		        //Bundle bundle = new Bundle();
		        //bundle.putSerializable(Constant.ADDRESSBEAN, addressArray.get(position));
		        //intent.putExtras(bundle);
		        //setResult(RESULT_OK, intent);
		        //finish();
        
				Intent intent = new Intent();
				intent.putExtra("key1","value two activity");
				// 专门用于向上一个活动返回数据。第一个参数用于向上一个活动返回结果码,一般只使用RESULT_OK或RESULT_CANCELED这两个值
				setResult(RESULT_OK, intent);
				finish();
			}
		});
	}
}

5.5 自定义方法 actionStart(); 最佳数据传值方法

button1.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
			/*最佳数据传值方法:调用在下个activity自定义的方法*/
            TwoActivity.actionStart(MainActivity.this, "data1", "data2");
	}
});

public class TwoActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
	}
    //最佳数据传值方法
	public static void actionStart(Context context, String data1, String data2) {
		Intent intent = new Intent(context, TwoActivity.class);
		intent.putExtra("param1", data1);
		intent.putExtra("param2", data2);
		context.startActivity(intent);
	}
}

5.6 通过 Bundle 传递数据

// 1. 数据传递
// a. 创建Intent对象(显示Intent)
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);     

// b. 创建bundle对象
Bundle bundle = new Bundle();

// c. 放入数据到Bundle
bundle.putString("name", "carson");
bundle.putInt("age", 28);

// d. 将Bundle放入到Intent中
intent.putExtras(bundle);

// e. 启动Activity
startActivity(intent);
// 2. 数据取出(在被启动的Activity中)
// a. 获取用于启动SecondActivit的Intent
Intent intent = getIntent();

// b. 通过Intent获取bundle
Bundle bundle = intent.getExtras();

// c. 通过bundle获取数据传入相应的键名,就可得到传来的数据
// 注意数据类型 与 传入时保持一致
String nameString = bundle.getString("name");
int age = bundle.getInt("age");

5.7 putExtra和Bundle

Bundle 意为 捆绑 的意思,更多适用于:

  • 连续传递数据
    若需实现连续传递:Activity A -> B -> C;若使用putExtra(),则需写两次 intent = A->B 先写一遍 + 在B中取出来 & 再把值重新写到Intent中再跳到C;若使用 Bundle,则只需取出 & 传入 Bundle对象即可

  • 可传递的值:对象
    putExtra()无法传递对象,而 Bundle 则可通过 putSerializable 传递对象

// 如传递User类的对象

public class User implements Serializable {
    ...
}

// 传递时
User user = new User();
Intent intent = new Intent(MyActivity.this,OthereActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("user", user);
intent.putExtras(bundle);

putExtra() 更多使用于单次传递、传递简单数据类型的应用场景

  • 引用
  1.  Activity关于生命周期一些问题的实践验证 
  2. 老生常谈-Activity