每次使用 Visual Studio 的模板创建一个 UWP 程序,我们会在项目中发现大量的项目文件、配置、应用启动流程代码和界面代码。然而这些文件在 UWP 程序中到底是如何工作起来的?

我从零开始创建了一个 UWP 程序,用于探索这些文件的用途,了解 UWP 程序的启动流程。


本文分为两个部分:

本文将从 Main 函数开始,一步步跑起来一个应用程序,显示一个窗口,并在窗口中显示一些内容。重点在了解在 UWP 中运行应用程序,并显示窗口。

 

启动应用

在上一篇文章中的末尾,我们成功启动了程序并进入了 Main 函数的断点,但实际上运行会报错。我们能看见一个窗口显示出来,随后提示进程已启动,但应用尚未运行。

The Walterlv.Demo.ZeroUwp.exe process started, but the activation request failed with error ‘The app didn’t start’.

是的,我们只有一个什么都没做的 Main 函数,进程当然能够成功启动;但我们需要能够启动应用。那么 UWP 的应用是什么呢?是 CoreApplication。

所以我们使用 CoreApplication 类型执行 Run 静态方法。

此方法要求传入一个 IFrameworkViewSource。事实上 UWP 已经有一个 IFrameworkViewSource 的实现了,是 FrameworkViewSource。不过,我希望自己写一个,了解其原理。

所以,就用 ReSharper 生成了 IFrameworkViewSource 的一个实现:

using Windows.ApplicationModel.Core;

namespace Walterlv.Demo.ZeroUwp
{
internal sealed class WalterlvViewSource : IFrameworkViewSource
{
public IFrameworkView CreateView() => new WalterlvFrameworkView();
}
}

IFrameworkViewSource 接口中只有一个方法 CreateView,返回一个新的 IFrameworkView 的实例。

只是写一个 NotImplementedException 的异常,当然是跑不起来的,得返回一个真的 IFrameworkView 的实例。UWP 自带的实现为 FrameworkView,那么我也自己实现一个。

这次需要实现的方法会多一些:

using Windows.ApplicationModel.Core;
using Windows.UI.Core; namespace Walterlv.Demo.ZeroUwp
{
internal sealed class WalterlvFrameworkView : IFrameworkView
{
public void Initialize(CoreApplicationView applicationView) => throw new System.NotImplementedException();
public void SetWindow(CoreWindow window) => throw new System.NotImplementedException();
public void Load(string entryPoint) => throw new System.NotImplementedException();
public void Run() => throw new System.NotImplementedException();
public void Uninitialize() => throw new System.NotImplementedException();
}
}

因此,我们需要理解这些方法的执行时机以及含义才能正确实现这些方法。庆幸的是,这些方法的含义都能在官方文档中找到(其实就是平时看到的注释):

为了方便查看,我将其整理到这些方法上作为注释。

顺便的,下面这些方法刚好是按照应用生命周期的顺序被调用,也就是 Initialize->SetWindow->Load->Run->Uninitialize

/// <summary>
/// 当应用启动时将执行此方法。进行必要的初始化。
/// </summary>
public void Initialize(CoreApplicationView applicationView) { } /// <summary>
/// 每次应用需要显示一个窗口的时候,此方法就会被调用。用于为当前应用程序显示一个新的窗口视图。
/// </summary>
public void SetWindow(CoreWindow window) { } /// <summary>
/// 会在 <see cref="Run"/> 方法执行之前执行。如果需要使用外部资源,那么这时需要将其加载或激活。
/// </summary>
public void Load(string entryPoint) { } /// <summary>
/// 当此方法调用时,需要让应用内的视图(View)显示出来。
/// </summary>
public void Run() { } /// <summary>
/// 当应用退出时将执行此方法。如果应用启动期间使用到了外部资源,需要在此时进行释放。
/// </summary>
public void Uninitialize() { }

