(2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
每次使用 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();
    }
}
因此,我们需要理解这些方法的执行时机以及含义才能正确实现这些方法。庆幸的是,这些方法的含义都能在官方文档中找到(其实就是平时看到的注释):
- IFrameworkView.Initialize(CoreApplicationView)
 - IFrameworkView.Load(String)
 - IFrameworkView.Run
 - IFrameworkView.SetWindow(CoreWindow)
 - IFrameworkView.Uninitialize
 
为了方便查看,我将其整理到这些方法上作为注释。
顺便的,下面这些方法刚好是按照应用生命周期的顺序被调用,也就是 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/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
		
每次使用 Visual Studio 的模板创建一个 UWP 程序,我们会在项目中发现大量的项目文件.配置.应用启动流程代码和界面代码.然而这些文件在 UWP 程序中到底是如何工作起来的? 我从零开始 ...
 - Flink on Yarn模式启动流程源代码分析
		
此文已由作者岳猛授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. Flink on yarn的启动流程可以参见前面的文章 Flink on Yarn启动流程,下面主要是从源码角 ...
 - 浅析Linux启动流程
		
Linux系统启动流程 Linux 系统的启动,从计算机开机通电自检开始,一直到登陆系统,需要经历多个过程.了解 Linux 的启动过程,有助于了解 Linux 系统的结构,也对系统的排错有很大的帮助 ...
 - React Native 启动流程简析
		
导读:本文以 react-native-cli 创建的示例工程(安卓部分)为例,分析 React Native 的启动流程. 工程创建步骤可以参考官网.本文所分析 React Native 版本为 v ...
 - Chromium的GPU进程启动流程
		
转载请注明出处:http://www.cnblogs.com/fangkm/p/3960327.html 硬件渲染依赖计算机的GPU,GPU种类繁多,兼容这么多种类的硬件,稳定性是个大问题,虽然Chr ...
 - 《转》深入理解Activity启动流程(四)–Activity Task的调度算法
		
本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...
 - 《转》深入理解Activity启动流程(三)–Activity启动的详细流程2
		
本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...
 - 《转》深入理解Activity启动流程(三)–Activity启动的详细流程1
		
本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...
 - 《转》深入理解Activity启动流程(二)–Activity启动相关类的类图
		
本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 在介绍Activity的详细启动流程之前,先为大家介绍Act ...
 
随机推荐
- IIS服务器管理学习
			
工欲善其事必先利其器 首先给服务器配上强力的软件,用于安全防护和监控. 公司服务器用的阿里云的ECS,已经有防护和监控了,之后又选择额外加了一个安全狗 为了监控服务器上系统的各项运行指标,又买了听云平 ...
 - ADO.net笔记
			
1.DbConnectionConnection对象也称为数据库连接对象,Connection对象的功能是负责对数据源的连接.所有Connection对象的基类都是DbConnection类.Conn ...
 - [Vue]使用axios实现ajax请求
			
1.定义myAjax export const myAjax=function createHttpClient(ajaxConfig) { let httpClient = null; if ...
 - django 使用form组件提交数据之form表单提交
			
django的form组件可以减少后台在进行一些重复性的验证工作,极大降低开发效率. 最近遇到一个问题: 当使用form表单提交数据后,如果数据格式不符合后台定义的规则,需要重新在前端页面填写数据. ...
 - jQuery 获取Select选择的Text和 Value
			
jQuery获取Select选择的Text和Value:语法解释: 1. $("#select_id").change(function(){//code...}); / ...
 - 关于BFS和dijkstra(2019.04.20)
			
我的BFS板子 struct node{/*略*/};//表示一个状态 std::map<node,bool>vis;//判断每个状态是否已访问过 std::queue<node&g ...
 - CodeForces - 91B单调队列
			
有一个数列,对于每一个数,求比它小的在他右边距离他最远的那个数和他的距离 用单调队列做,维护单调队列时可采用如下方法,对于每一个数,如果队列中没有数,则加入队列,如果队列头的数比当前数大,则舍弃该数 ...
 - SpringBoot下的值注入
			
在我们实际开发项目中,经常会遇到一些常量的配置,比如url,暂时不会改变的字段参数,这个时候我们最好是不要直接写死在代码里的,因为这样编写的程序,应用扩展性太差了,我们可以直接写在配置文件中然后通过配 ...
 - HDU-4714-贪心
			
Tree2cycle Time Limit: 15000/8000 MS (Java/Others) Memory Limit: 102400/102400 K (Java/Others)Tot ...
 - Gruntjs提高生产力(一)
			
gruntjs是一个基于nodejs的自动化工具,只要熟悉nodejs或者又一定js经验就可以熟练应用. 1. 安装 a. 保证已安装了nodejs,并带有npm b.安装客户端命令行工具,grunt ...