分类:C#、Android、VS2015;
创建日期:2016-02-21
一、基本概念
1、必须理解这些方法,否则你编的程序根本就没法在实际项目中使用
当然,如果仅仅是为了玩玩,或者仅仅是作为例子为了专注于介绍某个知识点而有意忽略其他的实现细节,那就另当别论了。
Android应用程序可自动创建、暂停、恢复、销毁Activity,下图是典型的Activity生命周期(虽然不同资料画的图不一样,但殊途同归,目的都是为了帮助理解):
要创建一个自定义的Activity,必须创建一个父类为Activity的类,或者父类是Activity的子类的类。自定义类中的每个回调方法实际上都是一个钩子(hook),通过重写(override))对应的回调方法,即可在Activity的生命周期中控制你的活动,比如控制什么时候显示、什么时候让其从屏幕上消失等。
(1)【Back】按钮和【Home】按钮
所有Android手机都有两个特殊的按钮:【Back(后退)】按钮和【Home(主页)】按钮,下图是Android 4.4的屏幕快照:
当用户单击【Back】按钮时,Android会销毁当前活动。但是,当用户单击【Home】按钮时,该活动只是从前台转到了后台,Android实际上并没有销毁该活动。
(2)【重要】OnCreate()【必须重写】
当活动(Activity)加载到内存时,首先调用的就是这个方法,此时该活动还没有在屏幕上呈现界面。
参数bundle是一个有一个或多个“key/value”组成的字典,利用它可在不同的活动或对象间存储和传递状态信息。
OnCreate()是必须重写(override)的方法,一般在这个方法中调用SetContentView()指定和用户交互的是哪个布局页面。
在重写的OnCreate()方法内,需要执行下面的操作:
- 创建视图
- 初始化变量
- 将静态数据绑定到列表
例如:
protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); string extraString; bool extraBool; if (bundle != null) { intentString = bundle.GetString("myString"); intentBool = bundle.GetBoolean("myBool"); } SetContentView(Resource.Layout.Main); }
在这个重写的方法中,通过调用SetContentView()方法传入Resources/layout下的布局资源(.xml文件或者.axml文件),以便让Android加载它,然后再调用FindViewById()方法找到.xml文件或者.axml文件中对应的控件,剩下的就和C#对控件的操作完全相同了。
OnCreate()方法执行完毕后,接下来Android会自动执行OnStart()方法。
(3)【常用】OnStart()【可能需要重写】
OnCreate()方法执行完毕后,在将Activity呈现到界面上之前,Android会自动调用此方法。
如果将Android的活动页面生命周期和WPF窗口的生命周期相比,可这样来对照理解:
Android的OnCreate()方法:相当于WPF窗口的Initialize()方法;
Android的OnStart()方法:相当于WPF窗口的OnLoad()方法。
(4)【很少用】OnRestart()【很少重写】
当Activity被再次启动时,Android会自动调用此方法。
例如,某个活动正在运行时用户按了中间的那个【Home】按钮,这种情况下,该活动将依次调用OnPause()方法和OnStop()方法,并将该活动从前台移至后台,但不会销毁该活动。如果用户使用任务管理器或功能类似的应用程序恢复该活动,此时Android将调用OnRestart()方法。
由于无论是否正在创建活动或正在重新启动,Android总是会自动调用OnStart()方法,所以一般不在OnRestart()方法中编写代码逻辑。换言之,恢复活动所需要的资源应该在OnStart()方法中进行处理,而不是在OnRestart()方法去处理。
Android调用OnRestart()后,随后将接着调用OnStart()方法转入下一个生命周期。
(5)【有时用】OnResume()【可能需要重写】
当活动界面重新显示到屏幕上,且在该活动与用户交互之前,Android将自动调用此方法。一般在OnResume()方法中执行下面的操作:
- 加大帧速率(例如创建游戏时执行的公共操作)
- 启动动画
- 监听GPS更新
- 显示相关的对话框(Alerts或Dialogs)
- 启动外部事件控制
例如,下面的代码在该方法中初始化相机:
public void OnResume() { base.OnResume(); // 必须先调用基类的方法 if (camera == null) { //在此处编写初始化相机的代码 } }
(6)【重要】OnPause()【常用】
当活动界面从屏幕上消失时Android将自动调用此方法(虽然这并不意味着Activity正在被销毁)。通常在这个方法中永久保存对该活动所做的更改(比如将数据保存到数据库中等),这样做是因为用户可能不会再回到这个Activity。
具体来说,一般在该方法中执行下面的操作:
- 将未保存的更改提交到可持久性保存的存储中(数据库或文件或提交到服务器)。
- 销毁或清理可能会过度消耗资源的对象。
- 降低帧速率,并暂停动画播放
- 注销外部事件或通知,例如注销已经注册的Service等,以便释放其占用的内存。
- 如果该活动显示了对话框,此时还需要调用Dismiss()方法清除它们。
例如:
public void OnPause() { base.OnPause(); //必须先调用基类的方法 //释放相机占用的内存资源 if (_camera != null) { camera.Release(); camera = null; } }
Activity生命周期回调方法的顺序是很明确的,特别是两个activity位于同一个进程中、一个Activity启动另一个Activity的时候。
下面就是Aactivity A启动Activity B时的操作顺序:
- Activity A调用OnPause()方法,如果该活动在后台(不可见),还会调用OnStop()方法。
- Activity B依次调用OnCreate()方法、OnStart()方法、OnResume()方法。然后Activity B呈现到街面上。
注意:如果第一个activity停止时你须将数据写入数据库以便后续的activity可以读取这些数据,那么你应该在OnPause()方法中写入数据库而不是在OnStop()方法中写入数据库。
(7)【极少用】OnStop()【极少需要重写】
当某个活动对用户不可见时,即:发生下列情况之一时,将引发OnStop方法:
- 当一个新活动被启动且覆盖在当前活动的上面时。
- 当一个现有的活动从后台被带到前台时。
- 当该活动正在被销毁时。
注意,OnStop()方法并不总是在内存不够用的情况下才会被调用,例如,内存虽然够用,但是Android不能进行正常的后台活动等情况时,Android也会调用OnStop()方法。因此,不要依靠重写的OnStop()去销毁某个活动,如果需要编写执行销毁活动的代码,应该在重写的OnDestroy()方法中去实现。
(8)【有时用】OnDestroy()【有时需要重写】
这是当某个活动已被摧毁和完全从内存中删除时Android最后调用的方法。但是还有一种极端的情况:Android可能会杀死应用程序进程,但却不会调用该Activity的OnDestroy()方法。
重写OnDestroy()方法一般用于清理长时间运行的资源,以避免可用的内存越来越少。例如,摧毁一个在OnCreate()方法中启动的后台线程等。
由于Android在OnPause()方法和OnStop()方法中已经完成了大多数清理和关闭任务,因此多数活动都不会再通过重写该方法去销毁资源。
2、Activity的三种基本状态
一个Activity可能处于三种基本的状态之一:Running、Paused、Stopped。
(1)Resumed或Running
“Resumed”状态是指某个Activity在屏幕的前台并且拥有用户焦点的情况,这个状态也叫“Running”状态。
(2)Paused
当活动B在前台并拥有焦点,但是活动A仍可见时,此时活动A处于Paused状态。或者说,当活动B覆盖在活动A的上面,并且活动B可能是部分透明的或没有覆盖整个屏幕时,这种情况下活动A就处于Paused状态。
当某个活动处于Paused状态时,这个Activity对象仍然保留在内存里,保持着所有的状态和成员信息,并且保持与Window Manager的联接。但是,在系统内存严重不足的情况下,可杀死这个活动(介绍进程和线程一章时,再说如何杀死某个活动)。
当某个activity启动另一个activity时,这两个活动的生命周期的状态都会发生转换。 第一个activity处于Paused并Stopped(尽管它也可能不会被Stopped,如果它仍然可见的话),而另一个activity是被Created。如果这两个activity共用了保存在磁盘或其它地方的数据,那么一定要明白:在第二个activity被Created之前,第一个activity还没有完全被Stopped,这点非常重要。这是因为:或多或少,第二个activity的启动进程与第一个activity的关闭进程在时间上会发生重叠。
(3)Stopped
当某个Activity被其它的Activity完全遮挡住了(此时这个Activity在后台)。一个处于Stopped的Activity也仍然是存活的,即:这个Activity对象仍然保留在内存中,它保持着所有的状态和成员信息,但不再与Window Manager联接。但是,此时用户已经不可能再看见它了,并且当其它地方需要内存时它将会被杀死。
如果某个Activity被设置为Paused或Stopped,则意味着可以从内存中删除它,即通过调用它的Finish()方法或者直接杀死它的进程。当这个activity被再次启动时(被Finish或者Kill后),它必须被完全重建才可以在界面中重新显示出来。
3、基本处理原则
总体来讲,Activity这些可重写的方法定义了一个activity的完整生命周期。你可以通过重写这些方法,监控activity生命周期中三个嵌套的循环:
(1)activity的完整生存期会在OnCreate() 和OnDestroy() 之间发生。 你的activity应该在重写的OnCreate() 方法里完成所有“全局(global)”状态的设置(比如定义layout), 而在重写的OnDestroy() 方法里释放所有你自己创建的资源。 例如,如果你的activity有一个后台运行的线程(该线程用于从网络下载数据),那么你应该在OnCreate() 方法里创建这个线程并且在 OnDestroy() 方法里停止这个线程。
(2)activity的可见生存期会在OnStart()和OnStop()之间发生。在这期间,用户可在屏幕上看见这个activity并可与之交互。 例如,当一个新的activity启动后调用了OnStop() 方法,则这个activity就无法被看见了。 在这两个方法之间,你可以管理那些显示activity所需的资源。例如,可以在重写的OnStart()方法里注册一个BroadcastReceiver来监控影响用户界面的改动。并且当用户不再看到你的显示内容时,在重写的OnStop()方法里注销掉它。
由于activity可能会在显示和隐藏之间不断地来回切换,因此系统可能会在activity的整个生存期内多次调用OnStart()和OnStop()。
(3)activity的前台生存期会在 OnResume()和OnPause()之间发生。在这期间,activity位于屏幕上所有其它的activity之前,并且拥有用户的输入焦点。 activity可以频繁地进入和退出前台。例如,当设备进入休眠状态时或者弹出一个对话框时,OnPause()就会被调用。因为这个状态可能会经常发生转换,为了避免切换迟缓引起用户等待,这两个方法中的代码应该相当地轻量化。
二、示例1—理解Activity的生命周期
目的:通过观察运行过程中Log.Debug输出的结果,理解Activity生命周期中各个方法执行的顺序。
1、运行截图
下面左图为单击【按钮1】3次后显示的结果,右图为单击【按钮2】后显示的结果。目的是为了让你明白如何在不同的Activity之间传递简单值:
下图为返回到Activity1,然后按【Ctrl】+【F11】旋转屏幕为横向放置后显示的结果(注意按钮单击次数的值应该为3但是旋转后却变为0了,下一节将重点解决此问题):
另外,程序运行期间,既可以在【输出】窗口中观察Log.Debug的输出的结果,也可以在【Android Device Logging】窗口中观察Log.Debug的输出结果,这里不再截图。
2、主要设计步骤
(1)添加ch1101Layout1.axml文件
在Resources/layout文件夹下添加该文件。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:text="这是Activity1的界面" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/textView1" android:layout_margin="10dp" android:gravity="center_horizontal" /> <TextView android:text="你单击了第1个按钮 0 次" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/textViewCount" android:layout_margin="20dp" android:gravity="center_horizontal" /> <Button android:id="@+id/btn1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="按钮1--统计单击次数" /> <TextView android:text="提示:【Ctrl】+【F11】用于控制模拟器的屏幕【纵向/横向】转换,多次按它观察屏幕旋转后单击第1个按钮的次数显示的情况。" android:textAppearance="?android:attr/textAppearanceSmall" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/textView2" android:layout_margin="20dp" /> <Button android:id="@+id/btn2" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="按钮2--启动活动2" /> </LinearLayout>
(2)添加ch1101Layout2.axml文件
在Resources/layout文件夹下添加该文件。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:text="这是Activity2的界面" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/textView1" android:layout_margin="20dp" android:gravity="center_horizontal" /> <TextView android:text="你单击了Activity1的第1个按钮 0 次" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/textViewCount" android:layout_margin="20dp" android:gravity="center_horizontal" /> <TextView android:text="提示:按【Back】按钮回到Activity1" android:textAppearance="?android:attr/textAppearanceSmall" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:layout_margin="20dp" /> </LinearLayout>
(3)添加ch1101Acivity1.cs文件
在SrcDemos文件夹下添加该文件。
using Android.App; using Android.Content; using Android.OS; using Android.Widget; using Android.Util; namespace MyDemos.SrcDemos { [Activity(Label = "【例11-1】理解Activity的生命周期")] public class ch1101Activity1 : Activity { private string logTag = nameof(ch1101Activity1); private int count = 0; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); Log.Debug(logTag, "调用OnCreate方法,该活动被创建"); SetContentView(Resource.Layout.ch1101Layout1); var txtCount = FindViewById<TextView>(Resource.Id.textViewCount); var btn1 = FindViewById<Button>(Resource.Id.btn1); btn1.Click += (s, e) => { count++; txtCount.Text = $"你单击了第1个按钮 {count} 次"; }; var btn2 = FindViewById<Button>(Resource.Id.btn2); btn2.Click += (s, e) => { var intent = new Intent(this, typeof(ch1101Activity2)); //将count传递给ch1101Activity2 intent.PutExtra("count", count); StartActivity(intent); }; } protected override void OnStart() { Log.Debug(logTag, "调用OnStart方法,此时该活动的界面还没有显示出来"); base.OnStart(); } protected override void OnResume() { Log.Debug(logTag, "调用OnResume方法,该活动的界面显示出来了,并且已经准备好与用户交互"); base.OnResume(); } protected override void OnPause() { Log.Debug(logTag, "调用OnPause方法,此时该活动被转到后台"); base.OnPause(); } protected override void OnStop() { Log.Debug(logTag, "调用OnStop方法,此时该活动被转到后台"); base.OnStop(); } protected override void OnDestroy() { Log.Debug(logTag, "调用OnDestroy方法,该活动被销毁"); base.OnDestroy(); } } }
(4)添加ch1101Acivity2.cs文件
在SrcDemos文件夹下添加该文件。
using Android.App; using Android.Content; using Android.OS; using Android.Widget; namespace MyDemos.SrcDemos { [Activity(Label = "【例11-1】理解Activity的生命周期")] public class ch1101Activity2 : Activity { protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.ch1101Layout2); //获取ch1101Activity1传递过来的count,如果不存在count,就返回-1 var a = Intent.GetIntExtra("count", -1); var txt = FindViewById<TextView>(Resource.Id.textViewCount); txt.Text = $"你单击了Activity1的第1个按钮 {a} 次"; } } }
通过这个例子,我们应该对Activity的生命周期有了更深层次的理解。