在此接口的所有方法留空地实现完以后,我们的 UWP 应用终于能跑起来了。当按下 F5 调试之后,不会再提示错误,而是依次执行这五个方法后,正常退出应用。

启动窗口

注意到以上所有方法都留空之后,应用程序很快就退出了。这与我们开发传统 Win32 应用时的效果是一致的 —— 是的,我们缺一个消息循环。我们需要一个不断处理的消息循环用来阻断主线程的退出,同时又能够不断响应消息。而这样的方法需要写到 Run() 方法里面。

UWP 中开启一个消息循环是非常容易的,不过我们需要一个 CoreDispatcher 对象。在我们目前的接口实现中,CoreDispatcher 对象可以从 CoreWindow 中获取到。所以我们需要在 SetWindow 方法中拿到 CoreWindow 的实例,然后在 Run 中使用它开启窗口消息循环。

public void SetWindow(CoreWindow window)
{
_window = window;
} public void Run()
{
_window.Activate();
_window.Dispatcher.ProcessEvents(CoreProcessEventsOption.ProcessUntilQuit);
} private CoreWindow _window;


▲ 开启了消息循环之后,应用不会直接退出了

在窗口中显示点东西

我们使用 CompositionAPI 可以在窗口中创建 Visual 并显示出来。

public void SetWindow(CoreWindow window)
{
_window = window; var compositor = new Compositor();
var root = compositor.CreateContainerVisual();
var compositionTarget = compositor.CreateTargetForCurrentView();
compositionTarget.Root = root; var child = compositor.CreateSpriteVisual();
child.Size = new Vector2(100f, 100f);
child.Brush = compositor.CreateColorBrush(Color.FromArgb(0xFF, 0x00, 0x80, 0xFF));
root.Children.InsertAtTop(child);
}

在窗口中做一些交互

CoreWindow 除了为我们提供了消息循环之外,也可以提供交互。监听 PointerMoved 事件,我们可以做一些简单的交互。

下面我用 Git 标准差异比较的方式添加了交互的代码 PointerMoved

  public void SetWindow(CoreWindow window)
{
_window = window;
+ _window.PointerMoved += OnPointerMoved; var compositor = new Compositor();
- var root = compositor.CreateContainerVisual();
+ _root = compositor.CreateContainerVisual();
var compositionTarget = compositor.CreateTargetForCurrentView();
- compositionTarget.Root = _root;
+ compositionTarget.Root = _root; var child = compositor.CreateSpriteVisual();
child.Size = new Vector2(100f, 100f);
child.Brush = compositor.CreateColorBrush(Color.FromArgb(0xFF, 0x00, 0x80, 0xFF));
- root.Children.InsertAtTop(child);
+ _root.Children.InsertAtTop(child);
} + private void OnPointerMoved(CoreWindow sender, PointerEventArgs args)
+ {
+ var visual = _root.Children.First();
+ var position = args.CurrentPoint.Position;
+ visual.Offset = new Vector3((float) (position.X - 50f), (float) (position.Y - 50f), 0f);
+ } private CoreWindow _window;
+ private ContainerVisual _root;

能够完成一些简单的交互。

总结

在本文中,我们了解到 UWP 的应用程序启动中也一样需要有窗口消息循环。不过 UWP 中创建消息循环还是非常简单的。

我们使用 CompositionAPI 进行了一些界面显示和简单的交互。了解到即便是如此复杂的 UWP 程序,其启动流程也没有那么复杂。

