每次使用 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. Android -- ContentProvider, 读取和保存系统 联系人

    1. 示例代码 需要的读写联系人的权限 <uses-permission android:name="android.permission.WRITE_CONTACTS"/& ...

  2. 机器学习笔记—K-均值聚类

    在聚类问题中,给定训练集 {x(1),...,x(m)},要把数据分成内聚的“簇”.这里 x(i)∈R,没有 y(i).所以,这是一个无监督学习问题. k-均值聚类算法如下: 1.随机初始化簇中心 μ ...

  3. Git 设置 SOCKS 代理

    $ export all_proxy=socks5://127.0.0.1:1080

  4. 读取文件并找出年龄最大的N个人-兰亭集市笔试题

    C++ code: #include <iostream> #include <fstream> #include <map> #include <strin ...

  5. 基于usb4java实现的java下的usb通信

    项目地址:点击打开 使用java开发的好处就是跨平台,基本上java的开发的程序在linux.mac.MS上都可以运行,对应这java的那句经典名言:一次编写,到处运行.这个项目里面有两种包选择,一个 ...

  6. javaScript tips —— 标签上的data属性

    HTML5规定可以为元素添加非标准型的属性,只需添加前缀data-,这些属性可以随意添加,随意命名,目的是为元素提供与渲染无关的信息,或提供语义信息. 传统获取方式 'getAttribute' da ...

  7. NSMutableString和NSString区别,及相互转换方法

    NSString是一个不可变的字符串对象.这不是表示这个对象声明的变量的值不可变,而是表示它初始化以后,你不能改变该变量所分配的内存中的值,但你可以重新分配该变量所处的内存空间.而NSMutableS ...

  8. leetcode算法总结

    算法思想 二分查找 贪心思想 双指针 排序 快速选择 堆排序 桶排序 搜索 BFS DFS Backtracking 分治 动态规划 分割整数 矩阵路径 斐波那契数列 最长递增子序列 最长公共子系列 ...

  9. pyhon SyntaxError: Non-ASCII character '\xe8' in file xxx on line xx, but no encoding

    import math if __name__ == '__main__':    name1 = raw_input("请输入您的编号:")    print name1 完整的 ...

  10. CodeForces 55D Beautiful numbers (SPOJ JZPEXT 数位DP)

    题意 求[X,Y]区间内能被其各位数(除0)均整除的数的个数. CF 55D 有些时候因为问题的一些"整体性"而导致在按位统计的过程中不能顺便计算出某些量,所以只能在枚举到最后一位 ...