每次使用 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. P3600 随机数生成器

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...

  2. MySQL索引最左原则

    通过实例理解单列索引.多列索引以及最左前缀原则 实例:现在我们想查出满足以下条件的用户id: 因为我们不想扫描整表,故考虑用索引. 单列索引: ALTER TABLE people ADD INDEX ...

  3. $.ajax 的用法以及参数设置

    url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. type: 要求为String类型的参数,请求方式(post或get)默认为get.注意其他http请求方法,例如put和 ...

  4. mysql数据库建表

    主键的问题: 简单的id递加.不过在实习的时候,因为id递增的问题,导致一个项目最后好像产生了严重的问题.所以递增适合小型项目,对我们的项目来说90%足够可以用的. 一些限制: 一般就非空就行,唯一的 ...

  5. cookie与session(略谈)

    cookie (储存在用户本地终端上的数据) Cookie,有时也用其复数形式 Cookies,指某些网站为了辨别用户身份.进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密).定义 ...

  6. numpy.random之常用函数

    在实际开发中,我们经常会使用随机函数,比如交叉验证,构造测试数据等.下面,是我常用的几个生成随机样本的函数: 1,rand(n1,n2,…,nn) 每一维度都是[0.0,1.0)半闭半开区间上的随机分 ...

  7. Linux 系统启动过程,Linux 系统目录结构

    一.Linux 系统启动过程 linux启动时我们会看到许多启动信息. Linux系统的启动过程并不是大家想象中的那么复杂,其过程可以分为5个阶段: 内核的引导. 运行 init. 系统初始化. 建立 ...

  8. 验证email是否合法

    https://buluo.qq.com/p/detail.html?bid=339910&pid=6675390-1514450689&from=grp_sub_obj 场景1:验证 ...

  9. VS展开当前目录

  10. python获取文件路径, 文件名, 后缀名

    def get_filePath_fileName_fileExt(fileUrl): """ 获取文件路径, 文件名, 后缀名 :param fileUrl: :ret ...