不过,如果你阅读了前面一篇 (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序,会发现复杂的部分都在项目文件和系统的部分。

(2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序的更多相关文章

  1. (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序

    每次使用 Visual Studio 的模板创建一个 UWP 程序,我们会在项目中发现大量的项目文件.配置.应用启动流程代码和界面代码.然而这些文件在 UWP 程序中到底是如何工作起来的? 我从零开始 ...

  2. Flink on Yarn模式启动流程源代码分析

    此文已由作者岳猛授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. Flink on yarn的启动流程可以参见前面的文章 Flink on Yarn启动流程,下面主要是从源码角 ...

  3. 浅析Linux启动流程

    Linux系统启动流程 Linux 系统的启动,从计算机开机通电自检开始,一直到登陆系统,需要经历多个过程.了解 Linux 的启动过程,有助于了解 Linux 系统的结构,也对系统的排错有很大的帮助 ...

  4. React Native 启动流程简析

    导读:本文以 react-native-cli 创建的示例工程(安卓部分)为例,分析 React Native 的启动流程. 工程创建步骤可以参考官网.本文所分析 React Native 版本为 v ...

  5. Chromium的GPU进程启动流程

    转载请注明出处:http://www.cnblogs.com/fangkm/p/3960327.html 硬件渲染依赖计算机的GPU,GPU种类繁多,兼容这么多种类的硬件,稳定性是个大问题,虽然Chr ...

  6. 《转》深入理解Activity启动流程(四)–Activity Task的调度算法

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...

  7. 《转》深入理解Activity启动流程(三)–Activity启动的详细流程2

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...

  8. 《转》深入理解Activity启动流程(三)–Activity启动的详细流程1

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...

  9. 《转》深入理解Activity启动流程(二)–Activity启动相关类的类图

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 在介绍Activity的详细启动流程之前,先为大家介绍Act ...

随机推荐

  1. 面试代码基础(一)从strstr说起

    对于写程序要注意:要能在面试官的提示下把代码写出来(把思想实现的能力)!还要注意边界检查!递归找到出口! 开场来个简单字符串匹配 int strstr(char* target,char* sourc ...

  2. Outlook.com 系列邮箱 POP3 及 IMAP 设置方法

    支持 Exchange ActiveSync 的应用 有了 EAS,你可以立即获取电子邮件,以及在一个位置查看所有文件夹.日历和联系人. 如果你的电子邮件应用支持Exchange ActiveSync ...

  3. Learning Perl 第六章习题第一题

    按照first name找last name 知识点: 1. hash的使用和初始化 2. 使用exists函数检测hash中的键是否存在

  4. mysql数据库优化课程---14、常用的sql技巧

    mysql数据库优化课程---14.常用的sql技巧 一.总结 一句话总结:其实就是sql中那些函数的使用 1.mysql中函数如何使用? 选择字段 其实就是作用域select的选择字段 3.转大写: ...

  5. spring mvc: 参数方法名称解析器(用参数来解析控制器下的方法)MultiActionController/ParameterMethodNameResolver/ControllerClassNameHandlerMapping

    spring mvc: 参数方法名称解析器(用参数来解析控制器下的方法)MultiActionController/ParameterMethodNameResolver/ControllerClas ...

  6. JSP 标准标签库(JSTL)

    JSP 标准标签库(JSTL) JSP标准标签库(JSTL)是一个JSP标签集合,它封装了JSP应用的通用核心功能. JSTL支持通用的.结构化的任务,比如迭代,条件判断,XML文档操作,国际化标签, ...

  7. Python 输出百分比的两种方式

    Python 输出百分比的两种方式 注: 在python3环境下测试. 方式1:直接使用参数格式化:{:.2%} {:.2%}: 显示小数点后2位 显示小数点后2位: >>> pri ...

  8. dva-loading使用方法

    1. 下载dva-loading npm install dva-loading --save 2. 导入并使用dva-loading import createLoading from 'dva-l ...

  9. C#/JAVA 程序员转GO/GOLANG程序员笔记大全(DAY 03)

    go语言当中,没有 class 的概念,那么面向对象的编程思想如何展现呢,go语言中对结构体的使用 struct. package main import "fmt" type P ...

  10. 【hive】解析url格式字符串

    解析url格式字符串 parse_url() parse_url(url_str,’xxx’):第一个参数是url格式字符串,第二个参数为要解析出来的属性 parse_url(‘http://face ...