本节将介绍应用程序的激活。特别地,我们将讨论Windows如何为应用创建线程,并允许应用对自身实现初始化;之后我们将研究如何让应用为用户开始工作。
某个应用被激活可能出于多种原因。最显而易见的一个原因是当用户从开始屏幕中单击应用的磁贴。这种类型的激活被称为启动激活(launch activation)。所有Windows应用商店应用必须无一例外地支持启动激活。此外,Windows应用商店应用还可能因用户单击了开始屏幕中该应用的次磁贴或因用户选择了应用中显示的消息通知而激活(详情请参阅第8章)。应用因第二个磁贴或消息通知而激活也被称为启动激活。除了对启动激活提供支持,应用还可有选择地支持其他激活方式。例如,你可允许在用户在文件浏览器中打开文件时、将某个设备(如照相机)连接到PC时、试图在其他应用与该应用之间实现内容共享时等场合中应用被激活。有一个名为Windows.ApplicationModel.Activation.ActivationKind的WinRT枚举类型专门用于表示应用被激活的方式。表3.1展示了该枚举类型所提供的枚举值,并对其进行了简短描述。其中涉及的一些激活方式将其本书其余章节进行讨论,另外由于一些方式极少被使用,因此笔者在本书中将其略去。
术语应用声明(app declaration)、应用扩展(app extension)、应用激活(app activation)以及协定(contract)都确切地与同一事物有关。即,在程序包中,开发人员必须声明应用扩展,以允许系统激活该应用。当某个应用对激活做出响应时,称该应用实现了一个协定。遗憾的是,MSDN网页http://msdn.microsoft.com/en-us/library/windows/apps/hh464906.aspx对协定和扩展的介绍并不十分准确。
图3.1展示了构成处于运行状态的应用的各种WinRT类型之间的关系,而图3.2则展示了一幅用于解释这些WinRT对象如何在应用激活期间在运行时被创建的流程图。在继续该话题的讨论时,你可能需要经常性地回顾这两幅图。
可以看到,该方法所做工作不多。当进程的主线程调用Main方法时,后者内部会调用Windows.UI.Xaml.Application类的静态方法Start,以创建另一个称为主视图线程(main view thread)的线程。之后,该线程将创建一个Windows.ApplicaitonModel.Core.CoreApplicationView类型的对象,即该应用的主绘图图面。该CoreApplicationView对象与主视图线程关联,仅可通过由主视图线程执行的代码对其进行操纵。接着,主视图线程将对作为参数传入Application类的Start方法(该方法将构造应用App类的一个实例)的回调函数进行调用。
基类Application的构造方法在一个私有静态域中保存了App对象的一个引用,以确保该对象在该进程的整个生命期内不参与垃圾回收。通过调用Application类的静态属性Current,总能获取指向应用的单例App对象的引用。
App对象是一个单例对象,它与应用的进程具有相同的生命期。由于该对象在进程生命期内不会被销毁,因此被任何静态或实例域直接或间接引用的任何其他对象都不会参与垃圾收集。请务必留意这一点,因为它可能是造成内存泄露的原因之一。
当App的单例对象创建后,主线程会检查ActivationKind的取值,以确定应用被激活的原因。所有的激活方式不外乎两种类型:主视图激活(main view activation)或宿主视图激活(hosted view activation)。(请参阅表3.1中最后一列)对于主视图激活,大多数开发人员已耳熟能详。主视图激活会使应用的主窗口成为前台窗口,并允许用户与应用进行交互。
宿主视图激活对于大多数开发人员来说,可能并不十分熟悉。当某个应用希望利用其他应用所提供的功能来完成某些操作时,便可使用宿主视图激活。这时,用户与之交互的应用会请求Windows创建一个新窗口,接着Windows将另一个应用激活。后一个应用将创建一个小窗口,并托管在Windows的大窗口内。宿主视图激活正由此而得名。宿主视图激活的另一个例子是用户希望通过邮件应用与朋友分享某个网页。例如,图3.3展示了Bing News应用,它是一个用户与之交互的主应用。当用户单击共享超级按钮,并选择Mail应用时,Windows将在屏幕边界处创建一个狭窄、高度与屏幕一致的窗口。在Windows所创建的窗口的顶端会有标题显示,其中包括后退箭头、应用名称(Mail)及标识。在标题下方是一个由Mail应用自身所创建和管理的宿主视图窗口。
App类由Windows.UI.Xaml.Application类派生而来,后者定义了一些虚方法,如下所示:
public class Application { // 重载该方法以了解何时主视图线程或宿主视图线程的窗口被创建 protected virtual void OnWindowCreated(WindowCreatedEventArgs args); // 重载主视图激活: protected virtual void OnLaunched(LaunchActivatedEventArgs args); protected virtual void OnSearchActivated(SearchActivatedEventArgs args); protected virtual void OnFileActivated(FileActivatedEventArgs args); // 重载宿主视图激活: protected virtual void OnShareTargetActivated( ShareTargetActivatedEventArgs args); protected virtual void OnFileOpenPickerActivated( FileOpenPickerActivatedEventArgs args); protected virtual void OnFileSavePickerActivated( FileSavePickerActivatedEventArgs args); protected virtual void OnCachedFileUpdaterActivated( CachedFileUpdaterActivatedEventArgs args); // 对使用频率较低的主视图(Protocol, Device, AppointmentsProvider, Contact, LockScreenCall)及 // 宿主视图(ContactPicker, PrintTaskSettings, CameraSettings)重载下列方法: protected virtual void OnActivated(IActivatedEventArgs args); }
主视图或宿主视图窗口一旦被创建,创建该窗口的线程将调用虚方法OnWindowCreated。如果重载了该方法,传递给该方法的WindowsCreatedEventArgs对象将包含该线程新创建的那个窗口的引用。在这个方法中,可以为它所提供的任何事件(包括Activated、SizeChanged、VisibilityChanged或Closed)注册回调方法。当OnWindowCreated方法返回时,有且仅有其他虚方法之一将被调用,具体哪个虚方法被调用取决于应用被激活的具体原因。OnActivated方法仅为一些较少使用的激活类型所调用。
在上述某一个虚方法中,可执行特定激活类型所需的任意初始化,创建自己期望的用户界面元素树,并激活该视图的CoreApplicationView对象,将应用的窗口切换至前台,以便用户与之进行交互。
如果应用是因宿主视图激活而被激活,应用的主线程将创建一个宿主视图线程。接着,该线程将为自己创建CoreApplicaitonView对象,即托管期间的绘图图面。当主应用不再需要宿主视图时,主窗口CoreApplicaitonView及宿主视图线程将被销毁。在每次使用宿主视图激活来激活应用时,一个全新的宿主视图线程和一个CoreApplicationView窗口都将被创建。实际上,多个应用可同时托管某一应用。例如,一些应用可同时托管实现了FileOpenPicker协定的应用。这种情形下,被托管的那个应用的进程将为每个当前正将其托管的应用维护一个宿主视图线程和一个CoreApplicationView窗口。另一方面,应用的进程所拥有的主视图线程和主CoreApplicationView窗口的数目永远不会超过一个。
当应用运行时,可随多个主视图激活而被激活。通常当用户单击应用的次磁贴或消息通知时,会出现这种情况。在这种情况下,应用将切换至前台,但单击磁贴或消息通知这种情形可能会引导应用在被切换至前台时显示某些特定的内容。当已处于运行状态的应用随一个新的主视图激活而被激活时,该进程的主线程将不会创建主视图线程及其CoreApplicationView窗口,因为它们已经被创建。由于窗口已被创建,虚方法OnWindowCreated将不会被调用,但表明主视图为何被重新激活的正确的虚方法会被调用。这个虚方法应通过确定要显示什么样的用户界面,并通过激活主视图窗口以便用户能够与之交互来相应地做出响应。
避免在某个主视图激活的虚方法中注册事件处理方法,因为这些虚方法可能会被多次调用,你一定不希望在应用进程的整个生命期内为单个事件注册多个回调方法。但在OnWindowCreated方法内部注册回调函数是可以的,因为每个线程或窗口对该方法只会调用一次。
应用不再运行时,用户可为某个宿主视图而将该应用激活。此时,应用的主线程将被创建,接着宿主视图线程及其窗口被创建,但此时应用的主视图线程及窗口并不会被创建。如果用户用一个主视图激活将该应用激活,Windows将立即创建该应用的主视图线程和窗口,调用OnWindowCreated方法,然后调用表明该应用为何会随主视图激活而被激活的虚方法。